Replace Class.forName() with const-class in presence of clinits
Change-Id: I1a0b7727dde7e59c9384b489a55f409c91d2f739
diff --git a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
index 6ba885d..519bd27 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Sets;
@@ -76,8 +77,8 @@
return result;
}
- public FeatureSplit getFeatureSplit(DexProgramClass clazz) {
- return getFeatureSplit(clazz.getType());
+ public FeatureSplit getFeatureSplit(ProgramDefinition clazz) {
+ return getFeatureSplit(clazz.getContextType());
}
public FeatureSplit getFeatureSplit(DexType type) {
@@ -92,7 +93,7 @@
return getFeatureSplit(clazz).isBase();
}
- public boolean isInBaseOrSameFeatureAs(DexProgramClass clazz, DexProgramClass context) {
+ public boolean isInBaseOrSameFeatureAs(DexProgramClass clazz, ProgramDefinition context) {
FeatureSplit split = getFeatureSplit(clazz);
return split.isBase() || split == getFeatureSplit(context);
}
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index ed19598..f872a56 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -27,8 +27,7 @@
return OptionalBool.FALSE;
}
if (clazz.isProgramClass()
- && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(
- clazz.asProgramClass(), context.getContextClass())) {
+ && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(clazz.asProgramClass(), context)) {
return OptionalBool.UNKNOWN;
}
return OptionalBool.TRUE;
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index e827a25..c49b888 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.GenericSignature.ClassSignature;
import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.TraversalContinuation;
@@ -659,6 +660,16 @@
appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet());
}
+ public boolean classInitializationMayHaveSideEffectsInContext(
+ AppView<AppInfoWithLiveness> appView, ProgramDefinition context) {
+ return classInitializationMayHaveSideEffects(
+ appView,
+ // Types that are a super type of the current context are guaranteed to be initialized
+ // already.
+ type -> appView.appInfo().isSubtype(context.getContextType(), type),
+ Sets.newIdentityHashSet());
+ }
+
public boolean classInitializationMayHaveSideEffects(
AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) {
if (!seen.add(type)) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index e3fa34e..a3f8421 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -4,11 +4,13 @@
package com.android.tools.r8.ir.code;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -247,6 +249,19 @@
}
@Override
+ public void replaceCurrentInstructionWithConstClass(
+ AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
+ if (current == null) {
+ throw new IllegalStateException();
+ }
+
+ TypeElement typeElement = TypeElement.classClassType(appView, definitelyNotNull());
+ Value value = code.createValue(typeElement, localInfo);
+ ConstClass constClass = new ConstClass(value, type);
+ replaceCurrentInstruction(constClass);
+ }
+
+ @Override
public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
if (current == null) {
throw new IllegalStateException();
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 90e6097..8e7236c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -55,7 +55,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
-public class IRCode {
+public class IRCode implements ValueFactory {
private static final int MAX_MARKING_COLOR = 0x40000000;
@@ -1097,12 +1097,9 @@
return thisValue;
}
- public Value createValue(TypeElement typeLattice, DebugLocalInfo local) {
- return new Value(valueNumberGenerator.next(), typeLattice, local);
- }
-
- public Value createValue(TypeElement typeLattice) {
- return createValue(typeLattice, null);
+ @Override
+ public Value createValue(TypeElement type, DebugLocalInfo local) {
+ return new Value(valueNumberGenerator.next(), type, local);
}
public ConstNumber createNumberConstant(long value, TypeElement type) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index bec911e..cb33b18 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -41,6 +42,12 @@
}
@Override
+ public void replaceCurrentInstructionWithConstClass(
+ AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
+ instructionIterator.replaceCurrentInstructionWithConstClass(appView, code, type, localInfo);
+ }
+
+ @Override
public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
instructionIterator.replaceCurrentInstructionWithConstInt(code, value);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InitClass.java b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
index 61a1882..c878d9a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InitClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
@@ -36,6 +36,10 @@
this.clazz = clazz;
}
+ public static Builder builder() {
+ return new Builder();
+ }
+
public DexType getClassValue() {
return clazz;
}
@@ -175,4 +179,26 @@
public String toString() {
return super.toString() + "; " + clazz.toSourceString();
}
+
+ public static class Builder extends BuilderBase<Builder, InitClass> {
+
+ private DexType type;
+
+ private Builder() {}
+
+ public Builder setType(DexType type) {
+ this.type = type;
+ return this;
+ }
+
+ @Override
+ public InitClass build() {
+ return amend(new InitClass(outValue, type));
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+ }
}
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 9711ff2..7fc0d77 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
@@ -1546,6 +1546,7 @@
public abstract static class BuilderBase<B extends BuilderBase<B, I>, I extends Instruction> {
+ protected Value outValue;
protected Position position;
public abstract I build();
@@ -1559,6 +1560,15 @@
return instruction;
}
+ public B setOutValue(Value outValue) {
+ this.outValue = outValue;
+ return self();
+ }
+
+ public B setFreshOutValue(ValueFactory factory, TypeElement type) {
+ return setOutValue(factory.createValue(type));
+ }
+
public B setPosition(Position position) {
this.position = position;
return self();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 2790e66..792018b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -19,6 +20,12 @@
public interface InstructionListIterator
extends InstructionIterator, ListIterator<Instruction>, PreviousUntilIterator<Instruction> {
+ default void addBefore(Instruction instruction) {
+ previous();
+ add(instruction);
+ next();
+ }
+
/** See {@link #replaceCurrentInstruction(Instruction, Set)}. */
default void replaceCurrentInstruction(Instruction newInstruction) {
replaceCurrentInstruction(newInstruction, null);
@@ -84,6 +91,9 @@
Value insertConstStringInstruction(AppView<?> appView, IRCode code, DexString value);
+ void replaceCurrentInstructionWithConstClass(
+ AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo);
+
void replaceCurrentInstructionWithConstInt(IRCode code, int value);
void replaceCurrentInstructionWithStaticGet(
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 47478df..9bae360 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -244,7 +244,6 @@
public static class Builder extends BuilderBase<Builder, InvokeStatic> {
private DexMethod method;
- private Value outValue;
private List<Value> arguments = Collections.emptyList();
public Builder setArguments(List<Value> arguments) {
@@ -266,19 +265,13 @@
return setMethod(method.getReference());
}
- public Builder setOutValue(Value outValue) {
- this.outValue = outValue;
- return this;
- }
-
@Override
public InvokeStatic build() {
assert arguments != null;
assert method != null;
assert method.getArity() == arguments.size();
assert outValue == null || !method.getReturnType().isVoidType();
- InvokeStatic result = new InvokeStatic(method, outValue, arguments);
- return amend(result);
+ return amend(new InvokeStatic(method, outValue, arguments));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index d851eb9..0dd2895 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -54,6 +55,12 @@
}
@Override
+ public void replaceCurrentInstructionWithConstClass(
+ AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
+ currentBlockIterator.replaceCurrentInstructionWithConstClass(appView, code, type, localInfo);
+ }
+
+ @Override
public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
currentBlockIterator.replaceCurrentInstructionWithConstInt(code, value);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueFactory.java b/src/main/java/com/android/tools/r8/ir/code/ValueFactory.java
new file mode 100644
index 0000000..ef1b16c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueFactory.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, 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.code;
+
+import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+
+public interface ValueFactory {
+
+ default Value createValue(TypeElement type) {
+ return createValue(type, null);
+ }
+
+ Value createValue(TypeElement type, DebugLocalInfo localInfo);
+}
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 324ad93..b2c5888 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
@@ -111,7 +111,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -124,7 +123,7 @@
private static final int PEEPHOLE_OPTIMIZATION_PASSES = 2;
public final AppView<?> appView;
- public final Set<DexType> mainDexClasses;
+ public final MainDexTracingResult mainDexClasses;
private final Timing timing;
private final Outliner outliner;
@@ -196,7 +195,7 @@
this.appView = appView;
this.options = appView.options();
this.printer = printer;
- this.mainDexClasses = mainDexClasses.getClasses();
+ this.mainDexClasses = mainDexClasses;
this.codeRewriter = new CodeRewriter(appView, this);
this.constantCanonicalizer = new ConstantCanonicalizer(codeRewriter);
this.classInitializerDefaultsOptimization =
@@ -1305,7 +1304,8 @@
if (appView.appInfo().hasLiveness()) {
// Reflection optimization 1. getClass() / forName() -> const-class
timing.begin("Rewrite to const class");
- ReflectionOptimizer.rewriteGetClassOrForNameToConstClass(appView.withLiveness(), code);
+ ReflectionOptimizer.rewriteGetClassOrForNameToConstClass(
+ appView.withLiveness(), code, mainDexClasses);
timing.end();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index 4e89840..cc5e7ed 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -4,8 +4,9 @@
package com.android.tools.r8.ir.optimize;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
+import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexItemFactory;
@@ -13,36 +14,35 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InitClass;
import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.MainDexTracingResult;
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.Sets;
import java.util.Set;
+import java.util.function.BiConsumer;
public class ReflectionOptimizer {
// Rewrite getClass() to const-class if the type of the given instance is effectively final.
// Rewrite forName() to const-class if the type is resolvable, accessible and already initialized.
public static void rewriteGetClassOrForNameToConstClass(
- AppView<AppInfoWithLiveness> appView, IRCode code) {
+ AppView<AppInfoWithLiveness> appView, IRCode code, MainDexTracingResult mainDexClasses) {
if (!appView.appInfo().canUseConstClassInstructions(appView.options())) {
return;
}
Set<Value> affectedValues = Sets.newIdentityHashSet();
ProgramMethod context = code.context();
- ClassInitializationAnalysis classInitializationAnalysis =
- new ClassInitializationAnalysis(appView, code);
for (BasicBlock block : code.blocks) {
// Conservatively bail out if the containing block has catch handlers.
// TODO(b/118509730): unless join of all catch types is ClassNotFoundException ?
@@ -51,32 +51,28 @@
}
InstructionListIterator it = block.listIterator(code);
while (it.hasNext()) {
- Instruction current = it.next();
- if (!current.hasOutValue() || !current.outValue().isUsed()) {
+ InvokeMethod invoke = it.nextUntil(x -> x.isInvokeStatic() || x.isInvokeVirtual());
+ if (invoke == null) {
continue;
}
- DexType type = null;
- if (current.isInvokeVirtual()) {
- type = getTypeForGetClass(appView, context, current.asInvokeVirtual());
- } else if (current.isInvokeStatic()) {
- type = getTypeForClassForName(
- appView, classInitializationAnalysis, context, current.asInvokeStatic());
- }
- if (type != null) {
- affectedValues.addAll(current.outValue().affectedValues());
- TypeElement typeLattice = TypeElement.classClassType(appView, definitelyNotNull());
- Value value = code.createValue(typeLattice, current.getLocalInfo());
- ConstClass constClass = new ConstClass(value, type);
- it.replaceCurrentInstruction(constClass);
- if (appView.options().isGeneratingClassFiles()) {
- code.method()
- .upgradeClassFileVersion(
- appView.options().requiredCfVersionForConstClassInstructions());
- }
+
+ if (invoke.isInvokeStatic()) {
+ applyTypeForClassForNameTo(
+ appView,
+ context,
+ invoke.asInvokeStatic(),
+ rewriteSingleGetClassOrForNameToConstClass(
+ appView, code, it, invoke, affectedValues, mainDexClasses));
+ } else {
+ applyTypeForGetClassTo(
+ appView,
+ context,
+ invoke.asInvokeVirtual(),
+ rewriteSingleGetClassOrForNameToConstClass(
+ appView, code, it, invoke, affectedValues, mainDexClasses));
}
}
}
- classInitializationAnalysis.finish();
// Newly introduced const-class is not null, and thus propagate that information.
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
@@ -84,24 +80,79 @@
assert code.isConsistentSSA();
}
- private static DexType getTypeForGetClass(
- AppView<AppInfoWithLiveness> appView, ProgramMethod context, InvokeVirtual invoke) {
+ private static BiConsumer<DexType, DexClass> rewriteSingleGetClassOrForNameToConstClass(
+ AppView<AppInfoWithLiveness> appView,
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ Set<Value> affectedValues,
+ MainDexTracingResult mainDexClasses) {
+ return (type, baseClass) -> {
+ if (invoke.getInvokedMethod().match(appView.dexItemFactory().classMethods.forName)) {
+ // Bail-out if the optimization could increase the size of the main dex.
+ if (baseClass.isProgramClass()
+ && !mainDexClasses.canReferenceItemFromContextWithoutIncreasingMainDexSize(
+ baseClass.asProgramClass(), code.context())) {
+ return;
+ }
+
+ // We need to initialize the type if it may have observable side effects.
+ if (type.isClassType()
+ && baseClass.classInitializationMayHaveSideEffectsInContext(appView, code.context())) {
+ if (!baseClass.isProgramClass() || !appView.canUseInitClass()) {
+ // No way to trigger the class initialization of the given class without
+ // Class.forName(), so skip.
+ return;
+ }
+
+ instructionIterator.addBefore(
+ InitClass.builder()
+ .setFreshOutValue(code, TypeElement.getInt())
+ .setType(type)
+ .setPosition(invoke)
+ .build());
+ }
+ }
+
+ // If there are no users of the const-class then simply remove the instruction.
+ if (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers()) {
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ return;
+ }
+
+ // Otherwise insert a const-class instruction.
+ affectedValues.addAll(invoke.outValue().affectedValues());
+ instructionIterator.replaceCurrentInstructionWithConstClass(
+ appView, code, type, invoke.getLocalInfo());
+ if (appView.options().isGeneratingClassFiles()) {
+ code.method()
+ .upgradeClassFileVersion(
+ appView.options().requiredCfVersionForConstClassInstructions());
+ }
+ };
+ }
+
+ private static void applyTypeForGetClassTo(
+ AppView<AppInfoWithLiveness> appView,
+ ProgramMethod context,
+ InvokeVirtual invoke,
+ BiConsumer<DexType, ? super DexClass> consumer) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
DexMethod invokedMethod = invoke.getInvokedMethod();
// Class<?> Object#getClass() is final and cannot be overridden.
if (invokedMethod != dexItemFactory.objectMembers.getClass) {
- return null;
+ return;
}
Value in = invoke.getReceiver();
if (in.hasLocalInfo()) {
- return null;
+ return;
}
TypeElement inType = in.getType();
// Check the receiver is either class type or array type. Also make sure it is not
// nullable.
if (!(inType.isClassType() || inType.isArrayType())
|| inType.isNullable()) {
- return null;
+ return;
}
DexType type =
inType.isClassType()
@@ -110,49 +161,49 @@
DexType baseType = type.toBaseType(dexItemFactory);
// Make sure base type is a class type.
if (!baseType.isClassType()) {
- return null;
+ return;
}
// Only consider program class, e.g., platform can introduce subtypes in different
// versions.
DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(baseType));
if (clazz == null) {
- return null;
+ return;
}
// Only consider effectively final class. Exception: new Base().getClass().
if (!clazz.isEffectivelyFinal(appView)
&& (in.isPhi() || !in.definition.isCreatingInstanceOrArray())) {
- return null;
+ return;
}
// Make sure the target (base) type is visible.
ConstraintWithTarget constraints =
ConstraintWithTarget.classIsVisible(context, baseType, appView);
if (constraints == ConstraintWithTarget.NEVER) {
- return null;
+ return;
}
- return type;
+
+ consumer.accept(type, clazz);
}
- private static DexType getTypeForClassForName(
+ private static void applyTypeForClassForNameTo(
AppView<AppInfoWithLiveness> appView,
- ClassInitializationAnalysis classInitializationAnalysis,
ProgramMethod context,
- InvokeStatic invoke) {
+ InvokeStatic invoke,
+ BiConsumer<DexType, ? super DexClass> consumer) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
DexMethod invokedMethod = invoke.getInvokedMethod();
// Class<?> Class#forName(String) is final and cannot be overridden.
if (invokedMethod != dexItemFactory.classMethods.forName) {
- return null;
+ return;
}
- assert invoke.inValues().size() == 1;
- Value in = invoke.inValues().get(0).getAliasedValue();
+ assert invoke.arguments().size() == 1;
+ Value in = invoke.getArgument(0).getAliasedValue();
// Only consider const-string input without locals.
if (in.hasLocalInfo() || in.isPhi()) {
- return null;
+ return;
}
// Also, check if the result of forName() is updatable via locals.
- Value out = invoke.outValue();
- if (out != null && out.hasLocalInfo()) {
- return null;
+ if (invoke.hasOutValue() && invoke.outValue().hasLocalInfo()) {
+ return;
}
DexType type = null;
if (in.definition.isDexItemBasedConstString()) {
@@ -172,46 +223,39 @@
}
if (descriptor == null
|| descriptor.indexOf(DescriptorUtils.JAVA_PACKAGE_SEPARATOR) > 0) {
- return null;
+ return;
}
type = dexItemFactory.createType(descriptor);
// Check if the given name refers to a reference type.
if (!type.isReferenceType()) {
- return null;
+ return;
}
} else {
// Bail out for non-deterministic input to Class<?>#forName(name).
- return null;
+ return;
}
if (type == null) {
- return null;
+ return;
}
// Make sure the (base) type is resolvable.
DexType baseType = type.toBaseType(dexItemFactory);
- DexClass baseClazz = appView.appInfo().definitionForWithoutExistenceAssert(baseType);
- if (baseClazz == null || !baseClazz.isResolvable(appView)) {
- return null;
+ DexClass baseClass = appView.appInfo().definitionForWithoutExistenceAssert(baseType);
+ if (baseClass == null || !baseClass.isResolvable(appView)) {
+ return;
}
- // Don't allow the instantiated class to be in a feature, if it is, we can get a
- // NoClassDefFoundError from dalvik/art.
- if (baseClazz.isProgramClass()
- && appView.appInfo().getClassToFeatureSplitMap().isInFeature(baseClazz.asProgramClass())) {
- return null;
- }
// Make sure the (base) type is visible.
- ConstraintWithTarget constraints =
- ConstraintWithTarget.classIsVisible(context, baseType, appView);
- if (constraints == ConstraintWithTarget.NEVER) {
- return null;
+ ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
+ if (AccessControl.isClassAccessible(baseClass, context, classToFeatureSplitMap)
+ .isPossiblyFalse()) {
+ return;
}
- // Make sure the type is already initialized.
- // Note that, if the given name refers to an array type, the corresponding Class<?> won't
- // be initialized. So, it's okay to rewrite the instruction.
- if (type.isClassType()
- && !classInitializationAnalysis.isClassDefinitelyLoadedBeforeInstruction(type, invoke)) {
- return null;
- }
- return type;
+
+ // If the type is guaranteed to be visible, it must be in the same feature as the current method
+ // or in the base.
+ assert !baseClass.isProgramClass()
+ || classToFeatureSplitMap.isInBaseOrSameFeatureAs(baseClass.asProgramClass(), context);
+
+ consumer.accept(type, baseClass);
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index a406a8d..01157b9 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -12,6 +12,7 @@
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.DexMember;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
@@ -299,7 +300,7 @@
AppView<AppInfoWithLiveness> appView, DexString dexString) {
// "fully.qualified.ClassName.fieldOrMethodName"
// "fully.qualified.ClassName#fieldOrMethodName"
- DexReference itemBasedString = inferMemberFromNameString(appView, dexString);
+ DexMember<?, ?> itemBasedString = inferMemberFromNameString(appView, dexString);
if (itemBasedString == null) {
// "fully.qualified.ClassName"
return inferTypeFromNameString(appView, dexString);
@@ -333,7 +334,7 @@
return null;
}
- private static DexReference inferMemberFromNameString(
+ private static DexMember<?, ?> inferMemberFromNameString(
AppView<AppInfoWithLiveness> appView, DexString dexString) {
String identifier = dexString.toString();
String typeIdentifier = null;
@@ -368,7 +369,7 @@
if (holder == null) {
return null;
}
- DexReference itemBasedString = inferFieldInHolder(holder, memberIdentifier, null);
+ DexMember<?, ?> itemBasedString = inferFieldInHolder(holder, memberIdentifier, null);
if (itemBasedString == null) {
itemBasedString = inferMethodNameInHolder(holder, memberIdentifier);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java b/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
index 76b6d8a..6383473 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
@@ -6,8 +6,8 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramDefinition;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.Collection;
@@ -76,6 +76,17 @@
this.classes = Sets.union(roots, dependencies);
}
+ public boolean canReferenceItemFromContextWithoutIncreasingMainDexSize(
+ ProgramDefinition item, ProgramDefinition context) {
+ // If the context is not a root, then additional references from inside the context will not
+ // increase the size of the main dex.
+ if (!isRoot(context)) {
+ return true;
+ }
+ // Otherwise, require that the item is a root itself.
+ return isRoot(item);
+ }
+
public boolean isEmpty() {
assert !roots.isEmpty() || dependencies.isEmpty();
return roots.isEmpty();
@@ -93,8 +104,8 @@
return classes;
}
- public boolean contains(DexProgramClass clazz) {
- return contains(clazz.type);
+ public boolean contains(ProgramDefinition clazz) {
+ return contains(clazz.getContextType());
}
public boolean contains(DexType type) {
@@ -111,6 +122,14 @@
});
}
+ public boolean isRoot(ProgramDefinition definition) {
+ return getRoots().contains(definition.getContextType());
+ }
+
+ public boolean isDependency(ProgramDefinition definition) {
+ return getDependencies().contains(definition.getContextType());
+ }
+
public MainDexTracingResult prunedCopy(AppInfoWithLiveness appInfo) {
Builder builder = builder(appInfo);
Predicate<DexType> wasPruned = appInfo::wasPruned;
diff --git a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
index ea450e4..dbe0d64 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
@@ -3,26 +3,25 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.compatproguard;
-import com.android.tools.r8.CompatProguardCommandBuilder;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.R8CompatTestBuilder;
+import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.smali.SmaliTestBase;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.nio.file.Path;
import java.util.List;
class CompatProguardSmaliTestBase extends SmaliTestBase {
- CodeInspector runCompatProguard(SmaliBuilder builder, List<String> proguardConfigurations)
- throws Exception {
- Path dexOutputDir = temp.newFolder().toPath();
- R8Command.Builder commandBuilder =
- new CompatProguardCommandBuilder(true)
- .setOutput(dexOutputDir, OutputMode.DexIndexed)
- .addProguardConfiguration(proguardConfigurations, Origin.unknown());
- ToolHelper.getAppBuilder(commandBuilder).addDexProgramData(builder.compile(), Origin.unknown());
- return new CodeInspector(ToolHelper.runR8(commandBuilder.build()));
+
+ CodeInspector runCompatProguard(SmaliBuilder builder, List<String> keepRules) throws Exception {
+ return runCompatProguard(builder, testBuilder -> testBuilder.addKeepRules(keepRules));
+ }
+
+ CodeInspector runCompatProguard(
+ SmaliBuilder builder, ThrowableConsumer<R8CompatTestBuilder> configuration) throws Exception {
+ return testForR8Compat(Backend.DEX)
+ .addProgramDexFileData(builder.compile())
+ .applyIf(configuration != null, configuration)
+ .compile()
+ .inspector();
}
}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
index 6aa8805..9976c33 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
@@ -12,11 +12,10 @@
import com.android.tools.r8.code.ReturnVoid;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import java.util.List;
import org.junit.Test;
public class ForNameTest extends CompatProguardSmaliTestBase {
@@ -35,11 +34,17 @@
"return-void");
builder.addClass(BOO);
- List<String> pgConfigs = ImmutableList.of(
- keepMainProguardConfiguration(CLASS_NAME),
- "-dontshrink",
- "-dontoptimize");
- CodeInspector inspector = runCompatProguard(builder, pgConfigs);
+ CodeInspector inspector =
+ runCompatProguard(
+ builder,
+ testBuilder ->
+ testBuilder
+ .addKeepMainRule(CLASS_NAME)
+ // Add main dex rule to disable Class.forName() optimization.
+ .addMainDexRules("-keep class " + CLASS_NAME)
+ .noOptimization()
+ .noTreeShaking()
+ .setMinApi(AndroidApiLevel.B));
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -65,12 +70,18 @@
"return-void");
builder.addClass(BOO);
- List<String> pgConfigs = ImmutableList.of(
- keepMainProguardConfiguration(CLASS_NAME),
- "-dontshrink",
- "-dontoptimize",
- "-dontobfuscate");
- CodeInspector inspector = runCompatProguard(builder, pgConfigs);
+ CodeInspector inspector =
+ runCompatProguard(
+ builder,
+ testBuilder ->
+ testBuilder
+ .addKeepMainRule(CLASS_NAME)
+ // Add main dex rule to disable Class.forName() optimization.
+ .addMainDexRules("-keep class " + CLASS_NAME)
+ .noOptimization()
+ .noMinification()
+ .noTreeShaking()
+ .setMinApi(AndroidApiLevel.B));
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java b/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java
index 9c006cb..5fbca11 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java
@@ -60,7 +60,8 @@
companionClassSubject
.allMethods()
.forEach(
- method -> assertTrue(method.streamInstructions().anyMatch(this::isInvokeClassForName)));
+ method ->
+ assertTrue(method.streamInstructions().noneMatch(this::isInvokeClassForName)));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java
index f99639f..26b927e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java
@@ -161,7 +161,7 @@
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 2, 2);
+ test(result, 1, 3);
// R8 release, minification.
result =
@@ -173,6 +173,6 @@
.setMinApi(parameters.getApiLevel())
// We are not checking output because it can't be matched due to minification. Just run.
.run(parameters.getRuntime(), MAIN);
- test(result, 2, 2);
+ test(result, 1, 3);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 3f7bb60..fa7b74e 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
@@ -61,6 +62,12 @@
}
@Override
+ public void replaceCurrentInstructionWithConstClass(
+ AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
+ throw new Unimplemented();
+ }
+
+ @Override
public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
throw new Unimplemented();
}
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 ad8c526..8775bd7 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -115,19 +115,19 @@
// Without -adaptclassstrings
private static void test1_rule1(TestParameters parameters, CodeInspector inspector) {
- int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 2 : 1;
+ int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 0 : 0;
test1_rules(inspector, expectedRenamedIdentifierInMain, 0, 0);
}
// With -adaptclassstrings *.*A
private static void test1_rule2(TestParameters parameters, CodeInspector inspector) {
- int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 2 : 1;
+ int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 0 : 0;
test1_rules(inspector, expectedRenamedIdentifierInMain, 1, 1);
}
// With -adaptclassstrings (no filter)
private static void test1_rule3(TestParameters parameters, CodeInspector inspector) {
- int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 3 : 2;
+ int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 1 : 1;
test1_rules(inspector, expectedRenamedIdentifierInMain, 1, 1);
}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithDexItemBasedConstStringTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithDexItemBasedConstStringTest.java
index 8a36090..e374f2a 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithDexItemBasedConstStringTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithDexItemBasedConstStringTest.java
@@ -4,22 +4,38 @@
package com.android.tools.r8.repackage;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class RepackageWithDexItemBasedConstStringTest extends RepackageTestBase {
+ @Parameters(name = "{1}, kind: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+ getTestParameters()
+ .withAllRuntimes()
+ .withApiLevelsEndingAtExcluding(AndroidApiLevel.L)
+ .build());
+ }
+
public RepackageWithDexItemBasedConstStringTest(
String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
super(flattenPackageHierarchyOrRepackageClasses, parameters);
@@ -30,6 +46,7 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
+ .addMainDexClassRules(TestClass.class)
.apply(this::configureRepackaging)
.setMinApi(parameters.getApiLevel())
.compile()
diff --git a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java
index c92d7e7..abd2407 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java
@@ -87,7 +87,7 @@
@Test
public void test() throws Exception {
String expectedOutput = StringUtils.lines("In Api.api1 6", "Result 6");
- testForR8(parameters.getBackend())
+ testForR8Compat(parameters.getBackend())
.addInnerClasses(KeepParameterNamesUnsortedLocalVariablesTableTest.class)
.addProgramClassFileData(dumpApi())
.addKeepMainRule(TestClass.class)