Merge commit 'ee7803543ca0f13b223ff7bc02543c56cabbeacf' into dev-release
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 75f862e..4c4d2d5 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -370,6 +370,8 @@
classesToRetainInnerClassAttributeFor =
AnnotationRemover.computeClassesToRetainInnerClassAttributeFor(appView.withLiveness());
+ // TODO(b/149729626): Annotations should not be removed until after the second round of tree
+ // shaking, since they are needed for interpretation of keep rules.
new AnnotationRemover(appView.withLiveness(), classesToRetainInnerClassAttributeFor)
.ensureValid()
.run();
@@ -419,6 +421,8 @@
AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
appView.setGraphLense(new MemberRebindingAnalysis(appViewWithLiveness).run());
+ appView.appInfo().withLiveness().getFieldAccessInfoCollection().restrictToProgram(appView);
+
if (options.shouldDesugarNests()) {
timing.begin("NestBasedAccessDesugaring");
R8NestBasedAccessDesugaring analyzer = new R8NestBasedAccessDesugaring(appViewWithLiveness);
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index 7ab2a56..0878119 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -23,6 +23,7 @@
public static final String COMPILATION_MODE = "compilation-mode";
public static final String HAS_CHECKSUMS = "has-checksums";
public static final String PG_MAP_ID = "pg-map-id";
+ public static final String R8_MODE = "r8-mode";
public enum Tool {
D8,
@@ -128,6 +129,16 @@
return this;
}
+ public String getR8Mode() {
+ return jsonObject.get(R8_MODE).getAsString();
+ }
+
+ public Marker setR8Mode(String r8Mode) {
+ assert !jsonObject.has(R8_MODE);
+ jsonObject.addProperty(R8_MODE, r8Mode);
+ return this;
+ }
+
@Override
public String toString() {
// In order to make printing of markers deterministic we sort the entries by key.
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 141f742..eb972ca 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -17,6 +17,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
@@ -402,16 +403,32 @@
* 5.4.3.3 of the JVM Spec</a>. As this is the same for interfaces and classes, we share one
* implementation.
*/
- ResolutionResult resolveMethodStep3(DexClass clazz, DexMethod method) {
- MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder(clazz);
- resolveMethodStep3Helper(clazz, method, builder);
- return builder.resolve();
+ private ResolutionResult resolveMethodStep3(DexClass clazz, DexMethod method) {
+ MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
+ resolveMethodStep3Helper(method, clazz, builder);
+ return builder.resolve(clazz);
+ }
+
+ // Non-private lookup (ie, not resolution) to find interface targets.
+ DexClassAndMethod lookupMaximallySpecificTarget(DexClass clazz, DexMethod method) {
+ MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
+ resolveMethodStep3Helper(method, clazz, builder);
+ return builder.lookup();
}
/** Helper method that builds the set of maximally specific methods. */
private void resolveMethodStep3Helper(
- DexClass clazz, DexMethod method, MaximallySpecificMethodsBuilder builder) {
- for (DexType iface : clazz.interfaces.values) {
+ DexMethod method, DexClass clazz, MaximallySpecificMethodsBuilder builder) {
+ resolveMethodStep3Helper(
+ method, clazz.superType, Arrays.asList(clazz.interfaces.values), builder);
+ }
+
+ private void resolveMethodStep3Helper(
+ DexMethod method,
+ DexType superType,
+ List<DexType> interfaces,
+ MaximallySpecificMethodsBuilder builder) {
+ for (DexType iface : interfaces) {
DexClass definiton = definitionFor(iface);
if (definiton == null) {
// Ignore missing interface definitions.
@@ -424,14 +441,14 @@
builder.addCandidate(definiton, result, this);
} else {
// Look at the super-interfaces of this class and keep searching.
- resolveMethodStep3Helper(definiton, method, builder);
+ resolveMethodStep3Helper(method, definiton, builder);
}
}
// Now look at indirect super interfaces.
- if (clazz.superType != null) {
- DexClass superClass = definitionFor(clazz.superType);
+ if (superType != null) {
+ DexClass superClass = definitionFor(superType);
if (superClass != null) {
- resolveMethodStep3Helper(superClass, method, builder);
+ resolveMethodStep3Helper(method, superClass, builder);
}
}
}
@@ -579,8 +596,6 @@
private static class MaximallySpecificMethodsBuilder {
- private final DexClass initialResolutionHolder;
-
// The set of actual maximally specific methods.
// This set is linked map so that in the case where a number of methods remain a deterministic
// choice can be made. The map is from definition classes to their maximally specific method, or
@@ -589,10 +604,6 @@
// prior to writing.
LinkedHashMap<DexClass, DexEncodedMethod> maximallySpecificMethods = new LinkedHashMap<>();
- public MaximallySpecificMethodsBuilder(DexClass initialResolutionHolder) {
- this.initialResolutionHolder = initialResolutionHolder;
- }
-
void addCandidate(DexClass holder, DexEncodedMethod method, AppInfo appInfo) {
// If this candidate is already a candidate or it is shadowed, then no need to continue.
if (maximallySpecificMethods.containsKey(holder)) {
@@ -629,16 +640,26 @@
}
}
- ResolutionResult resolve() {
+ DexClassAndMethod lookup() {
+ SingleResolutionResult result = internalResolve(null).asSingleResolution();
+ return result != null
+ ? DexClassAndMethod.create(result.getResolvedHolder(), result.getResolvedMethod())
+ : null;
+ }
+
+ ResolutionResult resolve(DexClass initialResolutionHolder) {
+ assert initialResolutionHolder != null;
+ return internalResolve(initialResolutionHolder);
+ }
+
+ private ResolutionResult internalResolve(DexClass initialResolutionHolder) {
if (maximallySpecificMethods.isEmpty()) {
return NoSuchMethodResult.INSTANCE;
}
// Fast path in the common case of a single method.
if (maximallySpecificMethods.size() == 1) {
- Entry<DexClass, DexEncodedMethod> first =
- maximallySpecificMethods.entrySet().iterator().next();
- return new SingleResolutionResult(
- initialResolutionHolder, first.getKey(), first.getValue());
+ return singleResultHelper(
+ initialResolutionHolder, maximallySpecificMethods.entrySet().iterator().next());
}
Entry<DexClass, DexEncodedMethod> firstMaximallySpecificMethod = null;
List<Entry<DexClass, DexEncodedMethod>> nonAbstractMethods =
@@ -659,21 +680,24 @@
// If there are no non-abstract methods, then any candidate will suffice as a target.
// For deterministic resolution, we return the first mapped method (of the linked map).
if (nonAbstractMethods.isEmpty()) {
- return new SingleResolutionResult(
- initialResolutionHolder,
- firstMaximallySpecificMethod.getKey(),
- firstMaximallySpecificMethod.getValue());
+ return singleResultHelper(initialResolutionHolder, firstMaximallySpecificMethod);
}
// If there is exactly one non-abstract method (a default method) it is the resolution target.
if (nonAbstractMethods.size() == 1) {
- Entry<DexClass, DexEncodedMethod> entry = nonAbstractMethods.get(0);
- return new SingleResolutionResult(
- initialResolutionHolder, entry.getKey(), entry.getValue());
+ return singleResultHelper(initialResolutionHolder, nonAbstractMethods.get(0));
}
return IncompatibleClassResult.create(ListUtils.map(nonAbstractMethods, Entry::getValue));
}
}
+ private static SingleResolutionResult singleResultHelper(
+ DexClass initialResolutionResult, Entry<DexClass, DexEncodedMethod> entry) {
+ return new SingleResolutionResult(
+ initialResolutionResult != null ? initialResolutionResult : entry.getKey(),
+ entry.getKey(),
+ entry.getValue());
+ }
+
// TODO(b/149190785): Remove once fixed.
public void enableDefinitionForAssert() {}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index cefb55a..e0e8269 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -7,7 +7,6 @@
import static com.android.tools.r8.utils.TraversalContinuation.BREAK;
import static com.android.tools.r8.utils.TraversalContinuation.CONTINUE;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.utils.TraversalContinuation;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
@@ -207,16 +206,8 @@
* Helper method used for emulated interface resolution (not in JVM specifications). The result
* may be abstract.
*/
- public ResolutionResult resolveMaximallySpecificMethods(DexClass clazz, DexMethod method) {
- assert !clazz.type.isArrayType();
- if (clazz.isInterface()) {
- // Look for exact method on interface.
- DexEncodedMethod result = clazz.lookupMethod(method);
- if (result != null) {
- return new SingleResolutionResult(clazz, clazz, result);
- }
- }
- return resolveMethodStep3(clazz, method);
+ public DexClassAndMethod lookupMaximallySpecificMethod(DexClass clazz, DexMethod method) {
+ return lookupMaximallySpecificTarget(clazz, method);
}
/**
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 813ea84..9a07dd2 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.WorkList;
-import com.android.tools.r8.utils.WorkList.EqualityTest;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -42,7 +41,7 @@
DexType type,
Consumer<DexProgramClass> subTypeConsumer,
Consumer<DexCallSite> callSiteConsumer) {
- WorkList<DexType> workList = new WorkList<>(EqualityTest.IDENTITY);
+ WorkList<DexType> workList = WorkList.newIdentityWorkList();
workList.addIfNotSeen(type);
workList.addIfNotSeen(allImmediateSubtypes(type));
while (workList.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 5c8de46..4f8fa23 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.CfSourceCode;
import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.ClassNameMapper;
@@ -210,7 +211,7 @@
for (CfInstruction instruction : instructions) {
if (instruction instanceof CfFrame
&& (classFileVersion <= 49
- || (classFileVersion == 50 && !options.shouldKeepStackMapTable()))) {
+ || (classFileVersion == 50 && !options.shouldKeepStackMapTable()))) {
continue;
}
instruction.write(visitor, namingLens);
@@ -287,7 +288,7 @@
@Override
public IRCode buildIR(DexEncodedMethod encodedMethod, AppView<?> appView, Origin origin) {
return internalBuildPossiblyWithLocals(
- encodedMethod, encodedMethod, appView, null, null, origin);
+ encodedMethod, encodedMethod, appView, null, null, origin, null);
}
@Override
@@ -297,11 +298,18 @@
AppView<?> appView,
ValueNumberGenerator valueNumberGenerator,
Position callerPosition,
- Origin origin) {
+ Origin origin,
+ MethodProcessor methodProcessor) {
assert valueNumberGenerator != null;
assert callerPosition != null;
return internalBuildPossiblyWithLocals(
- context, encodedMethod, appView, valueNumberGenerator, callerPosition, origin);
+ context,
+ encodedMethod,
+ appView,
+ valueNumberGenerator,
+ callerPosition,
+ origin,
+ methodProcessor);
}
// First build entry. Will either strip locals or build with locals.
@@ -311,7 +319,8 @@
AppView<?> appView,
ValueNumberGenerator generator,
Position callerPosition,
- Origin origin) {
+ Origin origin,
+ MethodProcessor methodProcessor) {
if (!encodedMethod.keepLocals(appView.options())) {
return internalBuild(
Collections.emptyList(),
@@ -320,10 +329,11 @@
appView,
generator,
callerPosition,
- origin);
+ origin,
+ methodProcessor);
} else {
return internalBuildWithLocals(
- context, encodedMethod, appView, generator, callerPosition, origin);
+ context, encodedMethod, appView, generator, callerPosition, origin, methodProcessor);
}
}
@@ -334,7 +344,8 @@
AppView<?> appView,
ValueNumberGenerator generator,
Position callerPosition,
- Origin origin) {
+ Origin origin,
+ MethodProcessor methodProcessor) {
try {
return internalBuild(
Collections.unmodifiableList(localVariables),
@@ -343,7 +354,8 @@
appView,
generator,
callerPosition,
- origin);
+ origin,
+ methodProcessor);
} catch (InvalidDebugInfoException e) {
appView.options().warningInvalidDebugInfo(encodedMethod, origin, e);
return internalBuild(
@@ -353,19 +365,21 @@
appView,
generator,
callerPosition,
- origin);
+ origin,
+ methodProcessor);
}
}
// Inner-most subroutine for building. Must only be called by the two internalBuildXYZ above.
private IRCode internalBuild(
- List<CfCode.LocalVariableInfo> localVariables,
+ List<LocalVariableInfo> localVariables,
DexEncodedMethod context,
DexEncodedMethod encodedMethod,
AppView<?> appView,
ValueNumberGenerator generator,
Position callerPosition,
- Origin origin) {
+ Origin origin,
+ MethodProcessor methodProcessor) {
CfSourceCode source =
new CfSourceCode(
this,
@@ -375,7 +389,11 @@
callerPosition,
origin,
appView);
- return new IRBuilder(encodedMethod, appView, source, origin, generator).build(context);
+ IRBuilder builder = methodProcessor == null ?
+ IRBuilder.create(encodedMethod, appView, source, origin) :
+ IRBuilder
+ .createForInlining(encodedMethod, appView, source, origin, methodProcessor, generator);
+ return builder.build(context);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 0fbae38..8e81c8e 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.Outliner.OutlineCode;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
@@ -24,7 +25,8 @@
AppView<?> appView,
ValueNumberGenerator valueNumberGenerator,
Position callerPosition,
- Origin origin) {
+ Origin origin,
+ MethodProcessor methodProcessor) {
throw new Unreachable("Unexpected attempt to build IR graph for inlining from: "
+ getClass().getCanonicalName());
}
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 3601754..c474718 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -138,9 +138,7 @@
}
public Iterable<DexEncodedMethod> methods(Predicate<? super DexEncodedMethod> predicate) {
- return Iterables.concat(
- Iterables.filter(Arrays.asList(directMethods), predicate::test),
- Iterables.filter(Arrays.asList(virtualMethods), predicate::test));
+ return Iterables.concat(directMethods(predicate), virtualMethods(predicate));
}
@Override
@@ -156,6 +154,10 @@
return Arrays.asList(directMethods);
}
+ public Iterable<DexEncodedMethod> directMethods(Predicate<? super DexEncodedMethod> predicate) {
+ return Iterables.filter(Arrays.asList(directMethods), predicate::test);
+ }
+
public void appendDirectMethod(DexEncodedMethod method) {
cachedClassInitializer = null;
DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + 1];
@@ -223,6 +225,10 @@
return Arrays.asList(virtualMethods);
}
+ public Iterable<DexEncodedMethod> virtualMethods(Predicate<? super DexEncodedMethod> predicate) {
+ return Iterables.filter(Arrays.asList(virtualMethods), predicate::test);
+ }
+
public void appendVirtualMethod(DexEncodedMethod method) {
DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + 1];
System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
@@ -815,6 +821,15 @@
return !clinit.getOptimizationInfo().classInitializerMayBePostponed();
}
+ public void forEachImmediateSupertype(Consumer<DexType> fn) {
+ if (superType != null) {
+ fn.accept(superType);
+ }
+ for (DexType iface : interfaces.values) {
+ fn.accept(iface);
+ }
+ }
+
public Iterable<DexType> allImmediateSupertypes() {
Iterator<DexType> iterator =
superType != null
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index 281b58a..ded00bd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -11,7 +11,7 @@
private final DexClass holder;
private final DexEncodedMethod method;
- protected DexClassAndMethod(DexClass holder, DexEncodedMethod method) {
+ DexClassAndMethod(DexClass holder, DexEncodedMethod method) {
assert holder.type == method.method.holder;
this.holder = holder;
this.method = method;
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 d32abdf..9c02dec 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.DexSourceCode;
import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.StringUtils;
@@ -220,9 +221,7 @@
encodedMethod,
appView.graphLense().getOriginalMethodSignature(encodedMethod.method),
null);
- IRBuilder builder =
- new IRBuilder(encodedMethod, appView, source, origin, new ValueNumberGenerator());
- return builder.build(encodedMethod);
+ return IRBuilder.create(encodedMethod,appView,source,origin).build(encodedMethod);
}
@Override
@@ -232,15 +231,16 @@
AppView<?> appView,
ValueNumberGenerator valueNumberGenerator,
Position callerPosition,
- Origin origin) {
+ Origin origin,
+ MethodProcessor methodProcessor) {
DexSourceCode source =
new DexSourceCode(
this,
encodedMethod,
appView.graphLense().getOriginalMethodSignature(encodedMethod.method),
callerPosition);
- IRBuilder builder = new IRBuilder(encodedMethod, appView, source, origin, valueNumberGenerator);
- return builder.build(context);
+ return IRBuilder.createForInlining(
+ encodedMethod, appView, source, origin, methodProcessor, valueNumberGenerator).build(context);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
index b616649..8b35b54 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
@@ -16,5 +16,15 @@
DexProgramClass definitionForProgramType(DexType type);
+ default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+ DexClass definitionForHolder(DexEncodedMember<D, R> member) {
+ return definitionForHolder(member.toReference());
+ }
+
+ default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+ DexClass definitionForHolder(DexMember<D, R> member) {
+ return definitionFor(member.holder);
+ }
+
DexItemFactory dexItemFactory();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
index 0e436d6..638a9be 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -10,6 +10,10 @@
super(annotations);
}
+ public DexType holder() {
+ return toReference().holder;
+ }
+
@Override
public abstract R toReference();
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 b755c49..1fdd415 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -40,6 +40,7 @@
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring.DexFieldWithAccess;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -550,10 +551,11 @@
AppView<?> appView,
ValueNumberGenerator valueNumberGenerator,
Position callerPosition,
- Origin origin) {
+ Origin origin,
+ MethodProcessor methodProcessor) {
checkIfObsolete();
return code.buildInliningIR(
- context, this, appView, valueNumberGenerator, callerPosition, origin);
+ context, this, appView, valueNumberGenerator, callerPosition, origin, methodProcessor);
}
public void setCode(Code newCode, AppView<?> appView) {
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
index b9e1b5c..2bd8acf 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
@@ -40,5 +40,7 @@
boolean isWrittenInMethodSatisfying(Predicate<DexEncodedMethod> predicate);
+ boolean isWrittenOnlyInMethodSatisfying(Predicate<DexEncodedMethod> predicate);
+
boolean isWrittenOutside(DexEncodedMethod method);
}
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
index 5b0ebc2..ddab792 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
@@ -17,4 +17,6 @@
void forEach(Consumer<T> consumer);
void removeIf(BiPredicate<DexField, FieldAccessInfoImpl> predicate);
+
+ void restrictToProgram(DexDefinitionSupplier definitions);
}
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
index 47a9c30..7ef10ac 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
@@ -42,6 +42,11 @@
infos.entrySet().removeIf(entry -> predicate.test(entry.getKey(), entry.getValue()));
}
+ @Override
+ public void restrictToProgram(DexDefinitionSupplier definitions) {
+ removeIf((field, info) -> !definitions.definitionForHolder(field).isProgramClass());
+ }
+
public FieldAccessInfoCollectionImpl rewrittenWithLens(
DexDefinitionSupplier definitions, GraphLense lens) {
FieldAccessInfoCollectionImpl collection = new FieldAccessInfoCollectionImpl();
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index 2b813ea..4715169 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -230,6 +230,24 @@
}
/**
+ * Returns true if this field is only written by methods for which {@param predicate} returns
+ * true.
+ */
+ @Override
+ public boolean isWrittenOnlyInMethodSatisfying(Predicate<DexEncodedMethod> predicate) {
+ if (writesWithContexts != null) {
+ for (Set<DexEncodedMethod> encodedWriteContexts : writesWithContexts.values()) {
+ for (DexEncodedMethod encodedWriteContext : encodedWriteContexts) {
+ if (!predicate.test(encodedWriteContext)) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
* Returns true if this field is written by a method in the program other than {@param method}.
*/
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index edec3a9..41499ea 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -56,8 +56,10 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.MethodPosition;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.InternalOptions;
@@ -149,7 +151,8 @@
public void parseCode(ReparseContext context, boolean useJsrInliner) {
int parsingOptions = getParsingOptions(application, reachabilitySensitive);
ClassCodeVisitor classVisitor =
- new ClassCodeVisitor(context.owner, createCodeLocator(context), application, useJsrInliner);
+ new ClassCodeVisitor(
+ context.owner, createCodeLocator(context), application, useJsrInliner, origin);
new ClassReader(context.classCache).accept(classVisitor, parsingOptions);
}
@@ -198,10 +201,17 @@
AppView<?> appView,
ValueNumberGenerator valueNumberGenerator,
Position callerPosition,
- Origin origin) {
+ Origin origin,
+ MethodProcessor methodProcessor) {
return asCfCode()
.buildInliningIR(
- context, encodedMethod, appView, valueNumberGenerator, callerPosition, origin);
+ context,
+ encodedMethod,
+ appView,
+ valueNumberGenerator,
+ callerPosition,
+ origin,
+ methodProcessor);
}
@Override
@@ -250,17 +260,20 @@
private final BiFunction<String, String, LazyCfCode> codeLocator;
private final JarApplicationReader application;
private boolean usrJsrInliner;
+ private final Origin origin;
ClassCodeVisitor(
DexClass clazz,
BiFunction<String, String, LazyCfCode> codeLocator,
JarApplicationReader application,
- boolean useJsrInliner) {
+ boolean useJsrInliner,
+ Origin origin) {
super(InternalOptions.ASM_VERSION);
this.clazz = clazz;
this.codeLocator = codeLocator;
this.application = application;
this.usrJsrInliner = useJsrInliner;
+ this.origin = origin;
}
@Override
@@ -271,7 +284,8 @@
LazyCfCode code = codeLocator.apply(name, desc);
if (code != null) {
DexMethod method = application.getMethod(clazz.type, name, desc);
- MethodCodeVisitor methodVisitor = new MethodCodeVisitor(application, method, code);
+ MethodCodeVisitor methodVisitor =
+ new MethodCodeVisitor(application, method, code, origin);
if (!usrJsrInliner) {
return methodVisitor;
}
@@ -294,14 +308,17 @@
private Map<Label, CfLabel> labelMap;
private final LazyCfCode code;
private final DexMethod method;
+ private final Origin origin;
- MethodCodeVisitor(JarApplicationReader application, DexMethod method, LazyCfCode code) {
+ MethodCodeVisitor(
+ JarApplicationReader application, DexMethod method, LazyCfCode code, Origin origin) {
super(InternalOptions.ASM_VERSION);
assert code != null;
this.application = application;
this.factory = application.getFactory();
this.code = code;
this.method = method;
+ this.origin = origin;
}
@Override
@@ -316,6 +333,15 @@
@Override
public void visitEnd() {
+ if (instructions == null) {
+ // Everything that is initialized at `visitCode` should be null too.
+ assert tryCatchRanges == null && localVariables == null && labelMap == null;
+ // This code visitor is used only if the method is neither abstract nor native, hence it
+ // should have exactly one Code attribute:
+ // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3
+ throw new CompilationError("Absent Code attribute in method that is not native or abstract")
+ .withAdditionalOriginAndPositionInfo(origin, new MethodPosition(method));
+ }
code.setCode(
new CfCode(
method.holder, maxStack, maxLocals, instructions, tryCatchRanges, localVariables));
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index feffac7..4c5e239 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -155,21 +155,11 @@
public DexEncodedMethod lookupInvokeSpecialTarget(
DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
// If the resolution is non-accessible then no target exists.
- if (!isAccessibleFrom(context, appInfo)) {
- return null;
+ if (isAccessibleFrom(context, appInfo)) {
+ return internalInvokeSpecialOrSuper(
+ context, appInfo, (sup, sub) -> isSuperclass(sup, sub, appInfo));
}
- DexEncodedMethod target =
- internalInvokeSpecialOrSuper(
- context, appInfo, (sup, sub) -> isSuperclass(sup, sub, appInfo));
- if (target == null) {
- return null;
- }
- // Should we check access control again?
- DexClass holder = appInfo.definitionFor(target.method.holder);
- if (!AccessControl.isMethodAccessible(target, holder, context, appInfo)) {
- return null;
- }
- return target;
+ return null;
}
/**
@@ -191,7 +181,7 @@
public DexEncodedMethod lookupInvokeSuperTarget(
DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
// TODO(b/147848950): Investigate and remove the Compilation error. It could compile to
- // throw IAE.
+ // throw IAE.
if (resolvedMethod.isInstanceInitializer()
|| (appInfo.hasSubtyping()
&& initialResolutionHolder != context
@@ -199,19 +189,10 @@
throw new CompilationError(
"Illegal invoke-super to " + resolvedMethod.toSourceString(), context.getOrigin());
}
- if (!isAccessibleFrom(context, appInfo)) {
- return null;
+ if (isAccessibleFrom(context, appInfo)) {
+ return internalInvokeSpecialOrSuper(context, appInfo, (sup, sub) -> true);
}
- DexEncodedMethod target = internalInvokeSpecialOrSuper(context, appInfo, (sup, sub) -> true);
- if (target == null) {
- return null;
- }
- // Should we check access control again?
- DexClass holder = appInfo.definitionFor(target.method.holder);
- if (!AccessControl.isMethodAccessible(target, holder, context, appInfo)) {
- return null;
- }
- return target;
+ return null;
}
/**
@@ -305,7 +286,10 @@
}
// 4. Otherwise, it is the single maximally specific method:
if (target == null) {
- target = appInfo.resolveMaximallySpecificMethods(initialType, method).getSingleTarget();
+ DexClassAndMethod result = appInfo.lookupMaximallySpecificMethod(initialType, method);
+ if (result != null) {
+ target = result.getMethod();
+ }
}
if (target == null) {
return null;
@@ -341,6 +325,8 @@
AppView<? extends AppInfoWithClassHierarchy> appView,
InstantiatedSubTypeInfo instantiatedInfo) {
// Check that the initial resolution holder is accessible from the context.
+ assert appView.isSubtype(initialResolutionHolder.type, resolvedHolder.type).isTrue()
+ : initialResolutionHolder.type + " is not a subtype of " + resolvedHolder.type;
if (context != null && !isAccessibleFrom(context, appView.appInfo())) {
return LookupResult.createFailedResult();
}
@@ -356,7 +342,8 @@
instantiatedInfo.forEachInstantiatedSubType(
resolvedHolder.type,
subClass -> {
- DexClassAndMethod dexClassAndMethod = lookupVirtualDispatchTarget(subClass, appView);
+ DexClassAndMethod dexClassAndMethod =
+ lookupVirtualDispatchTarget(subClass, appView, resolvedHolder.type);
if (dexClassAndMethod != null) {
addVirtualDispatchTarget(
dexClassAndMethod.getMethod(), resolvedHolder.isInterface(), result);
@@ -418,6 +405,15 @@
@Override
public DexClassAndMethod lookupVirtualDispatchTarget(
DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
+ return lookupVirtualDispatchTarget(dynamicInstance, appView, initialResolutionHolder.type);
+ }
+
+ private DexClassAndMethod lookupVirtualDispatchTarget(
+ DexProgramClass dynamicInstance,
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ DexType resolutionHolder) {
+ assert appView.isSubtype(dynamicInstance.type, resolutionHolder).isTrue()
+ : dynamicInstance.type + " is not a subtype of " + resolutionHolder;
// TODO(b/148591377): Enable this assertion.
// The dynamic type cannot be an interface.
// assert !dynamicInstance.isInterface();
@@ -441,24 +437,19 @@
}
return DexClassAndMethod.create(current, candidate);
}
- // TODO(b/149557233): Enable assertion.
- // assert resolvedHolder.isInterface();
- DexEncodedMethod maximalSpecific =
- lookupMaximallySpecificDispatchTarget(dynamicInstance, appView);
- return maximalSpecific == null
- ? null
- : DexClassAndMethod.create(
- appView.definitionFor(maximalSpecific.method.holder), maximalSpecific);
+ // If we have not found a candidate and the holder is not an interface it must be because the
+ // class is missing.
+ if (!resolvedHolder.isInterface()) {
+ return null;
+ }
+ return lookupMaximallySpecificDispatchTarget(dynamicInstance, appView);
}
- private DexEncodedMethod lookupMaximallySpecificDispatchTarget(
+ private DexClassAndMethod lookupMaximallySpecificDispatchTarget(
DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
- ResolutionResult maximallySpecificResult =
- appView.appInfo().resolveMaximallySpecificMethods(dynamicInstance, resolvedMethod.method);
- if (maximallySpecificResult.isSingleResolution()) {
- return maximallySpecificResult.asSingleResolution().resolvedMethod;
- }
- return null;
+ return appView
+ .appInfo()
+ .lookupMaximallySpecificMethod(dynamicInstance, resolvedMethod.method);
}
/**
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
index 1049fb8..80c81e9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.analysis;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinition;
@@ -13,6 +14,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
import com.android.tools.r8.ir.code.DominatorTree;
@@ -34,11 +36,13 @@
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.OptionalBool;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import java.util.ArrayDeque;
+import java.util.BitSet;
import java.util.Deque;
+import java.util.List;
import java.util.Set;
/**
@@ -192,33 +196,10 @@
}
for (CatchHandler<BasicBlock> catchHandler : dominator.getCatchHandlers()) {
- if (!catchHandler.target.isMarked(markingColor)) {
- // There is no path from this catch handler to the instruction of interest, so we can
- // ignore it.
- continue;
- }
-
- DexType guard = catchHandler.guard;
- if (exceptionalExit.isInstanceGet()
- || exceptionalExit.isInstancePut()
- || exceptionalExit.isInvokeMethodWithReceiver()) {
- // If an instance-get, instance-put, or instance-invoke instruction does not fail with a
- // NullPointerException, then the receiver class must have been initialized.
- OptionalBool isCatchingNPE = appView.isSubtype(dexItemFactory.npeType, guard);
- if (isCatchingNPE.isPossiblyTrue()) {
- return AnalysisAssumption.NONE;
- }
- }
- if (exceptionalExit.isStaticGet()
- || exceptionalExit.isStaticPut()
- || exceptionalExit.isInvokeStatic()) {
- // If a static-get, static-put, or invoke-static does not fail with an ExceptionIn-
- // InitializerError, then the holder class must have been initialized.
- OptionalBool isCatchingInitError =
- appView.isSubtype(dexItemFactory.exceptionInInitializerErrorType, guard);
- if (isCatchingInitError.isPossiblyTrue()) {
- return AnalysisAssumption.NONE;
- }
+ if (catchHandler.target.isMarked(markingColor)) {
+ // There is a path from this catch handler to the instruction of interest, so we can't make
+ // any assumptions.
+ return AnalysisAssumption.NONE;
}
}
@@ -288,7 +269,7 @@
}
}
DexEncodedField field = appView.appInfo().resolveField(instruction.getField());
- return field != null && isTypeInitializedBy(type, field, appView, mode);
+ return field != null && isTypeInitializedBy(instruction, type, field, appView, mode);
}
public static boolean forInvokeDirect(
@@ -304,7 +285,7 @@
}
}
DexEncodedMethod method = appView.definitionFor(instruction.getInvokedMethod());
- return method != null && isTypeInitializedBy(type, method, appView, mode);
+ return method != null && isTypeInitializedBy(instruction, type, method, appView, mode);
}
public static boolean forInvokeInterface(
@@ -330,7 +311,7 @@
DexEncodedMethod singleTarget =
instruction.lookupSingleTarget(appView.withLiveness(), context);
if (singleTarget != null) {
- return isTypeInitializedBy(type, singleTarget, appView, mode);
+ return isTypeInitializedBy(instruction, type, singleTarget, appView, mode);
}
}
DexMethod method = instruction.getInvokedMethod();
@@ -346,6 +327,7 @@
public static boolean forInvokeStatic(
InvokeStatic instruction,
DexType type,
+ DexType context,
AppView<?> appView,
Query mode,
AnalysisAssumption assumption) {
@@ -353,8 +335,8 @@
// Class initialization may fail with ExceptionInInitializerError.
return false;
}
- DexEncodedMethod method = appView.definitionFor(instruction.getInvokedMethod());
- return method != null && isTypeInitializedBy(type, method, appView, mode);
+ DexEncodedMethod method = instruction.lookupSingleTarget(appView, context);
+ return method != null && isTypeInitializedBy(instruction, type, method, appView, mode);
}
public static boolean forInvokeSuper(
@@ -380,7 +362,7 @@
DexEncodedMethod singleTarget =
instruction.lookupSingleTarget(appView.withLiveness(), context);
if (singleTarget != null) {
- return isTypeInitializedBy(type, singleTarget, appView, mode);
+ return isTypeInitializedBy(instruction, type, singleTarget, appView, mode);
}
}
DexMethod method = instruction.getInvokedMethod();
@@ -424,7 +406,7 @@
DexEncodedMethod singleTarget =
instruction.lookupSingleTarget(appView.withLiveness(), context);
if (singleTarget != null) {
- return isTypeInitializedBy(type, singleTarget, appView, mode);
+ return isTypeInitializedBy(instruction, type, singleTarget, appView, mode);
}
}
DexMethod method = instruction.getInvokedMethod();
@@ -448,7 +430,7 @@
return false;
}
DexClass clazz = appView.definitionFor(instruction.clazz);
- return clazz != null && isTypeInitializedBy(type, clazz, appView, mode);
+ return clazz != null && isTypeInitializedBy(instruction, type, clazz, appView, mode);
}
public static boolean forStaticGet(
@@ -481,23 +463,23 @@
return false;
}
DexEncodedField field = appView.appInfo().resolveField(instruction.getField());
- return field != null && isTypeInitializedBy(type, field, appView, mode);
+ return field != null && isTypeInitializedBy(instruction, type, field, appView, mode);
}
private static boolean isTypeInitializedBy(
- DexType typeToBeInitialized, DexDefinition definition, AppView<?> appView, Query mode) {
+ Instruction instruction,
+ DexType typeToBeInitialized,
+ DexDefinition definition,
+ AppView<?> appView,
+ Query mode) {
if (mode == Query.DIRECTLY) {
if (definition.isDexClass()) {
return definition.asDexClass().type == typeToBeInitialized;
}
- if (definition.isDexEncodedField()) {
- return definition.asDexEncodedField().field.holder == typeToBeInitialized;
+ if (definition.isDexEncodedMember()) {
+ return definition.asDexEncodedMember().toReference().holder == typeToBeInitialized;
}
- if (definition.isDexEncodedMethod()) {
- return definition.asDexEncodedMethod().method.holder == typeToBeInitialized;
- }
- assert false;
- return false;
+ throw new Unreachable();
}
Set<DexType> visited = Sets.newIdentityHashSet();
@@ -510,9 +492,10 @@
DexEncodedField field = definition.asDexEncodedField();
enqueue(field.field.holder, visited, worklist);
} else if (definition.isDexEncodedMethod()) {
+ assert instruction.isInvokeMethod();
DexEncodedMethod method = definition.asDexEncodedMethod();
enqueue(method.method.holder, visited, worklist);
- enqueueInitializedClassesOnNormalExit(method, visited, worklist);
+ enqueueInitializedClassesOnNormalExit(method, instruction.inValues(), visited, worklist);
} else {
assert false;
}
@@ -529,7 +512,8 @@
if (clazz != null) {
DexEncodedMethod classInitializer = clazz.getClassInitializer();
if (classInitializer != null) {
- enqueueInitializedClassesOnNormalExit(classInitializer, visited, worklist);
+ enqueueInitializedClassesOnNormalExit(
+ classInitializer, ImmutableList.of(), visited, worklist);
}
}
}
@@ -544,10 +528,36 @@
}
private static void enqueueInitializedClassesOnNormalExit(
- DexEncodedMethod method, Set<DexType> visited, Deque<DexType> worklist) {
+ DexEncodedMethod method,
+ List<Value> arguments,
+ Set<DexType> visited,
+ Deque<DexType> worklist) {
for (DexType type : method.getOptimizationInfo().getInitializedClassesOnNormalExit()) {
enqueue(type, visited, worklist);
}
+ // If an invoke to an instance method succeeds, then the receiver must be non-null, which
+ // implies that the type of the receiver must be initialized.
+ if (!method.isStatic()) {
+ assert arguments.size() > 0;
+ TypeLatticeElement type = arguments.get(0).getTypeLattice();
+ if (type.isClassType()) {
+ enqueue(type.asClassTypeLatticeElement().getClassType(), visited, worklist);
+ }
+ }
+ // If an invoke to a method succeeds, and the method would have thrown and exception if the
+ // i'th argument was null, then the i'th argument must be non-null, which implies that the
+ // type of the i'th argument must be initialized.
+ BitSet nonNullParamOrThrowFacts = method.getOptimizationInfo().getNonNullParamOrThrow();
+ if (nonNullParamOrThrowFacts != null) {
+ for (int i = 0; i < arguments.size(); i++) {
+ if (nonNullParamOrThrowFacts.get(i)) {
+ TypeLatticeElement type = arguments.get(i).getTypeLattice();
+ if (type.isClassType()) {
+ enqueue(type.asClassTypeLatticeElement().getClassType(), visited, worklist);
+ }
+ }
+ }
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index 0bef6b1..4064d79 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.analysis.fieldaccess;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -12,6 +14,7 @@
import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.BottomValue;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.InvokeDirect;
@@ -164,12 +167,22 @@
initializationInfo.asArgumentInitializationInfo();
Value argument = invoke.arguments().get(argumentInitializationInfo.getArgumentIndex());
AbstractValue abstractValue =
- argument.getAbstractValue(appView, context.method.holder).join(entry.getValue());
+ entry.getValue().join(argument.getAbstractValue(appView, context.method.holder));
assert !abstractValue.isBottom();
if (!abstractValue.isUnknown()) {
entry.setValue(abstractValue);
continue;
}
+ } else if (initializationInfo.isSingleValue()) {
+ SingleValue singleValueInitializationInfo = initializationInfo.asSingleValue();
+ AbstractValue abstractValue = entry.getValue().join(singleValueInitializationInfo);
+ assert !abstractValue.isBottom();
+ if (!abstractValue.isUnknown()) {
+ entry.setValue(abstractValue);
+ continue;
+ }
+ } else if (initializationInfo.isTypeInitializationInfo()) {
+ // TODO(b/149732532): Not handled, for now.
} else {
assert initializationInfo.isUnknown();
}
@@ -182,10 +195,56 @@
private void recordAllFieldPutsProcessed(
DexEncodedField field, OptimizationFeedbackDelayed feedback) {
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionForHolder(field));
+ if (clazz == null) {
+ assert false;
+ return;
+ }
+
if (isAlwaysZero(field)) {
feedback.recordFieldHasAbstractValue(
field, appView, appView.abstractValueFactory().createSingleNumberValue(0));
}
+
+ if (!field.isStatic()) {
+ recordAllInstanceFieldPutsProcessed(clazz, field, feedback);
+ }
+ }
+
+ private void recordAllInstanceFieldPutsProcessed(
+ DexProgramClass clazz, DexEncodedField field, OptimizationFeedbackDelayed feedback) {
+ if (appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)) {
+ AbstractValue abstractValue = BottomValue.getInstance();
+ for (DexEncodedMethod method : clazz.directMethods(DexEncodedMethod::isInstanceInitializer)) {
+ InstanceFieldInitializationInfo fieldInitializationInfo =
+ method
+ .getOptimizationInfo()
+ .getInstanceInitializerInfo()
+ .fieldInitializationInfos()
+ .get(field);
+ if (fieldInitializationInfo.isSingleValue()) {
+ abstractValue = abstractValue.join(fieldInitializationInfo.asSingleValue());
+ if (abstractValue.isUnknown()) {
+ break;
+ }
+ } else if (fieldInitializationInfo.isTypeInitializationInfo()) {
+ // TODO(b/149732532): Not handled, for now.
+ abstractValue = UnknownValue.getInstance();
+ break;
+ } else {
+ assert fieldInitializationInfo.isArgumentInitializationInfo()
+ || fieldInitializationInfo.isUnknown();
+ abstractValue = UnknownValue.getInstance();
+ break;
+ }
+ }
+
+ assert !abstractValue.isBottom();
+
+ if (!abstractValue.isUnknown()) {
+ feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
+ }
+ }
}
private void recordAllAllocationsSitesProcessed(
@@ -211,11 +270,16 @@
}
public void waveDone(Collection<DexEncodedMethod> wave, OptimizationFeedbackDelayed feedback) {
+ // This relies on the instance initializer info in the method optimization feedback. It is
+ // therefore important that the optimization info has been flushed in advance.
+ assert feedback.noUpdatesLeft();
for (DexEncodedMethod method : wave) {
fieldAccessGraph.markProcessed(method, field -> recordAllFieldPutsProcessed(field, feedback));
objectAllocationGraph.markProcessed(
method, clazz -> recordAllAllocationsSitesProcessed(clazz, feedback));
}
+ feedback.refineAppInfoWithLiveness(appView.appInfo().withLiveness());
+ feedback.updateVisibleOptimizationInfo();
}
private boolean verifyValueIsConsistentWithFieldOptimizationInfo(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
index 2289a28..a9c7cac 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -4,24 +4,12 @@
package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
-import static com.android.tools.r8.ir.code.Opcodes.ARRAY_PUT;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
-import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
-
import com.android.tools.r8.graph.AppView;
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.DexProgramClass;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.SingleEnumValue;
-import com.android.tools.r8.ir.analysis.value.UnknownValue;
-import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.DominatorTree;
import com.android.tools.r8.ir.code.DominatorTree.Assumption;
@@ -29,8 +17,6 @@
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.InvokeDirect;
-import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
@@ -45,11 +31,11 @@
public abstract class FieldValueAnalysis {
- private final AppView<AppInfoWithLiveness> appView;
- private final DexProgramClass clazz;
+ final AppView<AppInfoWithLiveness> appView;
+ final DexProgramClass clazz;
private final IRCode code;
- private final OptimizationFeedback feedback;
- private final DexEncodedMethod method;
+ final OptimizationFeedback feedback;
+ final DexEncodedMethod method;
private Map<BasicBlock, AbstractFieldSet> fieldsMaybeReadBeforeBlockInclusiveCache;
@@ -75,13 +61,13 @@
return fieldsMaybeReadBeforeBlockInclusiveCache;
}
+ abstract boolean isSubjectToOptimization(DexEncodedField field);
+
/** This method analyzes initializers with the purpose of computing field optimization info. */
void computeFieldOptimizationInfo(ClassInitializerDefaultsResult classInitializerDefaultsResult) {
AppInfoWithLiveness appInfo = appView.appInfo();
DominatorTree dominatorTree = null;
- DexType context = method.method.holder;
-
// Find all the static-put instructions that assign a field in the enclosing class which is
// guaranteed to be assigned only in the current initializer.
boolean isStraightLineCode = true;
@@ -95,10 +81,7 @@
FieldInstruction fieldPut = instruction.asFieldInstruction();
DexField field = fieldPut.getField();
DexEncodedField encodedField = appInfo.resolveField(field);
- if (encodedField != null
- && encodedField.field.holder == context
- && encodedField.isStatic() == method.isStatic()
- && appInfo.isFieldOnlyWrittenInMethod(encodedField, method)) {
+ if (encodedField != null && isSubjectToOptimization(encodedField)) {
putsPerField.computeIfAbsent(encodedField, ignore -> new LinkedList<>()).add(fieldPut);
}
}
@@ -126,7 +109,7 @@
if (!priorReadsWillReadSameValue && fieldMaybeReadBeforeInstruction(encodedField, fieldPut)) {
continue;
}
- updateFieldOptimizationInfo(encodedField, fieldPut.value());
+ updateFieldOptimizationInfo(encodedField, fieldPut, fieldPut.value());
}
}
@@ -275,141 +258,6 @@
return true;
}
- void updateFieldOptimizationInfo(DexEncodedField field, Value value) {
- // Abstract value.
- Value root = value.getAliasedValue();
- AbstractValue abstractValue = computeAbstractValue(root);
- if (abstractValue.isUnknown()) {
- if (field.isStatic()) {
- feedback.recordFieldHasAbstractValue(
- field, appView, appView.abstractValueFactory().createSingleFieldValue(field.field));
- }
- } else {
- feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
- }
-
- // Dynamic upper bound type.
- TypeLatticeElement fieldType =
- TypeLatticeElement.fromDexType(field.field.type, Nullability.maybeNull(), appView);
- TypeLatticeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
- if (dynamicUpperBoundType.strictlyLessThan(fieldType, appView)) {
- feedback.markFieldHasDynamicUpperBoundType(field, dynamicUpperBoundType);
- }
-
- // Dynamic lower bound type.
- ClassTypeLatticeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
- if (dynamicLowerBoundType != null) {
- assert dynamicLowerBoundType.lessThanOrEqual(dynamicUpperBoundType, appView);
- feedback.markFieldHasDynamicLowerBoundType(field, dynamicLowerBoundType);
- }
- }
-
- private AbstractValue computeAbstractValue(Value value) {
- assert !value.hasAliasedValue();
- if (clazz.isEnum()) {
- SingleEnumValue singleEnumValue = getSingleEnumValue(value);
- if (singleEnumValue != null) {
- return singleEnumValue;
- }
- }
- if (!value.isPhi()) {
- return value.definition.getAbstractValue(appView, clazz.type);
- }
- return UnknownValue.getInstance();
- }
-
- /**
- * If {@param value} is defined by a new-instance instruction that instantiates the enclosing enum
- * class, and the value is assigned into exactly one static enum field on the enclosing enum
- * class, then returns a {@link SingleEnumValue} instance. Otherwise, returns {@code null}.
- *
- * <p>Note that enum constructors also store the newly instantiated enums in the {@code $VALUES}
- * array field on the enum. Therefore, this code also allows {@param value} to be stored into an
- * array as long as the array is identified as being the {@code $VALUES} array.
- */
- private SingleEnumValue getSingleEnumValue(Value value) {
- assert clazz.isEnum();
- assert !value.hasAliasedValue();
- if (value.isPhi() || !value.definition.isNewInstance()) {
- return null;
- }
-
- NewInstance newInstance = value.definition.asNewInstance();
- if (newInstance.clazz != clazz.type) {
- return null;
- }
-
- if (value.hasDebugUsers() || value.hasPhiUsers()) {
- return null;
- }
-
- DexEncodedField enumField = null;
- for (Instruction user : value.uniqueUsers()) {
- switch (user.opcode()) {
- case ARRAY_PUT:
- // Check that this is assigning the enum into the enum values array.
- ArrayPut arrayPut = user.asArrayPut();
- if (arrayPut.value().getAliasedValue() != value || !isEnumValuesArray(arrayPut.array())) {
- return null;
- }
- break;
-
- case INVOKE_DIRECT:
- // Check that this is the corresponding constructor call.
- InvokeDirect invoke = user.asInvokeDirect();
- if (!appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())
- || invoke.getReceiver() != value) {
- return null;
- }
- break;
-
- case STATIC_PUT:
- DexEncodedField field = clazz.lookupStaticField(user.asStaticPut().getField());
- if (field != null && field.accessFlags.isEnum()) {
- if (enumField != null) {
- return null;
- }
- enumField = field;
- }
- break;
-
- default:
- return null;
- }
- }
-
- if (enumField == null) {
- return null;
- }
-
- return appView.abstractValueFactory().createSingleEnumValue(enumField.field);
- }
-
- private boolean isEnumValuesArray(Value value) {
- assert clazz.isEnum();
- DexItemFactory dexItemFactory = appView.dexItemFactory();
- DexField valuesField =
- dexItemFactory.createField(
- clazz.type,
- clazz.type.toArrayType(1, dexItemFactory),
- dexItemFactory.enumValuesFieldName);
-
- Value root = value.getAliasedValue();
- if (root.isPhi()) {
- return false;
- }
-
- Instruction definition = root.definition;
- if (definition.isNewArrayEmpty()) {
- for (Instruction user : root.aliasedUsers()) {
- if (user.isStaticPut() && user.asStaticPut().getField() == valuesField) {
- return true;
- }
- }
- } else if (definition.isStaticGet()) {
- return definition.asStaticGet().getField() == valuesField;
- }
-
- return false;
- }
+ abstract void updateFieldOptimizationInfo(
+ DexEncodedField field, FieldInstruction fieldPut, Value value);
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
index ecc93b7..447834f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
@@ -4,11 +4,18 @@
package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Value;
@@ -55,13 +62,6 @@
if (!appView.options().enableValuePropagationForInstanceFields) {
return EmptyInstanceFieldInitializationInfoCollection.getInstance();
}
- DexEncodedMethod otherInstanceInitializer =
- clazz.lookupDirectMethod(other -> other.isInstanceInitializer() && other != method);
- if (otherInstanceInitializer != null) {
- // Conservatively bail out.
- // TODO(b/125282093): Handle multiple instance initializers on the same class.
- return EmptyInstanceFieldInitializationInfoCollection.getInstance();
- }
InstanceFieldValueAnalysis analysis =
new InstanceFieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method);
analysis.computeFieldOptimizationInfo(classInitializerDefaultsResult);
@@ -69,16 +69,53 @@
}
@Override
- void updateFieldOptimizationInfo(DexEncodedField field, Value value) {
- super.updateFieldOptimizationInfo(field, value);
+ boolean isSubjectToOptimization(DexEncodedField field) {
+ return !field.isStatic() && field.holder() == clazz.type;
+ }
- // If this instance field is initialized with an argument, then record this in the instance
- // field initialization info.
+ @Override
+ void updateFieldOptimizationInfo(DexEncodedField field, FieldInstruction fieldPut, Value value) {
+ if (fieldMaybeWrittenBetweenInstructionAndMethodExit(field, fieldPut)) {
+ return;
+ }
+
+ // If this instance field is initialized with an argument or a constant, then record this in the
+ // instance field initialization info.
Value root = value.getAliasedValue();
if (root.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
Argument argument = root.definition.asArgument();
builder.recordInitializationInfo(
field, factory.createArgumentInitializationInfo(argument.getIndex()));
+ return;
}
+
+ AbstractValue abstractValue = value.getAbstractValue(appView, clazz.type);
+ if (abstractValue.isSingleValue()) {
+ builder.recordInitializationInfo(field, abstractValue.asSingleValue());
+ return;
+ }
+
+ DexType fieldType = field.field.type;
+ if (fieldType.isClassType()) {
+ ClassTypeLatticeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
+ TypeLatticeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
+ TypeLatticeElement staticFieldType =
+ TypeLatticeElement.fromDexType(fieldType, maybeNull(), appView);
+ if (dynamicLowerBoundType != null || !dynamicUpperBoundType.equals(staticFieldType)) {
+ builder.recordInitializationInfo(
+ field,
+ factory.createTypeInitializationInfo(dynamicLowerBoundType, dynamicUpperBoundType));
+ }
+ }
+ }
+
+ private boolean fieldMaybeWrittenBetweenInstructionAndMethodExit(
+ DexEncodedField field, FieldInstruction fieldPut) {
+ if (field.isFinal()
+ || appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)) {
+ return false;
+ }
+ // Otherwise, conservatively return true.
+ return true;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 0c91855..f25c566 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -4,10 +4,27 @@
package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
+import static com.android.tools.r8.ir.code.Opcodes.*;
+
import com.android.tools.r8.graph.AppView;
+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.DexProgramClass;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleEnumValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
+import com.android.tools.r8.ir.code.ArrayPut;
+import com.android.tools.r8.ir.code.FieldInstruction;
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.NewInstance;
+import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -36,4 +53,148 @@
new StaticFieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method)
.computeFieldOptimizationInfo(classInitializerDefaultsResult);
}
+
+ @Override
+ boolean isSubjectToOptimization(DexEncodedField field) {
+ return field.isStatic()
+ && field.holder() == clazz.type
+ && appView.appInfo().isFieldOnlyWrittenInMethod(field, method);
+ }
+
+ @Override
+ void updateFieldOptimizationInfo(DexEncodedField field, FieldInstruction fieldPut, Value value) {
+ // Abstract value.
+ Value root = value.getAliasedValue();
+ AbstractValue abstractValue = computeAbstractValue(root);
+ if (abstractValue.isUnknown()) {
+ feedback.recordFieldHasAbstractValue(
+ field, appView, appView.abstractValueFactory().createSingleFieldValue(field.field));
+ } else {
+ feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
+ }
+
+ // Dynamic upper bound type.
+ TypeLatticeElement fieldType =
+ TypeLatticeElement.fromDexType(field.field.type, Nullability.maybeNull(), appView);
+ TypeLatticeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
+ if (dynamicUpperBoundType.strictlyLessThan(fieldType, appView)) {
+ feedback.markFieldHasDynamicUpperBoundType(field, dynamicUpperBoundType);
+ }
+
+ // Dynamic lower bound type.
+ ClassTypeLatticeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
+ if (dynamicLowerBoundType != null) {
+ assert dynamicLowerBoundType.lessThanOrEqual(dynamicUpperBoundType, appView);
+ feedback.markFieldHasDynamicLowerBoundType(field, dynamicLowerBoundType);
+ }
+ }
+
+ private AbstractValue computeAbstractValue(Value value) {
+ assert !value.hasAliasedValue();
+ if (clazz.isEnum()) {
+ SingleEnumValue singleEnumValue = getSingleEnumValue(value);
+ if (singleEnumValue != null) {
+ return singleEnumValue;
+ }
+ }
+ if (!value.isPhi()) {
+ return value.definition.getAbstractValue(appView, clazz.type);
+ }
+ return UnknownValue.getInstance();
+ }
+
+ /**
+ * If {@param value} is defined by a new-instance instruction that instantiates the enclosing enum
+ * class, and the value is assigned into exactly one static enum field on the enclosing enum
+ * class, then returns a {@link SingleEnumValue} instance. Otherwise, returns {@code null}.
+ *
+ * <p>Note that enum constructors also store the newly instantiated enums in the {@code $VALUES}
+ * array field on the enum. Therefore, this code also allows {@param value} to be stored into an
+ * array as long as the array is identified as being the {@code $VALUES} array.
+ */
+ private SingleEnumValue getSingleEnumValue(Value value) {
+ assert clazz.isEnum();
+ assert !value.hasAliasedValue();
+ if (value.isPhi() || !value.definition.isNewInstance()) {
+ return null;
+ }
+
+ NewInstance newInstance = value.definition.asNewInstance();
+ if (newInstance.clazz != clazz.type) {
+ return null;
+ }
+
+ if (value.hasDebugUsers() || value.hasPhiUsers()) {
+ return null;
+ }
+
+ DexEncodedField enumField = null;
+ for (Instruction user : value.uniqueUsers()) {
+ switch (user.opcode()) {
+ case ARRAY_PUT:
+ // Check that this is assigning the enum into the enum values array.
+ ArrayPut arrayPut = user.asArrayPut();
+ if (arrayPut.value().getAliasedValue() != value || !isEnumValuesArray(arrayPut.array())) {
+ return null;
+ }
+ break;
+
+ case INVOKE_DIRECT:
+ // Check that this is the corresponding constructor call.
+ InvokeDirect invoke = user.asInvokeDirect();
+ if (!appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())
+ || invoke.getReceiver() != value) {
+ return null;
+ }
+ break;
+
+ case STATIC_PUT:
+ DexEncodedField field = clazz.lookupStaticField(user.asStaticPut().getField());
+ if (field != null && field.accessFlags.isEnum()) {
+ if (enumField != null) {
+ return null;
+ }
+ enumField = field;
+ }
+ break;
+
+ default:
+ return null;
+ }
+ }
+
+ if (enumField == null) {
+ return null;
+ }
+
+ return appView.abstractValueFactory().createSingleEnumValue(enumField.field);
+ }
+
+ private boolean isEnumValuesArray(Value value) {
+ assert clazz.isEnum();
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ DexField valuesField =
+ dexItemFactory.createField(
+ clazz.type,
+ clazz.type.toArrayType(1, dexItemFactory),
+ dexItemFactory.enumValuesFieldName);
+
+ Value root = value.getAliasedValue();
+ if (root.isPhi()) {
+ return false;
+ }
+
+ Instruction definition = root.definition;
+ if (definition.isNewArrayEmpty()) {
+ for (Instruction user : root.aliasedUsers()) {
+ if (user.isStaticPut() && user.asStaticPut().getField() == valuesField) {
+ return true;
+ }
+ }
+ } else if (definition.isStaticGet()) {
+ return definition.asStaticGet().getField() == valuesField;
+ }
+
+ return false;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
index 44aa581..c7b249f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
@@ -10,8 +10,9 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
-public abstract class SingleValue extends AbstractValue {
+public abstract class SingleValue extends AbstractValue implements InstanceFieldInitializationInfo {
@Override
public boolean isNonTrivial() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index c8d6abc..69c6281 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -27,6 +27,15 @@
public abstract class FieldInstruction extends Instruction {
+ public enum Assumption {
+ NONE,
+ RECEIVER_NOT_NULL;
+
+ boolean canAssumeReceiverIsNotNull() {
+ return this == RECEIVER_NOT_NULL;
+ }
+ }
+
private final DexField field;
protected FieldInstruction(DexField field, Value dest, Value value) {
@@ -61,6 +70,11 @@
@Override
public AbstractError instructionInstanceCanThrow(AppView<?> appView, DexType context) {
+ return instructionInstanceCanThrow(appView, context, Assumption.NONE);
+ }
+
+ public AbstractError instructionInstanceCanThrow(
+ AppView<?> appView, DexType context, Assumption assumption) {
DexEncodedField resolvedField;
if (appView.enableWholeProgramOptimizations()) {
// TODO(b/123857022): Should be possible to use definitionFor().
@@ -106,9 +120,11 @@
// TODO(b/137168535): Without non-null tracking, only locally created receiver is allowed in D8.
// * NullPointerException (null receiver).
if (isInstanceGet() || isInstancePut()) {
- Value receiver = inValues.get(0);
- if (receiver.isAlwaysNull(appView) || receiver.typeLattice.isNullable()) {
- return AbstractError.specific(appView.dexItemFactory().npeType);
+ if (!assumption.canAssumeReceiverIsNotNull()) {
+ Value receiver = inValues.get(0);
+ if (receiver.isAlwaysNull(appView) || receiver.typeLattice.isNullable()) {
+ return AbstractError.specific(appView.dexItemFactory().npeType);
+ }
}
}
// For D8, reaching here means the field is in the same context, hence the class is guaranteed
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 3155fa1..2ca1b2a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -116,7 +116,12 @@
@Override
public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
- return instructionInstanceCanThrow(appView, context).isThrowing();
+ return instructionMayHaveSideEffects(appView, context, Assumption.NONE);
+ }
+
+ public boolean instructionMayHaveSideEffects(
+ AppView<?> appView, DexType context, Assumption assumption) {
+ return instructionInstanceCanThrow(appView, context, assumption).isThrowing();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 87b93a4..a42988e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -82,7 +82,8 @@
DexType refinedReceiverType =
TypeAnalysis.getRefinedReceiverType(appViewWithLiveness, this);
assert receiverLowerBoundType.getClassType() == refinedReceiverType
- || receiverLowerBoundType.isBasedOnMissingClass(appViewWithLiveness);
+ || receiverLowerBoundType.isBasedOnMissingClass(appViewWithLiveness)
+ : "The receiver lower bound does not match the receiver type";
}
}
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 3a7969a..da1c8fd 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
@@ -152,7 +152,7 @@
Query mode,
AnalysisAssumption assumption) {
return ClassInitializationAnalysis.InstructionUtils.forInvokeStatic(
- this, clazz, appView, mode, assumption);
+ this, clazz, context, appView, mode, assumption);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 78b1405..0e24fe9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -33,7 +33,6 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
@@ -424,50 +423,66 @@
// then the IR does not necessarily contain a const-string instruction).
private final IRMetadata metadata = new IRMetadata();
- public IRBuilder(DexEncodedMethod method, AppView<?> appView, SourceCode source, Origin origin) {
- this(method, appView, source, origin, new ValueNumberGenerator());
+ public static IRBuilder create(DexEncodedMethod method,
+ AppView<?> appView,
+ SourceCode source,
+ Origin origin) {
+ return new IRBuilder(method,
+ appView,
+ source,
+ origin,
+ lookupPrototypeChanges(appView, method.method),
+ new ValueNumberGenerator());
}
- public IRBuilder(
+ public static IRBuilder createForInlining(DexEncodedMethod method,
+ AppView<?> appView,
+ SourceCode source,
+ Origin origin,
+ MethodProcessor processor,
+ ValueNumberGenerator valueNumberGenerator) {
+ RewrittenPrototypeDescription protoChanges = processor.shouldApplyCodeRewritings(method) ?
+ lookupPrototypeChanges(appView, method.method) :
+ RewrittenPrototypeDescription.none();
+ return new IRBuilder(method,
+ appView,
+ source,
+ origin,
+ protoChanges,
+ valueNumberGenerator);
+ }
+
+ private static RewrittenPrototypeDescription lookupPrototypeChanges(AppView<?> appView,
+ DexMethod method) {
+ RewrittenPrototypeDescription prototypeChanges = appView.graphLense()
+ .lookupPrototypeChanges(method);
+ if (Log.ENABLED
+ && prototypeChanges.getRemovedArgumentInfoCollection().hasRemovedArguments()) {
+ Log.info(
+ IRBuilder.class,
+ "Removed "
+ + prototypeChanges.getRemovedArgumentInfoCollection().numberOfRemovedArguments()
+ + " arguments from "
+ + method.toSourceString());
+ }
+ return prototypeChanges;
+ }
+
+ private IRBuilder(
DexEncodedMethod method,
AppView<?> appView,
SourceCode source,
Origin origin,
+ RewrittenPrototypeDescription prototypeChanges,
ValueNumberGenerator valueNumberGenerator) {
assert source != null;
+ assert valueNumberGenerator != null;
this.method = method;
this.appView = appView;
this.source = source;
- this.valueNumberGenerator =
- valueNumberGenerator != null ? valueNumberGenerator : new ValueNumberGenerator();
this.origin = origin;
-
- if (method.isProcessed()) {
- // NOTE: This is currently assuming that we never remove additional arguments from methods
- // after they have already been processed once.
- assert verifyMethodSignature(method, appView.graphLense());
- this.prototypeChanges = RewrittenPrototypeDescription.none();
- } else {
- this.prototypeChanges = appView.graphLense().lookupPrototypeChanges(method.method);
-
- if (Log.ENABLED
- && prototypeChanges.getRemovedArgumentInfoCollection().hasRemovedArguments()) {
- Log.info(
- getClass(),
- "Removed "
- + prototypeChanges.getRemovedArgumentInfoCollection().numberOfRemovedArguments()
- + " arguments from "
- + method.toSourceString());
- }
- }
- }
-
- private static boolean verifyMethodSignature(DexEncodedMethod method, GraphLense graphLense) {
- RewrittenPrototypeDescription prototypeChanges =
- graphLense.lookupPrototypeChanges(method.method);
- assert !prototypeChanges.hasBeenChangedToReturnVoid()
- || method.method.proto.returnType.isVoidType();
- return true;
+ this.prototypeChanges = prototypeChanges;
+ this.valueNumberGenerator = valueNumberGenerator;
}
public DexEncodedMethod getMethod() {
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 ce65222..94e5cf4 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
@@ -716,12 +716,11 @@
// TODO(b/127694949): Adapt to PostOptimization.
staticizeClasses(feedback, executorService);
feedback.updateVisibleOptimizationInfo();
+ // The class staticizer lens shall not be applied through lens code rewriting or it breaks
+ // the lambda merger.
+ appView.clearCodeRewritings();
}
- // The class staticizer lens shall not be applied through lens code rewriting or it breaks
- // the lambda merger.
- appView.clearCodeRewritings();
-
// Build a new application with jumbo string info.
Builder<?> builder = application.builder();
builder.setHighestSortingString(highestSortingString);
@@ -831,11 +830,12 @@
}
private void waveDone(Collection<DexEncodedMethod> wave) {
+ delayedOptimizationFeedback.refineAppInfoWithLiveness(appView.appInfo().withLiveness());
+ delayedOptimizationFeedback.updateVisibleOptimizationInfo();
if (options.enableFieldAssignmentTracker) {
fieldAccessAnalysis.fieldAssignmentTracker().waveDone(wave, delayedOptimizationFeedback);
}
- delayedOptimizationFeedback.refineAppInfoWithLiveness(appView.appInfo().withLiveness());
- delayedOptimizationFeedback.updateVisibleOptimizationInfo();
+ assert delayedOptimizationFeedback.noUpdatesLeft();
onWaveDoneActions.forEach(com.android.tools.r8.utils.Action::execute);
onWaveDoneActions = null;
}
@@ -1147,7 +1147,7 @@
if (lambdaMerger != null) {
timing.begin("Merge lambdas");
- lambdaMerger.rewriteCode(method, code, inliner);
+ lambdaMerger.rewriteCode(method, code, inliner, methodProcessor);
timing.end();
assert code.isConsistentSSA();
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index 34609c9..0c2eb60 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -15,6 +15,8 @@
Phase getPhase();
+ boolean shouldApplyCodeRewritings(DexEncodedMethod method);
+
default boolean isPrimary() {
return getPhase() == Phase.PRIMARY;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
index b55f54f..939395f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -31,6 +31,11 @@
}
@Override
+ public boolean shouldApplyCodeRewritings(DexEncodedMethod method) {
+ return true;
+ }
+
+ @Override
public Phase getPhase() {
return Phase.ONE_TIME;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 96c93a9..529bdc7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
@@ -31,8 +32,9 @@
private final AppView<AppInfoWithLiveness> appView;
private final Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap;
- private final Deque<Collection<DexEncodedMethod>> waves;
- private Collection<DexEncodedMethod> wave;
+ private final Deque<Set<DexEncodedMethod>> waves;
+ private Set<DexEncodedMethod> wave;
+ private final Set<DexEncodedMethod> processed = Sets.newIdentityHashSet();
private PostMethodProcessor(
AppView<AppInfoWithLiveness> appView,
@@ -48,6 +50,12 @@
return Phase.POST;
}
+ @Override
+ public boolean shouldApplyCodeRewritings(DexEncodedMethod method) {
+ assert !wave.contains(method);
+ return !processed.contains(method);
+ }
+
static class Builder {
private final Collection<CodeOptimization> defaultCodeOptimizations;
private final Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap =
@@ -107,9 +115,9 @@
}
}
- private Deque<Collection<DexEncodedMethod>> createWaves(AppView<?> appView, CallGraph callGraph) {
+ private Deque<Set<DexEncodedMethod>> createWaves(AppView<?> appView, CallGraph callGraph) {
IROrdering shuffle = appView.options().testing.irOrdering;
- Deque<Collection<DexEncodedMethod>> waves = new ArrayDeque<>();
+ Deque<Set<DexEncodedMethod>> waves = new ArrayDeque<>();
int waveCount = 1;
while (!callGraph.isEmpty()) {
@@ -141,6 +149,7 @@
forEachMethod(method, codeOptimizations, feedback);
},
executorService);
+ processed.addAll(wave);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index 8fb6afb..5b26255 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -65,6 +65,12 @@
}
@Override
+ public boolean shouldApplyCodeRewritings(DexEncodedMethod method) {
+ assert !wave.contains(method);
+ return !method.isProcessed();
+ }
+
+ @Override
public CallSiteInformation getCallSiteInformation() {
return callSiteInformation;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 2f54714..76e97df 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexLibraryClass;
@@ -319,14 +320,10 @@
// If target is a non-interface library class it may be an emulated interface.
if (!libraryHolder.isInterface()) {
// Here we use step-3 of resolution to find a maximally specific default interface method.
- target =
- appView
- .appInfo()
- .resolveMaximallySpecificMethods(libraryHolder, method)
- .getSingleTarget();
- if (target != null && rewriter.isEmulatedInterface(target.method.holder)) {
- targetHolder = appView.definitionFor(target.method.holder);
- addForward.accept(targetHolder, target);
+ DexClassAndMethod result =
+ appView.appInfo().lookupMaximallySpecificMethod(libraryHolder, method);
+ if (result != null && rewriter.isEmulatedInterface(result.getHolder().type)) {
+ addForward.accept(result.getHolder(), result.getMethod());
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 0e8cbae..f6693eb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -399,11 +400,18 @@
if (appView.rewritePrefix.hasRewrittenType(dexClass.type, appView)) {
return null;
}
- DexEncodedMethod singleTarget =
- appView
- .appInfo()
- .resolveMaximallySpecificMethods(dexClass, invokedMethod)
- .getSingleTarget();
+ DexEncodedMethod singleTarget = null;
+ if (dexClass.isInterface()) {
+ // Look for exact method on the interface.
+ singleTarget = dexClass.lookupMethod(invokedMethod);
+ }
+ if (singleTarget == null) {
+ DexClassAndMethod result =
+ appView.appInfo().lookupMaximallySpecificMethod(dexClass, invokedMethod);
+ if (result != null) {
+ singleTarget = result.getMethod();
+ }
+ }
if (singleTarget == null) {
// At this point we are in a library class. Failures can happen with NoSuchMethod if a
// library class implement a method with same signature but not related to emulated
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 0546e9d..023ef1c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -1,9 +1,9 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// 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.
// ***********************************************************************************
-// GENERATED FILE. DO NOT EDIT! Changes should be made to GenerateBackportMethods.java
+// GENERATED FILE. DO NOT EDIT! See GenerateBackportMethods.java.
// ***********************************************************************************
package com.android.tools.r8.ir.desugar.backports;
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 7618cb6..ee62dc3 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
@@ -718,11 +718,12 @@
}
}
- if (!target.isProcessed()) {
+ if (inliningIRProvider.shouldApplyCodeRewritings(code.method)) {
+ assert lensCodeRewriter != null;
lensCodeRewriter.rewrite(code, target);
}
if (lambdaMerger != null) {
- lambdaMerger.rewriteCodeForInlining(target, code, context);
+ lambdaMerger.rewriteCodeForInlining(target, code, context, inliningIRProvider);
}
assert code.isConsistentSSA();
return new InlineeWithReason(code, reason);
@@ -817,14 +818,6 @@
public void performForcedInlining(
DexEncodedMethod method,
IRCode code,
- Map<? extends InvokeMethod, InliningInfo> invokesToInline) {
- performForcedInlining(
- method, code, invokesToInline, new InliningIRProvider(appView, method, code));
- }
-
- public void performForcedInlining(
- DexEncodedMethod method,
- IRCode code,
Map<? extends InvokeMethod, InliningInfo> invokesToInline,
InliningIRProvider inliningIRProvider) {
ForcedInliningOracle oracle = new ForcedInliningOracle(appView, method, invokesToInline);
@@ -859,7 +852,8 @@
options.inliningInstructionLimit,
options.inliningInstructionAllowance - numberOfInstructions(code),
inliningReasonStrategy);
- InliningIRProvider inliningIRProvider = new InliningIRProvider(appView, method, code);
+ InliningIRProvider inliningIRProvider =
+ new InliningIRProvider(appView, method, code, methodProcessor);
assert inliningIRProvider.verifyIRCacheIsEmpty();
performInliningImpl(oracle, oracle, method, code, feedback, inliningIRProvider);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 722625f..a28181f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -20,9 +20,12 @@
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.ir.code.InstanceGet;
import com.android.tools.r8.ir.code.Instruction;
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.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
@@ -31,6 +34,7 @@
import com.android.tools.r8.shaking.ProguardMemberRuleReturnValue;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.ListIterator;
import java.util.Set;
@@ -336,12 +340,24 @@
target.valueAsConstInstruction(code, current.outValue().getLocalInfo(), appView);
if (replacement != null) {
affectedValues.addAll(current.outValue().affectedValues());
- if (current.instructionMayHaveSideEffects(appView, code.method.method.holder)) {
- // To preserve class initialization/NPE side effects, original field-get remains as-is,
- // but its value is replaced with constant.
- replacement.setPosition(current.getPosition());
+ DexType context = code.method.method.holder;
+ if (current.instructionMayHaveSideEffects(appView, context)) {
+ // All usages are replaced by the replacement value.
current.outValue().replaceUsers(replacement.outValue());
- if (current.getBlock().hasCatchHandlers()) {
+
+ // To preserve side effects, original field-get is replaced by an explicit null-check, if
+ // the field-get instruction may only fail with an NPE, or the field-get remains as-is.
+ Instruction currentOrNullCheck;
+ if (current.isInstanceGet()) {
+ currentOrNullCheck =
+ replaceInstanceGetByNullCheckIfPossible(current.asInstanceGet(), iterator, context);
+ } else {
+ currentOrNullCheck = current;
+ }
+
+ // Insert the definition of the replacement.
+ replacement.setPosition(currentOrNullCheck.getPosition());
+ if (currentOrNullCheck.getBlock().hasCatchHandlers()) {
iterator.split(code, blocks).listIterator(code).add(replacement);
} else {
iterator.add(replacement);
@@ -353,6 +369,26 @@
}
}
+ private Instruction replaceInstanceGetByNullCheckIfPossible(
+ InstanceGet instruction, InstructionListIterator iterator, DexType context) {
+ assert !instruction.outValue().hasAnyUsers();
+ if (instruction.instructionMayHaveSideEffects(
+ appView, context, FieldInstruction.Assumption.RECEIVER_NOT_NULL)) {
+ return instruction;
+ }
+ Value receiver = instruction.object();
+ InvokeMethod replacement;
+ if (appView.options().canUseRequireNonNull()) {
+ DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull;
+ replacement = new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(receiver));
+ } else {
+ DexMethod getClassMethod = appView.dexItemFactory().objectMethods.getClass;
+ replacement = new InvokeVirtual(getClassMethod, null, ImmutableList.of(receiver));
+ }
+ iterator.replaceCurrentInstruction(replacement);
+ return replacement;
+ }
+
/**
* Replace invoke targets and field accesses with constant values where possible.
*
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index ea419cc..002abc9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -1595,7 +1595,8 @@
@Override
public IRCode buildIR(DexEncodedMethod encodedMethod, AppView<?> appView, Origin origin) {
OutlineSourceCode source = new OutlineSourceCode(outline, encodedMethod.method);
- return new IRBuilder(encodedMethod, appView, source, origin).build(encodedMethod);
+ return IRBuilder.create(encodedMethod, appView, source, origin)
+ .build(encodedMethod);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index 856baa9..e8799da 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -35,6 +35,7 @@
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* ServiceLoaderRewriter will attempt to rewrite calls on the form of: ServiceLoader.load(X.class,
@@ -72,6 +73,8 @@
private ConcurrentHashMap<DexType, DexEncodedMethod> synthesizedServiceLoaders =
new ConcurrentHashMap<>();
+ private AtomicInteger atomicInteger = new AtomicInteger(0);
+
private final AppView<? extends AppInfoWithLiveness> appView;
public ServiceLoaderRewriter(AppView<? extends AppInfoWithLiveness> appView) {
@@ -216,7 +219,7 @@
.createMethod(
serviceLoaderType,
proto,
- SERVICE_LOADER_METHOD_PREFIX_NAME + synthesizedServiceLoaders.size());
+ SERVICE_LOADER_METHOD_PREFIX_NAME + atomicInteger.incrementAndGet());
MethodAccessFlags methodAccess =
MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_STATIC, false);
DexEncodedMethod encodedMethod =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
index 8799529..0aabe6e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
@@ -135,13 +135,12 @@
}
}
- assert targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()] == 0;
-
iterator.replaceCurrentInstruction(
new IntSwitch(
theSwitch.value(),
newKeys,
newTargetBlockIndices,
- theSwitch.getFallthroughBlockIndex()));
+ theSwitch.getFallthroughBlockIndex()
+ - targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()]));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index b20eba1..291f4d5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -227,7 +227,8 @@
assert processor.getReceivers().verifyReceiverSetsAreDisjoint();
// Is inlining allowed.
- InliningIRProvider inliningIRProvider = new InliningIRProvider(appView, method, code);
+ InliningIRProvider inliningIRProvider =
+ new InliningIRProvider(appView, method, code, methodProcessor);
ClassInlinerCostAnalysis costAnalysis =
new ClassInlinerCostAnalysis(
appView, inliningIRProvider, processor.getReceivers().getDefiniteReceiverAliases());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index f076b41..5612322 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -248,6 +248,10 @@
}
if (user.isInstancePut()) {
+ if (root.isStaticGet()) {
+ // We can't remove instructions that mutate the singleton instance.
+ return user; // Not eligible.
+ }
if (!receivers.addIllegalReceiverAlias(user.asInstancePut().value())) {
return user; // Not eligible.
}
@@ -747,6 +751,9 @@
if (encodedParent == null) {
return null;
}
+ if (methodProcessor.isProcessedConcurrently(encodedParent)) {
+ return null;
+ }
if (!encodedParent.isInliningCandidate(
method,
Reason.SIMPLE,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
new file mode 100644
index 0000000..0bf0aed
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
@@ -0,0 +1,195 @@
+// 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See GenerateEnumUnboxingMethods.java.
+// ***********************************************************************************
+
+package com.android.tools.r8.ir.optimize.enums;
+
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
+import com.android.tools.r8.cf.code.CfArrayLength;
+import com.android.tools.r8.cf.code.CfArrayStore;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfIfCmp;
+import com.android.tools.r8.cf.code.CfIinc;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+
+public final class EnumUnboxingCfMethods {
+
+ public static CfCode EnumUnboxingMethods_compareTo(InternalOptions options, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ CfLabel label2 = new CfLabel();
+ CfLabel label3 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 2,
+ 2,
+ ImmutableList.of(
+ label0,
+ new CfLoad(ValueType.INT, 0),
+ new CfIf(If.Type.EQ, ValueType.INT, label1),
+ new CfLoad(ValueType.INT, 1),
+ new CfIf(If.Type.NE, ValueType.INT, label2),
+ label1,
+ new CfNew(
+ options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;")),
+ new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+ new CfInvoke(
+ 183,
+ options.itemFactory.createMethod(
+ options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;"),
+ options.itemFactory.createProto(options.itemFactory.voidType),
+ options.itemFactory.createString("<init>")),
+ false),
+ new CfThrow(),
+ label2,
+ new CfLoad(ValueType.INT, 0),
+ new CfLoad(ValueType.INT, 1),
+ new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+ new CfReturn(ValueType.INT),
+ label3),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode EnumUnboxingMethods_equals(InternalOptions options, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ CfLabel label2 = new CfLabel();
+ CfLabel label3 = new CfLabel();
+ CfLabel label4 = new CfLabel();
+ CfLabel label5 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 2,
+ 2,
+ ImmutableList.of(
+ label0,
+ new CfLoad(ValueType.INT, 0),
+ new CfIf(If.Type.NE, ValueType.INT, label2),
+ label1,
+ new CfNew(
+ options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;")),
+ new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+ new CfInvoke(
+ 183,
+ options.itemFactory.createMethod(
+ options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;"),
+ options.itemFactory.createProto(options.itemFactory.voidType),
+ options.itemFactory.createString("<init>")),
+ false),
+ new CfThrow(),
+ label2,
+ new CfLoad(ValueType.INT, 0),
+ new CfLoad(ValueType.INT, 1),
+ new CfIfCmp(If.Type.NE, ValueType.INT, label3),
+ new CfConstNumber(1, ValueType.INT),
+ new CfGoto(label4),
+ label3,
+ new CfConstNumber(0, ValueType.INT),
+ label4,
+ new CfReturn(ValueType.INT),
+ label5),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode EnumUnboxingMethods_ordinal(InternalOptions options, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ CfLabel label2 = new CfLabel();
+ CfLabel label3 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 2,
+ 1,
+ ImmutableList.of(
+ label0,
+ new CfLoad(ValueType.INT, 0),
+ new CfIf(If.Type.NE, ValueType.INT, label2),
+ label1,
+ new CfNew(
+ options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;")),
+ new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+ new CfInvoke(
+ 183,
+ options.itemFactory.createMethod(
+ options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;"),
+ options.itemFactory.createProto(options.itemFactory.voidType),
+ options.itemFactory.createString("<init>")),
+ false),
+ new CfThrow(),
+ label2,
+ new CfLoad(ValueType.INT, 0),
+ new CfConstNumber(1, ValueType.INT),
+ new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+ new CfReturn(ValueType.INT),
+ label3),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode EnumUnboxingMethods_values(InternalOptions options, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ CfLabel label2 = new CfLabel();
+ CfLabel label3 = new CfLabel();
+ CfLabel label4 = new CfLabel();
+ CfLabel label5 = new CfLabel();
+ CfLabel label6 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 4,
+ 3,
+ ImmutableList.of(
+ label0,
+ new CfLoad(ValueType.INT, 0),
+ new CfNewArray(options.itemFactory.intArrayType),
+ new CfStore(ValueType.OBJECT, 1),
+ label1,
+ new CfConstNumber(0, ValueType.INT),
+ new CfStore(ValueType.INT, 2),
+ label2,
+ new CfLoad(ValueType.INT, 2),
+ new CfLoad(ValueType.OBJECT, 1),
+ new CfArrayLength(),
+ new CfIfCmp(If.Type.GE, ValueType.INT, label5),
+ label3,
+ new CfLoad(ValueType.OBJECT, 1),
+ new CfLoad(ValueType.INT, 2),
+ new CfLoad(ValueType.INT, 2),
+ new CfConstNumber(1, ValueType.INT),
+ new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT),
+ new CfArrayStore(MemberType.INT),
+ label4,
+ new CfIinc(2, 1),
+ new CfGoto(label2),
+ label5,
+ new CfLoad(ValueType.OBJECT, 1),
+ new CfReturn(ValueType.OBJECT),
+ label6),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
index c56c562..b9e7652 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
@@ -8,7 +8,7 @@
* Used to represent that a constructor initializes an instance field on the newly created instance
* with argument number {@link #argumentIndex} from the constructor's argument list.
*/
-public class InstanceFieldArgumentInitializationInfo extends InstanceFieldInitializationInfo {
+public class InstanceFieldArgumentInitializationInfo implements InstanceFieldInitializationInfo {
private final int argumentIndex;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java
index 312d7a0..2e9285a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.optimize.info.field;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
+
/**
* Information about the way a constructor initializes an instance field on the newly created
* instance.
@@ -11,17 +13,33 @@
* <p>For example, this can be used to represent that a constructor always initializes a particular
* instance field with a constant, or with an argument from the constructor's argument list.
*/
-public abstract class InstanceFieldInitializationInfo {
+public interface InstanceFieldInitializationInfo {
- public boolean isArgumentInitializationInfo() {
+ default boolean isArgumentInitializationInfo() {
return false;
}
- public InstanceFieldArgumentInitializationInfo asArgumentInitializationInfo() {
+ default InstanceFieldArgumentInitializationInfo asArgumentInitializationInfo() {
return null;
}
- public boolean isUnknown() {
+ default boolean isTypeInitializationInfo() {
+ return false;
+ }
+
+ default InstanceFieldTypeInitializationInfo asTypeInitializationInfo() {
+ return null;
+ }
+
+ default boolean isSingleValue() {
+ return false;
+ }
+
+ default SingleValue asSingleValue() {
+ return null;
+ }
+
+ default boolean isUnknown() {
return false;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java
index 42209b4..eb7cdda 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.optimize.info.field;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import java.util.concurrent.ConcurrentHashMap;
public class InstanceFieldInitializationInfoFactory {
@@ -16,4 +18,9 @@
return argumentInitializationInfos.computeIfAbsent(
argumentIndex, InstanceFieldArgumentInitializationInfo::new);
}
+
+ public InstanceFieldTypeInitializationInfo createTypeInitializationInfo(
+ ClassTypeLatticeElement dynamicLowerBoundType, TypeLatticeElement dynamicUpperBoundType) {
+ return new InstanceFieldTypeInitializationInfo(dynamicLowerBoundType, dynamicUpperBoundType);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
new file mode 100644
index 0000000..e6f84ff
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
@@ -0,0 +1,62 @@
+// 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.optimize.info.field;
+
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import java.util.Objects;
+
+/**
+ * Used to represent that a constructor initializes an instance field on the newly created instance
+ * with a known dynamic lower- and upper-bound type.
+ */
+public class InstanceFieldTypeInitializationInfo implements InstanceFieldInitializationInfo {
+
+ private final ClassTypeLatticeElement dynamicLowerBoundType;
+ private final TypeLatticeElement dynamicUpperBoundType;
+
+ /** Intentionally package private, use {@link InstanceFieldInitializationInfoFactory} instead. */
+ InstanceFieldTypeInitializationInfo(
+ ClassTypeLatticeElement dynamicLowerBoundType, TypeLatticeElement dynamicUpperBoundType) {
+ this.dynamicLowerBoundType = dynamicLowerBoundType;
+ this.dynamicUpperBoundType = dynamicUpperBoundType;
+ }
+
+ public ClassTypeLatticeElement getDynamicLowerBoundType() {
+ return dynamicLowerBoundType;
+ }
+
+ public TypeLatticeElement getDynamicUpperBoundType() {
+ return dynamicUpperBoundType;
+ }
+
+ @Override
+ public boolean isTypeInitializationInfo() {
+ return true;
+ }
+
+ @Override
+ public InstanceFieldTypeInitializationInfo asTypeInitializationInfo() {
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(dynamicLowerBoundType, dynamicUpperBoundType);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (getClass() != other.getClass()) {
+ return false;
+ }
+ InstanceFieldTypeInitializationInfo info = (InstanceFieldTypeInitializationInfo) other;
+ return Objects.equals(dynamicLowerBoundType, info.dynamicLowerBoundType)
+ && Objects.equals(dynamicUpperBoundType, info.dynamicUpperBoundType);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
index 66e882e..a10f045 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
@@ -8,7 +8,7 @@
* Represents that no information is known about the way a particular constructor initializes an
* instance field of the newly created instance.
*/
-public class UnknownInstanceFieldInitializationInfo extends InstanceFieldInitializationInfo {
+public class UnknownInstanceFieldInitializationInfo implements InstanceFieldInitializationInfo {
private static final UnknownInstanceFieldInitializationInfo INSTANCE =
new UnknownInstanceFieldInitializationInfo();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
index b1408dd..3a98637 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.origin.Origin;
import java.util.IdentityHashMap;
import java.util.Map;
@@ -19,13 +20,16 @@
private final AppView<?> appView;
private final DexEncodedMethod context;
private final ValueNumberGenerator valueNumberGenerator;
+ private final MethodProcessor methodProcessor;
private final Map<InvokeMethod, IRCode> cache = new IdentityHashMap<>();
- public InliningIRProvider(AppView<?> appView, DexEncodedMethod context, IRCode code) {
+ public InliningIRProvider(
+ AppView<?> appView, DexEncodedMethod context, IRCode code, MethodProcessor methodProcessor) {
this.appView = appView;
this.context = context;
this.valueNumberGenerator = code.valueNumberGenerator;
+ this.methodProcessor = methodProcessor;
}
public IRCode getInliningIR(InvokeMethod invoke, DexEncodedMethod method) {
@@ -35,7 +39,8 @@
}
Position position = Position.getPositionForInlining(appView, invoke, context);
Origin origin = appView.appInfo().originFor(method.method.holder);
- return method.buildInliningIR(context, appView, valueNumberGenerator, position, origin);
+ return method.buildInliningIR(
+ context, appView, valueNumberGenerator, position, origin, methodProcessor);
}
public IRCode getAndCacheInliningIR(InvokeMethod invoke, DexEncodedMethod method) {
@@ -53,4 +58,8 @@
assert cache.isEmpty();
return true;
}
+
+ public boolean shouldApplyCodeRewritings(DexEncodedMethod method) {
+ return methodProcessor.shouldApplyCodeRewritings(method);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 6512985..ab02906 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -29,6 +29,7 @@
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.MethodProcessor;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
@@ -36,6 +37,7 @@
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
+import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
import com.android.tools.r8.ir.optimize.lambda.kotlin.KotlinLambdaGroupIdFactory;
@@ -92,7 +94,11 @@
private abstract static class Mode {
void rewriteCode(
- DexEncodedMethod method, IRCode code, Inliner inliner, DexEncodedMethod context) {}
+ DexEncodedMethod method,
+ IRCode code,
+ Inliner inliner,
+ DexEncodedMethod context,
+ InliningIRProvider provider) {}
void analyzeCode(DexEncodedMethod method, IRCode code) {}
}
@@ -119,7 +125,11 @@
@Override
void rewriteCode(
- DexEncodedMethod method, IRCode code, Inliner inliner, DexEncodedMethod context) {
+ DexEncodedMethod method,
+ IRCode code,
+ Inliner inliner,
+ DexEncodedMethod context,
+ InliningIRProvider provider) {
DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
assert clazz != null;
@@ -150,7 +160,7 @@
assert invokesToInline.size() > 1;
- inliner.performForcedInlining(method, code, invokesToInline);
+ inliner.performForcedInlining(method, code, invokesToInline, provider);
}
}
@@ -291,20 +301,27 @@
* no more invalid lambda class references.
* </ol>
*/
- public final void rewriteCode(DexEncodedMethod method, IRCode code, Inliner inliner) {
+ public final void rewriteCode(
+ DexEncodedMethod method, IRCode code, Inliner inliner, MethodProcessor methodProcessor) {
if (mode != null) {
- mode.rewriteCode(method, code, inliner, null);
+ mode.rewriteCode(
+ method,
+ code,
+ inliner,
+ null,
+ new InliningIRProvider(appView, method, code, methodProcessor));
}
}
/**
- * Similar to {@link #rewriteCode(DexEncodedMethod, IRCode, Inliner)}, but for rewriting code for
- * inlining. The {@param context} is the caller that {@param method} is being inlined into.
+ * Similar to {@link #rewriteCode(DexEncodedMethod, IRCode, Inliner, MethodProcessor)}, but for
+ * rewriting code for inlining. The {@param context} is the caller that {@param method} is being
+ * inlined into.
*/
public final void rewriteCodeForInlining(
- DexEncodedMethod method, IRCode code, DexEncodedMethod context) {
+ DexEncodedMethod method, IRCode code, DexEncodedMethod context, InliningIRProvider provider) {
if (mode != null) {
- mode.rewriteCode(method, code, null, context);
+ mode.rewriteCode(method, code, null, context, provider);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
index f9da7c6..331f14d 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.SourceCode;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
@@ -35,14 +36,11 @@
@Override
public final IRCode buildIR(DexEncodedMethod encodedMethod, AppView<?> appView, Origin origin) {
- IRBuilder builder =
- new IRBuilder(
- encodedMethod,
- appView,
- getSourceCodeProvider().get(null),
- origin,
- new ValueNumberGenerator());
- return builder.build(encodedMethod);
+ return IRBuilder.create(
+ encodedMethod,
+ appView,
+ getSourceCodeProvider().get(null),
+ origin).build(encodedMethod);
}
@Override
@@ -52,15 +50,15 @@
AppView<?> appView,
ValueNumberGenerator valueNumberGenerator,
Position callerPosition,
- Origin origin) {
- IRBuilder builder =
- new IRBuilder(
- encodedMethod,
- appView,
- getSourceCodeProvider().get(callerPosition),
- origin,
- valueNumberGenerator);
- return builder.build(context);
+ Origin origin,
+ MethodProcessor methodProcessor) {
+ return IRBuilder.createForInlining(
+ encodedMethod,
+ appView,
+ getSourceCodeProvider().get(callerPosition),
+ origin,
+ methodProcessor,
+ valueNumberGenerator).build(context);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 96cd284..be788ab 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -69,9 +69,7 @@
this.packageObfuscationMode = options.getProguardConfiguration().getPackageObfuscationMode();
this.isAccessModificationAllowed =
options.getProguardConfiguration().isAccessModificationAllowed();
- this.keepInnerClassStructure =
- options.getProguardConfiguration().getKeepAttributes().signature
- || options.getProguardConfiguration().getKeepAttributes().innerClasses;
+ this.keepInnerClassStructure = options.keepInnerClassStructure();
// Initialize top-level naming state.
topLevelState = new Namespace(
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index b95f778..734c0b0 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -84,7 +84,8 @@
}
int index = innerTypeMapped.lastIndexOf(separator);
if (index < 0) {
- assert options.getProguardConfiguration().hasApplyMappingFile()
+ assert !options.keepInnerClassStructure()
+ || options.getProguardConfiguration().hasApplyMappingFile()
: innerType + " -> " + innerTypeMapped;
String descriptor = lookupDescriptor(innerType).toString();
return options.itemFactory.createString(
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index b4ddb8e..67d2fa9 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -266,7 +266,7 @@
int innerClassPos = enclosingRenamedBinaryName.length() + 1;
if (innerClassPos < fullRenamedBinaryName.length()) {
renamedSignature.append(fullRenamedBinaryName.substring(innerClassPos));
- } else {
+ } else if (appView.options().keepInnerClassStructure()) {
reporter.warning(
new StringDiagnostic(
"Should have retained InnerClasses attribute of " + type + ".",
@@ -275,7 +275,7 @@
}
} else {
// Did not find the class - keep the inner class name as is.
- // TODO(110085899): Warn about missing classes in signatures?
+ // TODO(b/110085899): Warn about missing classes in signatures?
renamedSignature.append(name);
}
return type;
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 509a832..fd8742d 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -834,6 +834,21 @@
return false;
}
+ public boolean isInstanceFieldWrittenOnlyInInstanceInitializers(DexEncodedField field) {
+ assert checkIfObsolete();
+ assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
+ if (isPinned(field.field)) {
+ return false;
+ }
+ FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
+ if (fieldAccessInfo == null || !fieldAccessInfo.isWritten()) {
+ return false;
+ }
+ DexType holder = field.field.holder;
+ return fieldAccessInfo.isWrittenOnlyInMethodSatisfying(
+ method -> method.isInstanceInitializer() && method.method.holder == holder);
+ }
+
public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexEncodedField field) {
assert checkIfObsolete();
assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
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 a7bb64b..4dd45b3 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedField;
@@ -54,6 +55,7 @@
import com.android.tools.r8.graph.FieldAccessInfoImpl;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.graph.ProgramMethod;
@@ -95,6 +97,7 @@
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.WorkList;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
@@ -290,8 +293,11 @@
private final Set<DexEncodedMethod> pendingReflectiveUses = Sets.newLinkedHashSet();
/** A cache for DexMethod that have been marked reachable. */
+ private final Map<DexProgramClass, Set<DexEncodedMethod>> reachableVirtualResolutions =
+ new IdentityHashMap<>();
+
private final Map<DexMethod, MarkedResolutionTarget> virtualTargetsMarkedAsReachable =
- Maps.newIdentityHashMap();
+ new IdentityHashMap<>();
/**
* A set of references we have reported missing to dedupe warnings.
@@ -415,22 +421,24 @@
return getProgramClassOrNull(type) != null;
}
- private DexProgramClass getProgramClassOrNull(DexType type) {
+ private DexClass definitionFor(DexType type) {
DexClass clazz = appView.definitionFor(type);
- if (clazz != null) {
- if (clazz.isProgramClass()) {
- return clazz.asProgramClass();
- }
- if (liveNonProgramTypes.add(clazz) && clazz.isLibraryClass()) {
- // TODO(b/149201735): This likely needs to apply to classpath too.
- ensureMethodsContinueToWidenAccess(clazz);
- // TODO(b/149201158): This should apply to classpath too (likely even hard fail).
- warnIfLibraryTypeInheritsFromProgramType(clazz.asLibraryClass());
- }
- } else {
+ if (clazz == null) {
reportMissingClass(type);
+ return null;
}
- return null;
+ if (liveNonProgramTypes.add(clazz) && clazz.isLibraryClass()) {
+ // TODO(b/149201735): This likely needs to apply to classpath too.
+ ensureMethodsContinueToWidenAccess(clazz);
+ // Only libraries must not derive program. Classpath classes can, assuming correct keep rules.
+ warnIfLibraryTypeInheritsFromProgramType(clazz.asLibraryClass());
+ }
+ return clazz;
+ }
+
+ private DexProgramClass getProgramClassOrNull(DexType type) {
+ DexClass clazz = definitionFor(type);
+ return clazz != null && clazz.isProgramClass() ? clazz.asProgramClass() : null;
}
private void warnIfLibraryTypeInheritsFromProgramType(DexLibraryClass clazz) {
@@ -725,6 +733,7 @@
// we have to look at the interface chain and mark default methods as reachable, not taking
// the shadowing of other interface chains into account.
// See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3
+ // TODO(b/148271337): Support lookupVirtualDispatchTarget(CallSite) and replace this.
ScopedDexMethodSet seen = new ScopedDexMethodSet();
for (DexType iface : descriptor.interfaces) {
DexProgramClass ifaceClazz = getProgramClassOrNull(iface);
@@ -1333,6 +1342,8 @@
}
}
+ // TODO(b/149729626): Consider marking types with a dependent instance constructor as being
+ // instantiated.
rootSet.forEachDependentStaticMember(holder, appView, this::enqueueDependentItem);
compatEnqueueHolderIfDependentNonStaticMember(
holder, rootSet.getDependentKeepClassCompatRule(holder.getType()));
@@ -1659,7 +1670,7 @@
markDirectAndIndirectClassInitializersAsLive(clazz);
// For all methods of the class, if we have seen a call, mark the method live.
// We only do this for virtual calls, as the other ones will be done directly.
- transitionMethodsForInstantiatedClass(clazz);
+ transitionMethodsForInstantiatedClass(clazz, keepReason);
// For all instance fields visible from the class, mark them live if we have seen a read.
transitionFieldsForInstantiatedClass(clazz);
// Add all dependent instance members to the workqueue.
@@ -1685,95 +1696,119 @@
}
/**
- * Marks all methods live that can be reached by calls previously seen.
+ * Marks all methods live that are overrides of reachable methods for a given class.
*
- * <p>This should only be invoked if the given type newly becomes instantiated. In essence, this
- * method replays all the invokes we have seen so far that could apply to this type and marks the
- * corresponding methods live.
- *
- * <p>Only methods that are visible in this type are considered. That is, only those methods that
- * are either defined directly on this type or that are defined on a supertype but are not
- * shadowed by another inherited method. Furthermore, default methods from implemented interfaces
- * that are not otherwise shadowed are considered, too.
- *
- * <p>Finally all methods on library types that resolve starting at the instantiated type are
- * marked live.
+ * <p>Only reachable methods in the hierarchy of the given class and above are considered, and
+ * only the lowest such reachable target (ie, mirroring resolution). All library and classpath
+ * methods are considered reachable.
*/
- private void transitionMethodsForInstantiatedClass(DexProgramClass instantiatedClass) {
+ private void transitionMethodsForInstantiatedClass(
+ DexProgramClass instantiatedClass, KeepReason instantiationReason) {
+ assert !instantiatedClass.isAnnotation();
+ assert !instantiatedClass.isInterface();
ScopedDexMethodSet seen = new ScopedDexMethodSet();
- Set<DexType> interfaces = Sets.newIdentityHashSet();
- DexProgramClass current = instantiatedClass;
- do {
- // We only have to look at virtual methods here, as only those can actually be executed at
- // runtime. Illegal dispatch situations and the corresponding exceptions are already handled
- // by the reachability logic.
- transitionReachableVirtualMethods(current, seen);
- Collections.addAll(interfaces, current.interfaces.values);
- current = getProgramClassOrNull(current.superType);
- } while (current != null && !objectAllocationInfoCollection.isInstantiatedDirectly(current));
-
- // The set now contains all virtual methods on the type and its supertype that are reachable.
- // In a second step, we now look at interfaces. We have to do this in this order due to JVM
- // semantics for default methods. A default method is only reachable if it is not overridden in
- // any superclass. Also, it is not defined which default method is chosen if multiple
- // interfaces define the same default method. Hence, for every interface (direct or indirect),
- // we have to look at the interface chain and mark default methods as reachable, not taking
- // the shadowing of other interface chains into account.
- // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3
- for (DexType iface : interfaces) {
- DexClass clazz = appView.definitionFor(iface);
- if (clazz == null) {
- reportMissingClass(iface);
- // TODO(herhut): In essence, our subtyping chain is broken here. Handle that case better.
- break;
- }
- transitionDefaultMethodsForInstantiatedClass(iface, seen);
- }
-
- // When tracing the main-dex content, library roots must be specified, thus there are no
- // implicit edges from library methods.
- if (getMode().isTracingMainDex()) {
- return;
- }
-
- // When a type becomes live, all library methods on that type become live too.
- // This is done by searching the library supertypes and then resolving each method defined by
- // such a library type from the point of the instantiated type. If the resolved targets are in
- // the program, i.e., the instantiated type has a method overidding a library method, then the
- // program method is live.
- Deque<DexClass> librarySearchItems = new ArrayDeque<>();
- librarySearchItems.add(instantiatedClass);
- while (!librarySearchItems.isEmpty()) {
- DexClass clazz = librarySearchItems.pop();
- if (clazz.isNotProgramClass()) {
- markLibraryAndClasspathMethodOverridesAsLive(clazz, instantiatedClass);
- }
- if (clazz.superType != null) {
- DexClass superClass = appView.definitionFor(clazz.superType);
- if (superClass != null) {
- librarySearchItems.add(superClass);
+ WorkList<DexType> worklist = WorkList.newIdentityWorkList();
+ // First we lookup and mark all targets on the instantiated class for each reachable method in
+ // the super chain (inclusive).
+ {
+ DexClass clazz = instantiatedClass;
+ while (clazz != null) {
+ if (clazz.isProgramClass()) {
+ markProgramMethodOverridesAsLive(
+ instantiatedClass, clazz.asProgramClass(), seen, instantiationReason);
+ } else {
+ markLibraryAndClasspathMethodOverridesAsLive(instantiatedClass, clazz);
}
+ worklist.addIfNotSeen(Arrays.asList(clazz.interfaces.values));
+ clazz = clazz.superType != null ? definitionFor(clazz.superType) : null;
}
- for (DexType iface : clazz.interfaces.values) {
- DexClass ifaceClass = appView.definitionFor(iface);
- if (ifaceClass != null) {
- librarySearchItems.add(ifaceClass);
- }
+ }
+ // The targets for methods on the type and its supertype that are reachable are now marked.
+ // In a second step, we look at interfaces. We order the search this way such that a
+ // method reachable on a class takes precedence when reporting edges. That order mirrors JVM
+ // resolution/dispatch.
+ while (worklist.hasNext()) {
+ DexType type = worklist.next();
+ DexClass iface = definitionFor(type);
+ if (iface == null) {
+ continue;
+ }
+ assert iface.superType == appInfo.dexItemFactory().objectType;
+ if (iface.isNotProgramClass()) {
+ markLibraryAndClasspathMethodOverridesAsLive(instantiatedClass, iface);
+ } else {
+ markProgramMethodOverridesAsLive(
+ instantiatedClass, iface.asProgramClass(), seen, instantiationReason);
+ }
+ worklist.addIfNotSeen(Arrays.asList(iface.interfaces.values));
+ }
+ }
+
+ private Set<DexEncodedMethod> getReachableVirtualResolutions(DexProgramClass clazz) {
+ return reachableVirtualResolutions.getOrDefault(clazz, Collections.emptySet());
+ }
+
+ private void markProgramMethodOverridesAsLive(
+ DexProgramClass instantiatedClass,
+ DexProgramClass superClass,
+ ScopedDexMethodSet seenMethods,
+ KeepReason instantiationReason) {
+ for (DexEncodedMethod resolution : getReachableVirtualResolutions(superClass)) {
+ if (seenMethods.addMethod(resolution)) {
+ markLiveOverrides(instantiatedClass, superClass, resolution, instantiationReason);
}
}
}
+ private void markLiveOverrides(
+ DexProgramClass instantiatedClass,
+ DexProgramClass reachableHolder,
+ DexEncodedMethod reachableMethod,
+ KeepReason instantiationReason) {
+ assert reachableHolder.type == reachableMethod.method.holder;
+ // The validity of the reachable method is checked at the point it becomes "reachable" and is
+ // resolved. If the method is private, then the dispatch is not "virtual" and the method is
+ // simply marked live on its holder.
+ if (reachableMethod.isPrivateMethod()) {
+ markVirtualMethodAsLive(
+ reachableHolder,
+ reachableMethod,
+ graphReporter.reportReachableMethodAsLive(
+ reachableMethod.method, new ProgramMethod(reachableHolder, reachableMethod)));
+ return;
+ }
+ // Otherwise, we set the initial holder type to be the holder of the reachable method, which
+ // ensures that access will be generally valid.
+ SingleResolutionResult result =
+ new SingleResolutionResult(reachableHolder, reachableHolder, reachableMethod);
+ DexClassAndMethod lookup = result.lookupVirtualDispatchTarget(instantiatedClass, appView);
+ if (lookup == null || !lookup.isProgramMethod() || lookup.getMethod().isAbstract()) {
+ return;
+ }
+ ProgramMethod method = lookup.asProgramMethod();
+ markTypeAsLive(method.getHolder().type, instantiationReason);
+ markVirtualMethodAsLive(
+ method.getHolder(),
+ method.getMethod(),
+ graphReporter.reportReachableMethodAsLive(reachableMethod.method, method));
+ }
+
private void markLibraryAndClasspathMethodOverridesAsLive(
- DexClass libraryClass, DexProgramClass instantiatedClass) {
+ DexProgramClass instantiatedClass, DexClass libraryClass) {
assert libraryClass.isNotProgramClass();
assert !instantiatedClass.isInterface() || instantiatedClass.isAnnotation();
+ if (mode.isTracingMainDex()) {
+ // Library roots must be specified for tracing of library methods. For classpath the expected
+ // use case is that the classes will be classloaded, thus they should have no bearing on the
+ // content of the main dex file.
+ return;
+ }
for (DexEncodedMethod method : libraryClass.virtualMethods()) {
- // Note: it may be worthwhile to add a resolution cache here. If so, it must still ensure
- // that all library override edges are reported to the kept-graph consumer.
- ResolutionResult firstResolution =
- appView.appInfo().resolveMethod(instantiatedClass, method.method);
- markResolutionAsLive(libraryClass, firstResolution);
- markOverridesAsLibraryMethodOverrides(method.method, instantiatedClass);
+ assert !method.isPrivateMethod();
+ // Note: It would be reasonable to not process methods already seen during the marking of
+ // program usages, but that would cause the methods to not be marked as library overrides.
+ markLibraryOrClasspathOverrideLive(
+ instantiatedClass, libraryClass, appInfo.resolveMethod(libraryClass, method.method));
// Due to API conversion, some overrides can be hidden since they will be rewritten. See
// class comment of DesugaredLibraryAPIConverter and vivifiedType logic.
@@ -1785,47 +1820,49 @@
DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
method.method, method.method.holder, appView);
assert methodToResolve != method.method;
- ResolutionResult secondResolution =
- appView.appInfo().resolveMethod(instantiatedClass, methodToResolve);
- markResolutionAsLive(libraryClass, secondResolution);
- markOverridesAsLibraryMethodOverrides(methodToResolve, instantiatedClass);
+ markLibraryOrClasspathOverrideLive(
+ instantiatedClass,
+ libraryClass,
+ appInfo.resolveMethod(instantiatedClass, methodToResolve));
}
-
}
}
- private void markResolutionAsLive(DexClass libraryClass, ResolutionResult resolution) {
- if (resolution.isVirtualTarget()) {
- DexEncodedMethod target = resolution.getSingleTarget();
- DexProgramClass targetHolder = getProgramClassOrNull(target.method.holder);
- if (targetHolder != null
- && shouldMarkLibraryMethodOverrideAsReachable(targetHolder, target)) {
- markVirtualMethodAsLive(
- targetHolder, target, KeepReason.isLibraryMethod(targetHolder, libraryClass.type));
- }
+ private void markLibraryOrClasspathOverrideLive(
+ DexProgramClass instantiatedClass,
+ DexClass libraryOrClasspathClass,
+ ResolutionResult resolution) {
+ DexClassAndMethod lookup = resolution.lookupVirtualDispatchTarget(instantiatedClass, appView);
+ if (lookup == null || !lookup.isProgramMethod() || lookup.getMethod().isAbstract()) {
+ return;
}
+ DexProgramClass clazz = lookup.asProgramMethod().getHolder();
+ DexEncodedMethod target = lookup.getMethod();
+ if (shouldMarkLibraryMethodOverrideAsReachable(clazz, target)) {
+ markVirtualMethodAsLive(
+ clazz, target, KeepReason.isLibraryMethod(clazz, libraryOrClasspathClass.type));
+ }
+ markOverridesAsLibraryMethodOverrides(instantiatedClass, target.method);
}
private void markOverridesAsLibraryMethodOverrides(
- DexMethod libraryMethod, DexProgramClass instantiatedClass) {
- Set<DexProgramClass> visited = SetUtils.newIdentityHashSet(instantiatedClass);
- Deque<DexProgramClass> worklist = DequeUtils.newArrayDeque(instantiatedClass);
- while (!worklist.isEmpty()) {
- DexProgramClass clazz = worklist.removeFirst();
- assert visited.contains(clazz);
- DexEncodedMethod libraryMethodOverride = clazz.lookupVirtualMethod(libraryMethod);
- if (libraryMethodOverride != null) {
- if (libraryMethodOverride.isLibraryMethodOverride().isTrue()) {
+ DexProgramClass instantiatedClass, DexMethod libraryMethodOverride) {
+ WorkList<DexType> worklist = WorkList.newIdentityWorkList();
+ worklist.addIfNotSeen(instantiatedClass.type);
+ while (worklist.hasNext()) {
+ DexType type = worklist.next();
+ DexProgramClass clazz = getProgramClassOrNull(type);
+ if (clazz == null) {
+ continue;
+ }
+ DexEncodedMethod override = clazz.lookupVirtualMethod(libraryMethodOverride);
+ if (override != null) {
+ if (override.isLibraryMethodOverride().isTrue()) {
continue;
}
- libraryMethodOverride.setLibraryMethodOverride(OptionalBool.TRUE);
+ override.setLibraryMethodOverride(OptionalBool.TRUE);
}
- for (DexType superType : clazz.allImmediateSupertypes()) {
- DexProgramClass superClass = getProgramClassOrNull(superType);
- if (superClass != null && visited.add(superClass)) {
- worklist.add(superClass);
- }
- }
+ clazz.forEachImmediateSupertype(worklist::addIfNotSeen);
}
}
@@ -2171,6 +2208,10 @@
// each possible target edge below.
assert resolution.holder.isProgramClass();
+ reachableVirtualResolutions
+ .computeIfAbsent(resolution.holder.asProgramClass(), k -> Sets.newIdentityHashSet())
+ .add(resolution.method);
+
assert interfaceInvoke == holder.isInterface();
DexProgramClass context = contextOrNull == null ? null : contextOrNull.getHolder();
LookupResult lookupResult =
@@ -2180,11 +2221,12 @@
if (!lookupResult.isLookupResultSuccess()) {
return;
}
- for (DexEncodedMethod encodedPossibleTarget :
- lookupResult.asLookupResultSuccess().getMethodTargets()) {
+ LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
+ for (DexEncodedMethod encodedPossibleTarget : lookupResultSuccess.getMethodTargets()) {
if (encodedPossibleTarget.isAbstract()) {
continue;
}
+ // TODO(b/139464956): Replace this downwards search once targets are found for live types.
markPossibleTargetsAsReachable(resolution, encodedPossibleTarget);
}
}
@@ -2196,20 +2238,15 @@
assert !encodedPossibleTarget.isAbstract();
DexMethod possibleTarget = encodedPossibleTarget.method;
DexProgramClass clazz = getProgramClassOrNull(possibleTarget.holder);
- if (clazz == null) {
- return;
- }
+ // Add the method to the reachable set to ensure targets to lambdas are still identified.
ReachableVirtualMethodsSet reachable =
reachableVirtualMethods.computeIfAbsent(clazz, ignore -> new ReachableVirtualMethodsSet());
if (!reachable.add(encodedPossibleTarget, reason)) {
return;
}
- // If the holder type is instantiated, the method is live. Otherwise check whether we find
- // a subtype that does not shadow this methods but is instantiated.
- // Note that library classes are always considered instantiated, as we do not know where
- // they are instantiated.
- if (!isInstantiatedOrHasInstantiatedSubtype(clazz)) {
+ // If the holder type is uninstantiated (directly or indirectly) the method is not live yet.
+ if (clazz == null || !isInstantiatedOrHasInstantiatedSubtype(clazz)) {
return;
}
@@ -2218,7 +2255,8 @@
markVirtualMethodAsLive(
clazz,
encodedPossibleTarget,
- graphReporter.reportReachableMethodAsLive(encodedPossibleTarget, reason));
+ graphReporter.reportReachableMethodAsLive(
+ reason.method.method, new ProgramMethod(clazz, encodedPossibleTarget)));
} else {
Deque<DexType> worklist =
new ArrayDeque<>(appInfo.allImmediateSubtypes(possibleTarget.holder));
@@ -2400,7 +2438,11 @@
finalizeLibraryMethodOverrideInformation();
analyses.forEach(EnqueuerAnalysis::done);
assert verifyKeptGraph();
- return createAppInfo(appInfo);
+ AppInfoWithLiveness appInfoWithLiveness = createAppInfo(appInfo);
+ if (options.testing.enqueuerInspector != null) {
+ options.testing.enqueuerInspector.accept(appInfoWithLiveness, mode);
+ }
+ return appInfoWithLiveness;
}
private void finalizeLibraryMethodOverrideInformation() {
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index c172b52..37be4b3 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.references.TypeReference;
@@ -248,6 +249,17 @@
}
public KeepReasonWitness reportReachableMethodAsLive(
+ DexMethod overriddenMethod, ProgramMethod derivedMethod) {
+ if (keptGraphConsumer != null && overriddenMethod != derivedMethod.getMethod().method) {
+ return reportEdge(
+ getMethodGraphNode(overriddenMethod),
+ getMethodGraphNode(derivedMethod.getMethod().method),
+ EdgeKind.OverridingMethod);
+ }
+ return KeepReasonWitness.INSTANCE;
+ }
+
+ public KeepReasonWitness reportReachableMethodAsLive(
DexEncodedMethod encodedMethod, MarkedResolutionTarget reason) {
if (keptGraphConsumer != null) {
return reportEdge(
diff --git a/src/main/java/com/android/tools/r8/utils/IROrdering.java b/src/main/java/com/android/tools/r8/utils/IROrdering.java
index 4b4ded3..db97cfa 100644
--- a/src/main/java/com/android/tools/r8/utils/IROrdering.java
+++ b/src/main/java/com/android/tools/r8/utils/IROrdering.java
@@ -8,7 +8,9 @@
import com.google.common.collect.Lists;
import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Set;
public interface IROrdering {
@@ -16,6 +18,8 @@
Collection<DexEncodedMethod> order(Collection<DexEncodedMethod> methods);
+ Set<DexEncodedMethod> order(Set<DexEncodedMethod> methods);
+
class IdentityIROrdering implements IROrdering {
private static final IdentityIROrdering INSTANCE = new IdentityIROrdering();
@@ -35,6 +39,11 @@
public Collection<DexEncodedMethod> order(Collection<DexEncodedMethod> methods) {
return methods;
}
+
+ @Override
+ public Set<DexEncodedMethod> order(Set<DexEncodedMethod> methods) {
+ return methods;
+ }
}
class NondeterministicIROrdering implements IROrdering {
@@ -58,5 +67,10 @@
public List<DexEncodedMethod> order(Collection<DexEncodedMethod> methods) {
return order((Iterable<DexEncodedMethod>) methods);
}
+
+ @Override
+ public Set<DexEncodedMethod> order(Set<DexEncodedMethod> methods) {
+ return new LinkedHashSet<>(order((Iterable<DexEncodedMethod>) methods));
+ }
}
}
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 c6330b1..232b54a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -37,6 +37,8 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.utils.IROrdering.IdentityIROrdering;
@@ -61,6 +63,7 @@
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
+import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import org.objectweb.asm.Opcodes;
@@ -311,6 +314,9 @@
if (Version.isDevelopmentVersion()) {
marker.setSha1(VersionProperties.INSTANCE.getSha());
}
+ if (tool == Tool.R8) {
+ marker.setR8Mode(forceProguardCompatibility ? "compatibility" : "full");
+ }
return marker;
}
@@ -450,6 +456,11 @@
return enableMinification;
}
+ public boolean keepInnerClassStructure() {
+ return getProguardConfiguration().getKeepAttributes().signature
+ || getProguardConfiguration().getKeepAttributes().innerClasses;
+ }
+
public boolean printCfg = false;
public String printCfgFile;
public boolean ignoreMissingClasses = false;
@@ -989,6 +1000,8 @@
? NondeterministicIROrdering.getInstance()
: IdentityIROrdering.getInstance();
+ public BiConsumer<AppInfoWithLiveness, Enqueuer.Mode> enqueuerInspector = null;
+
public Consumer<Deque<Collection<DexEncodedMethod>>> waveModifier = waves -> {};
/**
@@ -1094,6 +1107,15 @@
enablePropagationOfConstantsAtCallSites = true;
}
+ public boolean isCallSiteOptimizationEnabled() {
+ return enablePropagationOfConstantsAtCallSites || enablePropagationOfDynamicTypesAtCallSites;
+ }
+
+ public void disableCallSiteOptimization() {
+ enablePropagationOfConstantsAtCallSites = false;
+ enablePropagationOfDynamicTypesAtCallSites = false;
+ }
+
private boolean hasMinApi(AndroidApiLevel level) {
assert isGeneratingDex();
return minApiLevel >= level.getLevel();
@@ -1139,15 +1161,6 @@
return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.N);
}
- public boolean isCallSiteOptimizationEnabled() {
- return enablePropagationOfConstantsAtCallSites || enablePropagationOfDynamicTypesAtCallSites;
- }
-
- public void disableCallSiteOptimization() {
- enablePropagationOfConstantsAtCallSites = false;
- enablePropagationOfDynamicTypesAtCallSites = false;
- }
-
public boolean canUseDexPcAsDebugInformation() {
// TODO(b/37830524): Enable for min-api 26 (OREO) and above.
return enablePcDebugInfoOutput;
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 68f1749..6bce9c1 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexDebugEvent;
import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
@@ -35,7 +36,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser;
import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Result;
@@ -309,7 +309,7 @@
// they may be bridges for interface methods with covariant return types.
sortMethods(methods);
// TODO(b/149360203): Reenable assert.
- // assert verifyMethodsAreKeptDirectlyOrIndirectly(appView, methods);
+ assert true || verifyMethodsAreKeptDirectlyOrIndirectly(appView, methods);
}
boolean identityMapping =
@@ -457,15 +457,14 @@
}
// We use the same name for interface names even if it has different types.
DexProgramClass clazz = appView.definitionForProgramType(method.method.holder);
- ResolutionResult resolutionResult =
- appView.appInfo().resolveMaximallySpecificMethods(clazz, method.method);
- if (resolutionResult.isFailedResolution()) {
+ DexClassAndMethod lookupResult =
+ appView.appInfo().lookupMaximallySpecificMethod(clazz, method.method);
+ if (lookupResult == null) {
// We cannot rename methods we cannot look up.
continue;
}
String errorString = method.method.qualifiedName() + " is not kept but is overloaded";
- assert resolutionResult.isSingleResolution() : errorString;
- assert resolutionResult.asSingleResolution().getResolvedHolder().isInterface() : errorString;
+ assert lookupResult.getHolder().isInterface() : errorString;
assert originalName == null || originalName.equals(method.method.name) : errorString;
originalName = method.method.name;
}
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index b631262..835c528 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -15,7 +15,15 @@
private final Deque<T> workingList = new ArrayDeque<>();
private final Set<T> seen;
- public WorkList(EqualityTest equalityTest) {
+ public static <T> WorkList<T> newIdentityWorkList() {
+ return new WorkList<T>(EqualityTest.IDENTITY);
+ }
+
+ public WorkList() {
+ this(EqualityTest.HASH);
+ }
+
+ private WorkList(EqualityTest equalityTest) {
if (equalityTest == EqualityTest.HASH) {
seen = new HashSet<>();
} else {
diff --git a/src/test/java/com/android/tools/r8/NeverInline.java b/src/test/java/com/android/tools/r8/NeverInline.java
index 082d446..ca0c2a4 100644
--- a/src/test/java/com/android/tools/r8/NeverInline.java
+++ b/src/test/java/com/android/tools/r8/NeverInline.java
@@ -6,5 +6,5 @@
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
-@Target({ElementType.METHOD})
+@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface NeverInline {}
diff --git a/src/test/java/com/android/tools/r8/R8ModeMarkerTest.java b/src/test/java/com/android/tools/r8/R8ModeMarkerTest.java
new file mode 100644
index 0000000..c830c8a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8ModeMarkerTest.java
@@ -0,0 +1,126 @@
+// 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;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.dex.Marker;
+import java.util.Collection;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class R8ModeMarkerTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public R8ModeMarkerTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ interface GetMarker {
+ Marker getMarker();
+ }
+
+ static class ExtractDexMarkerConsumer extends DexIndexedConsumer.ForwardingConsumer
+ implements GetMarker {
+ private Marker marker;
+
+ ExtractDexMarkerConsumer() {
+ super(null);
+ }
+
+ @Override
+ public void accept(
+ int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+ try {
+ Collection<Marker> markers =
+ ExtractMarker.extractMarkerFromDexProgramData(data.copyByteData());
+ assertEquals(1, markers.size());
+ marker = markers.iterator().next();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Marker getMarker() {
+ return marker;
+ }
+ }
+
+ static class ExtractClassFileMarkerConsumer extends ClassFileConsumer.ForwardingConsumer
+ implements GetMarker {
+ private Marker marker;
+
+ ExtractClassFileMarkerConsumer() {
+ super(null);
+ }
+
+ @Override
+ public void accept(ByteDataView data, String descriptors, DiagnosticsHandler handler) {
+ try {
+ Collection<Marker> markers =
+ ExtractMarker.extractMarkerFromClassProgramData(data.copyByteData());
+ assertEquals(1, markers.size());
+ marker = markers.iterator().next();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Marker getMarker() {
+ return marker;
+ }
+ }
+
+ @Test
+ public void testFullMode() throws Exception {
+ ProgramConsumer consumer =
+ parameters.getBackend() == Backend.DEX
+ ? new ExtractDexMarkerConsumer()
+ : new ExtractClassFileMarkerConsumer();
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .setProgramConsumer(consumer)
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello, world");
+ assertEquals("full", ((GetMarker) consumer).getMarker().getR8Mode());
+ }
+
+ @Test
+ public void testCompatMode() throws Exception {
+ ProgramConsumer consumer =
+ parameters.getBackend() == Backend.DEX
+ ? new ExtractDexMarkerConsumer()
+ : new ExtractClassFileMarkerConsumer();
+ testForR8Compat(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .setProgramConsumer(consumer)
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello, world");
+ assertEquals("compatibility", ((GetMarker) consumer).getMarker().getR8Mode());
+ }
+
+ static class TestClass {
+ public static void main(String[] args) {
+ System.out.println("Hello, world");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index d3bc8da..5cc8194 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -447,7 +447,7 @@
PreloadedClassFileProvider.Builder libraryBuilder = PreloadedClassFileProvider.builder();
for (Class<?> clazz : libraryClasses) {
Path file = ToolHelper.getClassFileForTestClass(clazz);
- libraryBuilder.addResource(DescriptorUtils.javaTypeToDescriptor(clazz.getCanonicalName()),
+ libraryBuilder.addResource(DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName()),
Files.readAllBytes(file));
}
builder.addLibraryResourceProvider(libraryBuilder.build());
@@ -1432,4 +1432,16 @@
throws IOException, CompilationFailedException {
return buildOnDexRuntime(parameters, Arrays.asList(paths));
}
+
+ public static String binaryName(Class<?> clazz) {
+ return DescriptorUtils.getBinaryNameFromJavaType(typeName(clazz));
+ }
+
+ public static String descriptor(Class<?> clazz) {
+ return DescriptorUtils.javaTypeToDescriptor(typeName(clazz));
+ }
+
+ public static String typeName(Class<?> clazz) {
+ return clazz.getTypeName();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 13afb51..fab7d11 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -206,7 +206,11 @@
}
public T addKeepAttributeLineNumberTable() {
- return addKeepRules("-keepattributes LineNumberTable");
+ return addKeepAttributes("LineNumberTable");
+ }
+
+ public T addKeepRuntimeVisibleAnnotations() {
+ return addKeepAttributes("RuntimeVisibleAnnotations");
}
public T addKeepAllAttributes() {
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
new file mode 100644
index 0000000..862bb79
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -0,0 +1,150 @@
+// 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.cfmethodgeneration;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.cf.CfCodePrinter;
+import com.android.tools.r8.graph.ClassKind;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.JarApplicationReader;
+import com.android.tools.r8.graph.JarClassFileReader;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Calendar;
+import java.util.List;
+
+public abstract class MethodGenerationBase extends TestBase {
+
+ private static final Path GOOGLE_FORMAT_DIR =
+ Paths.get(ToolHelper.THIRD_PARTY_DIR, "google-java-format");
+ private static final Path GOOGLE_FORMAT_JAR =
+ GOOGLE_FORMAT_DIR.resolve("google-java-format-1.7-all-deps.jar");
+
+ protected final DexItemFactory factory = new DexItemFactory();
+
+ protected static String getJavaExecutable() {
+ return ToolHelper.getSystemJavaExecutable();
+ }
+
+ protected abstract DexType getGeneratedType();
+
+ protected abstract List<Class<?>> getMethodTemplateClasses();
+
+ private String getHeaderString(Class<? extends MethodGenerationBase> generationClass) {
+ int year = Calendar.getInstance().get(Calendar.YEAR);
+ String simpleName = generationClass.getSimpleName();
+ return StringUtils.lines(
+ "// Copyright (c) " + year + ", 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.",
+ "",
+ "// ***********************************************************************************",
+ "// GENERATED FILE. DO NOT EDIT! See " + simpleName + ".java.",
+ "// ***********************************************************************************",
+ "",
+ "package " + getGeneratedClassPackageName() + ";");
+ }
+
+ protected Path getGeneratedFile() {
+ return Paths.get(ToolHelper.SOURCE_DIR, getGeneratedType().getInternalName() + ".java");
+ }
+
+ private String getGeneratedClassName() {
+ return getGeneratedType().getName();
+ }
+
+ private String getGeneratedClassPackageName() {
+ return getGeneratedType().getPackageName();
+ }
+
+ // Running this method will regenerate / overwrite the content of the generated class.
+ protected void generateMethodsAndWriteThemToFile() throws IOException {
+ FileUtils.writeToFile(getGeneratedFile(), null, generateMethods().getBytes());
+ }
+
+ // Running this method generate the content of the generated class but does not overwrite it.
+ protected String generateMethods() throws IOException {
+ CfCodePrinter codePrinter = new CfCodePrinter();
+
+ File tempFile = File.createTempFile("output-", ".java");
+
+ readMethodTemplatesInto(codePrinter);
+ generateRawOutput(codePrinter, tempFile.toPath());
+ String result = formatRawOutput(tempFile.toPath());
+
+ tempFile.deleteOnExit();
+ return result;
+ }
+
+ private void readMethodTemplatesInto(CfCodePrinter codePrinter) throws IOException {
+ InternalOptions options = new InternalOptions();
+ JarClassFileReader reader =
+ new JarClassFileReader(
+ new JarApplicationReader(options),
+ clazz -> {
+ for (DexEncodedMethod method : clazz.allMethodsSorted()) {
+ if (method.isInitializer()) {
+ continue;
+ }
+ String methodName =
+ method.method.holder.getName() + "_" + method.method.name.toString();
+ codePrinter.visitMethod(methodName, method.getCode().asCfCode());
+ }
+ });
+ for (Class<?> clazz : getMethodTemplateClasses()) {
+ try (InputStream stream = Files.newInputStream(ToolHelper.getClassFileForTestClass(clazz))) {
+ reader.read(Origin.unknown(), ClassKind.PROGRAM, stream);
+ }
+ }
+ }
+
+ private void generateRawOutput(CfCodePrinter codePrinter, Path tempFile) throws IOException {
+ try (PrintStream printer = new PrintStream(Files.newOutputStream(tempFile))) {
+ printer.print(getHeaderString(this.getClass()));
+ codePrinter.getImports().forEach(i -> printer.println("import " + i + ";"));
+ printer.println("public final class " + getGeneratedClassName() + " {\n");
+ codePrinter.getMethods().forEach(printer::println);
+ printer.println("}");
+ }
+ }
+
+ private String formatRawOutput(Path tempFile) throws IOException {
+ // Apply google format.
+ ProcessBuilder builder =
+ new ProcessBuilder(
+ ImmutableList.of(
+ getJavaExecutable(),
+ "-jar",
+ GOOGLE_FORMAT_JAR.toString(),
+ tempFile.toAbsolutePath().toString()));
+ String commandString = String.join(" ", builder.command());
+ System.out.println(commandString);
+ Process process = builder.start();
+ ProcessResult result = ToolHelper.drainProcessOutputStreams(process, commandString);
+ if (result.exitCode != 0) {
+ throw new IllegalStateException(result.toString());
+ }
+ // Fix line separators.
+ String content = result.stdout;
+ if (!StringUtils.LINE_SEPARATOR.equals("\n")) {
+ return content.replace(StringUtils.LINE_SEPARATOR, "\n");
+ }
+ return content;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java
index 8dcad81..dafc7db 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java
@@ -4,10 +4,15 @@
package com.android.tools.r8.classmerging;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -50,11 +55,16 @@
.addProgramClasses(Base.class, B.class, Main.class)
.addProgramClassFileData(getAWithRewrittenInvokeSpecialToBase())
.addKeepMainRule(Main.class)
- .addKeepClassRules(Base.class, B.class)
.enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableMergeAnnotations()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines(EXPECTED);
+ .assertSuccessWithOutputLines(EXPECTED)
+ .inspect(
+ inspector -> {
+ assertThat(inspector.clazz(A.class), not(isPresent()));
+ });
}
private byte[] getAWithRewrittenInvokeSpecialToBase() throws IOException {
@@ -72,6 +82,7 @@
.transform();
}
+ @NeverMerge
public static class Base {
public void collect() {
@@ -93,8 +104,10 @@
}
}
+ @NeverClassInline
public static class B extends A {
+ @NeverInline
public void bar() {
collect();
}
diff --git a/src/test/java/com/android/tools/r8/desugar/LambdaWithDefaultMethodsTest.java b/src/test/java/com/android/tools/r8/desugar/LambdaWithDefaultMethodsTest.java
new file mode 100644
index 0000000..ce7a552
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/LambdaWithDefaultMethodsTest.java
@@ -0,0 +1,89 @@
+// 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.desugar;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LambdaWithDefaultMethodsTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("A::bar", "I::bar");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public LambdaWithDefaultMethodsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(I.class, A.class, TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, A.class, TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ interface I {
+ void foo();
+
+ default void bar() {
+ System.out.println("I::bar");
+ }
+ }
+
+ static class A implements I {
+
+ @Override
+ public void foo() {
+ System.out.println("A::foo");
+ }
+
+ @Override
+ public void bar() {
+ System.out.println("A::bar");
+ }
+ }
+
+ static class TestClass {
+
+ public static void runDefault(I i) {
+ i.bar();
+ }
+
+ public static I createLambda() {
+ return () -> System.out.println("lambda::foo");
+ }
+
+ public static void main(String[] args) {
+ // Target the default method, causing it to be marked reachable.
+ // This is done directly in main to ensure that it is the first thing hit in tracing.
+ I i = new A();
+ i.bar();
+ // Create a call-site instance that will need to identify the default method as live.
+ // The creation is outlined to ensure that it is not hit before the method is reachable.
+ runDefault(createLambda());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
index 66cdcbc..687a673 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
-import com.android.tools.r8.desugar.desugaredlibrary.conversiontests.MoreFunctionConversionTest.CustomLibClass;
import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingTest.java
similarity index 79%
rename from src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java
rename to src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingTest.java
index a15dc08..b13c250 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingTest.java
@@ -3,9 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.enumunboxing;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.R8TestRunResult;
@@ -17,7 +17,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class ComparisonEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+public class ComparisonEnumUnboxingTest extends EnumUnboxingTestBase {
private static final Class<?>[] INPUTS = new Class<?>[] {NullCheck.class, EnumComparison.class};
@@ -30,7 +30,7 @@
return enumUnboxingTestParameters();
}
- public ComparisonEnumUnboxingAnalysisTest(
+ public ComparisonEnumUnboxingTest(
TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
this.parameters = parameters;
this.enumValueOptimization = enumValueOptimization;
@@ -41,9 +41,10 @@
public void testEnumUnboxing() throws Exception {
R8TestCompileResult compile =
testForR8(parameters.getBackend())
- .addInnerClasses(ComparisonEnumUnboxingAnalysisTest.class)
+ .addInnerClasses(ComparisonEnumUnboxingTest.class)
.addKeepMainRules(INPUTS)
.enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
.addKeepRules(enumKeepRules ? KEEP_ENUM : "")
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
@@ -51,12 +52,8 @@
.compile()
.inspect(
inspector -> {
- assertThat(
- inspector.clazz(NullCheck.class).uniqueMethodWithName("nullCheck"),
- isPresent());
- assertThat(
- inspector.clazz(EnumComparison.class).uniqueMethodWithName("check"),
- isPresent());
+ assertEquals(3, inspector.clazz(NullCheck.class).allMethods().size());
+ assertEquals(2, inspector.clazz(EnumComparison.class).allMethods().size());
});
for (Class<?> input : INPUTS) {
R8TestRunResult run =
@@ -72,6 +69,7 @@
@SuppressWarnings("ConstantConditions")
static class NullCheck {
+ @NeverClassInline
enum MyEnum {
A,
B
@@ -84,6 +82,14 @@
System.out.println(false);
System.out.println(nullCheck(null));
System.out.println(true);
+ System.out.println(onlyNull());
+ System.out.println(true);
+ }
+
+ // This method has no outValue of type MyEnum but still needs to be reprocessed.
+ @NeverInline
+ static boolean onlyNull() {
+ return nullCheck(null);
}
// Do not resolve the == with constants after inlining.
@@ -95,6 +101,7 @@
static class EnumComparison {
+ @NeverClassInline
enum MyEnum {
A,
B
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMethods.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMethods.java
new file mode 100644
index 0000000..e1d51cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMethods.java
@@ -0,0 +1,54 @@
+// 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.enumunboxing;
+
+// This class implements support methods for enum unboxing. The enum unboxing optimization may
+// rewrite any call to an enum method into one of the following methods.
+// The methods Enum#name, Enum#toString and MyEnum#valueOf cannot be implemented here since they
+// are different on each Enum implementation.
+public class EnumUnboxingMethods {
+
+ // An enum is unboxed to ordinal + 1.
+ // For example, enum E {A,B}, is unboxed to null -> 0, A -> 1, B-> 2.
+ // Computing the ordinal of an unboxed enum throws a null pointer exception on 0,
+ // else answers the value - 1.
+ public static int ordinal(int unboxedEnum) {
+ if (unboxedEnum == 0) {
+ throw new NullPointerException();
+ }
+ return unboxedEnum - 1;
+ }
+
+ // The values methods normally reads the $VALUES field, then clones and returns it.
+ // In our case we just create a new instance each time.
+ // Note: This can replace a MyEnum#values() call, but not a MyEnum#$VALUES static get.
+ // numEnums is the number of elements in the enum.
+ public static int[] values(int numEnums) {
+ int[] ints = new int[numEnums];
+ for (int i = 0; i < ints.length; i++) {
+ ints[i] = i + 1;
+ }
+ return ints;
+ }
+
+ // We assume the enum could be unboxed only if both parameters were proven to be of the same
+ // enum types, so we do not check they belong to the same Enum type.
+ // We need the 0 checks for null pointer exception.
+ public static int compareTo(int unboxedEnum1, int unboxedEnum2) {
+ if (unboxedEnum1 == 0 || unboxedEnum2 == 0) {
+ throw new NullPointerException();
+ }
+ // Formula: unboxedEnum1 - 1 - (unboxedEnum2 - 1), simplified as follow:
+ return unboxedEnum1 - unboxedEnum2;
+ }
+
+ // Equals on Enum is implemented using directly ==. The invoke raises a NPE if the
+ // receiver is null, but returns false if the parameter is null.
+ public static boolean equals(int unboxedEnum1, int unboxedEnum2) {
+ if (unboxedEnum1 == 0) {
+ throw new NullPointerException();
+ }
+ return unboxedEnum1 == unboxedEnum2;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
similarity index 86%
rename from src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
rename to src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
index c0eeaf4..4f13c05 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
@@ -7,16 +7,17 @@
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumInstanceFieldMain.EnumInstanceField;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumInterfaceMain.EnumInterface;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumStaticFieldMain.EnumStaticField;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumStaticMethodMain.EnumStaticMethod;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumVirtualMethodMain.EnumVirtualMethod;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumInstanceFieldMain.EnumInstanceField;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumInterfaceMain.EnumInterface;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumStaticFieldMain.EnumStaticField;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumStaticMethodMain.EnumStaticMethod;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumVirtualMethodMain.EnumVirtualMethod;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.util.List;
import org.junit.Test;
@@ -25,7 +26,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class FailingEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+public class FailingEnumUnboxingTest extends EnumUnboxingTestBase {
private static final Class<?>[] FAILURES = {
EnumInterface.class,
@@ -44,7 +45,7 @@
return enumUnboxingTestParameters();
}
- public FailingEnumUnboxingAnalysisTest(
+ public FailingEnumUnboxingTest(
TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
this.parameters = parameters;
this.enumValueOptimization = enumValueOptimization;
@@ -54,7 +55,7 @@
@Test
public void testEnumUnboxingFailure() throws Exception {
R8FullTestBuilder r8FullTestBuilder =
- testForR8(parameters.getBackend()).addInnerClasses(FailingEnumUnboxingAnalysisTest.class);
+ testForR8(parameters.getBackend()).addInnerClasses(FailingEnumUnboxingTest.class);
for (Class<?> failure : FAILURES) {
r8FullTestBuilder.addKeepMainRule(failure.getEnclosingClass());
}
@@ -62,6 +63,7 @@
r8FullTestBuilder
.noTreeShaking() // Disabled to avoid merging Itf into EnumInterface.
.enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
.addKeepRules(enumKeepRules ? KEEP_ENUM : "")
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
@@ -96,6 +98,7 @@
System.out.println(0);
}
+ @NeverClassInline
enum EnumInterface implements Itf {
A,
B,
@@ -119,6 +122,7 @@
System.out.println(0);
}
+ @NeverClassInline
enum EnumStaticField {
A,
B,
@@ -129,6 +133,7 @@
static class EnumInstanceFieldMain {
+ @NeverClassInline
enum EnumInstanceField {
A(10),
B(20),
@@ -150,6 +155,7 @@
static class EnumStaticMethodMain {
+ @NeverClassInline
enum EnumStaticMethod {
A,
B,
@@ -181,6 +187,7 @@
System.out.println(-1);
}
+ @NeverClassInline
enum EnumVirtualMethod {
A,
B,
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
similarity index 92%
rename from src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
rename to src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
index 944c9c3..0391875 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
@@ -7,6 +7,7 @@
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.R8TestRunResult;
@@ -20,7 +21,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class FailingMethodEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+public class FailingMethodEnumUnboxingTest extends EnumUnboxingTestBase {
private static final Class<?>[] FAILURES = {
InstanceFieldPutObject.class,
@@ -39,7 +40,7 @@
return enumUnboxingTestParameters();
}
- public FailingMethodEnumUnboxingAnalysisTest(
+ public FailingMethodEnumUnboxingTest(
TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
this.parameters = parameters;
this.enumValueOptimization = enumValueOptimization;
@@ -50,12 +51,13 @@
public void testEnumUnboxingFailure() throws Exception {
R8TestCompileResult compile =
testForR8(parameters.getBackend())
- .addInnerClasses(FailingMethodEnumUnboxingAnalysisTest.class)
+ .addInnerClasses(FailingMethodEnumUnboxingTest.class)
.addKeepMainRules(FAILURES)
.addKeepRules(enumKeepRules ? KEEP_ENUM : "")
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
.enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::assertEnumsAsExpected);
@@ -90,6 +92,7 @@
static class InstanceFieldPutObject {
+ @NeverClassInline
enum MyEnum {
A,
B,
@@ -114,6 +117,7 @@
static class StaticFieldPutObject {
+ @NeverClassInline
enum MyEnum {
A,
B,
@@ -137,6 +141,7 @@
static class ToString {
+ @NeverClassInline
enum MyEnum {
A,
B,
@@ -152,6 +157,7 @@
static class EnumSetTest {
+ @NeverClassInline
enum MyEnum {
A,
B,
@@ -167,6 +173,7 @@
static class FailingPhi {
+ @NeverClassInline
enum MyEnum {
A,
B,
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
similarity index 90%
rename from src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
rename to src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
index daaf504..129230a 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
@@ -6,6 +6,7 @@
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.R8TestRunResult;
@@ -17,7 +18,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class FieldPutEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+public class FieldPutEnumUnboxingTest extends EnumUnboxingTestBase {
private static final Class<?>[] INPUTS =
new Class<?>[] {InstanceFieldPut.class, StaticFieldPut.class};
@@ -31,7 +32,7 @@
return enumUnboxingTestParameters();
}
- public FieldPutEnumUnboxingAnalysisTest(
+ public FieldPutEnumUnboxingTest(
TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
this.parameters = parameters;
this.enumValueOptimization = enumValueOptimization;
@@ -42,12 +43,13 @@
public void testEnumUnboxing() throws Exception {
R8TestCompileResult compile =
testForR8(parameters.getBackend())
- .addInnerClasses(FieldPutEnumUnboxingAnalysisTest.class)
+ .addInnerClasses(FieldPutEnumUnboxingTest.class)
.addKeepMainRules(INPUTS)
.addKeepRules(enumKeepRules ? KEEP_ENUM : "")
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
.enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.noMinification()
.compile()
@@ -72,6 +74,7 @@
static class InstanceFieldPut {
+ @NeverClassInline
enum MyEnum {
A,
B,
@@ -90,10 +93,12 @@
System.out.println(1);
}
+ @NeverInline
void setA() {
e = MyEnum.A;
}
+ @NeverInline
void setB() {
e = MyEnum.B;
}
@@ -101,6 +106,7 @@
static class StaticFieldPut {
+ @NeverClassInline
enum MyEnum {
A,
B,
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java b/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java
new file mode 100644
index 0000000..706cdc7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java
@@ -0,0 +1,65 @@
+// 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.enumunboxing;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Comparator;
+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 GenerateEnumUnboxingMethods extends MethodGenerationBase {
+
+ private final DexType GENERATED_TYPE =
+ factory.createType("Lcom/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods;");
+ private final List<Class<?>> METHOD_TEMPLATE_CLASSES =
+ ImmutableList.of(EnumUnboxingMethods.class);
+
+ protected final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withCfRuntime(CfVm.JDK9).build();
+ }
+
+ public GenerateEnumUnboxingMethods(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Override
+ protected DexType getGeneratedType() {
+ return GENERATED_TYPE;
+ }
+
+ @Override
+ protected List<Class<?>> getMethodTemplateClasses() {
+ return METHOD_TEMPLATE_CLASSES;
+ }
+
+ @Test
+ public void testEnumUtilityMethodsGenerated() throws Exception {
+ ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
+ sorted.sort(Comparator.comparing(Class::getTypeName));
+ assertEquals("Classes should be listed in sorted order", sorted, getMethodTemplateClasses());
+ assertEquals(
+ FileUtils.readTextFile(getGeneratedFile(), StandardCharsets.UTF_8), generateMethods());
+ }
+
+ public static void main(String[] args) throws Exception {
+ new GenerateEnumUnboxingMethods(null).generateMethodsAndWriteThemToFile();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java
similarity index 90%
rename from src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
rename to src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java
index afd2e6a..91cd718 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.enumunboxing;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
import java.util.List;
@@ -13,7 +14,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class OrdinalEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+public class OrdinalEnumUnboxingTest extends EnumUnboxingTestBase {
private static final Class<?> ENUM_CLASS = MyEnum.class;
@@ -26,7 +27,7 @@
return enumUnboxingTestParameters();
}
- public OrdinalEnumUnboxingAnalysisTest(
+ public OrdinalEnumUnboxingTest(
TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
this.parameters = parameters;
this.enumValueOptimization = enumValueOptimization;
@@ -41,6 +42,7 @@
.addProgramClasses(classToTest, ENUM_CLASS)
.addKeepMainRule(classToTest)
.addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+ .enableNeverClassInliningAnnotations()
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
.setMinApi(parameters.getApiLevel())
@@ -52,6 +54,7 @@
assertLines2By2Correct(run.getStdOut());
}
+ @NeverClassInline
enum MyEnum {
A,
B,
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
similarity index 91%
rename from src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
rename to src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
index b231d32..62ccaf8 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.enumunboxing;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
@@ -14,7 +15,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class PhiEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+public class PhiEnumUnboxingTest extends EnumUnboxingTestBase {
private static final Class<?> ENUM_CLASS = MyEnum.class;
@@ -27,7 +28,7 @@
return enumUnboxingTestParameters();
}
- public PhiEnumUnboxingAnalysisTest(
+ public PhiEnumUnboxingTest(
TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
this.parameters = parameters;
this.enumValueOptimization = enumValueOptimization;
@@ -43,6 +44,7 @@
.addKeepMainRule(classToTest)
.addKeepRules(enumKeepRules ? KEEP_ENUM : "")
.enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
.setMinApi(parameters.getApiLevel())
@@ -54,6 +56,7 @@
assertLines2By2Correct(run.getStdOut());
}
+ @NeverClassInline
enum MyEnum {
A,
B,
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
similarity index 89%
rename from src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
rename to src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
index a40dec0..88dd987 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.enumunboxing;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
@@ -14,7 +15,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class SwitchEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+public class SwitchEnumUnboxingTest extends EnumUnboxingTestBase {
private static final Class<?> ENUM_CLASS = MyEnum.class;
@@ -27,7 +28,7 @@
return enumUnboxingTestParameters();
}
- public SwitchEnumUnboxingAnalysisTest(
+ public SwitchEnumUnboxingTest(
TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
this.parameters = parameters;
this.enumValueOptimization = enumValueOptimization;
@@ -39,10 +40,11 @@
Class<Switch> classToTest = Switch.class;
R8TestRunResult run =
testForR8(parameters.getBackend())
- .addInnerClasses(SwitchEnumUnboxingAnalysisTest.class)
+ .addInnerClasses(SwitchEnumUnboxingTest.class)
.addKeepMainRule(classToTest)
.addKeepRules(enumKeepRules ? KEEP_ENUM : "")
.enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
.setMinApi(parameters.getApiLevel())
@@ -54,6 +56,7 @@
assertLines2By2Correct(run.getStdOut());
}
+ @NeverClassInline
enum MyEnum {
A,
B,
diff --git a/src/test/java/com/android/tools/r8/graph/MethodWithoutCodeAttributeTest.java b/src/test/java/com/android/tools/r8/graph/MethodWithoutCodeAttributeTest.java
new file mode 100644
index 0000000..c857e00
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/MethodWithoutCodeAttributeTest.java
@@ -0,0 +1,104 @@
+// 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.graph;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class MethodWithoutCodeAttributeTest extends TestBase {
+ private static final String MAIN = "com.android.tools.r8.Test";
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ public MethodWithoutCodeAttributeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue("D8 tests.", parameters.isDexRuntime());
+ try {
+ testForD8()
+ .addProgramClassFileData(TestDump.dump())
+ .compile();
+ fail("Expected to fail due to multiple annotations");
+ } catch (CompilationFailedException e) {
+ assertThat(
+ e.getCause().getMessage(),
+ containsString("Absent Code attribute in method that is not native or abstract"));
+ }
+ }
+
+ @Test
+ public void testJVMOutput() throws Exception {
+ assumeTrue("Only run JVM reference on CF runtimes", parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClassFileData(TestDump.dump())
+ .run(parameters.getRuntime(), MAIN)
+ .assertFailureWithErrorThatMatches(
+ containsString("Absent Code attribute in method that is not native or abstract"));
+ }
+
+ static class TestDump implements Opcodes {
+ public static byte[] dump () throws Exception {
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8, ACC_SUPER,
+ "com/android/tools/r8/Test",
+ null,
+ "java/lang/Object",
+ null);
+
+ {
+ methodVisitor = classWriter.visitMethod(
+ ACC_PUBLIC | ACC_SYNTHETIC, "foo", "(Ljava/lang/Object;)V", null, null);
+ methodVisitor.visitAnnotableParameterCount(1, false);
+ // b/149808321: no code attribute
+ methodVisitor.visitEnd();
+ }
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "main", "([Ljava/lang/String;)V", null, null);
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(42, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("Test::main");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(44, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
index fc8f3bf..7d04499 100644
--- a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
@@ -4,7 +4,9 @@
package com.android.tools.r8.internal;
import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
-import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index 4c3085e..774209e 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -10,8 +10,8 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
@@ -33,16 +33,14 @@
public class R8GMSCoreLookupTest extends TestBase {
- static final String APP_DIR = "third_party/gmscore/v5/";
- private AndroidApp app;
+ private static final String APP_DIR = "third_party/gmscore/v5/";
private DirectMappedDexApplication program;
- private AppInfoWithSubtyping appInfo;
- private AppView<? extends AppInfoWithClassHierarchy> appView;
+ private AppView<? extends AppInfoWithSubtyping> appView;
@Before
public void readGMSCore() throws Exception {
Path directory = Paths.get(APP_DIR);
- app = ToolHelper.builderFromProgramDirectory(directory).build();
+ AndroidApp app = ToolHelper.builderFromProgramDirectory(directory).build();
Path mapFile = directory.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE);
StringResource proguardMap = null;
if (Files.exists(mapFile)) {
@@ -54,19 +52,24 @@
new ApplicationReader(app, new InternalOptions(), timing)
.read(proguardMap, executorService)
.toDirect();
- appInfo = new AppInfoWithSubtyping(program);
- appView = computeAppViewWithSubtyping(app);
+ InternalOptions options = new InternalOptions();
+ appView = AppView.createForR8(new AppInfoWithSubtyping(program), options);
+ appView.setAppServices(AppServices.builder(appView).build());
+ }
+
+ private AppInfoWithSubtyping appInfo() {
+ return appView.appInfo();
}
private void testVirtualLookup(DexProgramClass clazz, DexEncodedMethod method) {
// Check lookup will produce the same result.
DexMethod id = method.method;
- assertEquals(appInfo.resolveMethod(id.holder, method.method).getSingleTarget(), method);
+ assertEquals(appInfo().resolveMethod(id.holder, method.method).getSingleTarget(), method);
// Check lookup targets with include method.
- ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(clazz, method.method);
+ ResolutionResult resolutionResult = appInfo().resolveMethodOnClass(clazz, method.method);
LookupResult lookupResult =
- resolutionResult.lookupVirtualDispatchTargets(clazz, appView, appInfo);
+ resolutionResult.lookupVirtualDispatchTargets(clazz, appView, appInfo());
assertTrue(lookupResult.isLookupResultSuccess());
Set<DexEncodedMethod> targets = lookupResult.asLookupResultSuccess().getMethodTargets();
assertTrue(targets.contains(method));
@@ -74,13 +77,13 @@
private void testInterfaceLookup(DexProgramClass clazz, DexEncodedMethod method) {
LookupResult lookupResult =
- appInfo
+ appInfo()
.resolveMethodOnInterface(clazz, method.method)
- .lookupVirtualDispatchTargets(clazz, appView, appInfo);
+ .lookupVirtualDispatchTargets(clazz, appView, appInfo());
assertTrue(lookupResult.isLookupResultSuccess());
Set<DexEncodedMethod> targets = lookupResult.asLookupResultSuccess().getMethodTargets();
- if (appInfo.subtypes(method.method.holder).stream()
- .allMatch(t -> appInfo.definitionFor(t).isInterface())) {
+ if (appInfo().subtypes(method.method.holder).stream()
+ .allMatch(t -> appInfo().definitionFor(t).isInterface())) {
assertEquals(
0,
targets.stream()
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index 6a4bd17..43c8e17 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -216,6 +216,11 @@
}
@Override
+ public boolean shouldApplyCodeRewritings(DexEncodedMethod method) {
+ return false;
+ }
+
+ @Override
public boolean isProcessedConcurrently(DexEncodedMethod method) {
return false;
}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index 306c93a..05c11c4 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -1,33 +1,19 @@
// Copyright (c) 2019, 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.desugar.backports;
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.cf.CfCodePrinter;
-import com.android.tools.r8.graph.ClassKind;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.JarApplicationReader;
-import com.android.tools.r8.graph.JarClassFileReader;
-import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@@ -36,38 +22,12 @@
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-// Class to generate and validate CfCode for backport methods.
@RunWith(Parameterized.class)
-public class GenerateBackportMethods extends TestBase {
+public class GenerateBackportMethods extends MethodGenerationBase {
- static final Path googleFormatDir = Paths.get(ToolHelper.THIRD_PARTY_DIR, "google-java-format");
- static final Path googleFormatJar =
- googleFormatDir.resolve("google-java-format-1.7-all-deps.jar");
- static final Path backportMethodsFile =
- Paths.get(
- ToolHelper.SOURCE_DIR,
- "com",
- "android",
- "tools",
- "r8",
- "ir",
- "desugar",
- "backports",
- "BackportedMethods.java");
-
- static final String header =
- StringUtils.lines(
- "// Copyright (c) 2019, 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.",
- "",
- "// ***********************************************************************************",
- "// GENERATED FILE. DO NOT EDIT! Changes should be made to GenerateBackportMethods.java",
- "// ***********************************************************************************",
- "",
- "package com.android.tools.r8.ir.desugar.backports;");
-
- static final List<Class<?>> methodTemplateClasses =
+ private final DexType GENERATED_TYPE =
+ factory.createType("Lcom/android/tools/r8/ir/desugar/backports/BackportedMethods;");
+ private final List<Class<?>> METHOD_TEMPLATE_CLASSES =
ImmutableList.of(
BooleanMethods.class,
ByteMethods.class,
@@ -87,7 +47,7 @@
StreamMethods.class,
StringMethods.class);
- final TestParameters parameters;
+ protected final TestParameters parameters;
@Parameters(name = "{0}")
public static TestParametersCollection data() {
@@ -98,73 +58,26 @@
this.parameters = parameters;
}
+ @Override
+ protected DexType getGeneratedType() {
+ return GENERATED_TYPE;
+ }
+
+ @Override
+ protected List<Class<?>> getMethodTemplateClasses() {
+ return METHOD_TEMPLATE_CLASSES;
+ }
+
@Test
- public void test() throws Exception {
- ArrayList<Class<?>> sorted = new ArrayList<>(methodTemplateClasses);
+ public void testBackportsGenerated() throws Exception {
+ ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
sorted.sort(Comparator.comparing(Class::getTypeName));
- assertEquals("Classes should be listed in sorted order", sorted, methodTemplateClasses);
+ assertEquals("Classes should be listed in sorted order", sorted, getMethodTemplateClasses());
assertEquals(
- FileUtils.readTextFile(backportMethodsFile, StandardCharsets.UTF_8),
- generateBackportMethods(parameters.getRuntime().asCf().getJavaExecutable().toString()));
+ FileUtils.readTextFile(getGeneratedFile(), StandardCharsets.UTF_8), generateMethods());
}
- // Running this method will regenerate / overwrite the content of the backport methods.
public static void main(String[] args) throws Exception {
- FileUtils.writeToFile(
- backportMethodsFile,
- null,
- generateBackportMethods(ToolHelper.getSystemJavaExecutable()).getBytes());
- }
-
- private static String generateBackportMethods(String javaExecutable) throws IOException {
- InternalOptions options = new InternalOptions();
- CfCodePrinter codePrinter = new CfCodePrinter();
- JarClassFileReader reader =
- new JarClassFileReader(
- new JarApplicationReader(options),
- clazz -> {
- for (DexEncodedMethod method : clazz.allMethodsSorted()) {
- if (method.isInitializer()) {
- continue;
- }
- String methodName =
- method.method.holder.getName() + "_" + method.method.name.toString();
- codePrinter.visitMethod(methodName, method.getCode().asCfCode());
- }
- });
- for (Class<?> clazz : methodTemplateClasses) {
- try (InputStream stream = Files.newInputStream(ToolHelper.getClassFileForTestClass(clazz))) {
- reader.read(Origin.unknown(), ClassKind.PROGRAM, stream);
- }
- }
-
- Path outfile = Paths.get(ToolHelper.BUILD_DIR, "backports.java");
- try (PrintStream printer = new PrintStream(Files.newOutputStream(outfile))) {
- printer.println(header);
- codePrinter.getImports().forEach(i -> printer.println("import " + i + ";"));
- printer.println("public final class BackportedMethods {\n");
- codePrinter.getMethods().forEach(printer::println);
- printer.println("}");
- }
-
- ProcessBuilder builder =
- new ProcessBuilder(
- ImmutableList.of(
- javaExecutable,
- "-jar",
- googleFormatJar.toString(),
- outfile.toAbsolutePath().toString()));
- String commandString = String.join(" ", builder.command());
- System.out.println(commandString);
- Process process = builder.start();
- ProcessResult result = ToolHelper.drainProcessOutputStreams(process, commandString);
- if (result.exitCode != 0) {
- throw new IllegalStateException(result.toString());
- }
- String content = result.stdout;
- if (!StringUtils.LINE_SEPARATOR.equals("\n")) {
- return content.replace(StringUtils.LINE_SEPARATOR, "\n");
- }
- return content;
+ new GenerateBackportMethods(null).generateMethodsAndWriteThemToFile();
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java
index 7b0a595..24ffad1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java
@@ -12,14 +12,33 @@
import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
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 java.util.Objects;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class InliningAfterClassInitializationTest extends TestBase {
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InliningAfterClassInitializationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
@Test
public void testClass1() throws Exception {
Class<TestClass1> mainClass = TestClass1.class;
@@ -177,15 +196,35 @@
assertThat(testMethod, invokesMethod(notInlineableMethod));
}
- private CodeInspector buildAndRun(Class<?> mainClass, String expectedOutput) throws Exception {
- testForJvm().addTestClasspath().run(mainClass).assertSuccessWithOutput(expectedOutput);
+ @Test
+ public void testClass10() throws Exception {
+ Class<TestClass10> mainClass = TestClass10.class;
+ CodeInspector inspector =
+ buildAndRun(mainClass, StringUtils.lines("In A.<clinit>()", "In A.inlineable()"));
- return testForR8(Backend.DEX)
+ ClassSubject classA = inspector.clazz(A.class);
+ assertThat(classA, isPresent());
+
+ MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
+ assertThat(inlineableMethod, not(isPresent()));
+ }
+
+ private CodeInspector buildAndRun(Class<?> mainClass, String expectedOutput) throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), mainClass)
+ .assertSuccessWithOutput(expectedOutput);
+ }
+
+ return testForR8(parameters.getBackend())
.addInnerClasses(InliningAfterClassInitializationTest.class)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.addKeepMainRule(mainClass)
.enableConstantArgumentAnnotations()
.enableInliningAnnotations()
- .run(mainClass)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), mainClass)
.assertSuccessWithOutput(expectedOutput)
.inspector();
}
@@ -347,6 +386,21 @@
}
}
+ static class TestClass10 {
+
+ public static void main(String[] args) {
+ test(new A());
+ }
+
+ @NeverInline
+ private static void test(A obj) {
+ Objects.requireNonNull(obj);
+
+ // A is guaranteed to be initialized since requireNonNull() will throw if `obj` is null.
+ A.inlineable();
+ }
+ }
+
static class A {
public String instanceField = "Field A.instanceField";
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B149468959.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B149468959.java
new file mode 100644
index 0000000..dff369b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B149468959.java
@@ -0,0 +1,61 @@
+// 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.optimize.membervaluepropagation;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class B149468959 extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public B149468959(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(B149468959.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("A");
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ mutate();
+ for (MyEnum x : MyEnum.values()) {
+ if (x.include) {
+ System.out.println(x);
+ }
+ }
+ }
+
+ static void mutate() {
+ MyEnum.B.include = false;
+ }
+ }
+
+ enum MyEnum {
+ A,
+ B;
+
+ boolean include = true;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
index a74ac5f..2c8f4b3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
@@ -75,12 +75,10 @@
MethodSubject testMaybeNullMethodSubject =
testClassSubject.uniqueMethodWithName("testMaybeNull");
assertThat(testMaybeNullMethodSubject, isPresent());
- // TODO(b/125282093): Should synthesize a null-check and still propagate the field values even
- // when the receiver is nullable.
assertTrue(
testMaybeNullMethodSubject
.streamInstructions()
- .anyMatch(InstructionSubject::isInstanceGet));
+ .noneMatch(InstructionSubject::isInstanceGet));
// TODO(b/125282093): Should be able to remove the new-instance instruction since the instance
// ends up being unused.
assertTrue(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentForwardingConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentForwardingConstructorTest.java
index a118c85..5d0acf1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentForwardingConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentForwardingConstructorTest.java
@@ -96,10 +96,12 @@
int x;
+ @NeverInline
A() {
this(42);
}
+ @NeverInline
A(int x) {
this.x = x;
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java
index 6663b91..b6fe557 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverClassInline;
@@ -50,8 +51,7 @@
ClassSubject testClassSubject = inspector.clazz(TestClass.class);
assertThat(testClassSubject, isPresent());
assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
- // TODO(b/147652121): Should be absent.
- assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+ assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedBySameConstantInMultipleConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedBySameConstantInMultipleConstructorsTest.java
index 2f0cf08..3033e29 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedBySameConstantInMultipleConstructorsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedBySameConstantInMultipleConstructorsTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverClassInline;
@@ -50,8 +51,7 @@
ClassSubject testClassSubject = inspector.clazz(TestClass.class);
assertThat(testClassSubject, isPresent());
assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
- // TODO(b/147652121): Should be absent.
- assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+ assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
new file mode 100644
index 0000000..18badf8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
@@ -0,0 +1,84 @@
+// 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.kotlin.sealed;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiFunction;
+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 SealedClassTest extends KotlinTestBase {
+
+ private static final String MAIN = "com.android.tools.r8.kotlin.sealed.kt.FormatKt";
+ private static final String[] EXPECTED = new String[] {"ZIP"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), KotlinTargetVersion.values());
+ }
+
+ public SealedClassTest(TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static BiFunction<TestRuntime, KotlinTargetVersion, Path> compilationResults =
+ memoizeBiFunction(SealedClassTest::compileKotlinCode);
+
+ private static Path compileKotlinCode(TestRuntime runtime, KotlinTargetVersion targetVersion)
+ throws IOException {
+ CfRuntime cfRuntime = runtime.isCf() ? runtime.asCf() : TestRuntime.getCheckedInJdk9();
+ return kotlinc(cfRuntime, getStaticTemp(), KOTLINC, targetVersion)
+ .addSourceFiles(getFilesInTestFolderRelativeToClass(SealedClassTest.class, "kt", ".kt"))
+ .compile();
+ }
+
+ @Test
+ public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+ testForRuntime(parameters)
+ .addProgramFiles(compilationResults.apply(parameters.getRuntime(), targetVersion))
+ .addRunClasspathFiles(buildOnDexRuntime(parameters, ToolHelper.getKotlinStdlibJar()))
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws ExecutionException, CompilationFailedException, IOException {
+ testForR8(parameters.getBackend())
+ .addProgramFiles(compilationResults.apply(parameters.getRuntime(), targetVersion))
+ .addProgramFiles(buildOnDexRuntime(parameters, ToolHelper.getKotlinStdlibJar()))
+ .setMinApi(parameters.getApiLevel())
+ .allowAccessModification()
+ .allowDiagnosticWarningMessages(parameters.isCfRuntime())
+ .addKeepMainRule(MAIN)
+ .compileWithExpectedDiagnostics(
+ diagnosticMessages -> {
+ diagnosticMessages.assertAllWarningMessagesMatch(
+ containsString("Resource 'META-INF/MANIFEST.MF' already exists."));
+ })
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/sealed/kt/Format.kt b/src/test/java/com/android/tools/r8/kotlin/sealed/kt/Format.kt
new file mode 100644
index 0000000..2c1e189
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/sealed/kt/Format.kt
@@ -0,0 +1,20 @@
+// 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.kotlin.sealed.kt
+
+public sealed class Format(val name: String) {
+ object Zip : Format("ZIP")
+ object Directory : Format("DIRECTORY")
+}
+
+fun main() {
+ val value = when ("ZIP") {
+ Format.Zip.name -> Format.Zip
+ Format.Directory.name -> Format.Directory
+ else -> throw IllegalArgumentException(
+ "Valid formats: ${Format.Zip.name} or ${Format.Directory.name}.")
+ }
+ println(value.name)
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java b/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java
new file mode 100644
index 0000000..8fccb56
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java
@@ -0,0 +1,135 @@
+// 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.resolution;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 InvokeSuperCallInStaticTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"Base.collect()"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeSuperCallInStaticTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(
+ buildClasses(Base.class, Main.class)
+ .addClassProgramData(getAWithRewrittenInvokeSpecialToBase())
+ .build(),
+ Main.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexMethod method = buildNullaryVoidMethod(Base.class, "collect", appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+ assertTrue(resolutionResult.isSingleResolution());
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
+ DexEncodedMethod lookedUpMethod =
+ resolutionResult.lookupInvokeSuperTarget(context, appView.appInfo());
+ assertNotNull(lookedUpMethod);
+ assertEquals(lookedUpMethod.method, method);
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addProgramClasses(Base.class, Main.class)
+ .addProgramClassFileData(getAWithRewrittenInvokeSpecialToBase())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Base.class, Main.class)
+ .addProgramClassFileData(getAWithRewrittenInvokeSpecialToBase())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED)
+ .inspect(
+ inspector -> {
+ assertThat(inspector.clazz(A.class), not(isPresent()));
+ });
+ }
+
+ private byte[] getAWithRewrittenInvokeSpecialToBase() throws IOException {
+ return transformer(A.class)
+ .transformMethodInsnInMethod(
+ "callSuper",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ continuation.apply(
+ INVOKESPECIAL,
+ DescriptorUtils.getBinaryNameFromJavaType(Base.class.getTypeName()),
+ name,
+ descriptor,
+ false);
+ })
+ .transform();
+ }
+
+ public static class Base {
+
+ public void collect() {
+ System.out.println("Base.collect()");
+ }
+ }
+
+ public static class A extends Base {
+
+ @Override
+ public void collect() {
+ System.out.println("A.collect()");
+ }
+
+ public static void callSuper(A a) {
+ a.collect(); // Will be rewritten from invoke-virtual to invoke-special Base.collect();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ A.callSuper(new A());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
index 9136188..ce8ce66 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.resolution.interfacetargets;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@@ -130,14 +129,13 @@
@Test
public void testR8WithIndirectDefault()
throws IOException, CompilationFailedException, ExecutionException {
- // TODO(b/148686556): Fix test expectation.
testForR8(parameters.getBackend())
.addProgramClasses(I.class, J.class, K.class, Main.class)
.addProgramClassFileData(setAimplementsIandK())
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatMatches(containsString("java.lang.AbstractMethodError"));
+ .assertSuccessWithOutputLines(EXPECTED);
}
private byte[] setAImplementsIAndJ() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
index da24bcb..8eb3399 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
@@ -4,9 +4,11 @@
package com.android.tools.r8.resolution.virtualtargets;
+import static junit.framework.TestCase.assertNull;
import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertFalse;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
@@ -15,6 +17,7 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.graph.AppView;
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.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -43,16 +46,27 @@
@Test
public void testResolution() throws Exception {
assumeTrue(parameters.useRuntimeAsNoneRuntime());
- AppView<AppInfoWithLiveness> appView =
- computeAppViewWithLiveness(
- buildClasses(A.class).addClassProgramData(getMainWithModifiedReceiverCall()).build(),
- Main.class);
- AppInfoWithLiveness appInfo = appView.appInfo();
- DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
- DexType mainType = buildType(Main.class, appInfo.dexItemFactory());
- ResolutionResult resolutionResult = appInfo.resolveMethod(mainType, method);
- // TODO(b/149516194): This should be failed resolution.
- assertFalse(resolutionResult.isFailedResolution());
+ AssertionError foo =
+ assertThrows(
+ AssertionError.class,
+ () -> {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(
+ buildClasses(A.class)
+ .addClassProgramData(getMainWithModifiedReceiverCall())
+ .build(),
+ Main.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+ assertTrue(resolutionResult.isSingleResolution());
+ DexType mainType = buildType(Main.class, appInfo.dexItemFactory());
+ DexProgramClass main = appView.definitionForProgramType(mainType);
+ assertNull(resolutionResult.lookupVirtualDispatchTarget(main, appView));
+ });
+ assertThat(
+ foo.getMessage(),
+ containsString(Main.class.getTypeName() + " is not a subtype of " + A.class.getTypeName()));
}
@Test
@@ -66,17 +80,20 @@
@Test
public void testR8() throws IOException, CompilationFailedException, ExecutionException {
- CompilationFailedException compilationFailedException =
- assertThrows(
- CompilationFailedException.class,
- () -> {
- testForR8(parameters.getBackend())
- .addProgramClasses(A.class)
- .addProgramClassFileData(getMainWithModifiedReceiverCall())
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(Main.class)
- .compile();
- });
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class)
+ .addProgramClassFileData(getMainWithModifiedReceiverCall())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .compileWithExpectedDiagnostics(
+ diagnosticMessages -> {
+ diagnosticMessages.assertErrorMessageThatMatches(
+ containsString(
+ "The receiver lower bound does not match the receiver type"));
+ }));
}
private byte[] getMainWithModifiedReceiverCall() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/shaking/B149831282.java b/src/test/java/com/android/tools/r8/shaking/B149831282.java
new file mode 100644
index 0000000..910d44b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/B149831282.java
@@ -0,0 +1,107 @@
+// 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.shaking;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 B149831282 extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public B149831282(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .apply(this::addProgramInputs)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("In A.m()");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .apply(this::addProgramInputs)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("In A.m()");
+ }
+
+ private void addProgramInputs(TestBuilder<?, ?> builder) throws Exception {
+ builder
+ .addProgramClasses(A.class, B.class)
+ .addProgramClassFileData(
+ transformer(TestClass.class)
+ .transformTypeInsnInMethod(
+ "main",
+ (opcode, type, continuation) -> {
+ assertEquals(binaryName(C.class), type);
+ continuation.apply(opcode, "b149831282/C");
+ })
+ .transformMethodInsnInMethod(
+ "main",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ assertEquals(binaryName(C.class), owner);
+ continuation.apply(opcode, "b149831282/C", name, descriptor, isInterface);
+ })
+ .transform())
+ .addProgramClassFileData(
+ transformer(C.class).setClassDescriptor("Lb149831282/C;").transform());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new C().m();
+ }
+ }
+
+ @NeverMerge
+ static class A {
+
+ @NeverInline
+ protected void m() {
+ System.out.println("In A.m()");
+ }
+ }
+
+ @NeverMerge
+ public static class B extends A {}
+
+ @NeverClassInline
+ public static class /*b149831282.*/ C extends B {
+
+ @NeverInline
+ @Override
+ public void m() {
+ super.m();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
index dfd14da..397c054 100644
--- a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
import com.android.tools.r8.jasmin.JasminTestBase;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FieldSubject;
@@ -37,7 +38,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public FieldReadsJasminTest(TestParameters parameters) {
@@ -99,7 +100,7 @@
if (parameters.isDexRuntime()) {
testForD8()
.addProgramClassFileData(classes)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(inspector -> ensureNoFieldsRead(inspector, clazz.name, false));
}
@@ -107,7 +108,7 @@
testForR8(parameters.getBackend())
.addProgramClassFileData(classes)
.addKeepRules("-keep class * { <methods>; }")
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(inspector -> ensureNoFieldsRead(inspector, clazz.name, true));
}
@@ -167,20 +168,21 @@
testForR8(parameters.getBackend())
.addProgramClassFileData(app.buildClasses())
.addKeepRules("-keep class * { <methods>; }")
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
- .inspect(inspector -> {
- FieldSubject fld = inspector.clazz(fieldHolder.name).uniqueFieldWithName(fieldName);
- assertThat(fld, isRenamed());
+ .inspect(
+ inspector -> {
+ FieldSubject fld = inspector.clazz(fieldHolder.name).uniqueFieldWithName(fieldName);
+ assertThat(fld, isRenamed());
- ClassSubject classSubject = inspector.clazz(clazz.name);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.uniqueMethodWithName(method.name);
- assertThat(methodSubject, isPresent());
- Iterator<InstructionSubject> it =
- methodSubject.iterateInstructions(InstructionSubject::isFieldAccess);
- assertFalse(it.hasNext());
- });
+ ClassSubject classSubject = inspector.clazz(clazz.name);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.uniqueMethodWithName(method.name);
+ assertThat(methodSubject, isPresent());
+ Iterator<InstructionSubject> it =
+ methodSubject.iterateInstructions(InstructionSubject::isFieldAccess);
+ assertFalse(it.hasNext());
+ });
}
@Test
@@ -205,7 +207,27 @@
" invokestatic Empty/foo(L" + CLS + ";)V",
" return");
- ensureFieldExistsAndReadOnlyOnce(builder, empty, foo, empty, "aField");
+ inspect(
+ builder,
+ inspector ->
+ ensureFieldExistsAndReadOnlyOnce(
+ inspector, empty.name, foo.name, empty, "aField", false),
+ inspector -> {
+ ClassSubject emptyClassSubject = inspector.clazz(CLS);
+ assertThat(emptyClassSubject, isPresent());
+ assertTrue(emptyClassSubject.allFields().isEmpty());
+
+ MethodSubject fooMethodSubject = emptyClassSubject.uniqueMethodWithName("foo");
+ assertThat(fooMethodSubject, isPresent());
+ assertTrue(
+ fooMethodSubject
+ .streamInstructions()
+ .filter(InstructionSubject::isInvoke)
+ .anyMatch(
+ invoke ->
+ invoke.getMethod().toSourceString().contains("requireNonNull")
+ || invoke.getMethod().toSourceString().contains("getClass")));
+ });
}
@Test
@@ -275,6 +297,29 @@
ensureFieldExistsAndReadOnlyOnce(builder, main, mainMethod, main, "sField");
}
+ private void inspect(
+ JasminBuilder app,
+ ThrowingConsumer<CodeInspector, RuntimeException> d8Inspector,
+ ThrowingConsumer<CodeInspector, RuntimeException> r8Inspector)
+ throws Exception {
+ List<byte[]> classes = app.buildClasses();
+
+ if (parameters.isDexRuntime()) {
+ testForD8()
+ .addProgramClassFileData(classes)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(d8Inspector);
+ }
+
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(classes)
+ .addKeepRules("-keep class * { <methods>; }")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(r8Inspector);
+ }
+
private void ensureFieldExistsAndReadOnlyOnce(
JasminBuilder app,
ClassBuilder clazz,
@@ -282,24 +327,14 @@
ClassBuilder fieldHolder,
String fieldName)
throws Exception {
- List<byte[]> classes = app.buildClasses();
-
- if (parameters.isDexRuntime()) {
- testForD8()
- .addProgramClassFileData(classes)
- .setMinApi(parameters.getRuntime())
- .compile()
- .inspect(inspector -> ensureFieldExistsAndReadOnlyOnce(
- inspector, clazz.name, method.name, fieldHolder, fieldName, false));
- }
-
- testForR8(parameters.getBackend())
- .addProgramClassFileData(classes)
- .addKeepRules("-keep class * { <methods>; }")
- .setMinApi(parameters.getRuntime())
- .compile()
- .inspect(inspector -> ensureFieldExistsAndReadOnlyOnce(
- inspector, clazz.name, method.name, fieldHolder, fieldName, true));
+ inspect(
+ app,
+ inspector ->
+ ensureFieldExistsAndReadOnlyOnce(
+ inspector, clazz.name, method.name, fieldHolder, fieldName, false),
+ inspector ->
+ ensureFieldExistsAndReadOnlyOnce(
+ inspector, clazz.name, method.name, fieldHolder, fieldName, true));
}
private void ensureFieldExistsAndReadOnlyOnce(
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInInterfaceMarkingTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInInterfaceMarkingTest.java
new file mode 100644
index 0000000..bff8192
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInInterfaceMarkingTest.java
@@ -0,0 +1,111 @@
+// 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.shaking;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import java.util.AbstractList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LibraryMethodOverrideInInterfaceMarkingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public LibraryMethodOverrideInInterfaceMarkingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(LibraryMethodOverrideInInterfaceMarkingTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addOptionsModification(
+ options -> options.testing.enqueuerInspector = this::verifyLibraryOverrideInformation)
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("true", "true");
+ }
+
+ private void verifyLibraryOverrideInformation(AppInfoWithLiveness appInfo, Enqueuer.Mode mode) {
+ DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+ verifyIsEmptyMarkedAsOverridingLibraryMethod(
+ appInfo, dexItemFactory.createType(descriptor(A.class)));
+ verifyIsEmptyMarkedAsOverridingLibraryMethod(
+ appInfo, dexItemFactory.createType(descriptor(I.class)));
+ }
+
+ private void verifyIsEmptyMarkedAsOverridingLibraryMethod(
+ AppInfoWithLiveness appInfo, DexType type) {
+ DexProgramClass clazz = appInfo.definitionFor(type).asProgramClass();
+ DexEncodedMethod method =
+ clazz.lookupVirtualMethod(m -> m.method.name.toString().equals("isEmpty"));
+ assertTrue(method.isLibraryMethodOverride().isTrue());
+ }
+
+ static class TestClass {
+
+ static void onA(A a) {
+ System.out.println(a.isEmpty());
+ }
+
+ static void onI(I i) {
+ System.out.println(i.isEmpty());
+ }
+
+ public static void main(String[] args) {
+ Object object = args.length == 42 ? null : new B();
+ onA((A) object);
+ onI((I) object);
+ }
+ }
+
+ @NeverMerge
+ abstract static class A extends AbstractList<Object> {
+
+ @Override
+ public boolean isEmpty() {
+ return true;
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public A get(int index) {
+ return null;
+ }
+ }
+
+ @NeverMerge
+ interface I {
+
+ boolean isEmpty();
+ }
+
+ @NeverMerge
+ static class B extends A implements I {
+ // Intentionally empty.
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
new file mode 100644
index 0000000..e3925ca
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
@@ -0,0 +1,110 @@
+// 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.shaking;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.Enqueuer.Mode;
+import java.util.Iterator;
+import java.util.Spliterator;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LibraryMethodOverrideInLambdaMarkingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public LibraryMethodOverrideInLambdaMarkingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(LibraryMethodOverrideInLambdaMarkingTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addOptionsModification(
+ options -> options.testing.enqueuerInspector = this::verifyLibraryOverrideInformation)
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("null", "null");
+ }
+
+ private void verifyLibraryOverrideInformation(AppInfoWithLiveness appInfo, Mode mode) {
+ DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+ verifyIteratorMethodMarkedAsOverridingLibraryMethod(
+ appInfo, dexItemFactory.createType(descriptor(I.class)));
+ verifyIteratorMethodMarkedAsOverridingLibraryMethod(
+ appInfo, dexItemFactory.createType(descriptor(J.class)));
+ }
+
+ private void verifyIteratorMethodMarkedAsOverridingLibraryMethod(
+ AppInfoWithLiveness appInfo, DexType type) {
+ DexProgramClass clazz = appInfo.definitionFor(type).asProgramClass();
+ DexEncodedMethod method =
+ clazz.lookupVirtualMethod(m -> m.method.name.toString().equals("iterator"));
+ // TODO(b/149976493): Mark library overrides from lambda instances.
+ assertTrue(method.isLibraryMethodOverride().isFalse());
+ }
+
+ static class TestClass {
+
+ public static void onI(I i) {
+ System.out.println(i.iterator());
+ }
+
+ public static void onJ(J j) {
+ System.out.println(j.iterator());
+ }
+
+ public static void main(String[] args) {
+ Object f = ((I & J) () -> null);
+ onI((I) f);
+ onJ((J) f);
+ }
+ }
+
+ @FunctionalInterface
+ @NeverMerge
+ interface I {
+
+ Iterator<Object> iterator();
+ }
+
+ @FunctionalInterface
+ @NeverMerge
+ interface J extends Iterable<Object> {
+
+ @Override
+ Iterator<Object> iterator();
+
+ @Override
+ default void forEach(Consumer<? super Object> action) {
+ // Intentionally empty.
+ }
+
+ @Override
+ default Spliterator<Object> spliterator() {
+ return null;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideMarkingTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideMarkingTest.java
new file mode 100644
index 0000000..4afc009
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideMarkingTest.java
@@ -0,0 +1,90 @@
+// 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.shaking;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LibraryMethodOverrideMarkingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public LibraryMethodOverrideMarkingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(LibraryMethodOverrideMarkingTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addOptionsModification(
+ options -> options.testing.enqueuerInspector = this::verifyLibraryOverrideInformation)
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+ }
+
+ private void verifyLibraryOverrideInformation(AppInfoWithLiveness appInfo, Enqueuer.Mode mode) {
+ DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+ verifySingleVirtualMethodMarkedAsOverridingLibraryMethod(
+ appInfo, dexItemFactory.createType(descriptor(A.class)));
+ verifySingleVirtualMethodMarkedAsOverridingLibraryMethod(
+ appInfo, dexItemFactory.createType(descriptor(B.class)));
+ }
+
+ private void verifySingleVirtualMethodMarkedAsOverridingLibraryMethod(
+ AppInfoWithLiveness appInfo, DexType type) {
+ DexProgramClass clazz = appInfo.definitionFor(type).asProgramClass();
+ assertEquals(1, clazz.virtualMethods().size());
+ DexEncodedMethod method = clazz.virtualMethods().get(0);
+ assertTrue(method.isLibraryMethodOverride().isTrue());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(new C());
+ }
+ }
+
+ @NeverMerge
+ static class A {
+
+ @Override
+ public String toString() {
+ return super.toString();
+ }
+ }
+
+ @NeverMerge
+ static class B extends A {
+
+ @Override
+ public String toString() {
+ return super.toString();
+ }
+ }
+
+ static class C extends B {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/B149729626.java b/src/test/java/com/android/tools/r8/shaking/annotations/B149729626.java
new file mode 100644
index 0000000..ab3c663
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/B149729626.java
@@ -0,0 +1,116 @@
+// 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.shaking.annotations;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+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 B149729626 extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection params() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public B149729626(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8WithKeepClassMembersRule() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(B149729626.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules(
+ "-keepclassmembers @" + Marker.class.getTypeName() + " class * {",
+ " <init>(...);",
+ "}",
+ // TODO(b/149729626): Should not be required.
+ "-keep class " + Marked.class.getTypeName(),
+ // TODO(b/149729626): Should not be required.
+ "-keep class " + TestClass.class.getTypeName() + " { void makeMarkerLive(); }")
+ // TODO(b/149729626): Should not be required.
+ .addKeepRuntimeVisibleAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccess();
+ }
+
+ @Test
+ public void testR8WithIfRule() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(B149729626.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules(
+ "-if @" + Marker.class.getTypeName() + " class *",
+ "-keep class <1> {",
+ " <init>(...);",
+ "}",
+ // TODO(b/149729626): Should not be required.
+ "-keep class " + TestClass.class.getTypeName() + " { void makeMarkerLive(); }")
+ // TODO(b/149729626): Should not be required.
+ .addKeepRuntimeVisibleAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccess();
+ }
+
+ @Test
+ public void testCompat() throws Exception {
+ testForR8Compat(parameters.getBackend())
+ .addInnerClasses(B149729626.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccess();
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject markedClassSubject = inspector.clazz(Marked.class);
+ assertThat(markedClassSubject, isPresent());
+ assertThat(markedClassSubject.init(), isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(Marked.class);
+ }
+
+ static void makeMarkerLive() {
+ System.out.println(Marker.class);
+ }
+ }
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface Marker {}
+
+ @Marker
+ static class Marked {}
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index eb3929c..baadc68 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -348,6 +348,18 @@
void apply(int opcode, String owner, String name, String descriptor, boolean isInterface);
}
+ /** Abstraction of the MethodVisitor.visitTypeInsn method with its continuation. */
+ @FunctionalInterface
+ public interface TypeInsnTransform {
+ void visitTypeInsn(int opcode, String type, TypeInsnTransformContinuation continuation);
+ }
+
+ /** Continuation for transforming a method. Will continue with the super visitor if called. */
+ @FunctionalInterface
+ public interface TypeInsnTransformContinuation {
+ void apply(int opcode, String type);
+ }
+
public ClassFileTransformer transformMethodInsnInMethod(
String methodName, MethodInsnTransform transform) {
return addMethodTransformer(
@@ -365,6 +377,21 @@
});
}
+ public ClassFileTransformer transformTypeInsnInMethod(
+ String methodName, TypeInsnTransform transform) {
+ return addMethodTransformer(
+ new MethodTransformer() {
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ if (getContext().method.getMethodName().equals(methodName)) {
+ transform.visitTypeInsn(opcode, type, super::visitTypeInsn);
+ } else {
+ super.visitTypeInsn(opcode, type);
+ }
+ }
+ });
+ }
+
/** Abstraction of the MethodVisitor.visitLdcInsn method with its continuation. */
@FunctionalInterface
public interface LdcInsnTransform {
diff --git a/tools/r8_release.py b/tools/r8_release.py
index a9e66237..502666f 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -14,9 +14,7 @@
import xml
import xml.etree.ElementTree as et
import zipfile
-
import archive_desugar_jdk_libs
-import update_prebuilds_in_android
import utils
R8_DEV_BRANCH = '2.1'
@@ -31,6 +29,18 @@
GITHUB_DESUGAR_JDK_LIBS = 'https://github.com/google/desugar_jdk_libs'
+def checkout_r8(temp, branch):
+ subprocess.check_call(['git', 'clone', utils.REPO_SOURCE, temp])
+ with utils.ChangedWorkingDirectory(temp):
+ subprocess.check_call([
+ 'git',
+ 'new-branch',
+ '--upstream',
+ 'origin/%s' % branch,
+ 'dev-release'])
+ return temp
+
+
def prepare_release(args):
if args.version:
print "Cannot manually specify version when making a dev release."
@@ -40,15 +50,7 @@
commithash = args.dev_release
with utils.TempDir() as temp:
- subprocess.check_call(['git', 'clone', utils.REPO_SOURCE, temp])
- with utils.ChangedWorkingDirectory(temp):
- subprocess.check_call([
- 'git',
- 'new-branch',
- '--upstream',
- 'origin/%s' % R8_DEV_BRANCH,
- 'dev-release'])
-
+ with utils.ChangedWorkingDirectory(checkout_r8(temp, R8_DEV_BRANCH)):
# Compute the current and new version on the branch.
result = None
for line in open(R8_VERSION_FILE, 'r'):
@@ -123,6 +125,7 @@
maybe_check_call(args, [
'git', 'push', 'origin', 'refs/tags/%s' % version])
+
def version_change_diff(diff, old_version, new_version):
invalid_line = None
for line in diff.splitlines():
@@ -160,11 +163,12 @@
return subprocess.check_call(cmd)
-def update_prebuilds(version, checkout):
- update_prebuilds_in_android.main_download('', True, 'lib', checkout, version)
+def update_prebuilds(r8_checkout, version, checkout):
+ path = os.path.join(r8_checkout, 'tools', 'update_prebuilds_in_android.py')
+ subprocess.check_call([path, '--targets=lib', '--maps', '--version=' + version, checkout])
-def release_studio_or_aosp(path, options, git_message):
+def release_studio_or_aosp(r8_checkout, path, options, git_message):
with utils.ChangedWorkingDirectory(path):
if not options.use_existing_work_branch:
subprocess.call(['repo', 'abandon', 'update-r8'])
@@ -177,7 +181,7 @@
with utils.ChangedWorkingDirectory(prebuilts_r8):
subprocess.check_call(['repo', 'start', 'update-r8'])
- update_prebuilds(options.version, path)
+ update_prebuilds(r8_checkout, options.version, path)
with utils.ChangedWorkingDirectory(prebuilts_r8):
if not options.use_existing_work_branch:
@@ -213,7 +217,8 @@
Test: TARGET_PRODUCT=aosp_arm64 m -j core-oj"""
% (args.version, args.version, args.version))
- return release_studio_or_aosp(args.aosp, options, git_message)
+ return release_studio_or_aosp(
+ utils.REPO_ROOT, args.aosp, options, git_message)
return release_aosp
@@ -236,6 +241,7 @@
Built here: go/r8-releases/raw/%s/
Test: ./gradlew check
+
Bug: %s """ % (version, version, '\nBug: '.join(bugs))
@@ -249,10 +255,16 @@
if options.dry_run:
return 'DryRun: omitting studio release for %s' % options.version
- git_message = (git_message_dev(options.version)
- if 'dev' in options.version
- else git_message_release(options.version, options.bug))
- return release_studio_or_aosp(args.studio, options, git_message)
+ if 'dev' in options.version:
+ git_message = git_message_dev(options.version)
+ r8_checkout = utils.REPO_ROOT
+ return release_studio_or_aosp(
+ r8_checkout, args.studio, options, git_message)
+ else:
+ with utils.TempDir() as temp:
+ checkout_r8(temp, options.version[0:options.version.rindex('.')])
+ git_message = git_message_release(options.version, options.bug)
+ return release_studio_or_aosp(temp, args.studio, options, git_message)
return release_studio