Merge "Add support for -dontnote"
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index f830427..ca6c1de 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.cf.code;
import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexMethodHandle;
@@ -43,14 +42,7 @@
bsmArgs[i] = decodeBootstrapArgument(bootstrapArgs.get(i), lens);
}
Handle bsmHandle = bootstrapMethod.toAsmHandle(lens);
- DexString methodName;
- if (lens.isIdentityLens()) {
- methodName = callSite.methodName;
- } else if (!callSite.interfaceMethods.isEmpty()) {
- methodName = lens.lookupName(callSite.interfaceMethods.get(0));
- } else {
- throw new Unimplemented("Minification of non-lambda InvokeDynamic not supported");
- }
+ DexString methodName = lens.lookupMethodName(callSite);
visitor.visitInvokeDynamicInsn(
methodName.toString(), callSite.methodProto.toDescriptorString(lens), bsmHandle, bsmArgs);
}
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 f024c13..702d628 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -3,14 +3,19 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
@@ -190,6 +195,55 @@
return result;
}
+ /**
+ * Resolve the methods implemented by the lambda expression that created the {@code callSite}.
+ *
+ * <p>If {@code callSite} was not created as a result of a lambda expression (i.e. the metafactory
+ * is not {@code LambdaMetafactory}), the empty set is returned.
+ *
+ * <p>If the metafactory is neither {@code LambdaMetafactory} nor {@code StringConcatFactory}, a
+ * warning is issued.
+ *
+ * <p>The returned set of methods all have {@code callSite.methodName} as the method name.
+ *
+ * @param callSite Call site to resolve.
+ * @param reporter Reporter used when an unknown metafactory is encountered.
+ * @return Methods implemented by the lambda expression that created the {@code callSite}.
+ */
+ public Set<DexEncodedMethod> lookupLambdaImplementedMethods(
+ DexCallSite callSite, Reporter reporter) {
+ List<DexType> callSiteInterfaces =
+ LambdaDescriptor.getInterfaces(callSite, this, dexItemFactory);
+ if (callSiteInterfaces == null) {
+ if (!isStringConcat(callSite.bootstrapMethod)) {
+ Diagnostic message =
+ new StringDiagnostic("Unknown bootstrap method " + callSite.bootstrapMethod);
+ reporter.warning(message);
+ }
+ return Collections.emptySet();
+ }
+ Set<DexEncodedMethod> result = new HashSet<>();
+ for (DexType iface : callSiteInterfaces) {
+ assert iface.isInterface();
+ DexClass clazz = definitionFor(iface);
+ if (clazz != null) {
+ clazz.forEachMethod(
+ method -> {
+ if (method.method.name == callSite.methodName && method.accessFlags.isAbstract()) {
+ result.add(method);
+ }
+ });
+ }
+ }
+ return result;
+ }
+
+ private boolean isStringConcat(DexMethodHandle bootstrapMethod) {
+ return bootstrapMethod.type.isInvokeStatic()
+ && (bootstrapMethod.asMethod() == dexItemFactory.stringConcatWithConstantsMethod
+ || bootstrapMethod.asMethod() == dexItemFactory.stringConcatMethod);
+ }
+
@Override
public void registerNewType(DexType newType, DexType superType) {
// Register the relationship between this type and its superType.
diff --git a/src/main/java/com/android/tools/r8/graph/DexCallSite.java b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
index ac954f8..c193134 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCallSite.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
@@ -29,27 +29,22 @@
public final DexMethodHandle bootstrapMethod;
public final List<DexValue> bootstrapArgs;
- public final List<DexMethod> interfaceMethods;
-
private DexEncodedArray encodedArray = null;
DexCallSite(
DexString methodName,
DexProto methodProto,
DexMethodHandle bootstrapMethod,
- List<DexValue> bootstrapArgs,
- List<DexMethod> interfaceMethods) {
+ List<DexValue> bootstrapArgs) {
assert methodName != null;
assert methodProto != null;
assert bootstrapMethod != null;
assert bootstrapArgs != null;
- assert interfaceMethods != null;
this.methodName = methodName;
this.methodProto = methodProto;
this.bootstrapMethod = bootstrapMethod;
this.bootstrapArgs = bootstrapArgs;
- this.interfaceMethods = interfaceMethods;
}
public static DexCallSite fromAsmInvokeDynamic(
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 364c0ee..6b99724 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -15,9 +15,7 @@
import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
-import com.android.tools.r8.graph.DexValue.DexValueMethodType;
import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.naming.NamingLens;
import com.google.common.base.Strings;
@@ -27,7 +25,6 @@
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
@@ -257,6 +254,33 @@
createProto(callSiteType, lookupType, stringType, methodTypeType, objectArrayType),
createString(METAFACTORY_ALT_METHOD_NAME));
+ public final DexType stringConcatFactoryType =
+ createType("Ljava/lang/invoke/StringConcatFactory;");
+
+ public final DexMethod stringConcatWithConstantsMethod =
+ createMethod(
+ stringConcatFactoryType,
+ createProto(
+ callSiteType,
+ lookupType,
+ stringType,
+ methodTypeType,
+ stringType,
+ objectArrayType),
+ createString("makeConcatWithConstants")
+ );
+
+ public final DexMethod stringConcatMethod =
+ createMethod(
+ stringConcatFactoryType,
+ createProto(
+ callSiteType,
+ lookupType,
+ stringType,
+ methodTypeType),
+ createString("makeConcat")
+ );
+
private boolean skipNameValidationForTesting = false;
public void setSkipNameValidationForTesting(boolean skipNameValidationForTesting) {
@@ -588,46 +612,10 @@
DexString methodName, DexProto methodProto,
DexMethodHandle bootstrapMethod, List<DexValue> bootstrapArgs) {
assert !sorted;
- List<DexMethod> interfaceMethods =
- getCallSiteInterfaceMethods(methodName, methodProto, bootstrapMethod, bootstrapArgs);
- DexCallSite callSite =
- new DexCallSite(methodName, methodProto, bootstrapMethod, bootstrapArgs, interfaceMethods);
+ DexCallSite callSite = new DexCallSite(methodName, methodProto, bootstrapMethod, bootstrapArgs);
return canonicalize(callSites, callSite);
}
- private List<DexMethod> getCallSiteInterfaceMethods(
- DexString methodName,
- DexProto methodProto,
- DexMethodHandle bootstrapMethodHandle,
- List<DexValue> bootstrapArgs) {
- // TODO(mathiasr): Unify this with LambdaDescriptor.infer().
- if (!bootstrapMethodHandle.type.isInvokeStatic()) {
- return Collections.emptyList();
- }
- DexMethod bootstrapMethod = bootstrapMethodHandle.asMethod();
- if (bootstrapMethod != metafactoryMethod && bootstrapMethod != metafactoryAltMethod) {
- return Collections.emptyList();
- }
- DexType interfaceType = methodProto.returnType;
- assert bootstrapMethod == metafactoryAltMethod || bootstrapArgs.size() == 3;
- // Signature of main functional interface method.
- // In Java docs, this argument is named 'samMethodType'.
- DexValueMethodType funcErasedSignature = (DexValueMethodType) bootstrapArgs.get(0);
- DexMethod mainMethod = createMethod(interfaceType, funcErasedSignature.value, methodName);
- if (bootstrapMethod == metafactoryAltMethod) {
- List<DexMethod> result = new ArrayList<>();
- result.add(mainMethod);
- LambdaDescriptor.extractAltMetafactory(
- this,
- bootstrapArgs,
- type -> result.add(createMethod(type, funcErasedSignature.value, methodName)),
- bridge -> {});
- return result;
- } else {
- return Collections.singletonList(mainMethod);
- }
- }
-
public DexMethod createMethod(DexString clazzDescriptor, DexString name,
DexString returnTypeDescriptor,
DexString[] parameterDescriptors) {
@@ -738,4 +726,8 @@
synchronized public void forAllTypes(Consumer<DexType> f) {
new ArrayList<>(types.values()).forEach(f);
}
+
+ public void forAllCallSites(Consumer<DexCallSite> f) {
+ new ArrayList<>(callSites.values()).forEach(f);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index 49a797a..b7653fa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -56,10 +56,10 @@
targetMethod = null;
}
- private LambdaDescriptor(LambdaRewriter rewriter, DexCallSite callSite,
+ private LambdaDescriptor(AppInfo appInfo, DexCallSite callSite,
DexString name, DexProto erasedProto, DexProto enforcedProto,
DexMethodHandle implHandle, DexType mainInterface, DexTypeList captures) {
- assert rewriter != null;
+ assert appInfo != null;
assert callSite != null;
assert name != null;
assert erasedProto != null;
@@ -76,7 +76,7 @@
this.captures = captures;
this.interfaces.add(mainInterface);
- this.targetMethod = lookupTargetMethod(rewriter);
+ this.targetMethod = lookupTargetMethod(appInfo);
}
final DexType getImplReceiverType() {
@@ -88,13 +88,12 @@
return captures.length > 0 ? captures[0] : params[0];
}
- private DexEncodedMethod lookupTargetMethod(LambdaRewriter rewriter) {
+ private DexEncodedMethod lookupTargetMethod(AppInfo appInfo) {
// Find the lambda's impl-method target.
DexMethod method = implHandle.asMethod();
switch (implHandle.type) {
case INVOKE_DIRECT:
case INVOKE_INSTANCE: {
- AppInfo appInfo = rewriter.converter.appInfo;
DexEncodedMethod target = appInfo.lookupVirtualTarget(getImplReceiverType(), method);
if (target == null) {
target = appInfo.lookupDirectTarget(method);
@@ -106,21 +105,18 @@
}
case INVOKE_STATIC: {
- AppInfo appInfo = rewriter.converter.appInfo;
DexEncodedMethod target = appInfo.lookupStaticTarget(method);
assert target == null || target.accessFlags.isStatic();
return target;
}
case INVOKE_CONSTRUCTOR: {
- AppInfo appInfo = rewriter.converter.appInfo;
DexEncodedMethod target = appInfo.lookupDirectTarget(method);
assert target == null || target.accessFlags.isConstructor();
return target;
}
case INVOKE_INTERFACE: {
- AppInfo appInfo = rewriter.converter.appInfo;
DexEncodedMethod target = appInfo.lookupVirtualTarget(getImplReceiverType(), method);
assert target == null || isInstanceMethod(target);
return target;
@@ -220,7 +216,7 @@
* Matches call site for lambda metafactory invocation pattern and
* returns extracted match information, or null if match failed.
*/
- static LambdaDescriptor infer(LambdaRewriter rewriter, DexCallSite callSite) {
+ static LambdaDescriptor infer(DexCallSite callSite, AppInfo appInfo, DexItemFactory factory) {
// We expect bootstrap method to be either `metafactory` or `altMetafactory` method
// of `java.lang.invoke.LambdaMetafactory` class. Both methods are static.
if (!callSite.bootstrapMethod.type.isInvokeStatic()) {
@@ -228,8 +224,8 @@
}
DexMethod bootstrapMethod = callSite.bootstrapMethod.asMethod();
- boolean isMetafactoryMethod = bootstrapMethod == rewriter.factory.metafactoryMethod;
- boolean isAltMetafactoryMethod = bootstrapMethod == rewriter.factory.metafactoryAltMethod;
+ boolean isMetafactoryMethod = bootstrapMethod == factory.metafactoryMethod;
+ boolean isAltMetafactoryMethod = bootstrapMethod == factory.metafactoryAltMethod;
if (!isMetafactoryMethod && !isAltMetafactoryMethod) {
// It is not a lambda, thus no need to manage this call site.
return LambdaDescriptor.MATCH_FAILED;
@@ -254,7 +250,7 @@
DexValue.DexValueMethodType funcEnforcedSignature =
getBootstrapArgument(callSite.bootstrapArgs, 2, DexValue.DexValueMethodType.class);
if (!isEnforcedSignatureValid(
- rewriter, funcEnforcedSignature.value, funcErasedSignature.value)) {
+ factory, funcEnforcedSignature.value, funcErasedSignature.value)) {
throw new Unreachable(
"Enforced and erased signatures are inconsistent in " + callSite.toString());
}
@@ -268,7 +264,7 @@
DexTypeList captures = lambdaFactoryProto.parameters;
// Create a match.
- LambdaDescriptor match = new LambdaDescriptor(rewriter, callSite,
+ LambdaDescriptor match = new LambdaDescriptor(appInfo, callSite,
funcMethodName, funcErasedSignature.value, funcEnforcedSignature.value,
lambdaImplMethodHandle, mainFuncInterface, captures);
@@ -279,7 +275,7 @@
}
} else {
extractAltMetafactory(
- rewriter.factory,
+ factory,
callSite.bootstrapArgs,
interfaceType -> {
if (!match.interfaces.contains(interfaceType)) {
@@ -292,7 +288,7 @@
return match;
}
- public static void extractAltMetafactory(
+ private static void extractAltMetafactory(
DexItemFactory dexItemFactory,
List<DexValue> bootstrapArgs,
Consumer<DexType> interfaceConsumer,
@@ -333,6 +329,16 @@
}
}
+ public static List<DexType> getInterfaces(
+ DexCallSite callSite, AppInfo appInfo, DexItemFactory factory) {
+ LambdaDescriptor descriptor = infer(callSite, appInfo, factory);
+ if (descriptor == LambdaDescriptor.MATCH_FAILED) {
+ return null;
+ }
+ assert descriptor.interfaces != null;
+ return descriptor.interfaces;
+ }
+
@SuppressWarnings("unchecked")
private static <T> T getBootstrapArgument(List<DexValue> bootstrapArgs, int i, Class<T> clazz) {
if (bootstrapArgs.size() < i) {
@@ -347,8 +353,8 @@
}
private static boolean isEnforcedSignatureValid(
- LambdaRewriter rewriter, DexProto enforced, DexProto erased) {
- if (!isSameOrDerived(rewriter.factory, enforced.returnType, erased.returnType)) {
+ DexItemFactory factory, DexProto enforced, DexProto erased) {
+ if (!isSameOrDerived(factory, enforced.returnType, erased.returnType)) {
return false;
}
DexType[] enforcedValues = enforced.parameters.values;
@@ -358,7 +364,7 @@
return false;
}
for (int i = 0; i < count; i++) {
- if (!isSameOrDerived(rewriter.factory, enforcedValues[i], erasedValues[i])) {
+ if (!isSameOrDerived(factory, enforcedValues[i], erasedValues[i])) {
return false;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 82792df..11ff65b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -190,8 +190,12 @@
// in rare case when another thread has same call site processed concurrently,
// but this is a low price to pay comparing to making whole method synchronous.
LambdaDescriptor descriptor = getKnown(knownCallSites, callSite);
- return descriptor != null ? descriptor
- : putIfAbsent(knownCallSites, callSite, LambdaDescriptor.infer(this, callSite));
+ return descriptor != null
+ ? descriptor
+ : putIfAbsent(
+ knownCallSites,
+ callSite,
+ LambdaDescriptor.infer(callSite, this.converter.appInfo, this.factory));
}
private boolean isInMainDexList(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
index 9e85188..1358598 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
import com.google.common.collect.ImmutableList;
import java.util.IdentityHashMap;
import java.util.Map;
@@ -25,6 +26,7 @@
protected final NamingState<StateType, ?> globalState;
protected final boolean useUniqueMemberNames;
protected final boolean overloadAggressively;
+ protected final Reporter reporter;
MemberNameMinifier(AppInfoWithSubtyping appInfo, RootSet rootSet, InternalOptions options) {
this.appInfo = appInfo;
@@ -32,6 +34,7 @@
this.dictionary = options.proguardConfiguration.getObfuscationDictionary();
this.useUniqueMemberNames = options.proguardConfiguration.isUseUniqueClassMemberNames();
this.overloadAggressively = options.proguardConfiguration.isOverloadAggressively();
+ this.reporter = options.reporter;
this.globalState = NamingState.createRoot(
appInfo.dexItemFactory, dictionary, getKeyTransform(), useUniqueMemberNames);
}
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 25a84b6..c6729e8 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.naming;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
@@ -18,11 +19,13 @@
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.Sets;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;
@@ -88,6 +91,7 @@
class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> {
private final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
+ private final Map<DexCallSite, DexString> callSiteRenaming = new IdentityHashMap<>();
MethodNameMinifier(AppInfoWithSubtyping appInfo, RootSet rootSet, InternalOptions options) {
super(appInfo, rootSet, options);
@@ -104,7 +108,18 @@
}
}
- Map<DexMethod, DexString> computeRenaming(Timing timing) {
+ static class MethodRenaming {
+ final Map<DexMethod, DexString> renaming;
+ final Map<DexCallSite, DexString> callSiteRenaming;
+
+ private MethodRenaming(
+ Map<DexMethod, DexString> renaming, Map<DexCallSite, DexString> callSiteRenaming) {
+ this.renaming = renaming;
+ this.callSiteRenaming = callSiteRenaming;
+ }
+ }
+
+ MethodRenaming computeRenaming(Timing timing) {
// Phase 1: Reserve all the names that need to be kept and allocate linked state in the
// library part.
timing.begin("Phase 1");
@@ -133,7 +148,7 @@
assignNamesToClassesMethods(appInfo.dexItemFactory.objectType, true);
timing.end();
- return renaming;
+ return new MethodRenaming(renaming, callSiteRenaming);
}
private void assignNamesToClassesMethods(DexType type, boolean doPrivates) {
@@ -210,6 +225,53 @@
method, collectedStates, globalStateMap, sourceMethodsMap, originStates, iface));
}
});
+ // If the input program contains a multi-interface lambda expression that implements
+ // interface methods with different protos, we need to make sure that
+ // the implemented lambda methods are renamed to the same name.
+ // To achieve this, we map each DexCallSite that corresponds to a lambda expression to one of
+ // the DexMethods it implements, and we unify the DexMethods that need to be renamed together.
+ Map<DexCallSite, DexMethod> callSites = new IdentityHashMap<>();
+ // Union-find structure to keep track of methods that must be renamed together.
+ // Note that if the input does not use multi-interface lambdas,
+ // unificationParent will remain empty.
+ Map<Wrapper<DexMethod>, Wrapper<DexMethod>> unificationParent = new HashMap<>();
+ appInfo.dexItemFactory.forAllCallSites(
+ callSite -> {
+ Set<Wrapper<DexMethod>> callSiteMethods = new HashSet<>();
+ Set<DexEncodedMethod> implementedMethods =
+ appInfo.lookupLambdaImplementedMethods(callSite, reporter);
+ if (implementedMethods.isEmpty()) {
+ return;
+ }
+ callSites.put(callSite, implementedMethods.iterator().next().method);
+ for (DexEncodedMethod method : implementedMethods) {
+ DexType iface = method.method.holder;
+ assert iface.isInterface();
+ Set<NamingState<DexProto, ?>> collectedStates = getReachableStates(iface, frontierMap);
+ addStatesToGlobalMapForMethod(
+ method, collectedStates, globalStateMap, sourceMethodsMap, originStates, iface);
+ callSiteMethods.add(equivalence.wrap(method.method));
+ }
+ if (callSiteMethods.size() > 1) {
+ // Implemented interfaces have different return types. Unify them.
+ Wrapper<DexMethod> mainKey = callSiteMethods.iterator().next();
+ mainKey = unificationParent.getOrDefault(mainKey, mainKey);
+ for (Wrapper<DexMethod> key : callSiteMethods) {
+ unificationParent.put(key, mainKey);
+ }
+ }
+ });
+ Map<Wrapper<DexMethod>, Set<Wrapper<DexMethod>>> unification = new HashMap<>();
+ for (Wrapper<DexMethod> key : unificationParent.keySet()) {
+ // Find root with path-compression.
+ Wrapper<DexMethod> root = unificationParent.get(key);
+ while (unificationParent.get(root) != root) {
+ Wrapper<DexMethod> k = unificationParent.get(unificationParent.get(root));
+ unificationParent.put(root, k);
+ root = k;
+ }
+ unification.computeIfAbsent(root, k -> new HashSet<>()).add(key);
+ }
timing.end();
// Go over every method and assign a name.
timing.begin("Allocate names");
@@ -218,12 +280,36 @@
List<Wrapper<DexMethod>> methods = new ArrayList<>(globalStateMap.keySet());
methods.sort((a, b) -> globalStateMap.get(b).size() - globalStateMap.get(a).size());
for (Wrapper<DexMethod> key : methods) {
+ if (!unificationParent.getOrDefault(key, key).equals(key)) {
+ continue;
+ }
+ List<MethodNamingState> collectedStates = new ArrayList<>();
+ Set<DexMethod> sourceMethods = Sets.newIdentityHashSet();
+ for (Wrapper<DexMethod> k : unification.getOrDefault(key, Collections.singleton(key))) {
+ DexMethod unifiedMethod = k.get();
+ assert unifiedMethod != null;
+ sourceMethods.addAll(sourceMethodsMap.get(k));
+ for (NamingState<DexProto, ?> namingState : globalStateMap.get(k)) {
+ collectedStates.add(
+ new MethodNamingState(namingState, unifiedMethod.name, unifiedMethod.proto));
+ }
+ }
DexMethod method = key.get();
- assignNameForInterfaceMethodInAllStates(
- method,
- globalStateMap.get(key),
- sourceMethodsMap.get(key),
- originStates.get(key));
+ assert method != null;
+ MethodNamingState originState =
+ new MethodNamingState(originStates.get(key), method.name, method.proto);
+ assignNameForInterfaceMethodInAllStates(collectedStates, sourceMethods, originState);
+ }
+ for (Entry<DexCallSite, DexMethod> entry : callSites.entrySet()) {
+ DexMethod method = entry.getValue();
+ DexString renamed = renaming.get(method);
+ if (originStates.get(equivalence.wrap(method)).isReserved(method.name, method.proto)) {
+ assert renamed == null;
+ callSiteRenaming.put(entry.getKey(), method.name);
+ } else {
+ assert renamed != null;
+ callSiteRenaming.put(entry.getKey(), renamed);
+ }
}
timing.end();
}
@@ -264,41 +350,31 @@
}
private void assignNameForInterfaceMethodInAllStates(
- DexMethod method,
- Set<NamingState<DexProto, ?>> collectedStates,
+ List<MethodNamingState> collectedStates,
Set<DexMethod> sourceMethods,
- NamingState<DexProto, ?> originState) {
- boolean isReserved = false;
- if (globalState.isReserved(method.name, method.proto)) {
- for (NamingState<DexProto, ?> state : collectedStates) {
- if (state.isReserved(method.name, method.proto)) {
- isReserved = true;
- break;
- }
+ MethodNamingState originState) {
+ if (anyIsReserved(collectedStates)) {
+ // This method's name is reserved in at least one naming state, so reserve it everywhere.
+ for (MethodNamingState state : collectedStates) {
+ state.reserveName();
}
- if (isReserved) {
- // This method's name is reserved in at least on naming state, so reserve it everywhere.
- for (NamingState<DexProto, ?> state : collectedStates) {
- state.reserveName(method.name, method.proto);
- }
- return;
- }
+ return;
}
// We use the origin state to allocate a name here so that we can reuse names between different
// unrelated interfaces. This saves some space. The alternative would be to use a global state
// for allocating names, which would save the work to search here.
DexString candidate = null;
do {
- candidate = originState.assignNewNameFor(method.name, method.proto, false);
- for (NamingState<DexProto, ?> state : collectedStates) {
- if (!state.isAvailable(method.name, method.proto, candidate)) {
+ candidate = originState.assignNewNameFor(false);
+ for (MethodNamingState state : collectedStates) {
+ if (!state.isAvailable(candidate)) {
candidate = null;
break;
}
}
} while (candidate == null);
- for (NamingState<DexProto, ?> state : collectedStates) {
- state.addRenaming(method.name, method.proto, candidate);
+ for (MethodNamingState state : collectedStates) {
+ state.addRenaming(candidate);
}
// Rename all methods in interfaces that gave rise to this renaming.
for (DexMethod sourceMethod : sourceMethods) {
@@ -306,6 +382,20 @@
}
}
+ private boolean anyIsReserved(List<MethodNamingState> collectedStates) {
+ DexString name = collectedStates.get(0).getName();
+ Map<DexProto, Boolean> globalStateCache = new HashMap<>();
+ for (MethodNamingState state : collectedStates) {
+ assert state.getName() == name;
+ if (globalStateCache.computeIfAbsent(
+ state.getProto(), proto -> globalState.isReserved(name, proto))
+ && state.isReserved()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void reserveNamesInClasses(DexType type, DexType libraryFrontier,
NamingState<DexProto, ?> parent,
Map<DexType, DexType> frontierMap) {
@@ -356,4 +446,51 @@
globalState.reserveName(method.method.name, method.method.proto);
}
}
+
+ /**
+ * Capture a (name, proto)-pair for a {@link NamingState}. Each method methodState.METHOD(...)
+ * simply defers to parent.METHOD(name, proto, ...). This allows R8 to assign the same name to
+ * methods with different prototypes, which is needed for multi-interface lambdas.
+ */
+ private static class MethodNamingState {
+
+ private final NamingState<DexProto, ?> parent;
+ private final DexString name;
+ private final DexProto proto;
+
+ MethodNamingState(NamingState<DexProto, ?> parent, DexString name, DexProto proto) {
+ assert parent != null;
+ this.parent = parent;
+ this.name = name;
+ this.proto = proto;
+ }
+
+ DexString assignNewNameFor(boolean markAsUsed) {
+ return parent.assignNewNameFor(name, proto, markAsUsed);
+ }
+
+ void reserveName() {
+ parent.reserveName(name, proto);
+ }
+
+ boolean isReserved() {
+ return parent.isReserved(name, proto);
+ }
+
+ boolean isAvailable(DexString candidate) {
+ return parent.isAvailable(name, proto, candidate);
+ }
+
+ void addRenaming(DexString newName) {
+ parent.addRenaming(name, proto, newName);
+ }
+
+ DexString getName() {
+ return name;
+ }
+
+ DexProto getProto() {
+ return proto;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 6bd0c6d..382ac92 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.naming;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -11,6 +12,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
@@ -44,7 +46,7 @@
new ClassNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
timing.end();
timing.begin("MinifyMethods");
- Map<DexMethod, DexString> methodRenaming =
+ MethodRenaming methodRenaming =
new MethodNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
timing.end();
timing.begin("MinifyFields");
@@ -64,12 +66,15 @@
private final AppInfo appInfo;
private final Map<DexItem, DexString> renaming = new IdentityHashMap<>();
- private MinifiedRenaming(Map<DexType, DexString> classRenaming,
- Map<DexMethod, DexString> methodRenaming, Map<DexField, DexString> fieldRenaming,
+ private MinifiedRenaming(
+ Map<DexType, DexString> classRenaming,
+ MethodRenaming methodRenaming,
+ Map<DexField, DexString> fieldRenaming,
AppInfo appInfo) {
this.appInfo = appInfo;
renaming.putAll(classRenaming);
- renaming.putAll(methodRenaming);
+ renaming.putAll(methodRenaming.renaming);
+ renaming.putAll(methodRenaming.callSiteRenaming);
renaming.putAll(fieldRenaming);
}
@@ -90,6 +95,11 @@
}
@Override
+ public DexString lookupMethodName(DexCallSite callSite) {
+ return renaming.getOrDefault(callSite, callSite.methodName);
+ }
+
+ @Override
public DexString lookupName(DexField field) {
return renaming.getOrDefault(field, field.name);
}
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index fe73dc5..f1f13b3 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
+import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexMethod;
@@ -35,6 +36,8 @@
public abstract DexString lookupName(DexMethod method);
+ public abstract DexString lookupMethodName(DexCallSite callSite);
+
public abstract DexString lookupName(DexField field);
public static NamingLens getIdentityLens() {
@@ -86,6 +89,11 @@
}
@Override
+ public DexString lookupMethodName(DexCallSite callSite) {
+ return callSite.methodName;
+ }
+
+ @Override
public DexString lookupName(DexField field) {
return field.name;
}
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 3a31203..25f5cc1 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -158,6 +158,12 @@
private final SetWithReason<DexEncodedField> liveFields = new SetWithReason<>();
/**
+ * Set of interface types for which a lambda expression can be reached. These never have a single
+ * interface implementation.
+ */
+ private final SetWithReason<DexType> instantiatedLambdas = new SetWithReason<>();
+
+ /**
* A queue of items that need processing. Different items trigger different actions:
*/
private final Queue<Action> workList = Queues.newArrayDeque();
@@ -438,6 +444,15 @@
return false;
}
+ @Override
+ public void registerCallSite(DexCallSite callSite) {
+ super.registerCallSite(callSite);
+ for (DexEncodedMethod method :
+ appInfo.lookupLambdaImplementedMethods(callSite, options.reporter)) {
+ markLambdaInstantiated(method.method.holder, currentMethod);
+ }
+ }
+
private boolean registerConstClassOrCheckCast(DexType type) {
if (forceProguardCompatibility) {
DexType baseType = type.toBaseType(appInfo.dexItemFactory);
@@ -817,6 +832,10 @@
workList.add(Action.markInstantiated(clazz, KeepReason.instantiatedIn(method)));
}
+ private void markLambdaInstantiated(DexType itf, DexEncodedMethod method) {
+ instantiatedLambdas.add(itf, KeepReason.instantiatedIn(method));
+ }
+
private void markDirectStaticOrConstructorMethodAsLive(
DexEncodedMethod encodedMethod, KeepReason reason) {
assert encodedMethod != null;
@@ -1529,12 +1548,17 @@
*/
final Map<DexType, Reference2IntMap<DexField>> ordinalsMaps;
+ final ImmutableSortedSet<DexType> instantiatedLambdas;
+
private AppInfoWithLiveness(AppInfoWithSubtyping appInfo, Enqueuer enqueuer) {
super(appInfo);
this.liveTypes = ImmutableSortedSet.copyOf(
PresortedComparable<DexType>::slowCompareTo, enqueuer.liveTypes);
this.instantiatedTypes = ImmutableSortedSet.copyOf(
PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedTypes.getItems());
+ this.instantiatedLambdas =
+ ImmutableSortedSet.copyOf(
+ PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedLambdas.getItems());
this.targetedMethods = toSortedDescriptorSet(enqueuer.targetedMethods.getItems());
this.liveMethods = toSortedDescriptorSet(enqueuer.liveMethods.getItems());
this.liveFields = toSortedDescriptorSet(enqueuer.liveFields.getItems());
@@ -1568,6 +1592,7 @@
super(application);
this.liveTypes = previous.liveTypes;
this.instantiatedTypes = previous.instantiatedTypes;
+ this.instantiatedLambdas = previous.instantiatedLambdas;
this.targetedMethods = previous.targetedMethods;
this.liveMethods = previous.liveMethods;
this.liveFields = previous.liveFields;
@@ -1603,6 +1628,7 @@
super(application, lense);
this.liveTypes = rewriteItems(previous.liveTypes, lense::lookupType);
this.instantiatedTypes = rewriteItems(previous.instantiatedTypes, lense::lookupType);
+ this.instantiatedLambdas = rewriteItems(previous.instantiatedLambdas, lense::lookupType);
this.targetedMethods = rewriteItems(previous.targetedMethods, lense::lookupMethod);
this.liveMethods = rewriteItems(previous.liveMethods, lense::lookupMethod);
this.liveFields = rewriteItems(previous.liveFields, lense::lookupField);
@@ -1645,6 +1671,7 @@
super(previous);
this.liveTypes = previous.liveTypes;
this.instantiatedTypes = previous.instantiatedTypes;
+ this.instantiatedLambdas = previous.instantiatedLambdas;
this.targetedMethods = previous.targetedMethods;
this.liveMethods = previous.liveMethods;
this.liveFields = previous.liveFields;
@@ -2021,6 +2048,9 @@
public DexEncodedMethod lookupSingleInterfaceTarget(
DexMethod method, DexType refinedReceiverType) {
+ if (instantiatedLambdas.contains(method.holder)) {
+ return null;
+ }
DexClass holder = definitionFor(method.holder);
if ((holder == null) || holder.isLibraryClass() || !holder.accessFlags.isInterface()) {
return null;
diff --git a/src/main/java/com/android/tools/r8/utils/AbortException.java b/src/main/java/com/android/tools/r8/utils/AbortException.java
index 897e022..0253dfa 100644
--- a/src/main/java/com/android/tools/r8/utils/AbortException.java
+++ b/src/main/java/com/android/tools/r8/utils/AbortException.java
@@ -9,5 +9,11 @@
* {@link com.android.tools.r8.DiagnosticsHandler}.
*/
public class AbortException extends RuntimeException {
+ public AbortException() {
+ }
+
+ public AbortException(String message) {
+ super(message);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index 8ce1c09..63b95b3 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -15,6 +15,7 @@
private final DiagnosticsHandler clientHandler;
private int errorCount = 0;
+ private Diagnostic lastError;
private final Collection<Throwable> suppressedExceptions = new ArrayList<>();
public Reporter(DiagnosticsHandler clientHandler) {
@@ -35,6 +36,7 @@
public void error(Diagnostic error) {
clientHandler.error(error);
synchronized (this) {
+ lastError = error;
errorCount++;
}
}
@@ -46,6 +48,7 @@
public void error(Diagnostic error, Throwable suppressedException) {
clientHandler.error(error);
synchronized (this) {
+ lastError = error;
errorCount++;
suppressedExceptions.add(suppressedException);
}
@@ -75,7 +78,12 @@
public void failIfPendingErrors() {
synchronized (this) {
if (errorCount != 0) {
- AbortException abort = new AbortException();
+ AbortException abort;
+ if (lastError != null && lastError.getDiagnosticMessage() != null) {
+ abort = new AbortException("Error: " + lastError.getDiagnosticMessage());
+ } else {
+ abort = new AbortException();
+ }
throw addSuppressedExceptions(abort);
}
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index bd66301..43a443e 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1148,13 +1148,20 @@
public static ProcessResult runProguardRaw(
String proguardScript, Path inJar, Path outJar, List<Path> configs, Path map)
throws IOException {
+ return runProguardRaw(
+ proguardScript, inJar, outJar, ToolHelper.getDefaultAndroidJar(), configs, map);
+ }
+
+ public static ProcessResult runProguardRaw(
+ String proguardScript, Path inJar, Path outJar, Path lib, List<Path> configs, Path map)
+ throws IOException {
List<String> command = new ArrayList<>();
command.add(proguardScript);
command.add("-forceprocessing"); // Proguard just checks the creation time on the in/out jars.
command.add("-injars");
command.add(inJar.toString());
command.add("-libraryjars");
- command.add(ToolHelper.getDefaultAndroidJar().toString());
+ command.add(lib.toString());
configs.forEach(config -> command.add("@" + config));
command.add("-outjar");
command.add(outJar.toString());
@@ -1196,6 +1203,11 @@
return runProguardRaw(getProguard6Script(), inJar, outJar, ImmutableList.of(config), map);
}
+ public static ProcessResult runProguard6Raw(
+ Path inJar, Path outJar, Path lib, Path config, Path map) throws IOException {
+ return runProguardRaw(getProguard6Script(), inJar, outJar, lib, ImmutableList.of(config), map);
+ }
+
public static String runProguard6(Path inJar, Path outJar, Path config, Path map)
throws IOException {
return runProguard6(inJar, outJar, ImmutableList.of(config), map);
diff --git a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTest.java b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTest.java
new file mode 100644
index 0000000..e6b44b3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTest.java
@@ -0,0 +1,185 @@
+// 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;
+
+public class LambdaRenamingTest {
+
+ public static final Class[] CLASSES = {
+ LambdaRenamingTest.class,
+ Interface.class,
+ DummyInterface1.class,
+ DummyInterface2.class,
+ DummyInterface3.class,
+ DummyInterface4.class,
+ ObjectInterface.class,
+ IntegerInterface.class,
+ ReservedNameObjectInterface1.class,
+ ReservedNameIntegerInterface1.class,
+ ReservedNameObjectInterface2.class,
+ ReservedNameIntegerInterface2.class,
+ InexactImplementation.class,
+ DummyImplementation.class,
+ ReservedImplementation1.class,
+ ReservedImplementation2.class,
+ };
+
+ interface Interface {
+ String method();
+ }
+
+ // Interface methods are renamed in decreasing order of number of interfaces they appear in.
+ // Define many interfaces with "Object foo();" so that it will receive the first name "a".
+ // Define "Object inexactMethod();" in one of them so that it will be named "b".
+ // Then define "Integer inexactMethod();" in an unrelated interface so it will be named "a"
+ // (when renaming aggressively).
+ interface DummyInterface1 {
+ Object foo();
+
+ Object inexactMethod();
+ }
+
+ interface DummyInterface2 {
+ Object foo();
+ }
+
+ interface DummyInterface3 {
+ Object foo();
+ }
+
+ interface DummyInterface4 {
+ Object foo();
+ }
+
+ interface ObjectInterface {
+ Object inexactMethod();
+ }
+
+ interface IntegerInterface {
+ Integer inexactMethod();
+ }
+
+ interface ReservedNameObjectInterface1 {
+ // The following method is explicitly kept in the test's ProGuard config.
+ Object reservedMethod1();
+ }
+
+ interface ReservedNameIntegerInterface1 {
+ Integer reservedMethod1();
+ }
+
+ interface ReservedNameObjectInterface2 {
+ Object reservedMethod2();
+ }
+
+ interface ReservedNameIntegerInterface2 {
+ // The following method is explicitly kept in the test's ProGuard config.
+ Integer reservedMethod2();
+ }
+
+ static class InexactImplementation implements ObjectInterface, DummyInterface1, IntegerInterface {
+ @Override
+ public Object foo() {
+ return null;
+ }
+
+ @Override
+ public Integer inexactMethod() {
+ return 10;
+ }
+ }
+
+ static class DummyImplementation implements DummyInterface1 {
+ @Override
+ public Object foo() {
+ return null;
+ }
+
+ @Override
+ public Integer inexactMethod() {
+ return 11;
+ }
+ }
+
+ static class ReservedImplementation1
+ implements ReservedNameIntegerInterface1, ReservedNameObjectInterface1 {
+ @Override
+ public Integer reservedMethod1() {
+ return 101;
+ }
+ }
+
+ static class ReservedImplementation2
+ implements ReservedNameIntegerInterface2, ReservedNameObjectInterface2 {
+ @Override
+ public Integer reservedMethod2() {
+ return 102;
+ }
+ }
+
+ public static void main(String[] args) {
+ dummyMethod(new InexactImplementation(), () -> null, () -> null, () -> null);
+ dummyMethod(new DummyImplementation(), () -> null, () -> null, () -> null);
+ invokeInteger(new InexactImplementation());
+ invokeInteger((IntegerInterface) getInexactLambda());
+ invokeObject(new InexactImplementation());
+ invokeObject((ObjectInterface) getInexactLambda());
+ invokeIntegerReserved1(new ReservedImplementation1());
+ invokeIntegerReserved1((ReservedNameIntegerInterface1) getReservedLambda1());
+ invokeObjectReserved1(new ReservedImplementation1());
+ invokeObjectReserved1((ReservedNameObjectInterface1) getReservedLambda1());
+ invokeIntegerReserved2(new ReservedImplementation2());
+ invokeIntegerReserved2((ReservedNameIntegerInterface2) getReservedLambda2());
+ invokeObjectReserved2(new ReservedImplementation2());
+ invokeObjectReserved2((ReservedNameObjectInterface2) getReservedLambda2());
+ }
+
+ private static Object getInexactLambda() {
+ return (IntegerInterface & ObjectInterface) () -> 30;
+ }
+
+ private static Object getReservedLambda1() {
+ return (ReservedNameIntegerInterface1 & ReservedNameObjectInterface1) () -> 301;
+ }
+
+ private static Object getReservedLambda2() {
+ return (ReservedNameIntegerInterface2 & ReservedNameObjectInterface2) () -> 302;
+ }
+
+ private static void dummyMethod(
+ DummyInterface1 instance1,
+ DummyInterface2 instance2,
+ DummyInterface3 instance3,
+ DummyInterface4 instance4) {
+ System.out.println(instance1.foo());
+ System.out.println(instance2.foo());
+ System.out.println(instance3.foo());
+ System.out.println(instance4.foo());
+ System.out.println(instance1.inexactMethod());
+ }
+
+ private static void invokeInteger(IntegerInterface instance) {
+ System.out.println(instance.inexactMethod());
+ }
+
+ private static void invokeObject(ObjectInterface instance) {
+ System.out.println(instance.inexactMethod());
+ }
+
+ private static void invokeIntegerReserved1(ReservedNameIntegerInterface1 instance) {
+ System.out.println(instance.reservedMethod1());
+ }
+
+ private static void invokeObjectReserved1(ReservedNameObjectInterface1 instance) {
+ System.out.println(instance.reservedMethod1());
+ }
+
+ private static void invokeIntegerReserved2(ReservedNameIntegerInterface2 instance) {
+ System.out.println(instance.reservedMethod2());
+ }
+
+ private static void invokeObjectReserved2(ReservedNameObjectInterface2 instance) {
+ System.out.println(instance.reservedMethod2());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
new file mode 100644
index 0000000..7a2123e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
@@ -0,0 +1,148 @@
+// 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LambdaRenamingTestRunner extends TestBase {
+ static final Class CLASS = LambdaRenamingTest.class;
+ static final Class[] CLASSES = LambdaRenamingTest.CLASSES;
+
+ private Path inputJar;
+ private ProcessResult runInput;
+
+ @Before
+ public void writeAndRunInputJar() throws IOException {
+ inputJar = temp.getRoot().toPath().resolve("input.jar");
+ ArchiveConsumer buildInput = new ArchiveConsumer(inputJar);
+ for (Class clazz : CLASSES) {
+ buildInput.accept(
+ ToolHelper.getClassAsBytes(clazz),
+ DescriptorUtils.javaTypeToDescriptor(clazz.getName()),
+ null);
+ }
+ buildInput.finished(null);
+ runInput = ToolHelper.runJava(inputJar, CLASS.getCanonicalName());
+ assertEquals(0, runInput.exitCode);
+ }
+
+ private Path writeProguardRules(boolean aggressive) throws IOException {
+ Path pgConfig = temp.getRoot().toPath().resolve("keep.txt");
+ FileUtils.writeTextFile(
+ pgConfig,
+ "-keep public class " + CLASS.getCanonicalName() + " {",
+ " public static void main(...);",
+ "}",
+ "-keep interface " + CLASS.getCanonicalName() + "$ReservedNameObjectInterface1 {",
+ " public java.lang.Object reservedMethod1();",
+ "}",
+ "-keep interface " + CLASS.getCanonicalName() + "$ReservedNameIntegerInterface2 {",
+ " public java.lang.Integer reservedMethod2();",
+ "}",
+ aggressive ? "-overloadaggressively" : "# Not overloading aggressively");
+ return pgConfig;
+ }
+
+ @Test
+ public void testProguard() throws Exception {
+ buildAndRunProguard("pg.jar", false);
+ }
+
+ @Test
+ public void testProguardAggressive() throws Exception {
+ buildAndRunProguard("pg-aggressive.jar", true);
+ }
+
+ @Test
+ public void testCf() throws Exception {
+ buildAndRunCf("cf.zip", false);
+ }
+
+ @Test
+ public void testCfAggressive() throws Exception {
+ buildAndRunCf("cf-aggressive.zip", true);
+ }
+
+ @Test
+ public void testDex() throws Exception {
+ buildAndRunDex("dex.zip", false);
+ }
+
+ @Test
+ public void testDexAggressive() throws Exception {
+ buildAndRunDex("dex-aggressive.zip", true);
+ }
+
+ private void buildAndRunCf(String outName, boolean aggressive) throws Exception {
+ Path outCf = temp.getRoot().toPath().resolve(outName);
+ build(new ClassFileConsumer.ArchiveConsumer(outCf), aggressive);
+ ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName());
+ assertEquals(runInput.toString(), runCf.toString());
+ }
+
+ private void buildAndRunDex(String outName, boolean aggressive) throws Exception {
+ Path outDex = temp.getRoot().toPath().resolve(outName);
+ build(new DexIndexedConsumer.ArchiveConsumer(outDex), aggressive);
+ ProcessResult runDex =
+ ToolHelper.runArtNoVerificationErrorsRaw(outDex.toString(), CLASS.getCanonicalName());
+ assertEquals(runInput.stdout, runDex.stdout);
+ assertEquals(runInput.exitCode, runDex.exitCode);
+ }
+
+ private void build(ProgramConsumer consumer, boolean aggressive) throws Exception {
+ Builder builder =
+ ToolHelper.addProguardConfigurationConsumer(
+ R8Command.builder(), configuration -> configuration.setPrintMapping(true))
+ .setMode(CompilationMode.DEBUG)
+ .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+ .addProgramFiles(inputJar)
+ .setProgramConsumer(consumer)
+ .addProguardConfigurationFiles(writeProguardRules(aggressive));
+ if (consumer instanceof ClassFileConsumer) {
+ // TODO(b/75997473): Enable inlining when supported by CF backend
+ ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
+ } else {
+ builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
+ ToolHelper.runR8(builder.build());
+ }
+ }
+
+ private void buildAndRunProguard(String outName, boolean aggressive) throws Exception {
+ Path pgConfig = writeProguardRules(aggressive);
+ Path outPg = temp.getRoot().toPath().resolve(outName);
+ ProcessResult proguardResult =
+ ToolHelper.runProguard6Raw(
+ inputJar, outPg, Paths.get(ToolHelper.JAVA_8_RUNTIME), pgConfig, null);
+ System.out.println(proguardResult.stdout);
+ if (proguardResult.exitCode != 0) {
+ System.out.println(proguardResult.stderr);
+ }
+ assertEquals(0, proguardResult.exitCode);
+ ProcessResult runPg = ToolHelper.runJava(outPg, CLASS.getCanonicalName());
+ // Proguard renames IntegerInterface.inexactMethod() and ObjectInterface.inexactMethod()
+ // to different names, which causes AbstractMethodError.
+ assertNotEquals(-1, runPg.stderr.indexOf("AbstractMethodError"));
+ assertNotEquals(0, runPg.exitCode);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTest.java b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTest.java
new file mode 100644
index 0000000..4a5cfa8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTest.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+public class InstantiatedLambdasTest {
+
+ public static final Class[] CLASSES = {
+ InstantiatedLambdasTest.class,
+ Interface.class,
+ ClassImplementation.class,
+ };
+
+ interface Interface {
+ String method();
+ }
+
+ static class ClassImplementation implements Interface {
+ @Override
+ public String method() {
+ return "Class implementation";
+ }
+ }
+
+ public static void main(String[] args) {
+ invoke(getClassImplementation());
+ invoke(getLambdaImplementation());
+ }
+
+ private static Interface getClassImplementation() {
+ return new ClassImplementation();
+ }
+
+ private static Interface getLambdaImplementation() {
+ return () -> "Lambda implementation";
+ }
+
+ private static void invoke(Interface instance) {
+ System.out.println(instance.method());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
new file mode 100644
index 0000000..4e638df
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
@@ -0,0 +1,138 @@
+// 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.shaking;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Before;
+import org.junit.Test;
+
+public class InstantiatedLambdasTestRunner extends TestBase {
+ static final Class CLASS = InstantiatedLambdasTest.class;
+ static final Class[] CLASSES = InstantiatedLambdasTest.CLASSES;
+
+ private Path inputJar;
+ private ProcessResult runInput;
+
+ @Before
+ public void writeAndRunInputJar() throws IOException {
+ inputJar = temp.getRoot().toPath().resolve("input.jar");
+ ArchiveConsumer buildInput = new ArchiveConsumer(inputJar);
+ for (Class clazz : CLASSES) {
+ buildInput.accept(
+ ToolHelper.getClassAsBytes(clazz),
+ DescriptorUtils.javaTypeToDescriptor(clazz.getName()),
+ null);
+ }
+ buildInput.finished(null);
+ runInput = ToolHelper.runJava(inputJar, CLASS.getCanonicalName());
+ assertEquals(0, runInput.exitCode);
+ }
+
+ private Path writeProguardRules(boolean aggressive) throws IOException {
+ Path pgConfig = temp.getRoot().toPath().resolve("keep.txt");
+ FileUtils.writeTextFile(
+ pgConfig,
+ "-keep public class " + CLASS.getCanonicalName() + " {",
+ " public static void main(...);",
+ "}",
+ aggressive ? "-overloadaggressively" : "# Not overloading aggressively");
+ return pgConfig;
+ }
+
+ @Test
+ public void testProguard() throws Exception {
+ buildAndRunProguard("pg.jar", false);
+ }
+
+ @Test
+ public void testProguardAggressive() throws Exception {
+ buildAndRunProguard("pg-aggressive.jar", true);
+ }
+
+ @Test
+ public void testCf() throws Exception {
+ buildAndRunCf("cf.zip", false);
+ }
+
+ @Test
+ public void testCfAggressive() throws Exception {
+ buildAndRunCf("cf-aggressive.zip", true);
+ }
+
+ @Test
+ public void testDex() throws Exception {
+ buildAndRunDex("dex.zip", false);
+ }
+
+ @Test
+ public void testDexAggressive() throws Exception {
+ buildAndRunDex("dex-aggressive.zip", true);
+ }
+
+ private void buildAndRunCf(String outName, boolean aggressive) throws Exception {
+ Path outCf = temp.getRoot().toPath().resolve(outName);
+ build(new ClassFileConsumer.ArchiveConsumer(outCf), aggressive);
+ ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName());
+ assertEquals(runInput.toString(), runCf.toString());
+ }
+
+ private void buildAndRunDex(String outName, boolean aggressive) throws Exception {
+ Path outDex = temp.getRoot().toPath().resolve(outName);
+ build(new DexIndexedConsumer.ArchiveConsumer(outDex), aggressive);
+ ProcessResult runDex =
+ ToolHelper.runArtNoVerificationErrorsRaw(outDex.toString(), CLASS.getCanonicalName());
+ assertEquals(runInput.stdout, runDex.stdout);
+ assertEquals(runInput.exitCode, runDex.exitCode);
+ }
+
+ private void build(ProgramConsumer consumer, boolean aggressive) throws Exception {
+ Builder builder =
+ ToolHelper.addProguardConfigurationConsumer(
+ R8Command.builder(), configuration -> configuration.setPrintMapping(true))
+ .setMode(CompilationMode.DEBUG)
+ .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+ .addProgramFiles(inputJar)
+ .setProgramConsumer(consumer)
+ .addProguardConfigurationFiles(writeProguardRules(aggressive));
+ if (consumer instanceof ClassFileConsumer) {
+ // TODO(b/75997473): Enable inlining when supported by CF backend
+ ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
+ } else {
+ builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
+ ToolHelper.runR8(builder.build());
+ }
+ }
+
+ private void buildAndRunProguard(String outName, boolean aggressive) throws Exception {
+ Path pgConfig = writeProguardRules(aggressive);
+ Path outPg = temp.getRoot().toPath().resolve(outName);
+ ProcessResult proguardResult =
+ ToolHelper.runProguard6Raw(
+ inputJar, outPg, Paths.get(ToolHelper.JAVA_8_RUNTIME), pgConfig, null);
+ System.out.println(proguardResult.stdout);
+ if (proguardResult.exitCode != 0) {
+ System.out.println(proguardResult.stderr);
+ }
+ assertEquals(0, proguardResult.exitCode);
+ ProcessResult runPg = ToolHelper.runJava(outPg, CLASS.getCanonicalName());
+ assertEquals(0, runPg.exitCode);
+ }
+}