Merge "Add (ignored) test for tree shaking"
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 63c1173..409c437 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.3.5-dev";
+ public static final String LABEL = "1.3.6-dev";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 001e5b6..8de4a6e 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -110,6 +110,16 @@
public abstract DexField lookupField(DexField field);
+ // The method lookupMethod() maps a pair INVOKE=(method signature, invoke type) to a new pair
+ // INVOKE'=(method signature', invoke type'). This mapping can be context sensitive, meaning that
+ // the result INVOKE' depends on where the invocation INVOKE is in the program. This is, for
+ // example, used by the vertical class merger to translate invoke-super instructions that hit
+ // a method in the direct super class to invoke-direct instructions after class merging.
+ //
+ // This method can be used to determine if a graph lense is context sensitive. If a graph lense
+ // is context insensitive, it is safe to invoke lookupMethod() without a context (or to pass null
+ // as context). Trying to invoke a context sensitive graph lense without a context will lead to
+ // an assertion error.
public abstract boolean isContextFreeForMethods();
public boolean isContextFreeForMethod(DexMethod method) {
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 3288aa9..fffbc44 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
@@ -149,7 +149,8 @@
this.protoLiteRewriter =
options.forceProguardCompatibility ? null : new ProtoLitePruner(appInfo.withLiveness());
if (!appInfo.withLiveness().identifierNameStrings.isEmpty() && options.enableMinification) {
- this.identifierNameStringMarker = new IdentifierNameStringMarker(appInfo.withLiveness());
+ this.identifierNameStringMarker =
+ new IdentifierNameStringMarker(appInfo.withLiveness(), options);
} else {
this.identifierNameStringMarker = null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 1e9566a..073d6ae 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo.ResolutionResult;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
@@ -12,6 +13,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.Collection;
@@ -101,16 +103,37 @@
return Constraint.classIsVisible(invocationContext, type, appInfo);
}
- public Constraint forInvokeCustom() {
- return Constraint.NEVER;
- }
-
public Constraint forInstancePut(DexField field, DexType invocationContext) {
DexField lookup = graphLense.lookupField(field);
return forFieldInstruction(
lookup, appInfo.lookupInstanceTarget(lookup.clazz, lookup), invocationContext);
}
+ public Constraint forInvoke(DexMethod method, Type type, DexType invocationContext) {
+ switch (type) {
+ case DIRECT:
+ return forInvokeDirect(method, invocationContext);
+ case INTERFACE:
+ return forInvokeInterface(method, invocationContext);
+ case STATIC:
+ return forInvokeStatic(method, invocationContext);
+ case SUPER:
+ return forInvokeSuper(method, invocationContext);
+ case VIRTUAL:
+ return forInvokeVirtual(method, invocationContext);
+ case CUSTOM:
+ return forInvokeCustom();
+ case POLYMORPHIC:
+ return forInvokePolymorphic(method, invocationContext);
+ default:
+ throw new Unreachable("Unexpected type: " + type);
+ }
+ }
+
+ public Constraint forInvokeCustom() {
+ return Constraint.NEVER;
+ }
+
public Constraint forInvokeDirect(DexMethod method, DexType invocationContext) {
DexMethod lookup = graphLense.lookupMethod(method);
return forSingleTargetInvoke(lookup, appInfo.lookupDirectTarget(lookup), invocationContext);
diff --git a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
index 4807b89..459d7de 100644
--- a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.JarApplicationReader;
+import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.conversion.JarSourceCode;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.optimize.InliningConstraints;
@@ -34,6 +35,7 @@
private final JarApplicationReader application;
private final AppInfoWithLiveness appInfo;
+ private final GraphLense graphLense;
private final InliningConstraints inliningConstraints;
private final DexEncodedMethod method;
private final DexType invocationContext;
@@ -47,8 +49,10 @@
DexEncodedMethod method,
DexType invocationContext) {
super(ASM6);
+ assert graphLense.isContextFreeForMethods();
this.application = application;
this.appInfo = appInfo;
+ this.graphLense = graphLense;
this.inliningConstraints = new InliningConstraints(appInfo, graphLense);
this.method = method;
this.invocationContext = invocationContext;
@@ -116,48 +120,73 @@
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
DexType ownerType = application.getTypeFromName(owner);
DexMethod target = application.getMethod(ownerType, name, desc);
+
+ // Find the DEX invocation type.
+ Invoke.Type type;
switch (opcode) {
case Opcodes.INVOKEDYNAMIC:
- if (JarSourceCode.isCallToPolymorphicSignatureMethod(owner, name)) {
- updateConstraint(inliningConstraints.forInvokePolymorphic(target, invocationContext));
- } else {
- updateConstraint(inliningConstraints.forInvokeCustom());
- }
+ type =
+ JarSourceCode.isCallToPolymorphicSignatureMethod(owner, name)
+ ? Invoke.Type.POLYMORPHIC
+ : Invoke.Type.CUSTOM;
+ assert noNeedToUseGraphLense(target, type);
break;
case Opcodes.INVOKEINTERFACE:
- updateConstraint(inliningConstraints.forInvokeInterface(target, invocationContext));
+ // Could have changed to an invoke-virtual instruction due to vertical class merging
+ // (if an interface is merged into a class).
+ type = graphLense.lookupMethod(target, null, Invoke.Type.INTERFACE).getType();
+ assert type == Invoke.Type.INTERFACE || type == Invoke.Type.VIRTUAL;
break;
case Opcodes.INVOKESPECIAL:
- if (name.equals(Constants.INSTANCE_INITIALIZER_NAME) || ownerType == invocationContext) {
- updateConstraint(inliningConstraints.forInvokeDirect(target, invocationContext));
+ if (name.equals(Constants.INSTANCE_INITIALIZER_NAME)) {
+ type = Invoke.Type.DIRECT;
+ assert noNeedToUseGraphLense(target, type);
+ } else if (ownerType == invocationContext) {
+ // The method could have been publicized.
+ type = graphLense.lookupMethod(target, null, Invoke.Type.DIRECT).getType();
+ assert type == Invoke.Type.DIRECT || type == Invoke.Type.VIRTUAL;
} else {
- updateConstraint(inliningConstraints.forInvokeSuper(target, invocationContext));
+ // This is a super call. Note that the vertical class merger translates some invoke-super
+ // instructions to invoke-direct. However, when that happens, the invoke instruction and
+ // the target method end up being in the same class, and therefore, we will allow inlining
+ // it. The result of using type=SUPER below will be the same, since it leads to the
+ // inlining constraint SAMECLASS.
+ // TODO(christofferqa): Consider using graphLense.lookupMethod (to do this, we need the
+ // context for the graph lense, though).
+ type = Invoke.Type.SUPER;
+ assert noNeedToUseGraphLense(target, type);
}
break;
case Opcodes.INVOKESTATIC:
- updateConstraint(inliningConstraints.forInvokeStatic(target, invocationContext));
+ type = Invoke.Type.STATIC;
+ assert noNeedToUseGraphLense(target, type);
break;
case Opcodes.INVOKEVIRTUAL:
- // Instructions that target a private method in the same class are translated to
- // invoke-direct.
+ type = Invoke.Type.VIRTUAL;
+ // Instructions that target a private method in the same class translates to invoke-direct.
if (target.holder == method.method.holder) {
DexClass clazz = appInfo.definitionFor(target.holder);
if (clazz != null && clazz.lookupDirectMethod(target) != null) {
- updateConstraint(inliningConstraints.forInvokeDirect(target, invocationContext));
- break;
+ type = Invoke.Type.DIRECT;
}
}
-
- updateConstraint(inliningConstraints.forInvokeVirtual(target, invocationContext));
+ assert noNeedToUseGraphLense(target, type);
break;
default:
throw new Unreachable("Unexpected opcode " + opcode);
}
+
+ updateConstraint(inliningConstraints.forInvoke(target, type, invocationContext));
+ }
+
+ private boolean noNeedToUseGraphLense(DexMethod method, Invoke.Type type) {
+ assert graphLense.lookupMethod(method, null, type).getType() == type;
+ return true;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 57e8514..581ac8a 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentiferNameString;
import static com.android.tools.r8.naming.IdentifierNameStringUtils.inferMemberOrTypeFromNameString;
import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
+import static com.android.tools.r8.naming.IdentifierNameStringUtils.warnUndeterminedIdentifier;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexEncodedField;
@@ -31,7 +32,9 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Streams;
import java.util.Arrays;
import java.util.List;
@@ -44,12 +47,14 @@
private final AppInfo appInfo;
private final DexItemFactory dexItemFactory;
private final Set<DexItem> identifierNameStrings;
+ private final InternalOptions options;
- public IdentifierNameStringMarker(AppInfoWithLiveness appInfo) {
+ public IdentifierNameStringMarker(AppInfoWithLiveness appInfo, InternalOptions options) {
this.appInfo = appInfo;
this.dexItemFactory = appInfo.dexItemFactory;
// Note that this info is only available at AppInfoWithLiveness.
this.identifierNameStrings = appInfo.identifierNameStrings;
+ this.options = options;
}
public void decoupleIdentifierNameStringsInFields() {
@@ -77,6 +82,7 @@
}
public void decoupleIdentifierNameStringsInMethod(DexEncodedMethod encodedMethod, IRCode code) {
+ Origin origin = appInfo.originFor(code.method.method.getHolder());
ListIterator<BasicBlock> blocks = code.listIterator();
while (blocks.hasNext()) {
BasicBlock block = blocks.next();
@@ -104,11 +110,17 @@
? instruction.asStaticPut().inValue()
: instruction.asInstancePut().value();
if (!in.isConstString()) {
+ if (options.proguardConfiguration.isObfuscating()) {
+ warnUndeterminedIdentifier(options.reporter, field, origin, instruction, null);
+ }
continue;
}
DexString original = in.getConstInstruction().asConstString().getValue();
DexItemBasedString itemBasedString = inferMemberOrTypeFromNameString(appInfo, original);
if (itemBasedString == null) {
+ if (options.proguardConfiguration.isObfuscating()) {
+ warnUndeterminedIdentifier(options.reporter, field, origin, instruction, original);
+ }
continue;
}
// Move the cursor back to $fieldPut
@@ -162,6 +174,10 @@
if (isReflectionMethod(dexItemFactory, invokedMethod)) {
DexItemBasedString itemBasedString = identifyIdentiferNameString(appInfo, invoke);
if (itemBasedString == null) {
+ if (options.proguardConfiguration.isObfuscating()) {
+ warnUndeterminedIdentifier(
+ options.reporter, invokedMethod, origin, instruction, null);
+ }
continue;
}
DexType returnType = invoke.getReturnType();
@@ -206,12 +222,20 @@
for (int i = 0; i < ins.size(); i++) {
Value in = ins.get(i);
if (!in.isConstString()) {
+ if (options.proguardConfiguration.isObfuscating()) {
+ warnUndeterminedIdentifier(
+ options.reporter, invokedMethod, origin, instruction, null);
+ }
continue;
}
DexString original = in.getConstInstruction().asConstString().getValue();
DexItemBasedString itemBasedString =
inferMemberOrTypeFromNameString(appInfo, original);
if (itemBasedString == null) {
+ if (options.proguardConfiguration.isObfuscating()) {
+ warnUndeterminedIdentifier(
+ options.reporter, invokedMethod, origin, instruction, original);
+ }
continue;
}
// Move the cursor back to $invoke
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index 4c64cf6..471b3f8 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexItemBasedString;
import com.android.tools.r8.graph.DexItemFactory;
@@ -23,6 +24,10 @@
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.TextPosition;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import java.util.List;
import java.util.Map;
@@ -366,4 +371,27 @@
}
return new DexTypeList(types);
}
+
+ public static void warnUndeterminedIdentifier(
+ Reporter reporter,
+ DexItem member,
+ Origin origin,
+ Instruction instruction,
+ DexString original) {
+ assert member instanceof DexField || member instanceof DexMethod;
+ String kind = member instanceof DexField ? "field" : "method";
+ String originalMessage = original == null ? "what identifier string flows to "
+ : "what '" + original.toString() + "' refers to, which flows to ";
+ String message =
+ "Cannot determine " + originalMessage + member.toSourceString()
+ + " that is specified in -identifiernamestring rules."
+ + " Thus, not all identifier strings flowing to that " + kind
+ + " are renamed, which can cause resolution failures at runtime.";
+ StringDiagnostic diagnostic =
+ instruction.getPosition().line >= 1
+ ? new StringDiagnostic(message, origin,
+ new TextPosition(0L, instruction.getPosition().line, 1))
+ : new StringDiagnostic(message, origin);
+ reporter.warning(diagnostic);
+ }
}
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 a1cc440..061b9bf 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -5,6 +5,7 @@
import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentiferNameString;
import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
+import static com.android.tools.r8.naming.IdentifierNameStringUtils.warnUndeterminedIdentifier;
import static com.android.tools.r8.shaking.ProguardConfigurationUtils.buildIdentifierNameStringRule;
import com.android.tools.r8.Diagnostic;
@@ -39,6 +40,7 @@
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.shaking.protolite.ProtoLiteExtension;
@@ -1357,11 +1359,13 @@
}
private void handleProguardReflectiveBehavior(DexEncodedMethod method) {
- IRCode code = method.buildIR(appInfo, options, appInfo.originFor(method.method.holder));
- code.instructionIterator().forEachRemaining(this::handleProguardReflectiveBehavior);
+ Origin origin = appInfo.originFor(method.method.holder);
+ IRCode code = method.buildIR(appInfo, options, origin);
+ code.instructionIterator().forEachRemaining(instr ->
+ handleProguardReflectiveBehavior(instr, origin));
}
- private void handleProguardReflectiveBehavior(Instruction instruction) {
+ private void handleProguardReflectiveBehavior(Instruction instruction, Origin origin) {
if (!instruction.isInvokeMethod()) {
return;
}
@@ -1372,6 +1376,9 @@
}
DexItemBasedString itemBasedString = identifyIdentiferNameString(appInfo, invoke);
if (itemBasedString == null) {
+ if (options.proguardConfiguration.isObfuscating()) {
+ warnUndeterminedIdentifier(options.reporter, invokedMethod, origin, instruction, null);
+ }
return;
}
if (itemBasedString.basedOn instanceof DexType) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 2fbdab2..0840d91 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -146,8 +146,8 @@
private final Timing timing;
private Collection<DexMethod> invokes;
- // Set of merge candidates.
- private final Set<DexProgramClass> mergeCandidates = new HashSet<>();
+ // Set of merge candidates. Note that this must have a deterministic iteration order.
+ private final Set<DexProgramClass> mergeCandidates = new LinkedHashSet<>();
// Map from source class to target class.
private final Map<DexType, DexType> mergedClasses = new HashMap<>();
@@ -449,7 +449,7 @@
// different proto, it could be the case that a method with the given name is overloaded.
DexProto existing =
overloadingInfo.computeIfAbsent(signature.name, key -> signature.proto);
- if (!existing.equals(signature.proto)) {
+ if (existing != DexProto.SENTINEL && !existing.equals(signature.proto)) {
// Mark that this signature is overloaded by mapping it to SENTINEL.
overloadingInfo.put(signature.name, DexProto.SENTINEL);
}
@@ -815,25 +815,35 @@
mergeMethods(virtualMethods.values(), target.virtualMethods());
// Step 2: Merge fields
- Set<Wrapper<DexField>> existingFieldSignatures = new HashSet<>();
- addAll(existingFieldSignatures, target.fields(), FieldSignatureEquivalence.get());
+ Set<DexString> existingFieldNames = new HashSet<>();
+ for (DexEncodedField field : target.fields()) {
+ existingFieldNames.add(field.field.name);
+ }
+ // In principle, we could allow multiple fields with the same name, and then only rename the
+ // field in the end when we are done merging all the classes, if it it turns out that the two
+ // fields ended up having the same type. This would not be too expensive, since we visit the
+ // entire program using VerticalClassMerger.TreeFixer anyway.
+ //
+ // For now, we conservatively report that a signature is already taken if there is a field
+ // with the same name. If minification is used with -overloadaggressively, this is solved
+ // later anyway.
Predicate<DexField> availableFieldSignatures =
- field -> !existingFieldSignatures.contains(FieldSignatureEquivalence.get().wrap(field));
+ field -> !existingFieldNames.contains(field.name);
DexEncodedField[] mergedInstanceFields =
mergeFields(
source.instanceFields(),
target.instanceFields(),
availableFieldSignatures,
- existingFieldSignatures);
+ existingFieldNames);
DexEncodedField[] mergedStaticFields =
mergeFields(
source.staticFields(),
target.staticFields(),
availableFieldSignatures,
- existingFieldSignatures);
+ existingFieldNames);
// Step 3: Merge interfaces
Set<DexType> interfaces = mergeArrays(target.interfaces.values, source.interfaces.values);
@@ -1006,13 +1016,13 @@
DexEncodedField[] sourceFields,
DexEncodedField[] targetFields,
Predicate<DexField> availableFieldSignatures,
- Set<Wrapper<DexField>> existingFieldSignatures) {
+ Set<DexString> existingFieldNames) {
DexEncodedField[] result = new DexEncodedField[sourceFields.length + targetFields.length];
// Add fields from source
int i = 0;
for (DexEncodedField field : sourceFields) {
DexEncodedField resultingField = renameFieldIfNeeded(field, availableFieldSignatures);
- existingFieldSignatures.add(FieldSignatureEquivalence.get().wrap(resultingField.field));
+ existingFieldNames.add(resultingField.field.name);
deferredRenamings.map(field.field, resultingField.field);
result[i] = resultingField;
i++;
@@ -1396,14 +1406,32 @@
@Override
public DexType lookupType(DexType type) {
- return type == source ? target : type;
+ return type == source ? target : mergedClasses.getOrDefault(type, type);
}
@Override
public GraphLenseLookupResult lookupMethod(
DexMethod method, DexEncodedMethod context, Type type) {
- return new GraphLenseLookupResult(
- renamedMembersLense.methodMap.getOrDefault(method, method), type);
+ // First look up the method using the existing graph lense (for example, the type will have
+ // changed if the method was publicized by ClassAndMemberPublicizer).
+ GraphLenseLookupResult lookup = graphLense.lookupMethod(method, context, type);
+ DexMethod previousMethod = lookup.getMethod();
+ Type previousType = lookup.getType();
+ // Then check if there is a renaming due to the vertical class merger.
+ DexMethod newMethod = renamedMembersLense.methodMap.get(previousMethod);
+ if (newMethod != null) {
+ if (previousType == Type.INTERFACE) {
+ // If an interface has been merged into a class, invoke-interface needs to be translated
+ // to invoke-virtual.
+ DexClass clazz = appInfo.definitionFor(newMethod.holder);
+ if (clazz != null && !clazz.accessFlags.isInterface()) {
+ assert appInfo.definitionFor(method.holder).accessFlags.isInterface();
+ return new GraphLenseLookupResult(newMethod, Type.VIRTUAL);
+ }
+ }
+ return new GraphLenseLookupResult(newMethod, previousType);
+ }
+ return new GraphLenseLookupResult(previousMethod, previousType);
}
@Override
diff --git a/src/test/examples/classmerging/FieldCollisionTest.java b/src/test/examples/classmerging/FieldCollisionTest.java
new file mode 100644
index 0000000..5bb2260
--- /dev/null
+++ b/src/test/examples/classmerging/FieldCollisionTest.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package classmerging;
+
+public class FieldCollisionTest {
+
+ private static final B SENTINEL_A = new B("A");
+ private static final B SENTINEL_B = new B("B");
+
+ public static void main(String[] args) {
+ B obj = new B();
+ System.out.println(obj.toString());
+ }
+
+ // Will be merged into B.
+ public static class A {
+
+ // After class merging, this field will have the same name and type as the field B.obj,
+ // unless we handle the collision.
+ protected final A obj = SENTINEL_A;
+ }
+
+ public static class B extends A {
+
+ protected final String message;
+ protected final B obj = SENTINEL_B;
+
+ public B() {
+ this(null);
+ }
+
+ public B(String message) {
+ this.message = message;
+ }
+
+ @Override
+ public String toString() {
+ return obj.message + System.lineSeparator() + ((B) super.obj).message;
+ }
+ }
+}
diff --git a/src/test/examples/classmerging/MethodCollisionTest.java b/src/test/examples/classmerging/MethodCollisionTest.java
new file mode 100644
index 0000000..a9010a4
--- /dev/null
+++ b/src/test/examples/classmerging/MethodCollisionTest.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package classmerging;
+
+public class MethodCollisionTest {
+
+ public static void main(String[] args) {
+ new B().m();
+ new D().m();
+ }
+
+ public static class A {
+
+ // After class merging, this method will have the same signature as the method B.m,
+ // unless we handle the collision.
+ private A m() {
+ System.out.println("A.m");
+ return null;
+ }
+
+ public void invokeM() {
+ m();
+ }
+ }
+
+ public static class B extends A {
+
+ private B m() {
+ System.out.println("B.m");
+ invokeM();
+ return null;
+ }
+ }
+
+ public static class C {
+
+ // After class merging, this method will have the same signature as the method D.m,
+ // unless we handle the collision.
+ public C m() {
+ System.out.println("C.m");
+ return null;
+ }
+ }
+
+ public static class D extends C {
+
+ public D m() {
+ System.out.println("D.m");
+ super.m();
+ return null;
+ }
+ }
+
+
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index b3e0ad1..4ba014e 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -19,6 +19,12 @@
-keep public class classmerging.ExceptionTest {
public static void main(...);
}
+-keep public class classmerging.FieldCollisionTest {
+ public static void main(...);
+}
+-keep public class classmerging.MethodCollisionTest {
+ public static void main(...);
+}
-keep public class classmerging.RewritePinnedMethodTest {
public static void main(...);
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index e0d73e8..f28c86f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -187,6 +187,22 @@
}
@Test
+ public void testFieldCollision() throws Exception {
+ String main = "classmerging.FieldCollisionTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("FieldCollisionTest.class"),
+ CF_DIR.resolve("FieldCollisionTest$A.class"),
+ CF_DIR.resolve("FieldCollisionTest$B.class")
+ };
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.FieldCollisionTest",
+ "classmerging.FieldCollisionTest$B");
+ runTest(main, programFiles, preservedClassNames::contains);
+ }
+
+ @Test
public void testLambdaRewriting() throws Exception {
String main = "classmerging.LambdaRewritingTest";
Path[] programFiles =
@@ -210,6 +226,31 @@
}
@Test
+ public void testMethodCollision() throws Exception {
+ String main = "classmerging.MethodCollisionTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("MethodCollisionTest.class"),
+ CF_DIR.resolve("MethodCollisionTest$A.class"),
+ CF_DIR.resolve("MethodCollisionTest$B.class"),
+ CF_DIR.resolve("MethodCollisionTest$C.class"),
+ CF_DIR.resolve("MethodCollisionTest$D.class")
+ };
+ // TODO(christofferqa): Currently we do not allow merging A into B because we find a collision.
+ // However, we are free to change the names of private methods, so we should handle them similar
+ // to fields (i.e., we should allow merging A into B). This would also improve the performance
+ // of the collision detector, because it would only have to consider non-private methods.
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.MethodCollisionTest",
+ "classmerging.MethodCollisionTest$A",
+ "classmerging.MethodCollisionTest$B",
+ "classmerging.MethodCollisionTest$C",
+ "classmerging.MethodCollisionTest$D");
+ runTest(main, programFiles, preservedClassNames::contains);
+ }
+
+ @Test
public void testPinnedParameterTypes() throws Exception {
String main = "classmerging.PinnedParameterTypesTest";
Path[] programFiles =
diff --git a/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
new file mode 100644
index 0000000..ddd0aa2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
@@ -0,0 +1,167 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.DiagnosticsChecker;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.KeepingDiagnosticHandler;
+import com.android.tools.r8.utils.Reporter;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Method;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+class WarnReflectiveAccessTestMain {
+ int counter = 0;
+
+ WarnReflectiveAccessTestMain() {
+ }
+
+ public void foo() {
+ System.out.println("TestMain::foo(" + counter++ + ")");
+ }
+
+ public int boo() {
+ System.out.println("TestMain::boo(" + counter + ")");
+ return counter;
+ }
+
+ public static void main(String[] args) throws Exception {
+ WarnReflectiveAccessTestMain instance = new WarnReflectiveAccessTestMain();
+
+ StringBuilder builder = new StringBuilder();
+ builder.append("f");
+ builder.append("o").append("o");
+ Method foo = instance.getClass().getDeclaredMethod(builder.toString());
+ foo.invoke(instance);
+ }
+}
+
+@RunWith(VmTestRunner.class)
+public class WarnReflectiveAccessTest extends TestBase {
+
+ private KeepingDiagnosticHandler handler;
+ private Reporter reporter;
+
+ @Before
+ public void reset() {
+ handler = new KeepingDiagnosticHandler();
+ reporter = new Reporter(handler);
+ }
+
+ private AndroidApp runR8(
+ byte[][] classes,
+ Class main,
+ Path out,
+ boolean enableMinification,
+ boolean forceProguardCompatibility)
+ throws Exception {
+ String minificationModifier = enableMinification ? ",allowobfuscation " : " ";
+ String reflectionRules = forceProguardCompatibility ? ""
+ : "-identifiernamestring class java.lang.Class {\n"
+ + "static java.lang.Class forName(java.lang.String);\n"
+ + "java.lang.reflect.Method getDeclaredMethod(java.lang.String, java.lang.Class[]);\n"
+ + "}\n";
+ R8Command.Builder commandBuilder =
+ R8Command.builder(reporter)
+ .addProguardConfiguration(
+ ImmutableList.of(
+ "-keep" + minificationModifier + "class " + main.getCanonicalName() + " {"
+ + " <methods>;"
+ + "}",
+ "-printmapping",
+ reflectionRules),
+ Origin.unknown())
+ .setOutput(out, OutputMode.DexIndexed);
+ for (byte[] clazz : classes) {
+ commandBuilder.addClassProgramData(clazz, Origin.unknown());
+ }
+ commandBuilder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ return ToolHelper.runR8(commandBuilder.build(), o -> {
+ o.enableInlining = false;
+ o.forceProguardCompatibility = forceProguardCompatibility;
+ });
+ }
+
+ private void reflectionWithBuildter(
+ boolean enableMinification,
+ boolean forceProguardCompatibility) throws Exception {
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(WarnReflectiveAccessTestMain.class)
+ };
+ Path out = temp.getRoot().toPath();
+ AndroidApp processedApp = runR8(classes, WarnReflectiveAccessTestMain.class, out,
+ enableMinification, forceProguardCompatibility);
+
+ String main = WarnReflectiveAccessTestMain.class.getCanonicalName();
+ DexInspector dexInspector = new DexInspector(processedApp);
+ ClassSubject mainSubject = dexInspector.clazz(main);
+ assertThat(mainSubject, isPresent());
+
+ ProcessResult javaOutput = runOnJavaRaw(main, classes);
+ assertEquals(0, javaOutput.exitCode);
+ assertThat(javaOutput.stdout, containsString("TestMain::foo"));
+
+ ProcessResult artOutput = runOnArtRaw(processedApp,
+ enableMinification ? mainSubject.getFinalName() : main);
+ if (enableMinification) {
+ assertNotEquals(0, artOutput.exitCode);
+ assertThat(artOutput.stderr, containsString("NoSuchMethodError"));
+ } else {
+ assertEquals(0, artOutput.exitCode);
+ assertThat(artOutput.stdout, containsString("TestMain::foo"));
+ }
+ }
+
+ @Test
+ public void test_minification_forceProguardCompatibility() throws Exception {
+ reflectionWithBuildter(true, true);
+ assertFalse(handler.warnings.isEmpty());
+ DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 54, 1,
+ "Cannot determine", "getDeclaredMethod", "resolution failure");
+ }
+
+ @Test
+ public void test_noMinification_forceProguardCompatibility() throws Exception {
+ reflectionWithBuildter(false, true);
+ assertFalse(handler.warnings.isEmpty());
+ DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 54, 1,
+ "Cannot determine", "getDeclaredMethod", "resolution failure");
+ }
+
+ @Test
+ public void test_minification_R8() throws Exception {
+ reflectionWithBuildter(true, false);
+ assertFalse(handler.warnings.isEmpty());
+ DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 54, 1,
+ "Cannot determine", "getDeclaredMethod", "-identifiernamestring", "resolution failure");
+ }
+
+ @Test
+ public void test_noMinification_R8() throws Exception {
+ reflectionWithBuildter(false, false);
+ assertFalse(handler.warnings.isEmpty());
+ DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 54, 1,
+ "Cannot determine", "getDeclaredMethod", "-identifiernamestring", "resolution failure");
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 0b8de12..eba3be8 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -413,6 +413,8 @@
public abstract String getOriginalDescriptor();
+ public abstract String getFinalName();
+
public abstract String getFinalDescriptor();
public abstract boolean isMemberClass();
@@ -482,6 +484,11 @@
}
@Override
+ public String getFinalName() {
+ return null;
+ }
+
+ @Override
public String getFinalDescriptor() {
return null;
}
@@ -639,7 +646,7 @@
if (naming != null) {
return naming.originalName;
} else {
- return DescriptorUtils.descriptorToJavaType(getFinalDescriptor());
+ return getFinalName();
}
}
@@ -653,6 +660,11 @@
}
@Override
+ public String getFinalName() {
+ return DescriptorUtils.descriptorToJavaType(getFinalDescriptor());
+ }
+
+ @Override
public String getFinalDescriptor() {
return dexClass.type.descriptor.toString();
}