Merge "Preparation for -adaptresourcefilecontents support"
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 405096e..9f12853 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -160,6 +160,7 @@
options.enableMinification = false;
options.enableInlining = false;
options.enableClassInlining = false;
+ options.enableClassStaticizer = false;
options.outline.enabled = false;
DexApplication app = new ApplicationReader(inputApp, options, timing).read(executor);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 8c6c02b..99b0a1e 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -237,6 +237,8 @@
internal.enableInlining = false;
assert internal.enableClassInlining;
internal.enableClassInlining = false;
+ assert internal.enableClassStaticizer;
+ internal.enableClassStaticizer = false;
assert internal.enableSwitchMapRemoval;
internal.enableSwitchMapRemoval = false;
assert internal.outline.enabled;
diff --git a/src/main/java/com/android/tools/r8/DiagnosticsHandler.java b/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
index 74a9a96..666e358 100644
--- a/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
+++ b/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
@@ -4,6 +4,7 @@
package com.android.tools.r8;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
/**
* A DiagnosticsHandler can be provided to customize handling of diagnostics information.
@@ -20,7 +21,11 @@
*/
default void error(Diagnostic error) {
if (error.getOrigin() != Origin.unknown()) {
- System.err.print("Error in " + error.getOrigin() + ":\n ");
+ System.err.print("Error in " + error.getOrigin());
+ if (error.getPosition() != Position.UNKNOWN) {
+ System.err.print(" at " + error.getPosition().getDescription());
+ }
+ System.err.println(":");
} else {
System.err.print("Error: ");
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index e2b8ea8..215ced9 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -399,6 +399,7 @@
new IRConverter(
appView.getAppInfo(), options, timing, printer, appView.getGraphLense());
application = converter.optimize(application, executorService);
+ appView.setGraphLense(converter.getGraphLense());
} finally {
timing.end();
}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index f0ae9e0..4eb4b37 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -571,6 +571,7 @@
// TODO(zerny): Should we support inlining in debug mode? b/62937285
internal.enableInlining = false;
internal.enableClassInlining = false;
+ internal.enableClassStaticizer = false;
// TODO(zerny): Should we support outlining in debug mode? b/62937285
internal.outline.enabled = false;
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 0ef8a34..c29453a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -528,7 +528,7 @@
return false;
}
- private static boolean canTriggerStaticInitializer(DexClass clazz) {
+ public static boolean canTriggerStaticInitializer(DexClass clazz) {
// Assume it *may* trigger if we didn't find the definition.
return clazz == null || clazz.hasClassInitializer();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 4a0a483..d0c40e1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -62,6 +62,17 @@
hashCode(); // Cache the hash code eagerly.
}
+ public DexCode withoutThisParameter() {
+ // Note that we assume the original code has a register associated with 'this'
+ // argument of the (former) instance method. We also assume (but do not check)
+ // that 'this' register is never used, so when we decrease incoming register size
+ // by 1, it becomes just a regular register which is never used, and thus will be
+ // gone when we build an IR from this code. Rebuilding IR for methods 'staticized'
+ // this way is highly recommended to improve register allocation.
+ return new DexCode(registerSize, incomingRegisterSize - 1, outgoingRegisterSize,
+ instructions, tries, handlers, debugInfoWithoutFirstParameter(), highestSortingString);
+ }
+
@Override
public boolean isDexCode() {
return true;
@@ -107,6 +118,19 @@
return new DexDebugInfo(debugInfo.startLine, newParameters, debugInfo.events);
}
+ public DexDebugInfo debugInfoWithoutFirstParameter() {
+ if (debugInfo == null) {
+ return null;
+ }
+ DexString[] parameters = debugInfo.parameters;
+ if(parameters.length == 0) {
+ return debugInfo;
+ }
+ DexString[] newParameters = new DexString[parameters.length - 1];
+ System.arraycopy(parameters, 1, newParameters, 0, parameters.length - 1);
+ return new DexDebugInfo(debugInfo.startLine, newParameters, debugInfo.events);
+ }
+
public int codeSizeInBytes() {
Instruction last = instructions[instructions.length - 1];
return last.getOffset() + last.getSize();
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index cf1a105..e90839c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.JumboStringRewriter;
import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo.ResolutionResult;
import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.code.IRCode;
@@ -541,6 +542,14 @@
return builder.build();
}
+ public DexEncodedMethod toStaticMethodWithoutThis() {
+ assert !accessFlags.isStatic();
+ Builder builder = builder(this);
+ builder.setStatic();
+ builder.withoutThisParameter();
+ return builder.build();
+ }
+
/**
* Rewrites the code in this method to have JumboString bytecode if required by mapping.
* <p>
@@ -658,6 +667,7 @@
forceInline = template.forceInline;
useIdentifierNameString = template.useIdentifierNameString;
checksNullReceiverBeforeAnySideEffect = template.checksNullReceiverBeforeAnySideEffect;
+ trivialInitializerInfo = template.trivialInitializerInfo;
}
public void setParameterUsages(ParameterUsagesInfo parametersUsages) {
@@ -928,6 +938,19 @@
this.method = method;
}
+ public void setStatic() {
+ this.accessFlags.setStatic();
+ }
+
+ public void withoutThisParameter() {
+ assert code != null;
+ if (code.isDexCode()) {
+ code = code.asDexCode().withoutThisParameter();
+ } else {
+ throw new Unreachable("Code " + code.getClass().getSimpleName() + " is not supported.");
+ }
+ }
+
public void setCode(Code code) {
this.code = code;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 02d2898..006da92 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -118,6 +118,10 @@
return this == other || isStrictSubtypeOf(other, appInfo);
}
+ public boolean hasSubtypes() {
+ return !directSubtypes.isEmpty();
+ }
+
public boolean isStrictSubtypeOf(DexType other, AppInfo appInfo) {
if (this == other) {
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Goto.java b/src/main/java/com/android/tools/r8/ir/code/Goto.java
index cd5fcc8..80a76fe 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Goto.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Goto.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.utils.CfgPrinter;
import java.util.List;
+import java.util.ListIterator;
public class Goto extends JumpInstruction {
@@ -103,6 +104,18 @@
// Nothing to do.
}
+ public boolean isTrivialGotoToTheNextBlock(IRCode code) {
+ BasicBlock thisBlock = getBlock();
+ ListIterator<BasicBlock> blockIterator = code.blocks.listIterator();
+ while (blockIterator.hasNext()) {
+ BasicBlock block = blockIterator.next();
+ if (thisBlock == block) {
+ return blockIterator.hasNext() && blockIterator.next() == getTarget();
+ }
+ }
+ return false;
+ }
+
@Override
public void buildCf(CfBuilder builder) {
builder.add(new CfGoto(builder.getLabel(getTarget())));
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index d9bf8d0..deae051 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -266,6 +266,14 @@
block = null;
}
+ public void removeOrReplaceByDebugLocalRead() {
+ getBlock().listIterator(this).removeOrReplaceByDebugLocalRead();
+ }
+
+ public void replace(Instruction newInstruction) {
+ getBlock().listIterator(this).replaceCurrentInstruction(newInstruction);
+ }
+
/**
* Returns true if the instruction is in the IR and therefore has a block.
*/
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index f523b46..af98f4b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -50,6 +50,7 @@
import com.android.tools.r8.ir.optimize.RedundantFieldLoadElimination;
import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
+import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.kotlin.KotlinInfo;
@@ -94,9 +95,10 @@
private final InterfaceMethodRewriter interfaceMethodRewriter;
private final LambdaMerger lambdaMerger;
private final ClassInliner classInliner;
+ private final ClassStaticizer classStaticizer;
private final InternalOptions options;
private final CfgPrinter printer;
- private final GraphLense graphLense;
+ private GraphLense graphLense;
private final CodeRewriter codeRewriter;
private final MemberValuePropagation memberValuePropagation;
private final LensCodeRewriter lensCodeRewriter;
@@ -144,7 +146,7 @@
if (enableWholeProgramOptimizations) {
assert appInfo.hasLiveness();
this.nonNullTracker = new NonNullTracker();
- this.inliner = new Inliner(appInfo.withLiveness(), graphLense, options);
+ this.inliner = new Inliner(this, options);
this.outliner = new Outliner(appInfo.withLiveness(), options);
this.memberValuePropagation =
options.enableValuePropagation ?
@@ -182,6 +184,17 @@
? new ClassInliner(
appInfo.dexItemFactory, lambdaRewriter, options.classInliningInstructionLimit)
: null;
+ this.classStaticizer = options.enableClassStaticizer && appInfo.hasLiveness()
+ ? new ClassStaticizer(appInfo.withLiveness(), this) : null;
+ }
+
+ public void setGraphLense(GraphLense graphLense) {
+ assert graphLense != null;
+ this.graphLense = graphLense;
+ }
+
+ public GraphLense getGraphLense() {
+ return graphLense;
}
/**
@@ -257,6 +270,18 @@
}
}
+ private void staticizeClasses(OptimizationFeedback feedback) {
+ if (classStaticizer != null) {
+ classStaticizer.staticizeCandidates(feedback);
+ }
+ }
+
+ private void collectStaticizerCandidates(DexApplication application) {
+ if (classStaticizer != null) {
+ classStaticizer.collectCandidates(application);
+ }
+ }
+
private void desugarInterfaceMethods(
Builder<?> builder, InterfaceMethodRewriter.Flavor includeAllResources) {
if (interfaceMethodRewriter != null) {
@@ -408,6 +433,7 @@
throws ExecutionException {
removeLambdaDeserializationMethods();
collectLambdaMergingCandidates(application);
+ collectStaticizerCandidates(application);
// The process is in two phases.
// 1) Subject all DexEncodedMethods to optimization (except outlining).
@@ -438,6 +464,8 @@
Builder<?> builder = application.builder();
builder.setHighestSortingString(highestSortingString);
+ staticizeClasses(directFeedback);
+
// Second inlining pass for dealing with double inline callers.
if (inliner != null) {
// Use direct feedback still, since methods after inlining may
@@ -676,6 +704,11 @@
}
}
+ if (classStaticizer != null) {
+ classStaticizer.fixupMethodCode(method, code);
+ assert code.isConsistentSSA();
+ }
+
if (identifierNameStringMarker != null) {
identifierNameStringMarker.decoupleIdentifierNameStringsInMethod(method, code);
assert code.isConsistentSSA();
@@ -802,6 +835,9 @@
codeRewriter.identifyTrivialInitializer(method, code, feedback);
}
codeRewriter.identifyParameterUsages(method, code, feedback);
+ if (classStaticizer != null) {
+ classStaticizer.examineMethodCode(method, code);
+ }
if (options.canHaveNumberConversionRegisterAllocationBug()) {
codeRewriter.workaroundNumberConversionRegisterAllocationBug(code);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index d87a508..a8059bd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -953,6 +953,14 @@
continue;
}
+ if (insn.isGoto()) {
+ // Trivial goto to the next block.
+ if (insn.asGoto().isTrivialGotoToTheNextBlock(code)) {
+ continue;
+ }
+ return null;
+ }
+
// Other instructions make the instance initializer not eligible.
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 1cbd2d9..04cd083 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -43,8 +43,8 @@
public class Inliner {
private static final int INITIAL_INLINING_INSTRUCTION_ALLOWANCE = 1500;
+ private final IRConverter converter;
protected final AppInfoWithLiveness appInfo;
- private final GraphLense graphLense;
final InternalOptions options;
// State for inlining methods which are known to be called twice.
@@ -55,12 +55,9 @@
private final Set<DexMethod> blackList = Sets.newIdentityHashSet();
- public Inliner(
- AppInfoWithLiveness appInfo,
- GraphLense graphLense,
- InternalOptions options) {
- this.appInfo = appInfo;
- this.graphLense = graphLense;
+ public Inliner(IRConverter converter, InternalOptions options) {
+ this.converter = converter;
+ this.appInfo = converter.appInfo.withLiveness();
this.options = options;
fillInBlackList(appInfo);
}
@@ -588,11 +585,11 @@
}
assert invokePosition.callerPosition == null
|| invokePosition.getOutermostCaller().method
- == graphLense.getOriginalMethodSignature(method.method);
+ == converter.getGraphLense().getOriginalMethodSignature(method.method);
IRCode inlinee =
- result.buildInliningIR(
- code.valueNumberGenerator, appInfo, graphLense, options, invokePosition);
+ result.buildInliningIR(code.valueNumberGenerator,
+ appInfo, converter.getGraphLense(), options, invokePosition);
if (inlinee != null) {
// TODO(64432527): Get rid of this additional check by improved inlining.
if (block.hasCatchHandlers() && inlinee.computeNormalExitBlocks().isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
new file mode 100644
index 0000000..9b5af03
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -0,0 +1,628 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer;
+
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.OptimizationFeedback;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
+
+public final class ClassStaticizer {
+ final AppInfoWithLiveness appInfo;
+ final DexItemFactory factory;
+ final IRConverter converter;
+
+ private enum Phase {
+ None, Examine, Fixup
+ }
+
+ private Phase phase = Phase.None;
+ private BiConsumer<DexEncodedMethod, IRCode> fixupStrategy = null;
+
+ // Represents a staticizing candidate with all information
+ // needed for staticizing.
+ final class CandidateInfo {
+ final DexProgramClass candidate;
+ final DexEncodedField singletonField;
+ final AtomicBoolean preserveRead = new AtomicBoolean(false);
+ // Number of singleton field writes.
+ final AtomicInteger fieldWrites = new AtomicInteger();
+ // Number of instances created.
+ final AtomicInteger instancesCreated = new AtomicInteger();
+ final Set<DexEncodedMethod> referencedFrom = Sets.newConcurrentHashSet();
+ final AtomicReference<DexEncodedMethod> constructor = new AtomicReference<>();
+
+ CandidateInfo(DexProgramClass candidate, DexEncodedField singletonField) {
+ assert candidate != null;
+ assert singletonField != null;
+ this.candidate = candidate;
+ this.singletonField = singletonField;
+
+ // register itself
+ candidates.put(candidate.type, this);
+ }
+
+ boolean isHostClassInitializer(DexEncodedMethod method) {
+ return factory.isClassConstructor(method.method) && method.method.holder == hostType();
+ }
+
+ DexType hostType() {
+ return singletonField.field.clazz;
+ }
+
+ DexClass hostClass() {
+ DexClass hostClass = appInfo.definitionFor(hostType());
+ assert hostClass != null;
+ return hostClass;
+ }
+
+ CandidateInfo invalidate() {
+ candidates.remove(candidate.type);
+ return null;
+ }
+ }
+
+ // The map storing all the potential candidates for staticizing.
+ final ConcurrentHashMap<DexType, CandidateInfo> candidates = new ConcurrentHashMap<>();
+
+ public ClassStaticizer(AppInfoWithLiveness appInfo, IRConverter converter) {
+ this.appInfo = appInfo;
+ this.factory = appInfo.dexItemFactory;
+ this.converter = converter;
+ }
+
+ // Before doing any usage-based analysis we collect a set of classes that can be
+ // candidates for staticizing. This analysis is very simple, but minimizes the
+ // set of eligible classes staticizer tracks and thus time and memory it needs.
+ public final void collectCandidates(DexApplication app) {
+ Set<DexType> notEligible = Sets.newIdentityHashSet();
+ Map<DexType, DexEncodedField> singletonFields = new HashMap<>();
+
+ app.classes().forEach(cls -> {
+ // We only consider classes eligible for staticizing if there is just
+ // one single static field in the whole app which has a type of this
+ // class. This field will be considered to be a candidate for a singleton
+ // field. The requirements for the initialization of this field will be
+ // checked later.
+ for (DexEncodedField field : cls.staticFields()) {
+ DexType type = field.field.type;
+ if (singletonFields.put(type, field) != null) {
+ // There is already candidate singleton field found.
+ notEligible.add(type);
+ }
+ }
+
+ // Don't allow fields with this candidate types.
+ for (DexEncodedField field : cls.instanceFields()) {
+ notEligible.add(field.field.type);
+ }
+
+ // Let's also assume no methods should take or return a
+ // value of this type.
+ for (DexEncodedMethod method : cls.methods()) {
+ DexProto proto = method.method.proto;
+ notEligible.add(proto.returnType);
+ notEligible.addAll(Arrays.asList(proto.parameters.values));
+ }
+
+ // High-level limitations on what classes we consider eligible.
+ if (cls.isInterface() || // Must not be an interface or an abstract class.
+ cls.accessFlags.isAbstract() ||
+ // Don't support candidates with instance fields
+ cls.instanceFields().length > 0 ||
+ // Only support classes directly extending java.lang.Object
+ cls.superType != factory.objectType ||
+ // Instead of requiring the class being final,
+ // just ensure it does not have subtypes
+ cls.type.hasSubtypes() ||
+ // Staticizing classes implementing interfaces is more
+ // difficult, so don't support it until we really need it.
+ !cls.interfaces.isEmpty()) {
+ notEligible.add(cls.type);
+ }
+ });
+
+ // Finalize the set of the candidates.
+ app.classes().forEach(cls -> {
+ DexType type = cls.type;
+ if (!notEligible.contains(type)) {
+ DexEncodedField field = singletonFields.get(type);
+ if (field != null && // Singleton field found
+ !field.accessFlags.isVolatile() && // Don't remove volatile fields.
+ !isPinned(cls, field)) { // Don't remove pinned objects.
+ assert field.accessFlags.isStatic();
+ // Note: we don't check that the field is final, since we will analyze
+ // later how and where it is initialized.
+ new CandidateInfo(cls, field); // will self-register
+ }
+ }
+ });
+
+ // Next phase -- examine code for candidate usages
+ phase = Phase.Examine;
+ }
+
+ private boolean isPinned(DexClass clazz, DexEncodedField singletonField) {
+ if (appInfo.isPinned(clazz.type) || appInfo.isPinned(singletonField.field)) {
+ return true;
+ }
+ for (DexEncodedMethod method : clazz.methods()) {
+ if (!method.isStaticMethod() && appInfo.isPinned(method.method)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Check staticizing candidates' usages to ensure the candidate can be staticized.
+ //
+ // The criteria for type CANDIDATE to be eligible for staticizing fall into
+ // these categories:
+ //
+ // * checking that there is only one instance of the class created, and it is created
+ // inside the host class initializer, and it is guaranteed that nobody can access this
+ // field before it is assigned.
+ //
+ // * no other singleton field writes (except for those used to store the only candidate
+ // class instance described above) are allowed.
+ //
+ // * values read from singleton field should only be used for instance method calls.
+ //
+ // NOTE: there are more criteria eligible class needs to satisfy to be actually staticized,
+ // those will be checked later in staticizeCandidates().
+ //
+ // This method also collects all DexEncodedMethod instances that need to be rewritten if
+ // appropriate candidate is staticized. Essentially anything that references instance method
+ // or field defined in the class.
+ //
+ // NOTE: can be called concurrently.
+ public final void examineMethodCode(DexEncodedMethod method, IRCode code) {
+ if (phase != Phase.Examine) {
+ return;
+ }
+
+ Set<Instruction> alreadyProcessed = Sets.newIdentityHashSet();
+
+ CandidateInfo receiverClassCandidateInfo = candidates.get(method.method.holder);
+ Value receiverValue = code.getThis(); // NOTE: is null for static methods.
+ if (receiverClassCandidateInfo != null && receiverValue != null) {
+ // We are inside an instance method of candidate class (not an instance initializer
+ // which we will check later), check if all the references to 'this' are valid
+ // (the call will invalidate the candidate if some of them are not valid).
+ analyzeAllValueUsers(receiverClassCandidateInfo,
+ receiverValue, factory.isConstructor(method.method));
+
+ // If the candidate is still valid, ignore all instructions
+ // we treat as valid usages on receiver.
+ if (candidates.get(method.method.holder) != null) {
+ alreadyProcessed.addAll(receiverValue.uniqueUsers());
+ }
+ }
+
+ ListIterator<Instruction> iterator =
+ Lists.newArrayList(code.instructionIterator()).listIterator();
+ while (iterator.hasNext()) {
+ Instruction instruction = iterator.next();
+ if (alreadyProcessed.contains(instruction)) {
+ continue;
+ }
+
+ if (instruction.isNewInstance()) {
+ // Check the class being initialized against valid staticizing candidates.
+ NewInstance newInstance = instruction.asNewInstance();
+ CandidateInfo candidateInfo = processInstantiation(method, iterator, newInstance);
+ if (candidateInfo != null) {
+ // For host class initializers having eligible instantiation we also want to
+ // ensure that the rest of the initializer consist of code w/o side effects.
+ // This must guarantee that removing field access will not result in missing side
+ // effects, otherwise we can still staticize, but cannot remove singleton reads.
+ while (iterator.hasNext()) {
+ if (!isAllowedInHostClassInitializer(method.method.holder, iterator.next(), code)) {
+ candidateInfo.preserveRead.set(true);
+ iterator.previous();
+ break;
+ }
+ // Ignore just read instruction.
+ }
+ candidateInfo.referencedFrom.add(method);
+ }
+ continue;
+ }
+
+ if (instruction.isStaticPut()) {
+ // Check the field being written to: no writes to singleton fields are allowed
+ // except for those processed in processInstantiation(...).
+ DexType candidateType = instruction.asStaticPut().getField().type;
+ CandidateInfo candidateInfo = candidates.get(candidateType);
+ if (candidateInfo != null) {
+ candidateInfo.invalidate();
+ }
+ continue;
+ }
+
+ if (instruction.isStaticGet()) {
+ // Check the field being read: make sure all usages are valid.
+ CandidateInfo info = processStaticFieldRead(instruction.asStaticGet());
+ if (info != null) {
+ info.referencedFrom.add(method);
+ // If the candidate still valid, ignore all usages in further analysis.
+ Value value = instruction.outValue();
+ if (value != null) {
+ alreadyProcessed.addAll(value.uniqueUsers());
+ }
+ }
+ continue;
+ }
+
+ if (instruction.isInvokeMethodWithReceiver()) {
+ DexMethod invokedMethod = instruction.asInvokeMethodWithReceiver().getInvokedMethod();
+ CandidateInfo candidateInfo = candidates.get(invokedMethod.holder);
+ if (candidateInfo != null) {
+ // A call to instance method of the candidate class we don't know how to deal with.
+ candidateInfo.invalidate();
+ }
+ continue;
+ }
+
+ if (instruction.isInvokeCustom()) {
+ // Just invalidate any candidates referenced from non-static context.
+ CallSiteReferencesInvalidator invalidator = new CallSiteReferencesInvalidator();
+ invalidator.registerCallSite(instruction.asInvokeCustom().getCallSite());
+ continue;
+ }
+
+ if (instruction.isInstanceGet() || instruction.isInstancePut()) {
+ DexField fieldReferenced = instruction.asFieldInstruction().getField();
+ CandidateInfo candidateInfo = candidates.get(fieldReferenced.clazz);
+ if (candidateInfo != null) {
+ // Reads/writes to instance field of the candidate class are not supported.
+ candidateInfo.invalidate();
+ }
+ continue;
+ }
+ }
+ }
+
+ private boolean isAllowedInHostClassInitializer(
+ DexType host, Instruction insn, IRCode code) {
+ return (insn.isStaticPut() && insn.asStaticPut().getField().clazz == host) ||
+ insn.isConstNumber() ||
+ insn.isConstString() ||
+ (insn.isGoto() && insn.asGoto().isTrivialGotoToTheNextBlock(code)) ||
+ insn.isReturn();
+ }
+
+ private CandidateInfo processInstantiation(
+ DexEncodedMethod method, ListIterator<Instruction> iterator, NewInstance newInstance) {
+
+ DexType candidateType = newInstance.clazz;
+ CandidateInfo candidateInfo = candidates.get(candidateType);
+ if (candidateInfo == null) {
+ return null; // Not interested.
+ }
+
+ if (iterator.previousIndex() != 0) {
+ // Valid new instance must be the first instruction in the class initializer
+ return candidateInfo.invalidate();
+ }
+
+ if (!candidateInfo.isHostClassInitializer(method)) {
+ // A valid candidate must only have one instantiation which is
+ // done in the static initializer of the host class.
+ return candidateInfo.invalidate();
+ }
+
+ if (candidateInfo.instancesCreated.incrementAndGet() > 1) {
+ // Only one instance must be ever created.
+ return candidateInfo.invalidate();
+ }
+
+ Value candidateValue = newInstance.dest();
+ if (candidateValue == null) {
+ // Must be assigned to a singleton field.
+ return candidateInfo.invalidate();
+ }
+
+ if (candidateValue.numberOfPhiUsers() > 0) {
+ return candidateInfo.invalidate();
+ }
+
+ if (candidateValue.numberOfUsers() != 2) {
+ // We expect only two users for each instantiation: constructor call and
+ // static field write. We only check count here, since the exact instructions
+ // will be checked later.
+ return candidateInfo.invalidate();
+ }
+
+ // Check usages. Currently we only support the patterns like:
+ //
+ // static constructor void <clinit>() {
+ // new-instance v0, <candidate-type>
+ // (opt) const/4 v1, #int 0 // (optional)
+ // invoke-direct {v0, ...}, void <candidate-type>.<init>(...)
+ // sput-object v0, <instance-field>
+ // ...
+ // ...
+ //
+ // In case we guarantee candidate constructor does not access <instance-field>
+ // directly or indirectly we can guarantee that all the potential reads get
+ // same non-null value.
+
+ // Skip potential constant instructions
+ while (iterator.hasNext() && isNonThrowingConstInstruction(iterator.next())) {
+ // Intentionally empty.
+ }
+ iterator.previous();
+
+ if (!iterator.hasNext()) {
+ return candidateInfo.invalidate();
+ }
+ if (!isValidInitCall(candidateInfo, iterator.next(), candidateValue, candidateType)) {
+ iterator.previous();
+ return candidateInfo.invalidate();
+ }
+
+ if (!iterator.hasNext()) {
+ return candidateInfo.invalidate();
+ }
+ if (!isValidStaticPut(candidateInfo, iterator.next())) {
+ iterator.previous();
+ return candidateInfo.invalidate();
+ }
+ if (candidateInfo.fieldWrites.incrementAndGet() > 1) {
+ return candidateInfo.invalidate();
+ }
+
+ return candidateInfo;
+ }
+
+ private boolean isNonThrowingConstInstruction(Instruction instruction) {
+ return instruction.isConstInstruction() && !instruction.instructionTypeCanThrow();
+ }
+
+ private boolean isValidInitCall(
+ CandidateInfo info, Instruction instruction, Value candidateValue, DexType candidateType) {
+ if (!instruction.isInvokeDirect()) {
+ return false;
+ }
+
+ // Check constructor.
+ InvokeDirect invoke = instruction.asInvokeDirect();
+ DexEncodedMethod methodInvoked = appInfo.lookupDirectTarget(invoke.getInvokedMethod());
+ List<Value> values = invoke.inValues();
+
+ if (values.lastIndexOf(candidateValue) != 0 ||
+ methodInvoked == null || methodInvoked.method.holder != candidateType) {
+ return false;
+ }
+
+ // Check arguments.
+ for (int i = 1; i < values.size(); i++) {
+ if (!values.get(i).definition.isConstInstruction()) {
+ return false;
+ }
+ }
+
+ DexEncodedMethod previous = info.constructor.getAndSet(methodInvoked);
+ assert previous == null;
+ return true;
+ }
+
+ private boolean isValidStaticPut(CandidateInfo info, Instruction instruction) {
+ if (!instruction.isStaticPut()) {
+ return false;
+ }
+ // Allow single assignment to a singleton field.
+ StaticPut staticPut = instruction.asStaticPut();
+ DexEncodedField fieldAccessed =
+ appInfo.lookupStaticTarget(staticPut.getField().clazz, staticPut.getField());
+ return fieldAccessed == info.singletonField;
+ }
+
+ // Static field get: can be a valid singleton field for a
+ // candidate in which case we should check if all the usages of the
+ // value read are eligible.
+ private CandidateInfo processStaticFieldRead(StaticGet staticGet) {
+ DexField field = staticGet.getField();
+ DexType candidateType = field.type;
+ CandidateInfo candidateInfo = candidates.get(candidateType);
+ if (candidateInfo == null) {
+ return null;
+ }
+
+ assert candidateInfo.singletonField == appInfo.lookupStaticTarget(field.clazz, field)
+ : "Added reference after collectCandidates(...)?";
+
+ Value singletonValue = staticGet.dest();
+ if (singletonValue != null) {
+ candidateInfo = analyzeAllValueUsers(candidateInfo, singletonValue, false);
+ }
+ return candidateInfo;
+ }
+
+ private CandidateInfo analyzeAllValueUsers(
+ CandidateInfo candidateInfo, Value value, boolean ignoreSuperClassInitInvoke) {
+ assert value != null;
+
+ if (value.numberOfPhiUsers() > 0) {
+ return candidateInfo.invalidate();
+ }
+
+ for (Instruction user : value.uniqueUsers()) {
+ if (user.isInvokeVirtual() || user.isInvokeDirect() /* private methods */) {
+ InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
+ DexMethod methodReferenced = invoke.getInvokedMethod();
+ if (factory.isConstructor(methodReferenced)) {
+ assert user.isInvokeDirect();
+ if (ignoreSuperClassInitInvoke &&
+ invoke.inValues().lastIndexOf(value) == 0 &&
+ methodReferenced == factory.objectMethods.constructor) {
+ // If we are inside candidate constructor and analyzing usages
+ // of the receiver, we want to ignore invocations of superclass
+ // constructor which will be removed after staticizing.
+ continue;
+ }
+ return candidateInfo.invalidate();
+ }
+ DexEncodedMethod methodInvoked = user.isInvokeDirect()
+ ? appInfo.lookupDirectTarget(methodReferenced)
+ : appInfo.lookupVirtualTarget(methodReferenced.holder, methodReferenced);
+ if (invoke.inValues().lastIndexOf(value) == 0 &&
+ methodInvoked != null && methodInvoked.method.holder == candidateInfo.candidate.type) {
+ continue;
+ }
+ }
+
+ // All other users are not allowed.
+ return candidateInfo.invalidate();
+ }
+
+ return candidateInfo;
+ }
+
+ // Perform staticizing candidates:
+ //
+ // 1. After filtering candidates based on usage, finalize the list of candidates by
+ // filtering out candidates which don't satisfy the requirements:
+ //
+ // * there must be one instance of the class
+ // * constructor of the class used to create this instance must be a trivial one
+ // * class initializer should only be present if candidate itself is own host
+ // * no abstract or native instance methods
+ //
+ // 2. Rewrite instance methods of classes being staticized into static ones
+ // 3. Rewrite methods referencing staticized members, also remove instance creation
+ //
+ public final void staticizeCandidates(OptimizationFeedback feedback) {
+ phase = Phase.None; // We are done with processing/examining methods.
+ new StaticizingProcessor(this).run(feedback);
+ }
+
+ public final void fixupMethodCode(DexEncodedMethod method, IRCode code) {
+ if (phase == Phase.Fixup) {
+ assert fixupStrategy != null;
+ fixupStrategy.accept(method, code);
+ }
+ }
+
+ void setFixupStrategy(BiConsumer<DexEncodedMethod, IRCode> strategy) {
+ assert phase == Phase.None;
+ assert strategy != null;
+ phase = Phase.Fixup;
+ fixupStrategy = strategy;
+ }
+
+ void cleanFixupStrategy() {
+ assert phase == Phase.Fixup;
+ assert fixupStrategy != null;
+ phase = Phase.None;
+ fixupStrategy = null;
+ }
+
+ private class CallSiteReferencesInvalidator extends UseRegistry {
+ private boolean registerMethod(DexMethod method) {
+ registerTypeReference(method.holder);
+ registerProto(method.proto);
+ return true;
+ }
+
+ private boolean registerField(DexField field) {
+ registerTypeReference(field.clazz);
+ registerTypeReference(field.type);
+ return true;
+ }
+
+ @Override
+ public boolean registerInvokeVirtual(DexMethod method) {
+ return registerMethod(method);
+ }
+
+ @Override
+ public boolean registerInvokeDirect(DexMethod method) {
+ return registerMethod(method);
+ }
+
+ @Override
+ public boolean registerInvokeStatic(DexMethod method) {
+ return registerMethod(method);
+ }
+
+ @Override
+ public boolean registerInvokeInterface(DexMethod method) {
+ return registerMethod(method);
+ }
+
+ @Override
+ public boolean registerInvokeSuper(DexMethod method) {
+ return registerMethod(method);
+ }
+
+ @Override
+ public boolean registerInstanceFieldWrite(DexField field) {
+ return registerField(field);
+ }
+
+ @Override
+ public boolean registerInstanceFieldRead(DexField field) {
+ return registerField(field);
+ }
+
+ @Override
+ public boolean registerNewInstance(DexType type) {
+ return registerTypeReference(type);
+ }
+
+ @Override
+ public boolean registerStaticFieldRead(DexField field) {
+ return registerField(field);
+ }
+
+ @Override
+ public boolean registerStaticFieldWrite(DexField field) {
+ return registerField(field);
+ }
+
+ @Override
+ public boolean registerTypeReference(DexType type) {
+ CandidateInfo candidateInfo = candidates.get(type);
+ if (candidateInfo != null) {
+ candidateInfo.invalidate();
+ }
+ return true;
+ }
+ }
+}
+
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
new file mode 100644
index 0000000..4f6c33c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableMap;
+
+class ClassStaticizerGraphLense extends NestedGraphLense {
+ ClassStaticizerGraphLense(GraphLense previous, DexItemFactory factory,
+ BiMap<DexField, DexField> fieldMapping, BiMap<DexMethod, DexMethod> methodMapping) {
+ super(ImmutableMap.of(),
+ methodMapping,
+ fieldMapping,
+ fieldMapping.inverse(),
+ methodMapping.inverse(),
+ previous,
+ factory);
+ }
+
+ @Override
+ protected Type mapInvocationType(
+ DexMethod newMethod, DexMethod originalMethod,
+ DexEncodedMethod context, Type type) {
+ if (methodMap.get(originalMethod) == newMethod) {
+ assert type == Type.VIRTUAL || type == Type.DIRECT;
+ return Type.STATIC;
+ }
+ return super.mapInvocationType(newMethod, originalMethod, context, type);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
new file mode 100644
index 0000000..c5993dd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -0,0 +1,444 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer;
+
+import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CallSiteInformation;
+import com.android.tools.r8.ir.conversion.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.Outliner;
+import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer.CandidateInfo;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+final class StaticizingProcessor {
+ private final ClassStaticizer classStaticizer;
+
+ private final Set<DexEncodedMethod> referencingExtraMethods = Sets.newIdentityHashSet();
+ private final Map<DexEncodedMethod, CandidateInfo> hostClassInits = new IdentityHashMap<>();
+ private final Set<DexEncodedMethod> methodsToBeStaticized = Sets.newIdentityHashSet();
+ private final Map<DexField, CandidateInfo> singletonFields = new IdentityHashMap<>();
+ private final Map<DexType, DexType> candidateToHostMapping = new IdentityHashMap<>();
+
+ StaticizingProcessor(ClassStaticizer classStaticizer) {
+ this.classStaticizer = classStaticizer;
+ }
+
+ final void run(OptimizationFeedback feedback) {
+ // Filter out candidates based on the information we collected
+ // while examining methods.
+ finalEligibilityCheck();
+
+ // Prepare interim data.
+ prepareCandidates();
+
+ // Process all host class initializers (only remove instantiations).
+ processMethods(hostClassInits.keySet().stream(), this::removeCandidateInstantiation, feedback);
+
+ // Process instance methods to be staticized (only remove references to 'this').
+ processMethods(methodsToBeStaticized.stream(), this::removeReferencesToThis, feedback);
+
+ // Convert instance methods into static methods with an extra parameter.
+ Set<DexEncodedMethod> staticizedMethods = staticizeMethodSymbols();
+
+ // Process all other methods that may reference singleton fields
+ // and call methods on them. (Note that we exclude the former instance methods,
+ // but include new static methods created as a result of staticizing.
+ Stream<DexEncodedMethod> methods = Streams.concat(
+ referencingExtraMethods.stream(),
+ staticizedMethods.stream(),
+ hostClassInits.keySet().stream());
+ processMethods(methods, this::rewriteReferences, feedback);
+ }
+
+ private void finalEligibilityCheck() {
+ Iterator<Entry<DexType, CandidateInfo>> it =
+ classStaticizer.candidates.entrySet().iterator();
+ while (it.hasNext()) {
+ Entry<DexType, CandidateInfo> entry = it.next();
+ DexType candidateType = entry.getKey();
+ CandidateInfo info = entry.getValue();
+ DexProgramClass candidateClass = info.candidate;
+ DexType candidateHostType = info.hostType();
+ DexEncodedMethod constructorUsed = info.constructor.get();
+
+ int instancesCreated = info.instancesCreated.get();
+ assert instancesCreated == info.fieldWrites.get();
+ assert instancesCreated <= 1;
+ assert (instancesCreated == 0) == (constructorUsed == null);
+
+ // CHECK: One instance, one singleton field, known constructor
+ if (instancesCreated == 0) {
+ // Give up on the candidate, if there are any reads from instance
+ // field the user should read null.
+ it.remove();
+ continue;
+ }
+
+ // CHECK: instance initializer used to create an instance is trivial.
+ // NOTE: Along with requirement that candidate does not have instance
+ // fields this should guarantee that the constructor is empty.
+ assert candidateClass.instanceFields().length == 0;
+ assert constructorUsed.isProcessed();
+ TrivialInitializer trivialInitializer =
+ constructorUsed.getOptimizationInfo().getTrivialInitializerInfo();
+ if (trivialInitializer == null) {
+ it.remove();
+ continue;
+ }
+
+ // CHECK: class initializer should only be present if candidate itself is its own host.
+ DexEncodedMethod classInitializer = candidateClass.getClassInitializer();
+ assert classInitializer != null || candidateType != candidateHostType;
+ if (classInitializer != null && candidateType != candidateHostType) {
+ it.remove();
+ continue;
+ }
+
+ // CHECK: no abstract or native instance methods.
+ if (Streams.stream(candidateClass.methods()).anyMatch(
+ method -> !method.isStaticMethod() &&
+ (method.accessFlags.isAbstract() || method.accessFlags.isNative()))) {
+ it.remove();
+ continue;
+ }
+ }
+ }
+
+ private void prepareCandidates() {
+ Set<DexEncodedMethod> removedInstanceMethods = Sets.newIdentityHashSet();
+
+ for (CandidateInfo candidate : classStaticizer.candidates.values()) {
+ DexProgramClass candidateClass = candidate.candidate;
+ // Host class initializer
+ DexClass hostClass = candidate.hostClass();
+ DexEncodedMethod hostClassInitializer = hostClass.getClassInitializer();
+ assert hostClassInitializer != null;
+ CandidateInfo previous = hostClassInits.put(hostClassInitializer, candidate);
+ assert previous == null;
+
+ // Collect instance methods to be staticized.
+ for (DexEncodedMethod method : candidateClass.methods()) {
+ if (!method.isStaticMethod()) {
+ removedInstanceMethods.add(method);
+ if (!factory().isConstructor(method.method)) {
+ methodsToBeStaticized.add(method);
+ }
+ }
+ }
+ singletonFields.put(candidate.singletonField.field, candidate);
+ referencingExtraMethods.addAll(candidate.referencedFrom);
+ }
+
+ referencingExtraMethods.removeAll(removedInstanceMethods);
+ }
+
+ private void processMethods(Stream<DexEncodedMethod> methods,
+ BiConsumer<DexEncodedMethod, IRCode> strategy, OptimizationFeedback feedback) {
+ classStaticizer.setFixupStrategy(strategy);
+ methods.sorted(DexEncodedMethod::slowCompare).forEach(
+ method -> classStaticizer.converter.processMethod(method, feedback,
+ x -> false, CallSiteInformation.empty(), Outliner::noProcessing));
+ classStaticizer.cleanFixupStrategy();
+ }
+
+ private void removeCandidateInstantiation(DexEncodedMethod method, IRCode code) {
+ CandidateInfo candidateInfo = hostClassInits.get(method);
+ assert candidateInfo != null;
+
+ // Find and remove instantiation and its users.
+ InstructionIterator iterator = code.instructionIterator();
+ while (iterator.hasNext()) {
+ Instruction instruction = iterator.next();
+ if (instruction.isNewInstance() &&
+ instruction.asNewInstance().clazz == candidateInfo.candidate.type) {
+ // Remove all usages
+ // NOTE: requiring (a) the instance initializer to be trivial, (b) not allowing
+ // candidates with instance fields and (c) requiring candidate to directly
+ // extend java.lang.Object guarantees that the constructor is actually
+ // empty and does not need to be inlined.
+ assert candidateInfo.candidate.superType == factory().objectType;
+ assert candidateInfo.candidate.instanceFields().length == 0;
+
+ Value singletonValue = instruction.outValue();
+ assert singletonValue != null;
+ singletonValue.uniqueUsers().forEach(Instruction::removeOrReplaceByDebugLocalRead);
+ instruction.removeOrReplaceByDebugLocalRead();
+ return;
+ }
+ }
+
+ assert false : "Must always be able to find and remove the instantiation";
+ }
+
+ private void removeReferencesToThis(DexEncodedMethod method, IRCode code) {
+ fixupStaticizedValueUsers(code, code.getThis());
+ }
+
+ private void rewriteReferences(DexEncodedMethod method, IRCode code) {
+ // Process all singleton field reads and rewrite their users.
+ List<StaticGet> singletonFieldReads =
+ Streams.stream(code.instructionIterator())
+ .filter(Instruction::isStaticGet)
+ .map(Instruction::asStaticGet)
+ .filter(get -> singletonFields.containsKey(get.getField()))
+ .collect(Collectors.toList());
+
+ singletonFieldReads.forEach(read -> {
+ CandidateInfo candidateInfo = singletonFields.get(read.getField());
+ assert candidateInfo != null;
+ Value value = read.dest();
+ if (value != null) {
+ fixupStaticizedValueUsers(code, value);
+ }
+ if (!candidateInfo.preserveRead.get()) {
+ read.removeOrReplaceByDebugLocalRead();
+ }
+ });
+
+ if (!candidateToHostMapping.isEmpty()) {
+ remapMovedCandidates(code);
+ }
+ }
+
+ // Fixup value usages: rewrites all method calls so that they point to static methods.
+ private void fixupStaticizedValueUsers(IRCode code, Value thisValue) {
+ assert thisValue != null;
+ assert thisValue.numberOfPhiUsers() == 0;
+
+ for (Instruction user : thisValue.uniqueUsers()) {
+ assert user.isInvokeVirtual() || user.isInvokeDirect();
+ InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
+ Value newValue = null;
+ Value outValue = invoke.outValue();
+ if (outValue != null) {
+ newValue = code.createValue(outValue.outType());
+ DebugLocalInfo localInfo = outValue.getLocalInfo();
+ if (localInfo != null) {
+ newValue.setLocalInfo(localInfo);
+ }
+ }
+ List<Value> args = invoke.inValues();
+ invoke.replace(new InvokeStatic(
+ invoke.getInvokedMethod(), newValue, args.subList(1, args.size())));
+ }
+
+ assert thisValue.numberOfUsers() == 0;
+ }
+
+ private void remapMovedCandidates(IRCode code) {
+ InstructionIterator it = code.instructionIterator();
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+
+ if (instruction.isStaticGet()) {
+ StaticGet staticGet = instruction.asStaticGet();
+ DexField field = mapFieldIfMoved(staticGet.getField());
+ if (field != staticGet.getField()) {
+ Value outValue = staticGet.dest();
+ assert outValue != null;
+ it.replaceCurrentInstruction(
+ new StaticGet(
+ MemberType.fromDexType(field.type),
+ code.createValue(ValueType.fromDexType(field.type), outValue.getLocalInfo()),
+ field
+ )
+ );
+ }
+ continue;
+ }
+
+ if (instruction.isStaticPut()) {
+ StaticPut staticPut = instruction.asStaticPut();
+ DexField field = mapFieldIfMoved(staticPut.getField());
+ if (field != staticPut.getField()) {
+ it.replaceCurrentInstruction(
+ new StaticPut(MemberType.fromDexType(field.type), staticPut.inValue(), field)
+ );
+ }
+ continue;
+ }
+
+ if (instruction.isInvokeStatic()) {
+ InvokeStatic invoke = instruction.asInvokeStatic();
+ DexMethod method = invoke.getInvokedMethod();
+ DexType hostType = candidateToHostMapping.get(method.holder);
+ if (hostType != null) {
+ DexMethod newMethod = factory().createMethod(hostType, method.proto, method.name);
+ Value outValue = invoke.outValue();
+ Value newOutValue = method.proto.returnType.isVoidType() ? null
+ : code.createValue(
+ ValueType.fromDexType(method.proto.returnType),
+ outValue == null ? null : outValue.getLocalInfo());
+ it.replaceCurrentInstruction(
+ new InvokeStatic(newMethod, newOutValue, invoke.inValues()));
+ }
+ continue;
+ }
+ }
+ }
+
+ private DexField mapFieldIfMoved(DexField field) {
+ DexType hostType = candidateToHostMapping.get(field.clazz);
+ if (hostType != null) {
+ field = factory().createField(hostType, field.type, field.name);
+ }
+ hostType = candidateToHostMapping.get(field.type);
+ if (hostType != null) {
+ field = factory().createField(field.clazz, hostType, field.name);
+ }
+ return field;
+ }
+
+ private Set<DexEncodedMethod> staticizeMethodSymbols() {
+ BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
+ BiMap<DexField, DexField> fieldMapping = HashBiMap.create();
+
+ Set<DexEncodedMethod> staticizedMethods = Sets.newIdentityHashSet();
+ for (CandidateInfo candidate : classStaticizer.candidates.values()) {
+ DexProgramClass candidateClass = candidate.candidate;
+
+ // Move instance methods into static ones.
+ List<DexEncodedMethod> newDirectMethods = new ArrayList<>();
+ for (DexEncodedMethod method : candidateClass.methods()) {
+ if (method.isStaticMethod()) {
+ newDirectMethods.add(method);
+ } else if (!factory().isConstructor(method.method)) {
+ DexEncodedMethod staticizedMethod = method.toStaticMethodWithoutThis();
+ newDirectMethods.add(staticizedMethod);
+ staticizedMethods.add(staticizedMethod);
+ methodMapping.put(method.method, staticizedMethod.method);
+ }
+ }
+ candidateClass.setVirtualMethods(DexEncodedMethod.EMPTY_ARRAY);
+ candidateClass.setDirectMethods(
+ newDirectMethods.toArray(new DexEncodedMethod[newDirectMethods.size()]));
+
+ // Consider moving static members from candidate into host.
+ DexType hostType = candidate.hostType();
+ if (candidateClass.type != hostType) {
+ DexClass hostClass = classStaticizer.appInfo.definitionFor(hostType);
+ assert hostClass != null;
+ if (!classMembersConflict(candidateClass, hostClass)) {
+ // Move all members of the candidate class into host class.
+ moveMembersIntoHost(staticizedMethods,
+ candidateClass, hostType, hostClass, methodMapping, fieldMapping);
+ }
+ }
+ }
+
+ if (!methodMapping.isEmpty() || fieldMapping.isEmpty()) {
+ classStaticizer.converter.setGraphLense(
+ new ClassStaticizerGraphLense(
+ classStaticizer.converter.getGraphLense(),
+ classStaticizer.factory,
+ fieldMapping,
+ methodMapping));
+ }
+ return staticizedMethods;
+ }
+
+ private boolean classMembersConflict(DexClass a, DexClass b) {
+ assert Streams.stream(a.methods()).allMatch(DexEncodedMethod::isStaticMethod);
+ assert a.instanceFields().length == 0;
+ return Stream.of(a.staticFields()).anyMatch(fld -> b.lookupField(fld.field) != null) ||
+ Streams.stream(a.methods()).anyMatch(method -> b.lookupMethod(method.method) != null);
+ }
+
+ private void moveMembersIntoHost(Set<DexEncodedMethod> staticizedMethods,
+ DexProgramClass candidateClass,
+ DexType hostType, DexClass hostClass,
+ BiMap<DexMethod, DexMethod> methodMapping,
+ BiMap<DexField, DexField> fieldMapping) {
+ candidateToHostMapping.put(candidateClass.type, hostType);
+
+ // Process static fields.
+ // Append fields first.
+ if (candidateClass.staticFields().length > 0) {
+ DexEncodedField[] oldFields = hostClass.staticFields();
+ DexEncodedField[] extraFields = candidateClass.staticFields();
+ DexEncodedField[] newFields = new DexEncodedField[oldFields.length + extraFields.length];
+ System.arraycopy(oldFields, 0, newFields, 0, oldFields.length);
+ System.arraycopy(extraFields, 0, newFields, oldFields.length, extraFields.length);
+ hostClass.setStaticFields(newFields);
+ }
+
+ // Fixup field types.
+ DexEncodedField[] staticFields = hostClass.staticFields();
+ for (int i = 0; i < staticFields.length; i++) {
+ DexEncodedField field = staticFields[i];
+ DexField newField = mapCandidateField(field.field, candidateClass.type, hostType);
+ if (newField != field.field) {
+ staticFields[i] = field.toTypeSubstitutedField(newField);
+ fieldMapping.put(field.field, newField);
+ }
+ }
+
+ // Process static methods.
+ if (candidateClass.directMethods().length > 0) {
+ DexEncodedMethod[] oldMethods = hostClass.directMethods();
+ DexEncodedMethod[] extraMethods = candidateClass.directMethods();
+ DexEncodedMethod[] newMethods = new DexEncodedMethod[oldMethods.length + extraMethods.length];
+ System.arraycopy(oldMethods, 0, newMethods, 0, oldMethods.length);
+ for (int i = 0; i < extraMethods.length; i++) {
+ DexEncodedMethod method = extraMethods[i];
+ DexEncodedMethod newMethod = method.toTypeSubstitutedMethod(
+ factory().createMethod(hostType, method.method.proto, method.method.name));
+ newMethods[oldMethods.length + i] = newMethod;
+ staticizedMethods.add(newMethod);
+ staticizedMethods.remove(method);
+ DexMethod originalMethod = methodMapping.inverse().get(method.method);
+ if (originalMethod == null) {
+ methodMapping.put(method.method, newMethod.method);
+ } else {
+ methodMapping.put(originalMethod, newMethod.method);
+ }
+ }
+ hostClass.setDirectMethods(newMethods);
+ }
+ }
+
+ private DexField mapCandidateField(DexField field, DexType candidateType, DexType hostType) {
+ return field.clazz != candidateType && field.type != candidateType ? field
+ : factory().createField(
+ field.clazz == candidateType ? hostType : field.clazz,
+ field.type == candidateType ? hostType : field.type,
+ field.name);
+ }
+
+ private DexItemFactory factory() {
+ return classStaticizer.factory;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 9c61a7a..f45a3de 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -69,17 +69,17 @@
/** The parameters are forwarded to MappedRange constructor, see explanation there. */
@Override
public void addMappedRange(
- Range obfuscatedRange,
+ Range minifiedRange,
MemberNaming.MethodSignature originalSignature,
Object originalRange,
- String obfuscatedName) {
+ String renamedName) {
mappedRangesByName
- .computeIfAbsent(obfuscatedName, k -> new ArrayList<>())
- .add(new MappedRange(obfuscatedRange, originalSignature, originalRange, obfuscatedName));
+ .computeIfAbsent(renamedName, k -> new ArrayList<>())
+ .add(new MappedRange(minifiedRange, originalSignature, originalRange, renamedName));
}
}
- /** List of MappedRanges that belong to the same obfuscated name. */
+ /** List of MappedRanges that belong to the same renamed name. */
public static class MappedRangesOfName {
private final List<MappedRange> mappedRanges;
@@ -94,14 +94,14 @@
public MappedRange firstRangeForLine(int line) {
MappedRange bestRange = null;
for (MappedRange range : mappedRanges) {
- if (range.obfuscatedRange == null) {
+ if (range.minifiedRange == null) {
if (bestRange == null) {
// This is an "a() -> b" mapping (no concrete line numbers), remember this if there'll
// be no better one.
bestRange = range;
}
- } else if (range.obfuscatedRange.contains(line)) {
- // Concrete obfuscated range found ("x:y:a()[:u[:v]] -> b")
+ } else if (range.minifiedRange.contains(line)) {
+ // Concrete minified range found ("x:y:a()[:u[:v]] -> b")
return range;
}
}
@@ -109,25 +109,25 @@
}
/**
- * Search for a MappedRange where the obfuscated range contains the specified {@code line} and
- * return that and the subsequent MappedRanges with the same obfuscated range. Return general
+ * Search for a MappedRange where the minified range contains the specified {@code line} and
+ * return that and the subsequent MappedRanges with the same minified range. Return general
* MappedRange ("a() -> b") if no concrete mapping found or empty list if nothing found.
*/
public List<MappedRange> allRangesForLine(int line) {
MappedRange noLineRange = null;
for (int i = 0; i < mappedRanges.size(); ++i) {
MappedRange rangeI = mappedRanges.get(i);
- if (rangeI.obfuscatedRange == null) {
+ if (rangeI.minifiedRange == null) {
if (noLineRange == null) {
// This is an "a() -> b" mapping (no concrete line numbers), remember this if there'll
// be no better one.
noLineRange = rangeI;
}
- } else if (rangeI.obfuscatedRange.contains(line)) {
- // Concrete obfuscated range found ("x:y:a()[:u[:v]] -> b")
+ } else if (rangeI.minifiedRange.contains(line)) {
+ // Concrete minified range found ("x:y:a()[:u[:v]] -> b")
int j = i + 1;
for (; j < mappedRanges.size(); ++j) {
- if (!Objects.equals(mappedRanges.get(j).obfuscatedRange, rangeI.obfuscatedRange)) {
+ if (!Objects.equals(mappedRanges.get(j).minifiedRange, rangeI.minifiedRange)) {
break;
}
}
@@ -166,10 +166,10 @@
/**
* Mapping from the renamed signature to the naming information for a member.
- * <p>
- * A renamed signature is a signature where the member's name has been obfuscated but not the type
+ *
+ * <p>A renamed signature is a signature where the member's name has been renamed but not the type
* information.
- **/
+ */
private final ImmutableMap<MethodSignature, MemberNaming> methodMembers;
private final ImmutableMap<FieldSignature, MemberNaming> fieldMembers;
@@ -325,22 +325,22 @@
}
/**
- * MappedRange describes an (original line numbers, signature) <-> (obfuscated line numbers)
+ * MappedRange describes an (original line numbers, signature) <-> (minified line numbers)
* mapping. It can describe 3 different things:
*
* <p>1. The method is renamed. The original source lines are preserved. The corresponding
* Proguard-map syntax is "a(...) -> b"
*
- * <p>2. The source lines of a method in the original range are renumbered to the obfuscated
- * range. In this case the {@link MappedRange#originalRange} is either a {@code Range} or null,
+ * <p>2. The source lines of a method in the original range are renumbered to the minified range.
+ * In this case the {@link MappedRange#originalRange} is either a {@code Range} or null,
* indicating that the original range is unknown or is the same as the {@link
- * MappedRange#obfuscatedRange}. The corresponding Proguard-map syntax is "x:y:a(...) -> b" or
+ * MappedRange#minifiedRange}. The corresponding Proguard-map syntax is "x:y:a(...) -> b" or
* "x:y:a(...):u:v -> b"
*
* <p>3. The source line of a method is the inlining caller of the previous {@code MappedRange}.
* In this case the {@link MappedRange@originalRange} is either an {@code int} or null, indicating
- * that the original source line is unknown, or may be identical to a line of the obfuscated
- * range. The corresponding Proguard-map syntax is "x:y:a(...) -> b" or "x:y:a(...):u -> b"
+ * that the original source line is unknown, or may be identical to a line of the minified range.
+ * The corresponding Proguard-map syntax is "x:y:a(...) -> b" or "x:y:a(...):u -> b"
*/
public static class MappedRange {
@@ -350,10 +350,10 @@
return nextSequenceNumber++;
}
- public final Range obfuscatedRange; // Can be null, if so then originalRange must also be null.
+ public final Range minifiedRange; // Can be null, if so then originalRange must also be null.
public final MethodSignature signature;
public final Object originalRange; // null, Integer or Range.
- public final String obfuscatedName;
+ public final String renamedName;
/**
* The sole purpose of {@link #sequenceNumber} is to preserve the order of members read from a
@@ -362,52 +362,49 @@
private final int sequenceNumber = getNextSequenceNumber();
private MappedRange(
- Range obfuscatedRange,
- MethodSignature signature,
- Object originalRange,
- String obfuscatedName) {
+ Range minifiedRange, MethodSignature signature, Object originalRange, String renamedName) {
- assert obfuscatedRange != null || originalRange == null;
+ assert minifiedRange != null || originalRange == null;
assert originalRange == null
|| originalRange instanceof Integer
|| originalRange instanceof Range;
- this.obfuscatedRange = obfuscatedRange;
+ this.minifiedRange = minifiedRange;
this.signature = signature;
this.originalRange = originalRange;
- this.obfuscatedName = obfuscatedName;
+ this.renamedName = renamedName;
}
- public int originalLineFromObfuscated(int obfuscatedLineNumber) {
- if (obfuscatedRange == null) {
+ public int getOriginalLineNumber(int lineNumberAfterMinification) {
+ if (minifiedRange == null) {
// General mapping without concrete line numbers: "a() -> b"
- return obfuscatedLineNumber;
+ return lineNumberAfterMinification;
}
- assert obfuscatedRange.contains(obfuscatedLineNumber);
+ assert minifiedRange.contains(lineNumberAfterMinification);
if (originalRange == null) {
// Concrete identity mapping: "x:y:a() -> b"
- return obfuscatedLineNumber;
+ return lineNumberAfterMinification;
} else if (originalRange instanceof Integer) {
// Inlinee: "x:y:a():u -> b"
return (int) originalRange;
} else {
// "x:y:a():u:v -> b"
assert originalRange instanceof Range;
- return ((Range) originalRange).from + obfuscatedLineNumber - obfuscatedRange.from;
+ return ((Range) originalRange).from + lineNumberAfterMinification - minifiedRange.from;
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- if (obfuscatedRange != null) {
- builder.append(obfuscatedRange).append(':');
+ if (minifiedRange != null) {
+ builder.append(minifiedRange).append(':');
}
builder.append(signature);
if (originalRange != null) {
builder.append(":").append(originalRange);
}
- builder.append(" -> ").append(obfuscatedName);
+ builder.append(" -> ").append(renamedName);
return builder.toString();
}
@@ -424,19 +421,19 @@
MappedRange that = (MappedRange) o;
- return Objects.equals(obfuscatedRange, that.obfuscatedRange)
+ return Objects.equals(minifiedRange, that.minifiedRange)
&& Objects.equals(originalRange, that.originalRange)
&& signature.equals(that.signature)
- && obfuscatedName.equals(that.obfuscatedName);
+ && renamedName.equals(that.renamedName);
}
@Override
public int hashCode() {
// sequenceNumber is intentionally omitted from hashCode since it's not used in equality test.
- int result = Objects.hashCode(obfuscatedRange);
+ int result = Objects.hashCode(minifiedRange);
result = 31 * result + Objects.hashCode(originalRange);
result = 31 * result + signature.hashCode();
- result = 31 * result + obfuscatedName.hashCode();
+ result = 31 * result + renamedName.hashCode();
return result;
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index d92de6b..8714b28 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -26,15 +26,15 @@
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardClassFilter;
+import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import java.util.Map;
-import java.util.Set;
class IdentifierMinifier {
private final AppInfoWithLiveness appInfo;
private final ProguardClassFilter adaptClassStrings;
private final NamingLens lens;
- private final Set<DexItem> identifierNameStrings;
+ private final Object2BooleanMap<DexItem> identifierNameStrings;
IdentifierMinifier(
AppInfoWithLiveness appInfo, ProguardClassFilter adaptClassStrings, NamingLens lens) {
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index b0f4dc0..830391a 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -35,17 +35,17 @@
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Streams;
+import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
-import java.util.Set;
import java.util.stream.Collectors;
public class IdentifierNameStringMarker {
private final AppInfo appInfo;
private final DexItemFactory dexItemFactory;
- private final Set<DexItem> identifierNameStrings;
+ private final Object2BooleanMap<DexItem> identifierNameStrings;
private final InternalOptions options;
public IdentifierNameStringMarker(AppInfoWithLiveness appInfo, InternalOptions options) {
@@ -63,7 +63,7 @@
}
private void decoupleIdentifierNameStringInField(DexEncodedField encodedField) {
- if (!identifierNameStrings.contains(encodedField.field)) {
+ if (!identifierNameStrings.containsKey(encodedField.field)) {
return;
}
if (!encodedField.accessFlags.isStatic()) {
@@ -102,22 +102,27 @@
if (instruction.isStaticPut() || instruction.isInstancePut()) {
FieldInstruction fieldPut = instruction.asFieldInstruction();
DexField field = fieldPut.getField();
- if (!identifierNameStrings.contains(field)) {
+ if (!identifierNameStrings.containsKey(field)) {
continue;
}
+ boolean isExplicitRule = identifierNameStrings.getBoolean(field);
Value in = instruction.isStaticPut()
? instruction.asStaticPut().inValue()
: instruction.asInstancePut().value();
if (!in.isConstString()) {
- warnUndeterminedIdentifierIfNecessary(
- appInfo, options, field, originHolder, instruction, null);
+ if (isExplicitRule) {
+ warnUndeterminedIdentifierIfNecessary(
+ appInfo, options, field, originHolder, instruction, null);
+ }
continue;
}
DexString original = in.getConstInstruction().asConstString().getValue();
DexItemBasedString itemBasedString = inferMemberOrTypeFromNameString(appInfo, original);
if (itemBasedString == null) {
- warnUndeterminedIdentifierIfNecessary(
- appInfo, options, field, originHolder, instruction, original);
+ if (isExplicitRule) {
+ warnUndeterminedIdentifierIfNecessary(
+ appInfo, options, field, originHolder, instruction, original);
+ }
continue;
}
// Move the cursor back to $fieldPut
@@ -163,16 +168,19 @@
} else if (instruction.isInvokeMethod()) {
InvokeMethod invoke = instruction.asInvokeMethod();
DexMethod invokedMethod = invoke.getInvokedMethod();
- if (!identifierNameStrings.contains(invokedMethod)) {
+ if (!identifierNameStrings.containsKey(invokedMethod)) {
continue;
}
+ boolean isExplicitRule = identifierNameStrings.getBoolean(invokedMethod);
List<Value> ins = invoke.arguments();
Value[] changes = new Value [ins.size()];
if (isReflectionMethod(dexItemFactory, invokedMethod)) {
DexItemBasedString itemBasedString = identifyIdentiferNameString(appInfo, invoke);
if (itemBasedString == null) {
- warnUndeterminedIdentifierIfNecessary(
- appInfo, options, invokedMethod, originHolder, instruction, null);
+ if (isExplicitRule) {
+ warnUndeterminedIdentifierIfNecessary(
+ appInfo, options, invokedMethod, originHolder, instruction, null);
+ }
continue;
}
DexType returnType = invoke.getReturnType();
@@ -217,16 +225,20 @@
for (int i = 0; i < ins.size(); i++) {
Value in = ins.get(i);
if (!in.isConstString()) {
- warnUndeterminedIdentifierIfNecessary(
- appInfo, options, invokedMethod, originHolder, instruction, null);
+ if (isExplicitRule) {
+ warnUndeterminedIdentifierIfNecessary(
+ appInfo, options, invokedMethod, originHolder, instruction, null);
+ }
continue;
}
DexString original = in.getConstInstruction().asConstString().getValue();
DexItemBasedString itemBasedString =
inferMemberOrTypeFromNameString(appInfo, original);
if (itemBasedString == null) {
- warnUndeterminedIdentifierIfNecessary(
- appInfo, options, invokedMethod, originHolder, instruction, original);
+ if (isExplicitRule) {
+ warnUndeterminedIdentifierIfNecessary(
+ appInfo, options, invokedMethod, originHolder, instruction, original);
+ }
continue;
}
// Move the cursor back to $invoke
diff --git a/src/main/java/com/android/tools/r8/position/TextPosition.java b/src/main/java/com/android/tools/r8/position/TextPosition.java
index 0dd4090..08d13bc 100644
--- a/src/main/java/com/android/tools/r8/position/TextPosition.java
+++ b/src/main/java/com/android/tools/r8/position/TextPosition.java
@@ -54,12 +54,12 @@
@Override
public String toString() {
- return "Offset: " + offset + ", Line: " + line + ", column: " + column;
+ return "offset: " + offset + ", line: " + line + ", column: " + column;
}
@Override
public String getDescription() {
- return "Line: " + line + (column != UNKNOWN_COLUMN ? ", column: " + column: "");
+ return "line " + line + (column != UNKNOWN_COLUMN ? (", column " + column) : "");
}
@Override
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 19a116f..64387ce 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -5,8 +5,6 @@
import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentiferNameString;
import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
-import static com.android.tools.r8.naming.IdentifierNameStringUtils.warnUndeterminedIdentifierIfNecessary;
-import static com.android.tools.r8.shaking.ProguardConfigurationUtils.buildIdentifierNameStringRule;
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.dex.IndexedItemCollection;
@@ -58,6 +56,8 @@
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
+import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import java.util.ArrayDeque;
import java.util.Collection;
@@ -187,10 +187,9 @@
private final Queue<Action> proguardCompatibilityWorkList = Queues.newArrayDeque();
/**
- * A set of methods that need code inspection for Proguard compatibility rules.
+ * A set of methods that need code inspection for Java reflection in use.
*/
- private final Set<DexEncodedMethod> pendingProguardReflectiveCompatibility =
- Sets.newLinkedHashSet();
+ private final Set<DexEncodedMethod> pendingReflectiveUses = Sets.newLinkedHashSet();
/**
* A cache for DexMethod that have been marked reachable.
@@ -314,13 +313,10 @@
boolean registerInvokeVirtual(DexMethod method, KeepReason keepReason) {
if (appInfo.dexItemFactory.classMethods.isReflectiveMemberLookup(method)) {
- if (forceProguardCompatibility) {
- // TODO(b/76181966): whether or not add this rule in normal mode.
- if (identifierNameStrings.add(method) && compatibility != null) {
- compatibility.addRule(buildIdentifierNameStringRule(method));
- }
- pendingProguardReflectiveCompatibility.add(currentMethod);
- }
+ // Implicitly add -identifiernamestring rule for the Java reflection in use.
+ identifierNameStrings.add(method);
+ // Revisit the current method to implicitly add -keep rule for items with reflective access.
+ pendingReflectiveUses.add(currentMethod);
}
if (!registerItemWithTarget(virtualInvokes, method)) {
return false;
@@ -356,13 +352,10 @@
boolean registerInvokeStatic(DexMethod method, KeepReason keepReason) {
if (method == appInfo.dexItemFactory.classMethods.forName
|| appInfo.dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(method)) {
- if (forceProguardCompatibility) {
- // TODO(b/76181966): whether or not add this rule in normal mode.
- if (identifierNameStrings.add(method) && compatibility != null) {
- compatibility.addRule(buildIdentifierNameStringRule(method));
- }
- pendingProguardReflectiveCompatibility.add(currentMethod);
- }
+ // Implicitly add -identifiernamestring rule for the Java reflection in use.
+ identifierNameStrings.add(method);
+ // Revisit the current method to implicitly add -keep rule for items with reflective access.
+ pendingReflectiveUses.add(currentMethod);
}
if (!registerItemWithTarget(staticInvokes, method)) {
return false;
@@ -1279,15 +1272,15 @@
}
// Continue fix-point processing while there are additional work items to ensure
- // Proguard compatibility.
+ // items that are passed to Java reflections are traced.
if (proguardCompatibilityWorkList.isEmpty()
- && pendingProguardReflectiveCompatibility.isEmpty()) {
+ && pendingReflectiveUses.isEmpty()) {
break;
}
- pendingProguardReflectiveCompatibility.forEach(this::handleProguardReflectiveBehavior);
+ pendingReflectiveUses.forEach(this::handleReflectiveBehavior);
workList.addAll(proguardCompatibilityWorkList);
proguardCompatibilityWorkList.clear();
- pendingProguardReflectiveCompatibility.clear();
+ pendingReflectiveUses.clear();
}
if (Log.ENABLED) {
Set<DexEncodedMethod> allLive = Sets.newIdentityHashSet();
@@ -1484,15 +1477,15 @@
Action.markMethodLive(method, KeepReason.dueToProguardCompatibilityKeepRule(rule)));
}
- private void handleProguardReflectiveBehavior(DexEncodedMethod method) {
+ private void handleReflectiveBehavior(DexEncodedMethod method) {
DexType originHolder = method.method.holder;
Origin origin = appInfo.originFor(originHolder);
IRCode code = method.buildIR(appInfo, graphLense, options, origin);
code.instructionIterator().forEachRemaining(instr ->
- handleProguardReflectiveBehavior(instr, originHolder));
+ handleReflectiveBehavior(instr, originHolder));
}
- private void handleProguardReflectiveBehavior(Instruction instruction, DexType originHolder) {
+ private void handleReflectiveBehavior(Instruction instruction, DexType originHolder) {
if (!instruction.isInvokeMethod()) {
return;
}
@@ -1503,8 +1496,6 @@
}
DexItemBasedString itemBasedString = identifyIdentiferNameString(appInfo, invoke);
if (itemBasedString == null) {
- warnUndeterminedIdentifierIfNecessary(
- appInfo, options, invokedMethod, originHolder, instruction, null);
return;
}
if (itemBasedString.basedOn instanceof DexType) {
@@ -1692,8 +1683,10 @@
public final Set<DexMethod> neverInline;
/**
* All items with -identifiernamestring rule.
+ * Bound boolean value indicates the rule is explicitly specified by users (<code>true</code>)
+ * or not, i.e., implicitly added by R8 (<code>false</code>).
*/
- public final Set<DexItem> identifierNameStrings;
+ public final Object2BooleanMap<DexItem> identifierNameStrings;
/**
* Set of fields that have been identified as proto-lite fields by the
* {@link ProtoLiteExtension}.
@@ -1750,8 +1743,8 @@
this.alwaysInline = enqueuer.rootSet.alwaysInline;
this.forceInline = enqueuer.rootSet.forceInline;
this.neverInline = enqueuer.rootSet.neverInline;
- this.identifierNameStrings =
- Sets.union(enqueuer.rootSet.identifierNameStrings, enqueuer.identifierNameStrings);
+ this.identifierNameStrings = joinIdentifierNameStrings(
+ enqueuer.rootSet.identifierNameStrings, enqueuer.identifierNameStrings);
this.protoLiteFields = enqueuer.protoLiteFields;
this.prunedTypes = Collections.emptySet();
this.switchMaps = Collections.emptyMap();
@@ -1922,6 +1915,18 @@
.collect(ImmutableSortedSet.toImmutableSortedSet(PresortedComparable::slowCompare));
}
+ private Object2BooleanMap<DexItem> joinIdentifierNameStrings(
+ Set<DexItem> explicit, Set<DexItem> implicit) {
+ Object2BooleanMap<DexItem> result = new Object2BooleanArrayMap<>();
+ for (DexItem e : explicit) {
+ result.putIfAbsent(e, true);
+ }
+ for (DexItem i : implicit) {
+ result.putIfAbsent(i, false);
+ }
+ return result;
+ }
+
private <T extends PresortedComparable<T>> SortedSet<T> toSortedDescriptorSet(
Set<? extends KeyedDexItem<T>> set) {
ImmutableSortedSet.Builder<T> builder =
@@ -2041,6 +2046,32 @@
return builder.build();
}
+ private static Object2BooleanMap<DexItem> rewriteMixedItemsConservatively(
+ Object2BooleanMap<DexItem> original, GraphLense lense) {
+ Object2BooleanMap<DexItem> result = new Object2BooleanArrayMap<>();
+ for (Object2BooleanMap.Entry<DexItem> entry : original.object2BooleanEntrySet()) {
+ DexItem item = entry.getKey();
+ // TODO(b/67934123) There should be a common interface to perform the dispatch.
+ if (item instanceof DexType) {
+ result.put(lense.lookupType((DexType) item), entry.getBooleanValue());
+ } else if (item instanceof DexMethod) {
+ DexMethod method = (DexMethod) item;
+ if (lense.isContextFreeForMethod(method)) {
+ result.put(lense.lookupMethod(method), entry.getBooleanValue());
+ } else {
+ for (DexMethod candidate: lense.lookupMethodInAllContexts(method)) {
+ result.put(candidate, entry.getBooleanValue());
+ }
+ }
+ } else if (item instanceof DexField) {
+ result.put(lense.lookupField((DexField) item), entry.getBooleanValue());
+ } else {
+ throw new Unreachable();
+ }
+ }
+ return result;
+ }
+
private static <T> Set<T> mergeSets(Collection<T> first, Collection<T> second) {
ImmutableSet.Builder<T> builder = ImmutableSet.builder();
builder.addAll(first);
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index ae99990..d3c9e6d 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -84,6 +84,7 @@
enableNonNullTracking = false;
enableInlining = false;
enableClassInlining = false;
+ enableClassStaticizer = false;
enableSwitchMapRemoval = false;
outline.enabled = false;
enableValuePropagation = false;
@@ -102,6 +103,7 @@
public boolean enableInlining =
!Version.isDev() || System.getProperty("com.android.tools.r8.disableinlining") == null;
public boolean enableClassInlining = true;
+ public boolean enableClassStaticizer = true;
public int classInliningInstructionLimit = 50;
public int inliningInstructionLimit = 5;
public boolean enableSwitchMapRemoval = true;
diff --git a/src/test/examples/identifiernamestring/keep-rules-3.txt b/src/test/examples/identifiernamestring/keep-rules-3.txt
index aa23772..1c69314 100644
--- a/src/test/examples/identifiernamestring/keep-rules-3.txt
+++ b/src/test/examples/identifiernamestring/keep-rules-3.txt
@@ -11,7 +11,7 @@
-dontshrink
--identifiernamestring class * {
+-identifiernamestring class **.R {
static java.lang.reflect.Field *(java.lang.Class, java.lang.String);
static java.lang.reflect.Method *(java.lang.Class, java.lang.String, java.lang.Class[]);
}
diff --git a/src/test/examples/shaking1/print-mapping-cf.ref b/src/test/examples/shaking1/print-mapping-cf.ref
new file mode 100644
index 0000000..9bf8013
--- /dev/null
+++ b/src/test/examples/shaking1/print-mapping-cf.ref
@@ -0,0 +1,7 @@
+shaking1.Shaking -> shaking1.Shaking:
+shaking1.Used -> a.a:
+ java.lang.String name -> a
+ 1:1:java.lang.String method():17:17 -> a
+ 1:1:void main(java.lang.String[]):8:8 -> main
+ 1:2:void <init>(java.lang.String):12:13 -> <init>
+ 1:1:java.lang.String aMethodThatIsNotUsedButKept():21:21 -> aMethodThatIsNotUsedButKept
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index df2c3e0..e9ef5ed 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -701,7 +701,7 @@
if (range == null) {
return obfuscatedLineNumber;
}
- return range.originalLineFromObfuscated(obfuscatedLineNumber);
+ return range.getOriginalLineNumber(obfuscatedLineNumber);
}
@Override
@@ -724,8 +724,7 @@
for (MappedRange range : mappedRanges) {
lines.add(
new SignatureAndLine(
- range.signature.toString(),
- range.originalLineFromObfuscated(obfuscatedLineNumber)));
+ range.signature.toString(), range.getOriginalLineNumber(obfuscatedLineNumber)));
}
return lines;
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
new file mode 100644
index 0000000..9be118c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -0,0 +1,272 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.InvokeDirect;
+import com.android.tools.r8.code.InvokeStatic;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.code.SgetObject;
+import com.android.tools.r8.code.SputObject;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateConflictField;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateConflictMethod;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateOk;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateOkSideEffects;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostConflictField;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostConflictMethod;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostOk;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostOkSideEffects;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.MoveToHostTestClass;
+import com.android.tools.r8.ir.optimize.staticizer.trivial.Simple;
+import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithGetter;
+import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithParams;
+import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithSideEffects;
+import com.android.tools.r8.ir.optimize.staticizer.trivial.TrivialTestClass;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Streams;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class ClassStaticizerTest extends TestBase {
+ @Test
+ public void testTrivial() throws Exception {
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(TrivialTestClass.class),
+ ToolHelper.getClassAsBytes(Simple.class),
+ ToolHelper.getClassAsBytes(SimpleWithSideEffects.class),
+ ToolHelper.getClassAsBytes(SimpleWithParams.class),
+ ToolHelper.getClassAsBytes(SimpleWithGetter.class),
+ };
+ AndroidApp app = runR8(buildAndroidApp(classes), TrivialTestClass.class);
+
+ String javaOutput = runOnJava(TrivialTestClass.class);
+ String artOutput = runOnArt(app, TrivialTestClass.class);
+ assertEquals(javaOutput, artOutput);
+
+ CodeInspector inspector = new CodeInspector(app);
+ ClassSubject clazz = inspector.clazz(TrivialTestClass.class);
+
+ assertEquals(
+ Lists.newArrayList(
+ "STATIC: String trivial.Simple.bar(String)",
+ "STATIC: String trivial.Simple.foo()",
+ "STATIC: String trivial.TrivialTestClass.next()"),
+ references(clazz, "testSimple", "void"));
+
+ assertTrue(instanceMethods(inspector.clazz(Simple.class)).isEmpty());
+
+ assertEquals(
+ Lists.newArrayList(
+ "STATIC: String trivial.SimpleWithParams.bar(String)",
+ "STATIC: String trivial.SimpleWithParams.foo()",
+ "STATIC: String trivial.TrivialTestClass.next()"),
+ references(clazz, "testSimpleWithParams", "void"));
+
+ assertTrue(instanceMethods(inspector.clazz(SimpleWithParams.class)).isEmpty());
+
+ assertEquals(
+ Lists.newArrayList(
+ "STATIC: String trivial.SimpleWithSideEffects.bar(String)",
+ "STATIC: String trivial.SimpleWithSideEffects.foo()",
+ "STATIC: String trivial.TrivialTestClass.next()",
+ "trivial.SimpleWithSideEffects trivial.SimpleWithSideEffects.INSTANCE",
+ "trivial.SimpleWithSideEffects trivial.SimpleWithSideEffects.INSTANCE"),
+ references(clazz, "testSimpleWithSideEffects", "void"));
+
+ assertTrue(instanceMethods(inspector.clazz(SimpleWithSideEffects.class)).isEmpty());
+
+ // TODO(b/111832046): add support for singleton instance getters.
+ assertEquals(
+ Lists.newArrayList(
+ "STATIC: String trivial.TrivialTestClass.next()",
+ "VIRTUAL: String trivial.SimpleWithGetter.bar(String)",
+ "VIRTUAL: String trivial.SimpleWithGetter.foo()",
+ "trivial.SimpleWithGetter trivial.SimpleWithGetter.INSTANCE",
+ "trivial.SimpleWithGetter trivial.SimpleWithGetter.INSTANCE"),
+ references(clazz, "testSimpleWithGetter", "void"));
+
+ assertFalse(instanceMethods(inspector.clazz(SimpleWithGetter.class)).isEmpty());
+ }
+
+ @Test
+ public void testMoveToHost() throws Exception {
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(MoveToHostTestClass.class),
+ ToolHelper.getClassAsBytes(HostOk.class),
+ ToolHelper.getClassAsBytes(CandidateOk.class),
+ ToolHelper.getClassAsBytes(HostOkSideEffects.class),
+ ToolHelper.getClassAsBytes(CandidateOkSideEffects.class),
+ ToolHelper.getClassAsBytes(HostConflictMethod.class),
+ ToolHelper.getClassAsBytes(CandidateConflictMethod.class),
+ ToolHelper.getClassAsBytes(HostConflictField.class),
+ ToolHelper.getClassAsBytes(CandidateConflictField.class),
+ };
+ AndroidApp app = runR8(buildAndroidApp(classes), MoveToHostTestClass.class);
+
+ String javaOutput = runOnJava(MoveToHostTestClass.class);
+ String artOutput = runOnArt(app, MoveToHostTestClass.class);
+ assertEquals(javaOutput, artOutput);
+
+ CodeInspector inspector = new CodeInspector(app);
+ ClassSubject clazz = inspector.clazz(MoveToHostTestClass.class);
+
+ assertEquals(
+ Lists.newArrayList(
+ "STATIC: String movetohost.HostOk.bar(String)",
+ "STATIC: String movetohost.HostOk.foo()",
+ "STATIC: String movetohost.MoveToHostTestClass.next()",
+ "STATIC: String movetohost.MoveToHostTestClass.next()",
+ "STATIC: void movetohost.HostOk.blah(String)"),
+ references(clazz, "testOk", "void"));
+
+ assertFalse(inspector.clazz(CandidateOk.class).isPresent());
+
+ assertEquals(
+ Lists.newArrayList(
+ "STATIC: String movetohost.HostOkSideEffects.bar(String)",
+ "STATIC: String movetohost.HostOkSideEffects.foo()",
+ "STATIC: String movetohost.MoveToHostTestClass.next()",
+ "movetohost.HostOkSideEffects movetohost.HostOkSideEffects.INSTANCE",
+ "movetohost.HostOkSideEffects movetohost.HostOkSideEffects.INSTANCE"),
+ references(clazz, "testOkSideEffects", "void"));
+
+ assertFalse(inspector.clazz(CandidateOkSideEffects.class).isPresent());
+
+ assertEquals(
+ Lists.newArrayList(
+ "DIRECT: void movetohost.HostConflictMethod.<init>()",
+ "STATIC: String movetohost.CandidateConflictMethod.bar(String)",
+ "STATIC: String movetohost.CandidateConflictMethod.foo()",
+ "STATIC: String movetohost.MoveToHostTestClass.next()",
+ "STATIC: String movetohost.MoveToHostTestClass.next()",
+ "VIRTUAL: String movetohost.HostConflictMethod.bar(String)"),
+ references(clazz, "testConflictMethod", "void"));
+
+ assertTrue(inspector.clazz(CandidateConflictMethod.class).isPresent());
+
+ assertEquals(
+ Lists.newArrayList(
+ "DIRECT: void movetohost.HostConflictField.<init>()",
+ "STATIC: String movetohost.CandidateConflictField.bar(String)",
+ "STATIC: String movetohost.CandidateConflictField.foo()",
+ "STATIC: String movetohost.MoveToHostTestClass.next()",
+ "String movetohost.CandidateConflictField.field"),
+ references(clazz, "testConflictField", "void"));
+
+ assertTrue(inspector.clazz(CandidateConflictMethod.class).isPresent());
+ }
+
+ private List<String> instanceMethods(ClassSubject clazz) {
+ assertNotNull(clazz);
+ assertTrue(clazz.isPresent());
+ return Streams.stream(clazz.getDexClass().methods())
+ .filter(method -> !method.isStaticMethod())
+ .map(method -> method.method.toSourceString())
+ .sorted()
+ .collect(Collectors.toList());
+ }
+
+ private List<String> references(
+ ClassSubject clazz, String methodName, String retValue, String... params) {
+ assertNotNull(clazz);
+ assertTrue(clazz.isPresent());
+
+ MethodSignature signature = new MethodSignature(methodName, retValue, params);
+ DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
+ return Streams.concat(
+ filterInstructionKind(code, SgetObject.class)
+ .map(Instruction::getField)
+ .filter(fld -> isTypeOfInterest(fld.clazz))
+ .map(DexField::toSourceString),
+ filterInstructionKind(code, SputObject.class)
+ .map(Instruction::getField)
+ .filter(fld -> isTypeOfInterest(fld.clazz))
+ .map(DexField::toSourceString),
+ filterInstructionKind(code, InvokeStatic.class)
+ .map(insn -> (InvokeStatic) insn)
+ .map(InvokeStatic::getMethod)
+ .filter(method -> isTypeOfInterest(method.holder))
+ .map(method -> "STATIC: " + method.toSourceString()),
+ filterInstructionKind(code, InvokeVirtual.class)
+ .map(insn -> (InvokeVirtual) insn)
+ .map(InvokeVirtual::getMethod)
+ .filter(method -> isTypeOfInterest(method.holder))
+ .map(method -> "VIRTUAL: " + method.toSourceString()),
+ filterInstructionKind(code, InvokeDirect.class)
+ .map(insn -> (InvokeDirect) insn)
+ .map(InvokeDirect::getMethod)
+ .filter(method -> isTypeOfInterest(method.holder))
+ .map(method -> "DIRECT: " + method.toSourceString()))
+ .map(txt -> txt.replace("java.lang.", ""))
+ .map(txt -> txt.replace("com.android.tools.r8.ir.optimize.staticizer.", ""))
+ .sorted()
+ .collect(Collectors.toList());
+ }
+
+ private boolean isTypeOfInterest(DexType type) {
+ return type.toSourceString().startsWith("com.android.tools.r8.ir.optimize.staticizer");
+ }
+
+ private AndroidApp runR8(AndroidApp app, Class mainClass) throws Exception {
+ AndroidApp compiled =
+ compileWithR8(app, getProguardConfig(mainClass.getCanonicalName()), this::configure);
+
+ // Materialize file for execution.
+ Path generatedDexFile = temp.getRoot().toPath().resolve("classes.jar");
+ compiled.writeToZip(generatedDexFile, OutputMode.DexIndexed);
+
+ // Run with ART.
+ String artOutput = ToolHelper.runArtNoVerificationErrors(
+ generatedDexFile.toString(), mainClass.getCanonicalName());
+
+ // Compare with Java.
+ ProcessResult javaResult = ToolHelper.runJava(
+ ToolHelper.getClassPathForTests(), mainClass.getCanonicalName());
+
+ if (javaResult.exitCode != 0) {
+ System.out.println(javaResult.stdout);
+ System.err.println(javaResult.stderr);
+ fail("JVM failed for: " + mainClass);
+ }
+ assertEquals("JVM and ART output differ", javaResult.stdout, artOutput);
+
+ return compiled;
+ }
+
+ private String getProguardConfig(String main) {
+ return keepMainProguardConfiguration(main)
+ + System.lineSeparator()
+ + "-dontobfuscate"
+ + System.lineSeparator()
+ + "-allowaccessmodification";
+ }
+
+ private void configure(InternalOptions options) {
+ options.enableClassInlining = false;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java
new file mode 100644
index 0000000..f2108b0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class CandidateConflictField {
+ public static String field;
+
+ public String foo() {
+ synchronized ("") {
+ return bar("CandidateConflictMethod::foo()");
+ }
+ }
+
+ public String bar(String other) {
+ synchronized ("") {
+ return "CandidateConflictMethod::bar(" + other + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictMethod.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictMethod.java
new file mode 100644
index 0000000..40d0576
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictMethod.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class CandidateConflictMethod {
+ public String foo() {
+ synchronized ("") {
+ return bar("CandidateConflictMethod::foo()");
+ }
+ }
+
+ public String bar(String other) {
+ synchronized ("") {
+ return "CandidateConflictMethod::bar(" + other + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOk.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOk.java
new file mode 100644
index 0000000..daf5647
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOk.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class CandidateOk {
+ public String foo() {
+ synchronized ("") {
+ return bar("CandidateOk::foo()");
+ }
+ }
+
+ public String bar(String other) {
+ synchronized ("") {
+ return "CandidateOk::bar(" + other + ")";
+ }
+ }
+
+ public void blah(String other) {
+ synchronized ("") {
+ System.out.println("CandidateOk::blah(" + other + ")");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOkSideEffects.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOkSideEffects.java
new file mode 100644
index 0000000..2077056
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOkSideEffects.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class CandidateOkSideEffects {
+ public String foo() {
+ synchronized ("") {
+ return bar("CandidateOkSideEffects::foo()");
+ }
+ }
+
+ public String bar(String other) {
+ synchronized ("") {
+ return "CandidateOkSideEffects::bar(" + other + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictField.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictField.java
new file mode 100644
index 0000000..7166b81
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictField.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class HostConflictField {
+ static CandidateConflictField INSTANCE = new CandidateConflictField();
+
+ public String field;
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictMethod.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictMethod.java
new file mode 100644
index 0000000..ad86b35
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictMethod.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class HostConflictMethod {
+ static CandidateConflictMethod INSTANCE = new CandidateConflictMethod();
+
+ public String bar(String other) {
+ synchronized ("") {
+ return "HostConflictMethod::bar(" + other + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOk.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOk.java
new file mode 100644
index 0000000..8ecd7ac
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOk.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class HostOk {
+ static CandidateOk INSTANCE = new CandidateOk();
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOkSideEffects.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOkSideEffects.java
new file mode 100644
index 0000000..1f6ec7c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOkSideEffects.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class HostOkSideEffects {
+ static CandidateOkSideEffects INSTANCE = new CandidateOkSideEffects();
+
+ static {
+ System.out.println("Inside HostOkSideEffects::<clinit>()");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostTestClass.java
new file mode 100644
index 0000000..3f31839
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostTestClass.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class MoveToHostTestClass {
+ private static int ID = 0;
+
+ private static String next() {
+ return Integer.toString(ID++);
+ }
+
+ public static void main(String[] args) {
+ MoveToHostTestClass test = new MoveToHostTestClass();
+ test.testOk();
+ test.testOkSideEffects();
+ test.testConflictMethod();
+ test.testConflictField();
+ }
+
+ private synchronized void testOk() {
+ System.out.println(HostOk.INSTANCE.foo());
+ System.out.println(HostOk.INSTANCE.bar(next()));
+ HostOk.INSTANCE.blah(next());
+ }
+
+ private synchronized void testOkSideEffects() {
+ System.out.println(HostOkSideEffects.INSTANCE.foo());
+ System.out.println(HostOkSideEffects.INSTANCE.bar(next()));
+ }
+
+ private synchronized void testConflictMethod() {
+ System.out.println(new HostConflictMethod().bar(next()));
+ System.out.println(HostConflictMethod.INSTANCE.foo());
+ System.out.println(HostConflictMethod.INSTANCE.bar(next()));
+ }
+
+ private synchronized void testConflictField() {
+ System.out.println(new HostConflictField().field);
+ System.out.println(CandidateConflictField.field);
+ System.out.println(HostConflictField.INSTANCE.foo());
+ System.out.println(HostConflictField.INSTANCE.bar(next()));
+ }
+}
+
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/Simple.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/Simple.java
new file mode 100644
index 0000000..8df079c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/Simple.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.trivial;
+
+public class Simple {
+ static Simple INSTANCE = new Simple();
+
+ String foo() {
+ synchronized ("") {
+ return bar("Simple::foo()");
+ }
+ }
+
+ String bar(String other) {
+ synchronized ("") {
+ return "Simple::bar(" + other + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
new file mode 100644
index 0000000..9ff20c3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.trivial;
+
+public class SimpleWithGetter {
+ private static SimpleWithGetter INSTANCE = new SimpleWithGetter();
+
+ static SimpleWithGetter getInstance() {
+ return INSTANCE;
+ }
+
+ String foo() {
+ synchronized ("") {
+ return bar("Simple::foo()");
+ }
+ }
+
+ String bar(String other) {
+ synchronized ("") {
+ return "Simple::bar(" + other + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java
new file mode 100644
index 0000000..a71f1f5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.trivial;
+
+public class SimpleWithParams {
+ static SimpleWithParams INSTANCE = new SimpleWithParams(123);
+
+ SimpleWithParams(int i) {
+ }
+
+ String foo() {
+ synchronized ("") {
+ return bar("SimpleWithParams::foo()");
+ }
+ }
+
+ String bar(String other) {
+ synchronized ("") {
+ return "SimpleWithParams::bar(" + other + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithSideEffects.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithSideEffects.java
new file mode 100644
index 0000000..5ba97dc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithSideEffects.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.trivial;
+
+public class SimpleWithSideEffects {
+ static SimpleWithSideEffects INSTANCE = new SimpleWithSideEffects();
+
+ static {
+ System.out.println("SimpleWithSideEffects::<clinit>()");
+ }
+
+ String foo() {
+ synchronized ("") {
+ return bar("SimpleWithSideEffects::foo()");
+ }
+ }
+
+ String bar(String other) {
+ synchronized ("") {
+ return "SimpleWithSideEffects::bar(" + other + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/TrivialTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/TrivialTestClass.java
new file mode 100644
index 0000000..2dad273
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/TrivialTestClass.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.trivial;
+
+public class TrivialTestClass {
+ private static int ID = 0;
+
+ private static String next() {
+ return Integer.toString(ID++);
+ }
+
+ public static void main(String[] args) {
+ TrivialTestClass test = new TrivialTestClass();
+ test.testSimple();
+ test.testSimpleWithSideEffects();
+ test.testSimpleWithParams();
+ test.testSimpleWithGetter();
+ }
+
+ private synchronized void testSimple() {
+ System.out.println(Simple.INSTANCE.foo());
+ System.out.println(Simple.INSTANCE.bar(next()));
+ }
+
+ private synchronized void testSimpleWithSideEffects() {
+ System.out.println(SimpleWithSideEffects.INSTANCE.foo());
+ System.out.println(SimpleWithSideEffects.INSTANCE.bar(next()));
+ }
+
+ private synchronized void testSimpleWithParams() {
+ System.out.println(SimpleWithParams.INSTANCE.foo());
+ System.out.println(SimpleWithParams.INSTANCE.bar(next()));
+ }
+
+ private synchronized void testSimpleWithGetter() {
+ System.out.println(SimpleWithGetter.getInstance().foo());
+ System.out.println(SimpleWithGetter.getInstance().bar(next()));
+ }
+}
+
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
new file mode 100644
index 0000000..c19b80e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameters;
+
+public class KotlinClassStaticizerTest extends AbstractR8KotlinTestBase {
+ @Parameters(name = "allowAccessModification: {0} target: {1}")
+ public static Collection<Object[]> data() {
+ ImmutableList.Builder<Object[]> builder = new ImmutableList.Builder<>();
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ builder.add(new Object[]{Boolean.TRUE, targetVersion});
+ }
+ return builder.build();
+ }
+
+ @Test
+ public void testCompanionAndRegularObjects() throws Exception {
+ final String mainClassName = "class_staticizer.MainKt";
+
+ // Without class staticizer.
+ runTest("class_staticizer", mainClassName, false, (app) -> {
+ CodeInspector inspector = new CodeInspector(app);
+ assertTrue(inspector.clazz("class_staticizer.Regular$Companion").isPresent());
+ assertTrue(inspector.clazz("class_staticizer.Derived$Companion").isPresent());
+
+ ClassSubject utilClass = inspector.clazz("class_staticizer.Util");
+ assertTrue(utilClass.isPresent());
+ AtomicInteger nonStaticMethodCount = new AtomicInteger();
+ utilClass.forAllMethods(method -> {
+ if (!method.isStatic()) {
+ nonStaticMethodCount.incrementAndGet();
+ }
+ });
+ assertEquals(4, nonStaticMethodCount.get());
+ });
+
+ // With class staticizer.
+ runTest("class_staticizer", mainClassName, true, (app) -> {
+ CodeInspector inspector = new CodeInspector(app);
+ assertFalse(inspector.clazz("class_staticizer.Regular$Companion").isPresent());
+ assertFalse(inspector.clazz("class_staticizer.Derived$Companion").isPresent());
+
+ ClassSubject utilClass = inspector.clazz("class_staticizer.Util");
+ assertTrue(utilClass.isPresent());
+ utilClass.forAllMethods(method -> assertTrue(method.isStatic()));
+ });
+ }
+
+ protected void runTest(String folder, String mainClass,
+ boolean enabled, AndroidAppInspector inspector) throws Exception {
+ runTest(
+ folder, mainClass, null,
+ options -> {
+ options.enableClassInlining = false;
+ options.enableClassStaticizer = enabled;
+ }, inspector);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index 4b1b404..a3aa5e1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -16,11 +16,13 @@
import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FieldSubject;
import java.nio.file.Path;
import java.util.Collections;
+import java.util.function.Consumer;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
@@ -59,12 +61,15 @@
.addProperty("property", JAVA_LANG_STRING, Visibility.PRIVATE)
.addProperty("indirectPropertyGetter", JAVA_LANG_STRING, Visibility.PRIVATE);
+ private Consumer<InternalOptions> disableClassStaticizer =
+ opts -> opts.enableClassStaticizer = false;
+
@Test
public void testCompanionProperty_primitivePropertyIsAlwaysInlined() throws Exception {
final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
"companionProperties_usePrimitiveProp");
- runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PROPERTIES_PACKAGE_NAME, mainClass, disableClassStaticizer, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
String propertyName = "primitiveProp";
@@ -93,7 +98,7 @@
final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
"companionProperties_usePrivateProp");
- runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PROPERTIES_PACKAGE_NAME, mainClass, disableClassStaticizer, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
String propertyName = "privateProp";
@@ -123,7 +128,7 @@
final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
"companionProperties_useInternalProp");
- runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PROPERTIES_PACKAGE_NAME, mainClass, disableClassStaticizer, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
String propertyName = "internalProp";
@@ -152,7 +157,7 @@
final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
"companionProperties_usePublicProp");
- runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PROPERTIES_PACKAGE_NAME, mainClass, disableClassStaticizer, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
String propertyName = "publicProp";
@@ -181,7 +186,7 @@
final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_usePrivateLateInitProp");
- runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
+ runTest(PROPERTIES_PACKAGE_NAME, mainClass, disableClassStaticizer, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
String propertyName = "privateLateInitProp";
@@ -257,7 +262,7 @@
TestKotlinCompanionClass testedClass = ACCESSOR_COMPANION_PROPERTY_CLASS;
String mainClass = addMainToClasspath("accessors.AccessorKt",
"accessor_accessPropertyFromCompanionClass");
- runTest("accessors", mainClass, (app) -> {
+ runTest("accessors", mainClass, disableClassStaticizer, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName());
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index 89ff81d..08fcebe 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -83,16 +83,17 @@
.addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
.addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
- private Consumer<InternalOptions> disableClassInliningAndMerging = o -> {
+ private Consumer<InternalOptions> disableAggressiveClassOptimizations = o -> {
o.enableClassInlining = false;
o.enableClassMerging = false;
+ o.enableClassStaticizer = false;
};
@Test
public void testMutableProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_noUseOfProperties");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject classSubject = checkClassIsKept(codeInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
@@ -115,7 +116,7 @@
public void testMutableProperty_privateIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_usePrivateProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject classSubject = checkClassIsKept(codeInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
@@ -137,7 +138,7 @@
public void testMutableProperty_protectedIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_useProtectedProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject classSubject = checkClassIsKept(codeInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
@@ -160,7 +161,7 @@
public void testMutableProperty_internalIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_useInternalProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject classSubject = checkClassIsKept(codeInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
@@ -183,7 +184,7 @@
public void testMutableProperty_publicIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_usePublicProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject classSubject = checkClassIsKept(codeInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
@@ -206,7 +207,7 @@
public void testMutableProperty_primitivePropertyIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_usePrimitiveProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject classSubject = checkClassIsKept(codeInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
@@ -231,11 +232,12 @@
public void testLateInitProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
String mainClass = addMainToClasspath("properties/LateInitPropertyKt",
"lateInitProperty_noUseOfProperties");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject classSubject = checkClassIsKept(codeInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
- for (Entry<String, KotlinProperty> property : LATE_INIT_PROPERTY_CLASS.properties.entrySet()) {
+ for (Entry<String, KotlinProperty> property : LATE_INIT_PROPERTY_CLASS.properties
+ .entrySet()) {
MethodSignature getter = LATE_INIT_PROPERTY_CLASS.getGetterForProperty(property.getKey());
MethodSignature setter = LATE_INIT_PROPERTY_CLASS.getSetterForProperty(property.getKey());
if (property.getValue().getVisibility() == Visibility.PRIVATE) {
@@ -255,7 +257,7 @@
public void testLateInitProperty_privateIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties/LateInitPropertyKt", "lateInitProperty_usePrivateLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject classSubject = checkClassIsKept(codeInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -278,7 +280,7 @@
public void testLateInitProperty_protectedIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/LateInitPropertyKt",
"lateInitProperty_useProtectedLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject classSubject = checkClassIsKept(codeInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -299,7 +301,7 @@
public void testLateInitProperty_internalIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties/LateInitPropertyKt", "lateInitProperty_useInternalLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject classSubject = checkClassIsKept(codeInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -318,7 +320,7 @@
public void testLateInitProperty_publicIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties/LateInitPropertyKt", "lateInitProperty_usePublicLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject classSubject = checkClassIsKept(codeInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -337,7 +339,7 @@
public void testUserDefinedProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
String mainClass = addMainToClasspath(
"properties/UserDefinedPropertyKt", "userDefinedProperty_noUseOfProperties");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject classSubject = checkClassIsKept(codeInspector,
USER_DEFINED_PROPERTY_CLASS.getClassName());
@@ -354,7 +356,7 @@
public void testUserDefinedProperty_publicIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties/UserDefinedPropertyKt", "userDefinedProperty_useProperties");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject classSubject = checkClassIsKept(codeInspector,
USER_DEFINED_PROPERTY_CLASS.getClassName());
@@ -380,7 +382,7 @@
public void testCompanionProperty_primitivePropertyCannotBeInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties.CompanionPropertiesKt", "companionProperties_usePrimitiveProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass = checkClassIsKept(codeInspector,
"properties.CompanionProperties");
@@ -411,7 +413,7 @@
public void testCompanionProperty_privatePropertyIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties.CompanionPropertiesKt", "companionProperties_usePrivateProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass = checkClassIsKept(codeInspector,
"properties.CompanionProperties");
@@ -445,7 +447,7 @@
public void testCompanionProperty_internalPropertyCannotBeInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties.CompanionPropertiesKt", "companionProperties_useInternalProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass = checkClassIsKept(codeInspector,
"properties.CompanionProperties");
@@ -476,7 +478,7 @@
public void testCompanionProperty_publicPropertyCannotBeInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties.CompanionPropertiesKt", "companionProperties_usePublicProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass = checkClassIsKept(codeInspector,
"properties.CompanionProperties");
@@ -508,7 +510,7 @@
final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_usePrivateLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName());
@@ -539,7 +541,7 @@
final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_useInternalLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName());
@@ -563,7 +565,7 @@
final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_usePublicLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName());
@@ -587,7 +589,7 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_usePrimitiveProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "primitiveProp";
@@ -614,7 +616,7 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_usePrivateProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "privateProp";
@@ -641,7 +643,7 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_useInternalProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "internalProp";
@@ -668,7 +670,7 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_usePublicProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "publicProp";
@@ -695,7 +697,7 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_useLateInitPrivateProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "privateLateInitProp";
@@ -722,7 +724,7 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_useLateInitInternalProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "internalLateInitProp";
@@ -745,7 +747,7 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_useLateInitPublicProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "publicLateInitProp";
@@ -768,7 +770,7 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_usePrimitiveProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "primitiveProp";
@@ -795,7 +797,7 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_usePrivateProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "privateProp";
@@ -821,7 +823,7 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_useInternalProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "internalProp";
@@ -847,7 +849,7 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_usePublicProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "publicProp";
@@ -874,7 +876,7 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_useLateInitPrivateProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject fileClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "privateLateInitProp";
@@ -900,7 +902,7 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_useLateInitInternalProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "internalLateInitProp";
@@ -924,7 +926,7 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_useLateInitPublicProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "publicLateInitProp";
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index a9ee851..73cdaaf 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -142,7 +142,7 @@
FoundMethodSubject foundMain = (FoundMethodSubject) main;
verifyPresenceOfConstString(foundMain);
int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundMain);
- assertEquals(0, renamedYetFoundIdentifierCount);
+ assertEquals(4, renamedYetFoundIdentifierCount);
ClassSubject aClass = inspector.clazz("adaptclassstrings.A");
MethodSubject bar = aClass.method("void", "bar", ImmutableList.of());
@@ -165,7 +165,7 @@
FoundMethodSubject foundMain = (FoundMethodSubject) main;
verifyPresenceOfConstString(foundMain);
int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundMain);
- assertEquals(0, renamedYetFoundIdentifierCount);
+ assertEquals(4, renamedYetFoundIdentifierCount);
ClassSubject aClass = inspector.clazz("adaptclassstrings.A");
MethodSubject bar = aClass.method("void", "bar", ImmutableList.of());
@@ -231,7 +231,7 @@
FoundMethodSubject foundMain = (FoundMethodSubject) main;
verifyPresenceOfConstString(foundMain);
int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundMain);
- assertEquals(0, renamedYetFoundIdentifierCount);
+ assertEquals(1, renamedYetFoundIdentifierCount);
ClassSubject aClass = inspector.clazz("identifiernamestring.A");
MethodSubject aInit =
@@ -255,7 +255,7 @@
FoundMethodSubject foundMain = (FoundMethodSubject) main;
verifyPresenceOfConstString(foundMain);
int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundMain);
- assertEquals(1, renamedYetFoundIdentifierCount);
+ assertEquals(2, renamedYetFoundIdentifierCount);
ClassSubject aClass = inspector.clazz("identifiernamestring.A");
MethodSubject aInit =
@@ -271,7 +271,7 @@
assertEquals(2, renamedYetFoundIdentifierCount);
}
- // With -identifiernamestring for reflective methods
+ // With -identifiernamestring for reflective methods in testing class R.
private static void test2_rule3(CodeInspector inspector) {
ClassSubject mainClass = inspector.clazz("identifiernamestring.Main");
MethodSubject main = mainClass.method(CodeInspector.MAIN);
diff --git a/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
index 829cdd0..49ba186 100644
--- a/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
+++ b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
@@ -9,6 +9,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.DiagnosticsChecker;
import com.android.tools.r8.OutputMode;
@@ -72,15 +73,17 @@
byte[][] classes,
Class main,
Path out,
+ boolean explicitRule,
boolean enableMinification,
boolean forceProguardCompatibility)
throws Exception {
String minificationModifier = enableMinification ? ",allowobfuscation " : " ";
- String reflectionRules = forceProguardCompatibility ? ""
- : "-identifiernamestring class java.lang.Class {\n"
+ String reflectionRules = explicitRule
+ ? "-identifiernamestring class java.lang.Class {\n"
+ "static java.lang.Class forName(java.lang.String);\n"
+ "java.lang.reflect.Method getDeclaredMethod(java.lang.String, java.lang.Class[]);\n"
- + "}\n";
+ + "}\n"
+ : "";
R8Command.Builder commandBuilder =
R8Command.builder(reporter)
.addProguardConfiguration(
@@ -102,7 +105,8 @@
});
}
- private void reflectionWithBuildter(
+ private void reflectionWithBuilder(
+ boolean explicitRule,
boolean enableMinification,
boolean forceProguardCompatibility) throws Exception {
byte[][] classes = {
@@ -110,7 +114,7 @@
};
Path out = temp.getRoot().toPath();
AndroidApp processedApp = runR8(classes, WarnReflectiveAccessTestMain.class, out,
- enableMinification, forceProguardCompatibility);
+ explicitRule, enableMinification, forceProguardCompatibility);
String main = WarnReflectiveAccessTestMain.class.getCanonicalName();
CodeInspector codeInspector = new CodeInspector(processedApp);
@@ -133,35 +137,59 @@
}
@Test
- public void test_minification_forceProguardCompatibility() throws Exception {
- reflectionWithBuildter(true, true);
+ public void test_explicit_minification_forceProguardCompatibility() throws Exception {
+ reflectionWithBuilder(true, true, true);
assertFalse(handler.warnings.isEmpty());
- DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 54, 1,
- "Cannot determine", "getDeclaredMethod", "resolution failure");
- }
-
- @Test
- public void test_noMinification_forceProguardCompatibility() throws Exception {
- reflectionWithBuildter(false, true);
- assertFalse(handler.warnings.isEmpty());
- DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 54, 1,
- "Cannot determine", "getDeclaredMethod", "resolution failure");
- }
-
- @Test
- public void test_minification_R8() throws Exception {
- reflectionWithBuildter(true, false);
- assertFalse(handler.warnings.isEmpty());
- DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 54, 1,
+ DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 55, 1,
"Cannot determine", "getDeclaredMethod", "-identifiernamestring", "resolution failure");
}
@Test
- public void test_noMinification_R8() throws Exception {
- reflectionWithBuildter(false, false);
+ public void test_explicit_noMinification_forceProguardCompatibility() throws Exception {
+ reflectionWithBuilder(true, false, true);
assertFalse(handler.warnings.isEmpty());
- DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 54, 1,
+ DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 55, 1,
"Cannot determine", "getDeclaredMethod", "-identifiernamestring", "resolution failure");
}
+ @Test
+ public void test_explicit_minification_R8() throws Exception {
+ reflectionWithBuilder(true, true, false);
+ assertFalse(handler.warnings.isEmpty());
+ DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 55, 1,
+ "Cannot determine", "getDeclaredMethod", "-identifiernamestring", "resolution failure");
+ }
+
+ @Test
+ public void test_explicit_noMinification_R8() throws Exception {
+ reflectionWithBuilder(true, false, false);
+ assertFalse(handler.warnings.isEmpty());
+ DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 55, 1,
+ "Cannot determine", "getDeclaredMethod", "-identifiernamestring", "resolution failure");
+ }
+
+ @Test
+ public void test_implicit_minification_forceProguardCompatibility() throws Exception {
+ reflectionWithBuilder(false, true, true);
+ assertTrue(handler.warnings.isEmpty());
+ }
+
+ @Test
+ public void test_implicit_noMinification_forceProguardCompatibility() throws Exception {
+ reflectionWithBuilder(false, false, true);
+ assertTrue(handler.warnings.isEmpty());
+ }
+
+ @Test
+ public void test_implicit_minification_R8() throws Exception {
+ reflectionWithBuilder(false, true, false);
+ assertTrue(handler.warnings.isEmpty());
+ }
+
+ @Test
+ public void test_implicit_noMinification_R8() throws Exception {
+ reflectionWithBuilder(false, false, false);
+ assertTrue(handler.warnings.isEmpty());
+ }
+
}
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index cba8686..ad67919 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -14,12 +14,15 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import java.io.BufferedReader;
+import java.io.IOException;
import java.io.PrintStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Assert;
@@ -27,8 +30,28 @@
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+@RunWith(Parameterized.class)
public class TreeShakingSpecificTest {
+ enum Backend {
+ DEX,
+ CF
+ }
+
+ private Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public TreeShakingSpecificTest(Backend backend) {
+ this.backend = backend;
+ }
+
private static final String VALID_PROGUARD_DIR = "src/test/proguard/valid/";
@Rule
@@ -37,18 +60,28 @@
@Rule
public ExpectedException thrown = ExpectedException.none();
+ private void finishBuild(R8Command.Builder builder, Path out, String test) throws IOException {
+ Path input;
+ if (backend == Backend.DEX) {
+ builder.setOutput(out, OutputMode.DexIndexed);
+ input = Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex");
+ } else {
+ builder.setOutput(out, OutputMode.ClassFile);
+ input = Paths.get(EXAMPLES_BUILD_DIR, test + ".jar");
+ }
+ ToolHelper.getAppBuilder(builder).addProgramFiles(input);
+ }
+
@Test
public void testIgnoreWarnings() throws Exception {
// Generate R8 processed version without library option.
Path out = temp.getRoot().toPath();
String test = "shaking2";
- Path originalDex = Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex");
Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
Path ignoreWarnings = Paths.get(VALID_PROGUARD_DIR, "ignorewarnings.flags");
R8Command.Builder builder = R8Command.builder()
- .setOutput(out, OutputMode.DexIndexed)
.addProguardConfigurationFiles(keepRules, ignoreWarnings);
- ToolHelper.getAppBuilder(builder).addProgramFiles(originalDex);
+ finishBuild(builder, out, test);
R8.run(builder.build());
}
@@ -57,7 +90,6 @@
// Generate R8 processed version without library option.
Path out = temp.getRoot().toPath();
String test = "shaking2";
- Path originalDex = Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex");
Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
DiagnosticsHandler handler = new DiagnosticsHandler() {
@Override
@@ -68,9 +100,8 @@
}
};
R8Command.Builder builder = R8Command.builder(handler)
- .setOutput(out, OutputMode.DexIndexed)
.addProguardConfigurationFiles(keepRules);
- ToolHelper.getAppBuilder(builder).addProgramFiles(originalDex);
+ finishBuild(builder, out, test);
R8.run(builder.build());
}
@@ -79,7 +110,6 @@
// Generate R8 processed version without library option.
String test = "shaking1";
Path out = temp.getRoot().toPath();
- Path originalDex = Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex");
Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
// Create a flags file in temp dir requesting dump of the mapping.
@@ -90,10 +120,15 @@
}
R8Command.Builder builder = R8Command.builder()
- .setOutput(out, OutputMode.DexIndexed)
.addProguardConfigurationFiles(keepRules, printMapping);
- ToolHelper.getAppBuilder(builder).addProgramFiles(originalDex);
// Turn off inlining, as we want the mapping that is printed to be stable.
+ finishBuild(builder, out, test);
+ if (backend == Backend.DEX) {
+ builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ } else {
+ assert backend == Backend.CF;
+ builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ }
ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
Path outputmapping = out.resolve("mapping.txt");
@@ -102,8 +137,15 @@
Stream.of(new String(Files.readAllBytes(outputmapping), StandardCharsets.UTF_8).split("\n"))
.filter(line -> !line.startsWith("#"))
.collect(Collectors.joining("\n"));
- String refMapping = new String(Files.readAllBytes(
- Paths.get(EXAMPLES_DIR, "shaking1", "print-mapping.ref")), StandardCharsets.UTF_8);
+ // For the CF backend we treat ConstString/LDC as a (potentially always) throwing instruction,
+ // as opposed to the DEX backend where it's throwing only if its string is ill-formed.
+ // When ConstString is throwing we preserve its position which makes it show up in the
+ // the output Proguard map. That's why the reference CF map is different from the DEX one.
+ String mapping_ref = backend == Backend.CF ? "print-mapping-cf.ref" : "print-mapping.ref";
+ String refMapping =
+ new String(
+ Files.readAllBytes(Paths.get(EXAMPLES_DIR, "shaking1", mapping_ref)),
+ StandardCharsets.UTF_8);
Assert.assertEquals(sorted(refMapping), sorted(actualMapping));
}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 51baed2..9cf0a85 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -23,7 +23,6 @@
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationParser;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
-import com.android.tools.r8.shaking.ProguardIdentifierNameStringRule;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.shaking.ProguardKeepRule;
import com.android.tools.r8.shaking.ProguardKeepRuleType;
@@ -261,7 +260,7 @@
assertTrue(inspector.clazz(getJavacGeneratedClassName(mainClass)).isPresent());
forNameClasses.forEach(clazz -> {
ClassSubject subject = inspector.clazz(getJavacGeneratedClassName(clazz));
- assertEquals(forceProguardCompatibility, subject.isPresent());
+ assertTrue(subject.isPresent());
assertEquals(subject.isPresent() && allowObfuscation, subject.isRenamed());
});
@@ -273,8 +272,10 @@
ProguardConfiguration configuration = parser.getConfigRawForTesting();
if (forceProguardCompatibility) {
List<ProguardConfigurationRule> rules = configuration.getRules();
- assertEquals(3, rules.size());
- Iterables.filter(rules, ProguardKeepRule.class).forEach(rule -> {
+ assertEquals(2, rules.size());
+ for (ProguardConfigurationRule r : rules) {
+ assertTrue(r instanceof ProguardKeepRule);
+ ProguardKeepRule rule = (ProguardKeepRule) r;
assertEquals(ProguardKeepRuleType.KEEP, rule.getType());
assertTrue(rule.getModifiers().allowsObfuscation);
assertTrue(rule.getModifiers().allowsOptimization);
@@ -292,18 +293,7 @@
assertEquals(1, memberRules.size());
assertEquals(ProguardMemberType.INIT, memberRules.iterator().next().getRuleType());
}
- });
- Iterables.filter(rules, ProguardIdentifierNameStringRule.class).forEach(rule -> {
- List<ProguardMemberRule> memberRules = rule.getMemberRules();
- ProguardClassNameList classNames = rule.getClassNames();
- assertEquals(1, classNames.size());
- DexType type = classNames.asSpecificDexTypes().get(0);
- assertEquals("java.lang.Class", type.toSourceString());
- assertEquals(1, memberRules.size());
- ProguardMemberRule memberRule = memberRules.iterator().next();
- assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
- assertEquals("forName", memberRule.getName().toString());
- });
+ }
} else {
assertEquals(0, configuration.getRules().size());
}
@@ -367,11 +357,11 @@
assertTrue(classSubject.isPresent());
assertEquals(allowObfuscation, classSubject.isRenamed());
FieldSubject foo = classSubject.field("java.lang.String", "foo");
- assertEquals(forceProguardCompatibility, foo.isPresent());
+ assertTrue(foo.isPresent());
assertEquals(foo.isPresent() && allowObfuscation, foo.isRenamed());
MethodSubject bar =
classSubject.method("java.lang.String", "bar", ImmutableList.of("java.lang.String"));
- assertEquals(forceProguardCompatibility, bar.isPresent());
+ assertTrue(bar.isPresent());
assertEquals(bar.isPresent() && allowObfuscation, bar.isRenamed());
// Check the Proguard compatibility rules generated.
@@ -382,8 +372,10 @@
ProguardConfiguration configuration = parser.getConfigRawForTesting();
if (forceProguardCompatibility) {
List<ProguardConfigurationRule> rules = configuration.getRules();
- assertEquals(4, rules.size());
- Iterables.filter(rules, ProguardKeepRule.class).forEach(rule -> {
+ assertEquals(2, rules.size());
+ for (ProguardConfigurationRule r : rules) {
+ assertTrue(r instanceof ProguardKeepRule);
+ ProguardKeepRule rule = (ProguardKeepRule) r;
assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS, rule.getType());
assertTrue(rule.getModifiers().allowsObfuscation);
assertTrue(rule.getModifiers().allowsOptimization);
@@ -400,18 +392,7 @@
assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
assertTrue(memberRule.getName().matches("bar"));
}
- });
- Iterables.filter(rules, ProguardIdentifierNameStringRule.class).forEach(rule -> {
- List<ProguardMemberRule> memberRules = rule.getMemberRules();
- ProguardClassNameList classNames = rule.getClassNames();
- assertEquals(1, classNames.size());
- DexType type = classNames.asSpecificDexTypes().get(0);
- assertEquals("java.lang.Class", type.toSourceString());
- assertEquals(1, memberRules.size());
- ProguardMemberRule memberRule = memberRules.iterator().next();
- assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
- assertTrue(memberRule.getName().toString().startsWith("getDeclared"));
- });
+ }
} else {
assertEquals(0, configuration.getRules().size());
}
@@ -480,13 +461,13 @@
assertTrue(classSubject.isPresent());
assertEquals(allowObfuscation, classSubject.isRenamed());
FieldSubject f = classSubject.field("int", "intField");
- assertEquals(forceProguardCompatibility, f.isPresent());
+ assertTrue(f.isPresent());
assertEquals(f.isPresent() && allowObfuscation, f.isRenamed());
f = classSubject.field("long", "longField");
- assertEquals(forceProguardCompatibility, f.isPresent());
+ assertTrue(f.isPresent());
assertEquals(f.isPresent() && allowObfuscation, f.isRenamed());
f = classSubject.field("java.lang.Object", "objField");
- assertEquals(forceProguardCompatibility, f.isPresent());
+ assertTrue(f.isPresent());
assertEquals(f.isPresent() && allowObfuscation, f.isRenamed());
// Check the Proguard compatibility rules generated.
@@ -497,9 +478,11 @@
ProguardConfiguration configuration = parser.getConfigRawForTesting();
if (forceProguardCompatibility) {
List<ProguardConfigurationRule> rules = configuration.getRules();
- assertEquals(6, rules.size());
+ assertEquals(3, rules.size());
Object2BooleanMap<String> keptFields = new Object2BooleanArrayMap<>();
- Iterables.filter(rules, ProguardKeepRule.class).forEach(rule -> {
+ for (ProguardConfigurationRule r : rules) {
+ assertTrue(r instanceof ProguardKeepRule);
+ ProguardKeepRule rule = (ProguardKeepRule) r;
assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS, rule.getType());
assertTrue(rule.getModifiers().allowsObfuscation);
assertTrue(rule.getModifiers().allowsOptimization);
@@ -512,23 +495,11 @@
ProguardMemberRule memberRule = memberRules.iterator().next();
assertEquals(ProguardMemberType.FIELD, memberRule.getRuleType());
keptFields.put(memberRule.getName().toString(), true);
- });
+ }
assertEquals(3, keptFields.size());
assertTrue(keptFields.containsKey("intField"));
assertTrue(keptFields.containsKey("longField"));
assertTrue(keptFields.containsKey("objField"));
- Iterables.filter(rules, ProguardIdentifierNameStringRule.class).forEach(rule -> {
- List<ProguardMemberRule> memberRules = rule.getMemberRules();
- ProguardClassNameList classNames = rule.getClassNames();
- assertEquals(1, classNames.size());
- DexType type = classNames.asSpecificDexTypes().get(0);
- assertTrue(type.toSourceString().startsWith("java.util.concurrent.atomic.Atomic"));
- assertTrue(type.toSourceString().endsWith("FieldUpdater"));
- assertEquals(1, memberRules.size());
- ProguardMemberRule memberRule = memberRules.iterator().next();
- assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
- assertEquals("newUpdater", memberRule.getName().toString());
- });
} else {
assertEquals(0, configuration.getRules().size());
}
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
index b22dc1e..b3e6367 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
@@ -79,9 +79,11 @@
}
AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableDevirtualization = false);
inspection.accept(new CodeInspector(app, o -> o.enableCfFrontend = true));
- assertEquals(
- expectedResult,
- backend == Backend.DEX ? runOnArt(app, mainClass) : runOnJava(app, mainClass));
+ String result = backend == Backend.DEX ? runOnArt(app, mainClass) : runOnJava(app, mainClass);
+ if (ToolHelper.isWindows()) {
+ result = result.replace(System.lineSeparator(), "\n");
+ }
+ assertEquals(expectedResult, result);
}
private int countInstructionInX(CodeInspector inspector, Predicate<InstructionSubject> invoke) {
diff --git a/src/test/kotlinR8TestResources/class_staticizer/main.kt b/src/test/kotlinR8TestResources/class_staticizer/main.kt
new file mode 100644
index 0000000..01915c4
--- /dev/null
+++ b/src/test/kotlinR8TestResources/class_staticizer/main.kt
@@ -0,0 +1,43 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package class_staticizer
+
+private var COUNT = 0
+
+fun next() = "${COUNT++}".padStart(3, '0')
+
+fun main(args: Array<String>) {
+ println(Regular.foo)
+ println(Regular.bar)
+ println(Regular.blah(next()))
+ println(Derived.foo)
+ println(Derived.bar)
+ println(Derived.blah(next()))
+ println(Util.foo)
+ println(Util.bar)
+ println(Util.blah(next()))
+}
+
+open class Regular {
+ companion object {
+ var foo: String = "Regular::CC::foo[${next()}]"
+ var bar: String = blah(next())
+ fun blah(p: String) = "Regular::CC::blah($p)[${next()}]"
+ }
+}
+
+open class Derived : Regular() {
+ companion object {
+ var foo: String = "Derived::CC::foo[${next()}]"
+ var bar: String = blah(next())
+ fun blah(p: String) = "Derived::CC::blah($p)[${next()}]"
+ }
+}
+
+object Util {
+ var foo: String = "Util::foo[${next()}]"
+ var bar: String = Regular.blah(next()) + Derived.blah(next())
+ fun blah(p: String) = "Util::blah($p)[${next()}]"
+}