Merge commit '4455f6c687e63ebb44174423d4ef2b499d506c4d' into dev-release
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json
index ed216b0..5600b2e 100644
--- a/src/library_desugar/desugar_jdk_libs.json
+++ b/src/library_desugar/desugar_jdk_libs.json
@@ -2,7 +2,7 @@
"configuration_format_version": 3,
"group_id" : "com.tools.android",
"artifact_id" : "desugar_jdk_libs",
- "version": "1.1.1",
+ "version": "1.1.2",
"required_compilation_api_level": 26,
"synthesized_library_classes_package_prefix": "j$.",
"support_all_callbacks_from_library": true,
@@ -244,7 +244,7 @@
"-keepclassmembers class j$.util.concurrent.ConcurrentHashMap { int sizeCtl; int transferIndex; long baseCount; int cellsBusy; }",
"-keepclassmembers class j$.util.concurrent.ConcurrentHashMap$CounterCell { long value; }",
"-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); public static final !synthetic <fields>; }",
- "-keeppackagenames j$",
+ "-keeppackagenames j$.**",
"-keepclassmembers class j$.util.IntSummaryStatistics { long count; long sum; int min; int max; }",
"-keepclassmembers class j$.util.LongSummaryStatistics { long count; long sum; long min; long max; }",
"-keepclassmembers class j$.util.DoubleSummaryStatistics { long count; double sum; double min; double max; }",
diff --git a/src/library_desugar/desugar_jdk_libs_alternative_3.json b/src/library_desugar/desugar_jdk_libs_alternative_3.json
index 012d6ce..cb7f478 100644
--- a/src/library_desugar/desugar_jdk_libs_alternative_3.json
+++ b/src/library_desugar/desugar_jdk_libs_alternative_3.json
@@ -2,7 +2,7 @@
"configuration_format_version": 3,
"group_id" : "com.tools.android",
"artifact_id" : "desugar_jdk_libs_alternative_3",
- "version": "1.1.1",
+ "version": "1.2.0",
"required_compilation_api_level": 26,
"synthesized_library_classes_package_prefix": "j$.",
"support_all_callbacks_from_library": false,
@@ -188,6 +188,7 @@
"java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatistics",
"java.util.IntSummaryStatistics": "j$.util.IntSummaryStatistics",
"java.util.LongSummaryStatistics": "j$.util.LongSummaryStatistics",
+ "java.util.Objects": "j$.util.Objects",
"java.util.Optional": "j$.util.Optional",
"java.util.PrimitiveIterator": "j$.util.PrimitiveIterator",
"java.util.Spliterator": "j$.util.Spliterator",
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index fa5a935..4fdf194 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -166,7 +166,7 @@
AndroidApp inputApp, InternalOptions options, ExecutorService executor, Timing timing)
throws IOException {
PrefixRewritingMapper rewritePrefix =
- options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
+ options.desugaredLibraryConfiguration.getPrefixRewritingMapper();
ApplicationReader applicationReader = new ApplicationReader(inputApp, options, timing);
LazyLoadedDexApplication app = applicationReader.read(executor);
AppInfo appInfo = AppInfo.createInitialAppInfo(app, applicationReader.readMainDexClasses(app));
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index a06be47..40f48d2 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -159,7 +159,7 @@
new ApplicationReader(inputApp, options, timing).read(executor);
PrefixRewritingMapper rewritePrefix =
- options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
+ options.desugaredLibraryConfiguration.getPrefixRewritingMapper();
DexApplication app = new L8TreePruner(options).prune(lazyApp, rewritePrefix);
return AppView.createForL8(AppInfo.createInitialAppInfo(app), rewritePrefix);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 07b0e0c..6c7f76f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -5,7 +5,6 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCompareHelper;
@@ -222,7 +221,11 @@
if (method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME)) {
type = Type.DIRECT;
} else if (code.getOriginalHolder() == method.holder) {
- type = invokeTypeForInvokeSpecialToNonInitMethodOnHolder(builder.appView, code);
+ MethodAndInvokeType methodAndInvokeType =
+ transformInvokeSpecialToNonInitMethodOnHolder(
+ builder.appView, code, builder.getProgramMethod());
+ type = methodAndInvokeType.getInvokeType();
+ canonicalMethod = methodAndInvokeType.getMethod();
} else {
type = Type.SUPER;
}
@@ -357,8 +360,26 @@
return true;
}
- private Type invokeTypeForInvokeSpecialToNonInitMethodOnHolder(
- AppView<?> appView, CfSourceCode code) {
+ private static class MethodAndInvokeType {
+ private final DexMethod method;
+ private final Invoke.Type invokeType;
+
+ private MethodAndInvokeType(DexMethod method, Type invokeType) {
+ this.method = method;
+ this.invokeType = invokeType;
+ }
+
+ public DexMethod getMethod() {
+ return method;
+ }
+
+ public Type getInvokeType() {
+ return invokeType;
+ }
+ }
+
+ private MethodAndInvokeType transformInvokeSpecialToNonInitMethodOnHolder(
+ AppView<?> appView, CfSourceCode code, ProgramMethod context) {
boolean desugaringEnabled = appView.options().isInterfaceMethodDesugaringEnabled();
MethodLookupResult lookupResult = appView.graphLens().lookupMethod(method, method, Type.DIRECT);
if (lookupResult.getType() == Type.VIRTUAL) {
@@ -366,30 +387,38 @@
// publicized to be final. For example, if a private method A.m() is publicized, and A is
// subsequently merged with a class B, with declares a public non-final method B.m(), then the
// horizontal class merger will merge A.m() and B.m() into a new non-final public method.
- return Type.VIRTUAL;
+ return new MethodAndInvokeType(method, Type.VIRTUAL);
}
DexMethod rewrittenMethod = lookupResult.getReference();
DexEncodedMethod encodedMethod = lookupMethodOnHolder(appView, rewrittenMethod);
if (encodedMethod == null) {
// The method is not defined on the class, we can use super to target. When desugaring
// default interface methods, it is expected they are targeted with invoke-direct.
- return this.itf && desugaringEnabled ? Type.DIRECT : Type.SUPER;
+ return new MethodAndInvokeType(
+ method, this.itf && desugaringEnabled ? Type.DIRECT : Type.SUPER);
}
if (encodedMethod.isPrivateMethod() || !encodedMethod.isVirtualMethod()) {
- return Type.DIRECT;
+ return new MethodAndInvokeType(method, Type.DIRECT);
}
if (encodedMethod.accessFlags.isFinal()) {
// This method is final which indicates no subtype will overwrite it, we can use
// invoke-virtual.
- return Type.VIRTUAL;
+ return new MethodAndInvokeType(method, Type.VIRTUAL);
}
if (this.itf && encodedMethod.isDefaultMethod()) {
- return desugaringEnabled ? Type.DIRECT : Type.SUPER;
+ return new MethodAndInvokeType(method, desugaringEnabled ? Type.DIRECT : Type.SUPER);
}
- // We cannot emulate the semantics of invoke-special in this case and should throw a compilation
- // error.
- throw new CompilationError(
- "Failed to compile unsupported use of invokespecial", code.getOrigin());
+ assert encodedMethod.isNonPrivateVirtualMethod();
+ assert context.getHolderType() == method.holder;
+ // This is an invoke-special to a virtual method on invoke-special method holder.
+ // The invoke should be rewritten with a bridge.
+ DexMethod directMethod =
+ appView.getInvokeSpecialBridgeSynthesizer().registerBridgeForMethod(encodedMethod);
+ // In R8 the target should have been inserted in the enqueuer,
+ // while in D8, the target is inserted at the end of the compilation.
+ assert appView.enableWholeProgramOptimizations()
+ == (context.getHolder().lookupDirectMethod(directMethod) != null);
+ return new MethodAndInvokeType(directMethod, Type.DIRECT);
}
private DexEncodedMethod lookupMethodOnHolder(AppView<?> appView, DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 566fa31..d51e21c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.ir.analysis.proto.ProtoShrinker;
import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
import com.android.tools.r8.ir.conversion.MethodProcessingId;
+import com.android.tools.r8.ir.desugar.InvokeSpecialBridgeSynthesizer;
import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
@@ -67,8 +68,9 @@
new InstanceFieldInitializationInfoFactory();
private final MethodProcessingId.Factory methodProcessingIdFactory;
- // Desugared library prefix rewriter.
+ // Desugaring.
public final PrefixRewritingMapper rewritePrefix;
+ private final InvokeSpecialBridgeSynthesizer invokeSpecialBridgeSynthesizer;
// Modeling.
private final LibraryMethodSideEffectModelCollection libraryMethodSideEffectModelCollection;
@@ -108,6 +110,7 @@
this.methodProcessingIdFactory =
new MethodProcessingId.Factory(options().testing.methodProcessingIdConsumer);
this.rewritePrefix = mapper;
+ this.invokeSpecialBridgeSynthesizer = new InvokeSpecialBridgeSynthesizer(this);
if (enableWholeProgramOptimizations() && options().callSiteOptimizationOptions().isEnabled()) {
this.callSiteOptimizationInfoPropagator =
@@ -134,7 +137,7 @@
private static <T extends AppInfo> PrefixRewritingMapper defaultPrefixRewritingMapper(T appInfo) {
InternalOptions options = appInfo.options();
- return options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
+ return options.desugaredLibraryConfiguration.getPrefixRewritingMapper();
}
public static <T extends AppInfo> AppView<T> createForD8(T appInfo) {
@@ -294,6 +297,10 @@
return callSiteOptimizationInfoPropagator;
}
+ public InvokeSpecialBridgeSynthesizer getInvokeSpecialBridgeSynthesizer() {
+ return invokeSpecialBridgeSynthesizer;
+ }
+
public LibraryMemberOptimizer libraryMethodOptimizer() {
return libraryMemberOptimizer;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index bcbc160..fb1ad730 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1307,15 +1307,28 @@
return new ProgramMethod(holder, builder.build());
}
+ public DexEncodedMethod toPrivateSyntheticMethod(DexMethod method) {
+ assert !accessFlags.isStatic();
+ assert !accessFlags.isPrivate();
+ assert getHolderType() == method.holder;
+ checkIfObsolete();
+ Builder builder = syntheticBuilder(this);
+ builder.setMethod(method);
+ builder.accessFlags.setSynthetic();
+ builder.accessFlags.unsetProtected();
+ builder.accessFlags.unsetPublic();
+ builder.accessFlags.setPrivate();
+ return builder.build();
+ }
+
public DexEncodedMethod toForwardingMethod(DexClass holder, DexDefinitionSupplier definitions) {
+ DexMethod newMethod = method.withHolder(holder.type, definitions.dexItemFactory());
checkIfObsolete();
// Clear the final flag, as this method is now overwritten. Do this before creating the builder
// for the forwarding method, as the forwarding method will copy the access flags from this,
// and if different forwarding methods are created in different subclasses the first could be
// final.
accessFlags.demoteFromFinal();
- DexMethod newMethod = method.withHolder(holder.type, definitions.dexItemFactory());
- Invoke.Type type = accessFlags.isStatic() ? Invoke.Type.STATIC : Invoke.Type.SUPER;
Builder builder = syntheticBuilder(this);
builder.setMethod(newMethod);
if (accessFlags.isAbstract()) {
@@ -1328,10 +1341,10 @@
ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
ForwardMethodSourceCode.builder(newMethod);
forwardSourceCodeBuilder
- .setReceiver(accessFlags.isStatic() ? null : holder.type)
+ .setReceiver(accessFlags.isStatic() ? null : newMethod.getHolderType())
.setTargetReceiver(accessFlags.isStatic() ? null : method.holder)
.setTarget(method)
- .setInvokeType(type)
+ .setInvokeType(accessFlags.isStatic() ? Invoke.Type.STATIC : Invoke.Type.SUPER)
.setIsInterface(target.isInterface());
builder.setCode(
new SynthesizedCode(
@@ -1516,7 +1529,7 @@
public static class Builder {
private DexMethod method;
- private final MethodAccessFlags accessFlags;
+ private MethodAccessFlags accessFlags;
private final MethodTypeSignature genericSignature;
private final DexAnnotationSet annotations;
private OptionalBool isLibraryMethodOverride = OptionalBool.UNKNOWN;
@@ -1555,6 +1568,10 @@
}
}
+ public void setAccessFlags(MethodAccessFlags accessFlags) {
+ this.accessFlags = accessFlags.copy();
+ }
+
public void setMethod(DexMethod method) {
this.method = method;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 1ace989..71d89ef 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -588,6 +588,11 @@
methodCollection.addDirectMethod(directMethod);
}
+ public void replaceVirtualMethod(
+ DexMethod virtualMethod, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+ methodCollection.replaceVirtualMethod(virtualMethod, replacement);
+ }
+
public void replaceInterfaces(List<ClassTypeSignature> newInterfaces) {
if (newInterfaces.isEmpty()) {
return;
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index 641ef08..c5f516b 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -185,7 +185,7 @@
/** Returns true if this field is read by the program. */
@Override
public boolean isRead() {
- return !readsWithContexts.isEmpty() || isReadFromAnnotation();
+ return !readsWithContexts.isEmpty() || isReadFromAnnotation() || isReadFromMethodHandle();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
index 7ba6207..1af8be2 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
@@ -75,6 +75,10 @@
superInvokes.forEach(consumer);
}
+ public void forEachSuperInvokeContext(DexMethod method, Consumer<ProgramMethod> consumer) {
+ superInvokes.getOrDefault(method, ProgramMethodSet.empty()).forEach(consumer);
+ }
+
public void forEachVirtualInvoke(BiConsumer<DexMethod, ProgramMethodSet> consumer) {
virtualInvokes.forEach(consumer);
}
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index 3387ff2..e88d379 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -233,7 +233,7 @@
}
public boolean verifyAllocatedTypesAreLive(
- Set<DexType> liveTypes, DexDefinitionSupplier definitions) {
+ Set<DexType> liveTypes, Set<DexType> missingTypes, DexDefinitionSupplier definitions) {
for (DexProgramClass clazz : classesWithAllocationSiteTracking.keySet()) {
assert liveTypes.contains(clazz.getType());
}
@@ -244,7 +244,9 @@
assert liveTypes.contains(iface.getType());
}
for (DexType iface : instantiatedLambdas.keySet()) {
- assert definitions.definitionFor(iface).isNotProgramClass() || liveTypes.contains(iface);
+ assert missingTypes.contains(iface)
+ || definitions.definitionFor(iface).isNotProgramClass()
+ || liveTypes.contains(iface);
}
return true;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
index 3adad2e..b42cb24 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -122,17 +122,17 @@
}
void fixupField(DexField oldFieldSignature, DexField newFieldSignature) {
+ DexField representative = fieldMap.removeRepresentativeFor(oldFieldSignature);
Set<DexField> originalFieldSignatures = fieldMap.removeValue(oldFieldSignature);
if (originalFieldSignatures.isEmpty()) {
fieldMap.put(oldFieldSignature, newFieldSignature);
} else if (originalFieldSignatures.size() == 1) {
fieldMap.put(originalFieldSignatures.iterator().next(), newFieldSignature);
} else {
+ assert representative != null;
for (DexField originalFieldSignature : originalFieldSignatures) {
fieldMap.put(originalFieldSignature, newFieldSignature);
}
- DexField representative = fieldMap.removeRepresentativeFor(oldFieldSignature);
- assert representative != null;
fieldMap.setRepresentative(newFieldSignature, representative);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index c1d51db..27a8c72 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -490,6 +490,10 @@
return method.getDefinition();
}
+ public ProgramMethod getProgramMethod() {
+ return method;
+ }
+
public RewrittenPrototypeDescription getPrototypeChanges() {
return prototypeChanges;
}
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 a6203e9..e2b0e47 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
@@ -463,6 +463,12 @@
}
}
+ private void synthesizeInvokeSpecialBridges(ExecutorService executorService)
+ throws ExecutionException {
+ assert !appView.enableWholeProgramOptimizations();
+ appView.getInvokeSpecialBridgeSynthesizer().insertBridgesForD8(this, executorService);
+ }
+
private void synthesizeEnumUnboxingUtilityMethods(ExecutorService executorService)
throws ExecutionException {
if (enumUnboxer != null) {
@@ -521,7 +527,7 @@
synthesizeTwrCloseResourceUtilityClass(builder, executor);
processSynthesizedJava8UtilityClasses(executor);
synthesizeRetargetClass(builder, executor);
-
+ synthesizeInvokeSpecialBridges(executor);
processCovariantReturnTypeAnnotations(builder);
generateDesugaredLibraryAPIWrappers(builder, executor);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 11e6e33..d6adc3c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -82,7 +82,7 @@
AndroidApp androidApp, InternalOptions options, ExecutorService executor) throws IOException {
List<DexMethod> methods = new ArrayList<>();
PrefixRewritingMapper rewritePrefix =
- options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
+ options.desugaredLibraryConfiguration.getPrefixRewritingMapper();
AppInfo appInfo = null;
if (androidApp != null) {
DexApplication app =
@@ -193,10 +193,13 @@
private static final class RewritableMethods {
+ private final AppView<?> appView;
+
// Map backported method to a provider for creating the actual target method (with code).
private final Map<DexMethod, MethodProvider> rewritable = new IdentityHashMap<>();
RewritableMethods(InternalOptions options, AppView<?> appView) {
+ this.appView = appView;
if (!options.shouldBackportMethods()) {
return;
@@ -205,7 +208,7 @@
DexItemFactory factory = options.itemFactory;
if (options.minApiLevel < AndroidApiLevel.K.getLevel()) {
- initializeAndroidKMethodProviders(factory);
+ initializeAndroidKMethodProviders(factory, appView);
}
if (options.minApiLevel < AndroidApiLevel.N.getLevel()) {
initializeAndroidNMethodProviders(factory);
@@ -246,7 +249,7 @@
rewritable.keySet().forEach(consumer);
}
- private void initializeAndroidKMethodProviders(DexItemFactory factory) {
+ private void initializeAndroidKMethodProviders(DexItemFactory factory, AppView<?> appView) {
// Byte
DexType type = factory.boxedByteType;
// int Byte.compare(byte a, byte b)
@@ -1305,6 +1308,21 @@
}
private void addProvider(MethodProvider generator) {
+ if (appView.options().desugaredLibraryConfiguration.isSupported(generator.method, appView)) {
+ // TODO(b/174453232): Remove this after the configuration file format has bee updated
+ // with the "rewrite_method" section.
+ if (generator.method.getHolderType() == appView.dexItemFactory().objectsType) {
+ // Still backport the new API level 30 methods.
+ String methodName = generator.method.getName().toString();
+ if (!methodName.equals("requireNonNullElse")
+ && !methodName.equals("requireNonNullElseGet")
+ && !methodName.equals("checkIndex")
+ && !methodName.equals("checkFromToIndex")
+ && !methodName.equals("checkFromIndexSize")) {
+ return;
+ }
+ }
+ }
MethodProvider replaced = rewritable.put(generator.method, generator);
assert replaced == null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
index 5627d6e..f5ff0b0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -50,7 +50,8 @@
ImmutableMap.of(),
ImmutableSet.of(),
ImmutableList.of(),
- ImmutableList.of());
+ ImmutableList.of(),
+ PrefixRewritingMapper.empty());
private final AndroidApiLevel requiredCompilationAPILevel;
private final boolean libraryCompilation;
private final String synthesizedLibraryClassesPackagePrefix;
@@ -72,13 +73,14 @@
private final List<Pair<DexType, DexString>> dontRewriteInvocation;
private final List<String> extraKeepRules;
private final Set<DexType> wrapperConversions;
+ private final PrefixRewritingMapper prefixRewritingMapper;
public static Builder builder(DexItemFactory dexItemFactory, Reporter reporter, Origin origin) {
return new Builder(dexItemFactory, reporter, origin);
}
public static DesugaredLibraryConfiguration withOnlyRewritePrefixForTesting(
- Map<String, String> prefix) {
+ Map<String, String> prefix, InternalOptions options) {
return new DesugaredLibraryConfiguration(
AndroidApiLevel.B,
true,
@@ -93,7 +95,8 @@
ImmutableMap.of(),
ImmutableSet.of(),
ImmutableList.of(),
- ImmutableList.of());
+ ImmutableList.of(),
+ new DesugarPrefixRewritingMapper(prefix, options.itemFactory, true));
}
public static DesugaredLibraryConfiguration empty() {
@@ -114,7 +117,8 @@
Map<DexType, DexType> customConversions,
Set<DexType> wrapperConversions,
List<Pair<DexType, DexString>> dontRewriteInvocation,
- List<String> extraKeepRules) {
+ List<String> extraKeepRules,
+ PrefixRewritingMapper prefixRewritingMapper) {
this.requiredCompilationAPILevel = requiredCompilationAPILevel;
this.libraryCompilation = libraryCompilation;
this.synthesizedLibraryClassesPackagePrefix = packagePrefix;
@@ -129,12 +133,11 @@
this.wrapperConversions = wrapperConversions;
this.dontRewriteInvocation = dontRewriteInvocation;
this.extraKeepRules = extraKeepRules;
+ this.prefixRewritingMapper = prefixRewritingMapper;
}
- public PrefixRewritingMapper createPrefixRewritingMapper(InternalOptions options) {
- return rewritePrefix.isEmpty()
- ? PrefixRewritingMapper.empty()
- : new DesugarPrefixRewritingMapper(rewritePrefix, options);
+ public PrefixRewritingMapper getPrefixRewritingMapper() {
+ return prefixRewritingMapper;
}
public AndroidApiLevel getRequiredCompilationApiLevel() {
@@ -160,6 +163,11 @@
public Map<DexType, DexType> getEmulateLibraryInterface() {
return emulateLibraryInterface;
}
+
+ public boolean isSupported(DexMethod method, AppView<?> appView) {
+ return prefixRewritingMapper.hasRewrittenType(method.getHolderType(), appView);
+ }
+
// If the method is retargeted, answers the retargeted method, else null.
public DexMethod retargetMethod(DexEncodedMethod method, AppView<?> appView) {
Map<DexType, DexType> typeMap = retargetCoreLibMember.get(method.getName());
@@ -385,7 +393,10 @@
ImmutableMap.copyOf(customConversions),
ImmutableSet.copyOf(wrapperConversions),
ImmutableList.copyOf(dontRewriteInvocation),
- ImmutableList.copyOf(extraKeepRules));
+ ImmutableList.copyOf(extraKeepRules),
+ rewritePrefix.isEmpty()
+ ? PrefixRewritingMapper.empty()
+ : new DesugarPrefixRewritingMapper(rewritePrefix, factory, libraryCompilation));
}
private void validate() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InvokeSpecialBridgeSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/InvokeSpecialBridgeSynthesizer.java
new file mode 100644
index 0000000..51003cb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InvokeSpecialBridgeSynthesizer.java
@@ -0,0 +1,139 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.analysis.EnqueuerInvokeAnalysis;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
+import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
+import com.google.common.collect.Sets;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * It is possible in class files to have an invoke-special to a virtual method in the same class
+ * than the method holding the invoke-special. Such invoke-special are executed correctly on the
+ * JVM, but cannot be expressed in terms of invoke-direct or invoke-super in dex. This class
+ * introduces bridges to support the case described: the virtual method code is moved to a private
+ * synthetic method, and a bridging virtual method with the initial method name and flags is
+ * inserted.
+ */
+public class InvokeSpecialBridgeSynthesizer {
+
+ private static final String INVOKE_SPECIAL_BRIDGE_PREFIX = "$invoke$special$";
+
+ private final AppView<?> appView;
+
+ private final Map<DexMethod, DexMethod> bridges = new ConcurrentHashMap<>();
+ private final Set<DexMethod> seenBridges = Sets.newIdentityHashSet();
+
+ public InvokeSpecialBridgeSynthesizer(AppView<?> appView) {
+ this.appView = appView;
+ }
+
+ public DexMethod registerBridgeForMethod(DexEncodedMethod method) {
+ assert method.isVirtualMethod();
+ assert !method.getAccessFlags().isFinal();
+ return bridges.computeIfAbsent(
+ method.getReference(),
+ vMethod ->
+ vMethod.withName(
+ appView
+ .dexItemFactory()
+ .createString(INVOKE_SPECIAL_BRIDGE_PREFIX + vMethod.name.toString()),
+ appView.dexItemFactory()));
+ }
+
+ // In R8, insertBridgesForR8 is called multiple times until fixed point.
+ // The bridges are inserted prior to IR conversion.
+ public SortedProgramMethodSet insertBridgesForR8() {
+ SortedProgramMethodSet insertedDirectMethods = SortedProgramMethodSet.create();
+ bridges.forEach(
+ (vMethod, dMethod) -> {
+ if (seenBridges.add(vMethod)) {
+ insertedDirectMethods.add(insertBridge(getVirtualMethod(vMethod), dMethod));
+ }
+ });
+ return insertedDirectMethods;
+ }
+
+ // In D8, insertBridgesForD8 is called once.
+ // The bridges are inserted after IR conversion hence the bridges need to be processed.
+ public void insertBridgesForD8(IRConverter converter, ExecutorService executorService)
+ throws ExecutionException {
+ SortedProgramMethodSet insertedBridges = SortedProgramMethodSet.create();
+ bridges.forEach(
+ (virtualMethod, directMethod) -> {
+ ProgramMethod programVirtualMethod = getVirtualMethod(virtualMethod);
+ insertBridge(programVirtualMethod, directMethod);
+ insertedBridges.add(programVirtualMethod);
+ });
+ converter.processMethodsConcurrently(insertedBridges, executorService);
+ }
+
+ private ProgramMethod getVirtualMethod(DexMethod virtualMethod) {
+ DexProgramClass holder = appView.definitionFor(virtualMethod.holder).asProgramClass();
+ assert holder.lookupVirtualMethod(virtualMethod) != null;
+ DexEncodedMethod encodedVirtualMethod = holder.lookupVirtualMethod(virtualMethod);
+ return new ProgramMethod(holder, encodedVirtualMethod);
+ }
+
+ private ProgramMethod insertBridge(ProgramMethod virtualMethod, DexMethod directMethod) {
+ assert virtualMethod.getHolderType() == directMethod.holder;
+ DexProgramClass holder = virtualMethod.getHolder();
+ assert holder.lookupDirectMethod(directMethod) == null;
+ DexEncodedMethod initialVirtualMethod = virtualMethod.getDefinition();
+ DexEncodedMethod newDirectMethod = initialVirtualMethod.toPrivateSyntheticMethod(directMethod);
+ CfCode forwardingCode =
+ ForwardMethodBuilder.builder(appView.dexItemFactory())
+ .setDirectTarget(directMethod, holder.isInterface())
+ .setNonStaticSource(virtualMethod.getReference())
+ .build();
+ initialVirtualMethod.setCode(forwardingCode, appView);
+ initialVirtualMethod.markNotProcessed();
+ holder.addDirectMethod(newDirectMethod);
+ return new ProgramMethod(holder, newDirectMethod);
+ }
+
+ public EnqueuerInvokeAnalysis getEnqueuerInvokeAnalysis() {
+ return new InvokeSpecialBridgeAnalysis();
+ }
+
+ private class InvokeSpecialBridgeAnalysis implements EnqueuerInvokeAnalysis {
+
+ @Override
+ public void traceInvokeStatic(DexMethod invokedMethod, ProgramMethod context) {}
+
+ @Override
+ public void traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
+ DexEncodedMethod lookup = context.getHolder().lookupMethod(invokedMethod);
+ if (lookup != null
+ && lookup.isNonPrivateVirtualMethod()
+ && context.getHolderType() == invokedMethod.holder
+ && !context.getHolder().isInterface()
+ && !lookup.accessFlags.isFinal()) {
+ registerBridgeForMethod(lookup);
+ }
+ }
+
+ @Override
+ public void traceInvokeInterface(DexMethod invokedMethod, ProgramMethod context) {}
+
+ @Override
+ public void traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) {}
+
+ @Override
+ public void traceInvokeVirtual(DexMethod invokedMethod, ProgramMethod context) {}
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java b/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
index 9624c2a..8ad22c7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import java.util.Map;
@@ -58,9 +57,10 @@
private final DexItemFactory factory;
private final boolean l8Compilation;
- public DesugarPrefixRewritingMapper(Map<String, String> prefixes, InternalOptions options) {
- this.factory = options.itemFactory;
- this.l8Compilation = options.isDesugaredLibraryCompilation();
+ public DesugarPrefixRewritingMapper(
+ Map<String, String> prefixes, DexItemFactory itemFactory, boolean libraryCompilation) {
+ this.factory = itemFactory;
+ this.l8Compilation = libraryCompilation;
ImmutableMap.Builder<DexString, DexString> builder = ImmutableMap.builder();
for (String key : prefixes.keySet()) {
builder.put(toDescriptorPrefix(key), toDescriptorPrefix(prefixes.get(key)));
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 43a0511..1fdb08f 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -98,11 +98,26 @@
CharSource.wrap(contents).openBufferedStream(), diagnosticsHandler);
}
+ public static ClassNameMapper mapperFromString(
+ String contents, DiagnosticsHandler diagnosticsHandler, boolean allowEmptyMappedRanges)
+ throws IOException {
+ return mapperFromBufferedReader(
+ CharSource.wrap(contents).openBufferedStream(), diagnosticsHandler, allowEmptyMappedRanges);
+ }
+
private static ClassNameMapper mapperFromBufferedReader(
BufferedReader reader, DiagnosticsHandler diagnosticsHandler) throws IOException {
+ return mapperFromBufferedReader(reader, diagnosticsHandler, false);
+ }
+
+ private static ClassNameMapper mapperFromBufferedReader(
+ BufferedReader reader, DiagnosticsHandler diagnosticsHandler, boolean allowEmptyMappedRanges)
+ throws IOException {
try (ProguardMapReader proguardReader =
new ProguardMapReader(
- reader, diagnosticsHandler != null ? diagnosticsHandler : new Reporter())) {
+ reader,
+ diagnosticsHandler != null ? diagnosticsHandler : new Reporter(),
+ allowEmptyMappedRanges)) {
ClassNameMapper.Builder builder = ClassNameMapper.builder();
proguardReader.parse(builder);
return builder.build();
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 28cdcf5..12ea2a6 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -458,7 +458,6 @@
private MappedRange(
Range minifiedRange, MethodSignature signature, Object originalRange, String renamedName) {
- assert minifiedRange != null || originalRange == null;
assert originalRange == null
|| originalRange instanceof Integer
|| originalRange instanceof Range;
@@ -510,7 +509,7 @@
builder.append(minifiedRange).append(':');
}
builder.append(signature);
- if (originalRange != null && !minifiedRange.equals(originalRange)) {
+ if (originalRange != null && !originalRange.equals(minifiedRange)) {
builder.append(":").append(originalRange);
}
builder.append(" -> ").append(renamedName);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 49b46cd..7ddb042 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -63,15 +63,20 @@
private final BufferedReader reader;
private final JsonParser jsonParser = new JsonParser();
private final DiagnosticsHandler diagnosticsHandler;
+ private final boolean allowEmptyMappedRanges;
@Override
public void close() throws IOException {
reader.close();
}
- ProguardMapReader(BufferedReader reader, DiagnosticsHandler diagnosticsHandler) {
+ ProguardMapReader(
+ BufferedReader reader,
+ DiagnosticsHandler diagnosticsHandler,
+ boolean allowEmptyMappedRanges) {
this.reader = reader;
this.diagnosticsHandler = diagnosticsHandler;
+ this.allowEmptyMappedRanges = allowEmptyMappedRanges;
assert reader != null;
assert diagnosticsHandler != null;
}
@@ -293,7 +298,7 @@
throw new ParseException("No number follows the colon after the method signature.");
}
}
- if (mappedRange == null && originalRange != null) {
+ if (!allowEmptyMappedRanges && mappedRange == null && originalRange != null) {
throw new ParseException("No mapping for original range " + originalRange + ".");
}
diff --git a/src/main/java/com/android/tools/r8/naming/SeedMapper.java b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
index 5c50c06..927cf6e 100644
--- a/src/main/java/com/android/tools/r8/naming/SeedMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
@@ -74,7 +74,7 @@
private static SeedMapper seedMapperFromInputStream(Reporter reporter, InputStream in)
throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
- try (ProguardMapReader proguardReader = new ProguardMapReader(reader, reporter)) {
+ try (ProguardMapReader proguardReader = new ProguardMapReader(reader, reporter, false)) {
SeedMapper.Builder builder = SeedMapper.builder(reporter);
proguardReader.parse(builder);
return builder.build();
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
index cea7fc2..ea4ee0e 100644
--- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClassAndMember;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
@@ -31,6 +32,7 @@
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -85,6 +87,11 @@
.excludeInterfaces()
.visit(appView.appInfo().classes(), clazz -> processClass(clazz, subtypingInfo));
if (!result.isEmpty()) {
+ // Mapping from non-hoisted bridge methods to the set of contexts in which they are accessed.
+ MethodAccessInfoCollection.IdentityBuilder bridgeMethodAccessInfoCollectionBuilder =
+ MethodAccessInfoCollection.identityBuilder();
+ result.recordNonReboundMethodAccesses(bridgeMethodAccessInfoCollectionBuilder);
+
BridgeHoistingLens lens = result.buildLens();
appView.rewriteWithLens(lens);
@@ -96,7 +103,7 @@
// bridge methods are left as-is in the code, but they are rewritten to the hoisted bridges
// during the rewriting of AppInfoWithLiveness. Therefore, this conservatively records that
// there may be an invoke-virtual instruction that targets each of the removed bridges.
- methodAccessInfoCollectionModifier.addAll(result.getBridgeMethodAccessInfoCollection());
+ methodAccessInfoCollectionModifier.addAll(bridgeMethodAccessInfoCollectionBuilder.build());
// Additionally, we record the invokes from the newly synthesized bridge methods.
result.forEachHoistedBridge(
@@ -203,7 +210,9 @@
List<DexProgramClass> eligibleSubclasses = mostFrequentBridge.getValue();
// Choose one of the bridge definitions as the one that we will be moving to the superclass.
- ProgramMethod representative = findRepresentative(eligibleSubclasses, method);
+ List<ProgramMethod> eligibleBridgeMethods =
+ getBridgesEligibleForHoisting(eligibleSubclasses, method);
+ ProgramMethod representative = eligibleBridgeMethods.iterator().next();
// Guard against accessibility issues.
if (mayBecomeInaccessibleAfterHoisting(clazz, representative)) {
@@ -237,7 +246,10 @@
newMethod.getAccessFlags().demoteFromFinal();
}
clazz.addVirtualMethod(newMethod);
- result.move(representative.getReference(), newMethodReference);
+ result.move(
+ Iterables.transform(eligibleBridgeMethods, DexClassAndMember::getReference),
+ newMethodReference,
+ representative.getReference());
// Remove all of the bridges in the eligible subclasses.
for (DexProgramClass subclass : eligibleSubclasses) {
@@ -260,14 +272,17 @@
return result;
}
- private ProgramMethod findRepresentative(Iterable<DexProgramClass> subclasses, DexMethod method) {
+ private List<ProgramMethod> getBridgesEligibleForHoisting(
+ Iterable<DexProgramClass> subclasses, DexMethod reference) {
+ List<ProgramMethod> result = new ArrayList<>();
for (DexProgramClass subclass : subclasses) {
- DexEncodedMethod definition = subclass.lookupVirtualMethod(method);
- if (definition != null) {
- return new ProgramMethod(subclass, definition);
+ ProgramMethod method = subclass.lookupProgramMethod(reference);
+ if (method != null) {
+ result.add(method);
}
}
- throw new Unreachable();
+ assert !result.isEmpty();
+ return result;
}
private boolean mayBecomeInaccessibleAfterHoisting(
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java
index 9353111..4439aea 100644
--- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java
@@ -11,8 +11,9 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
-import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap;
+import com.google.common.collect.Sets;
import java.util.Set;
import java.util.function.BiConsumer;
@@ -21,12 +22,8 @@
private final AppView<AppInfoWithLiveness> appView;
// Mapping from non-hoisted bridge methods to hoisted bridge methods.
- private final MutableBidirectionalManyToOneMap<DexMethod, DexMethod> bridgeToHoistedBridgeMap =
- new BidirectionalManyToOneHashMap<>();
-
- // Mapping from non-hoisted bridge methods to the set of contexts in which they are accessed.
- private final MethodAccessInfoCollection.IdentityBuilder bridgeMethodAccessInfoCollectionBuilder =
- MethodAccessInfoCollection.identityBuilder();
+ private final MutableBidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod>
+ bridgeToHoistedBridgeMap = new BidirectionalManyToOneRepresentativeHashMap<>();
BridgeHoistingResult(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
@@ -43,30 +40,46 @@
});
}
- public MethodAccessInfoCollection getBridgeMethodAccessInfoCollection() {
- return bridgeMethodAccessInfoCollectionBuilder.build();
- }
-
public boolean isEmpty() {
return bridgeToHoistedBridgeMap.isEmpty();
}
- public void move(DexMethod from, DexMethod to) {
- Set<DexMethod> keys = bridgeToHoistedBridgeMap.getKeys(from);
- if (keys.isEmpty()) {
- bridgeToHoistedBridgeMap.put(from, to);
- } else {
- for (DexMethod original : keys) {
- bridgeToHoistedBridgeMap.put(original, to);
+ public void move(Iterable<DexMethod> from, DexMethod to, DexMethod representative) {
+ DexMethod originalRepresentative =
+ bridgeToHoistedBridgeMap.getRepresentativeKeyOrDefault(representative, representative);
+ Set<DexMethod> originalFrom = Sets.newLinkedHashSet();
+ for (DexMethod method : from) {
+ Set<DexMethod> keys = bridgeToHoistedBridgeMap.removeValue(method);
+ if (keys.isEmpty()) {
+ originalFrom.add(method);
+ } else {
+ originalFrom.addAll(keys);
}
}
+ assert originalFrom.contains(originalRepresentative);
+ bridgeToHoistedBridgeMap.put(originalFrom, to);
+ bridgeToHoistedBridgeMap.setRepresentative(to, originalRepresentative);
+ }
+ public void recordNonReboundMethodAccesses(
+ MethodAccessInfoCollection.IdentityBuilder bridgeMethodAccessInfoCollectionBuilder) {
MethodAccessInfoCollection methodAccessInfoCollection =
appView.appInfo().getMethodAccessInfoCollection();
- methodAccessInfoCollection.forEachVirtualInvokeContext(
- from,
- context ->
- bridgeMethodAccessInfoCollectionBuilder.registerInvokeVirtualInContext(from, context));
+ bridgeToHoistedBridgeMap
+ .keySet()
+ .forEach(
+ from -> {
+ methodAccessInfoCollection.forEachSuperInvokeContext(
+ from,
+ context ->
+ bridgeMethodAccessInfoCollectionBuilder.registerInvokeSuperInContext(
+ from, context));
+ methodAccessInfoCollection.forEachVirtualInvokeContext(
+ from,
+ context ->
+ bridgeMethodAccessInfoCollectionBuilder.registerInvokeVirtualInContext(
+ from, context));
+ });
}
public BridgeHoistingLens buildLens() {
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index ef0bff7..d202954 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.retrace;
+import static com.android.tools.r8.retrace.internal.RetraceUtils.firstNonWhiteSpaceCharacterFromIndex;
import static com.android.tools.r8.utils.ExceptionUtils.STATUS_ERROR;
import static com.android.tools.r8.utils.ExceptionUtils.failWithFakeEntry;
@@ -35,9 +36,11 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
+import java.util.Set;
import java.util.function.BiConsumer;
/**
@@ -207,6 +210,7 @@
boolean isVerbose) {
List<String> retracedStrings = new ArrayList<>();
Box<StackTraceElementStringProxy> lastReportedFrame = new Box<>();
+ Set<String> seenSetForLastReportedFrame = new HashSet<>();
run(
stackTraceVisitor,
StackTraceElementProxyRetracer.createDefault(retracer),
@@ -214,10 +218,22 @@
frames.forEach(
frame -> {
StackTraceElementStringProxy originalItem = frame.getOriginalItem();
- retracedStrings.add(
- originalItem.toRetracedItem(
- frame, lastReportedFrame.get() == stackTraceElement, isVerbose));
- lastReportedFrame.set(stackTraceElement);
+ boolean newFrame =
+ lastReportedFrame.getAndSet(stackTraceElement) != stackTraceElement;
+ if (newFrame) {
+ seenSetForLastReportedFrame.clear();
+ }
+ String retracedString = originalItem.toRetracedItem(frame, isVerbose);
+ if (seenSetForLastReportedFrame.add(retracedString)) {
+ if (frame.isAmbiguous() && !newFrame) {
+ int firstCharIndex = firstNonWhiteSpaceCharacterFromIndex(retracedString, 0);
+ retracedString =
+ retracedString.substring(0, firstCharIndex)
+ + "<OR> "
+ + retracedString.substring(firstCharIndex);
+ }
+ retracedStrings.add(retracedString);
+ }
});
});
return retracedStrings;
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java b/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java
index 90fedbd..9272f58 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java
@@ -4,7 +4,8 @@
package com.android.tools.r8.retrace.internal;
-import static com.google.common.base.Predicates.not;
+import static com.android.tools.r8.retrace.internal.RetraceUtils.firstCharFromIndex;
+import static com.android.tools.r8.retrace.internal.RetraceUtils.firstNonWhiteSpaceCharacterFromIndex;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.retrace.RetraceInvalidStackTraceLineDiagnostics;
@@ -14,7 +15,6 @@
import com.android.tools.r8.utils.DescriptorUtils;
import java.util.List;
import java.util.function.Consumer;
-import java.util.function.Predicate;
public final class PlainStackTraceVisitor
implements StackTraceVisitor<StackTraceElementStringProxy> {
@@ -34,22 +34,6 @@
}
}
- static int firstNonWhiteSpaceCharacterFromIndex(String line, int index) {
- return firstFromIndex(line, index, not(Character::isWhitespace));
- }
-
- static int firstCharFromIndex(String line, int index, char ch) {
- return firstFromIndex(line, index, c -> c == ch);
- }
-
- static int firstFromIndex(String line, int index, Predicate<Character> predicate) {
- for (int i = index; i < line.length(); i++) {
- if (predicate.test(line.charAt(i))) {
- return i;
- }
- }
- return line.length();
- }
/**
* Captures a stack trace line of the following formats:
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
index 35f1f63..341ac8d 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
@@ -124,9 +124,12 @@
private RetracedMethodImpl getRetracedMethod(
MethodReference methodReference, MappedRange mappedRange, int obfuscatedPosition) {
- if (obfuscatedPosition == -1
- || mappedRange.minifiedRange == null
- || !mappedRange.minifiedRange.contains(obfuscatedPosition)) {
+ if (mappedRange.minifiedRange == null) {
+ int originalLineNumber = mappedRange.getFirstLineNumberOfOriginalRange();
+ return RetracedMethodImpl.create(
+ methodReference, originalLineNumber > 0 ? originalLineNumber : obfuscatedPosition);
+ }
+ if (obfuscatedPosition == -1 || !mappedRange.minifiedRange.contains(obfuscatedPosition)) {
return RetracedMethodImpl.create(methodReference);
}
return RetracedMethodImpl.create(
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java
index d475594..fb79984 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java
@@ -166,7 +166,8 @@
}
}
- private static final String anyLetterWithMarkers = "\\p{L}\\p{M}*+";
+ private static final String anyNonDigitLetterCharWithMarkers = "\\p{L}\\p{M}*+";
+ private static final String anyDigit = "\\p{N}";
// TODO(b/145731185): Extend support for identifiers with strings inside back ticks.
private static final String javaIdentifierSegment =
@@ -275,7 +276,10 @@
@Override
String subExpression() {
- return "(?:([" + anyLetterWithMarkers + "_: \\.]*[" + anyLetterWithMarkers + "_\\.])?)";
+ String anyNonDigitSourceFileChar = anyNonDigitLetterCharWithMarkers + "_ \\.";
+ String anyChar = anyNonDigitSourceFileChar + anyDigit;
+ String colonWithNonDigitSuffix = ":[" + anyNonDigitSourceFileChar + ":" + "]";
+ return "((?:(?:(?:" + colonWithNonDigitSuffix + "))|(?:[" + anyChar + "]))+)?";
}
@Override
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
index dc96687..0ca25c7 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.retrace.internal;
+import static com.google.common.base.Predicates.not;
+
import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.references.ClassReference;
@@ -24,6 +26,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+import java.util.function.Predicate;
public class RetraceUtils {
@@ -147,4 +150,21 @@
formalTypes,
returnType);
}
+
+ public static int firstNonWhiteSpaceCharacterFromIndex(String line, int index) {
+ return firstFromIndex(line, index, not(Character::isWhitespace));
+ }
+
+ public static int firstCharFromIndex(String line, int index, char ch) {
+ return firstFromIndex(line, index, c -> c == ch);
+ }
+
+ public static int firstFromIndex(String line, int index, Predicate<Character> predicate) {
+ for (int i = index; i < line.length(); i++) {
+ if (predicate.test(line.charAt(i))) {
+ return i;
+ }
+ }
+ return line.length();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
index eed4f28..c2a6749 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -32,7 +32,7 @@
}
try {
ClassNameMapper classNameMapper =
- ClassNameMapper.mapperFromString(proguardMapProducer.get(), diagnosticsHandler);
+ ClassNameMapper.mapperFromString(proguardMapProducer.get(), diagnosticsHandler, true);
return new RetracerImpl(classNameMapper);
} catch (Throwable throwable) {
throw new InvalidMappingFileException(throwable);
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
index 5a76e44..f0ba88d 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.retrace.internal;
-import static com.android.tools.r8.retrace.internal.PlainStackTraceVisitor.firstNonWhiteSpaceCharacterFromIndex;
import static com.android.tools.r8.retrace.internal.RetraceUtils.methodDescriptionFromRetraceMethod;
import static com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.StringIndex.noIndex;
@@ -136,16 +135,9 @@
}
public String toRetracedItem(
- RetraceStackTraceProxy<StackTraceElementStringProxy> retracedProxy,
- boolean printAmbiguous,
- boolean verbose) {
+ RetraceStackTraceProxy<StackTraceElementStringProxy> retracedProxy, boolean verbose) {
StringBuilder sb = new StringBuilder();
int lastSeenIndex = 0;
- if (retracedProxy.isAmbiguous() && printAmbiguous) {
- lastSeenIndex = firstNonWhiteSpaceCharacterFromIndex(line, 0);
- sb.append(line, 0, lastSeenIndex);
- sb.append("<OR> ");
- }
for (StringIndex index : orderedIndices) {
sb.append(line, lastSeenIndex, index.startIndex);
sb.append(index.retracedString.apply(retracedProxy, this, verbose));
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 9061db8..632c74c 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -361,7 +361,8 @@
private void verify() {
assert keepInfo.verifyPinnedTypesAreLive(liveTypes);
- assert objectAllocationInfoCollection.verifyAllocatedTypesAreLive(liveTypes, this);
+ assert objectAllocationInfoCollection.verifyAllocatedTypesAreLive(
+ liveTypes, missingTypes, this);
}
private static KeepInfoCollection extendPinnedItems(
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 095ba07..e43e72a 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -116,6 +116,7 @@
import com.android.tools.r8.utils.collections.ProgramFieldSet;
import com.android.tools.r8.utils.collections.ProgramMethodMap;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
@@ -2927,6 +2928,8 @@
registerAnalysis(new GenericSignatureEnqueuerAnalysis(enqueuerDefinitionSupplier));
}
if (mode.isInitialTreeShaking()) {
+ registerInvokeAnalysis(
+ appView.getInvokeSpecialBridgeSynthesizer().getEnqueuerInvokeAnalysis());
// This is simulating the effect of the "root set" applied rules.
// This is done only in the initial pass, in subsequent passes the "rules" are reapplied
// by iterating the instances.
@@ -3144,6 +3147,7 @@
// In particular these additions are order independent, i.e., it does not matter which are
// registered first and no dependencies may exist among them.
SyntheticAdditions additions = new SyntheticAdditions();
+ synthesizeInvokeSpecialBridges(additions);
synthesizeInterfaceMethodBridges(additions);
synthesizeLambdas(additions);
synthesizeLibraryConversionWrappers(additions);
@@ -3169,6 +3173,12 @@
additions.enqueueWorkItems(this);
}
+ private void synthesizeInvokeSpecialBridges(SyntheticAdditions additions) {
+ SortedProgramMethodSet bridges =
+ appView.getInvokeSpecialBridgeSynthesizer().insertBridgesForR8();
+ bridges.forEach(additions::addLiveMethod);
+ }
+
private void synthesizeInterfaceMethodBridges(SyntheticAdditions additions) {
for (ProgramMethod bridge : syntheticInterfaceMethodBridges.values()) {
DexProgramClass holder = bridge.getHolder();
@@ -4431,14 +4441,11 @@
fieldReference, new FieldAccessInfoImpl(fieldReference));
fieldAccessInfo.setReadFromAnnotation();
markStaticFieldAsLive(field, KeepReason.referencedInAnnotation(annotationHolder));
- // When an annotation has a field of an enum type with a default value then Java VM
- // will use the values() method on that enum class.
- if (options.isGeneratingClassFiles()
- && annotationHolder == dexItemFactory.annotationDefault) {
- if (field.getHolder().isEnum()) {
- markEnumValuesAsReachable(
- field.getHolder(), KeepReason.referencedInAnnotation(annotationHolder));
- }
+ // When an annotation has a field of an enum type the JVM will use the values() method on
+ // that enum class if the field is referenced.
+ if (options.isGeneratingClassFiles() && field.getHolder().isEnum()) {
+ markEnumValuesAsReachable(
+ field.getHolder(), KeepReason.referencedInAnnotation(annotationHolder));
}
} else {
// There is no dispatch on annotations, so only keep what is directly referenced.
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
index d00bc9a..ae80f4c 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
@@ -332,13 +332,15 @@
@Override
public boolean matches(DexType type) {
+ boolean lastWasNegated = false;
for (Entry<ProguardTypeMatcher> className : classNames.object2BooleanEntrySet()) {
if (className.getKey().matches(type)) {
// If we match a negation, abort as non-match. If we match a positive, return true.
return !className.getBooleanValue();
}
+ lastWasNegated = className.getBooleanValue();
}
- return false;
+ return lastWasNegated;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/utils/BooleanUtils.java b/src/main/java/com/android/tools/r8/utils/BooleanUtils.java
index 75d47f6..e99ff55 100644
--- a/src/main/java/com/android/tools/r8/utils/BooleanUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/BooleanUtils.java
@@ -6,6 +6,8 @@
public class BooleanUtils {
private static final Boolean[] VALUES = new Boolean[] { Boolean.TRUE, Boolean.FALSE };
+ private static final Boolean[] TRUE_VALUES = new Boolean[] {Boolean.TRUE};
+ private static final Boolean[] FALSE_VALUES = new Boolean[] {Boolean.FALSE};
public static int intValue(boolean value) {
return value ? 1 : 0;
@@ -18,4 +20,12 @@
public static Boolean[] values() {
return VALUES;
}
+
+ public static Boolean[] trueValues() {
+ return TRUE_VALUES;
+ }
+
+ public static Boolean[] falseValues() {
+ return FALSE_VALUES;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/Box.java b/src/main/java/com/android/tools/r8/utils/Box.java
index 8938bc8..77be1c9 100644
--- a/src/main/java/com/android/tools/r8/utils/Box.java
+++ b/src/main/java/com/android/tools/r8/utils/Box.java
@@ -34,4 +34,10 @@
public boolean isSet() {
return value != null;
}
+
+ public T getAndSet(T newValue) {
+ T oldValue = value;
+ value = newValue;
+ return oldValue;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index dfbeff6..fe3388b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -508,6 +508,8 @@
public boolean enableLambdaMerging = false;
// Flag to turn on/off desugaring in D8/R8.
public DesugarState desugarState = DesugarState.ON;
+ // Flag to turn on/off desugaring of invoke-special to a virtual method on the current class.
+ public boolean enableInvokeSpecialToVirtualMethodDesugaring = true;
// Flag to turn on/off JDK11+ nest-access control
public boolean enableNestBasedAccessDesugaring = true;
// Flag to turn on/off reduction of nest to improve class merging optimizations.
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeHashMap.java
index 15e1bec..4c2f3a8 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeHashMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeHashMap.java
@@ -52,4 +52,20 @@
}
return Collections.emptySet();
}
+
+ @Override
+ public V remove(K key) {
+ V value = super.remove(key);
+ if (getKeys(value).size() <= 1 || getRepresentativeKey(value) == key) {
+ removeRepresentativeFor(value);
+ }
+ return value;
+ }
+
+ @Override
+ public Set<K> removeValue(V value) {
+ Set<K> keys = super.removeValue(value);
+ removeRepresentativeFor(value);
+ return keys;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/SingleTestRunResult.java b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
index 7f04357..875e50d 100644
--- a/src/test/java/com/android/tools/r8/SingleTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
@@ -182,6 +182,14 @@
return self();
}
+ public RR forCfRuntime(Consumer<RR> action) {
+ if (runtime.isCf()) {
+ action.accept(self());
+ executedSatisfyingRuntime = true;
+ }
+ return self();
+ }
+
public RR otherwise(Consumer<RR> action) {
if (!executedSatisfyingRuntime) {
action.accept(self());
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 367a8f3..ad512cd 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -34,6 +34,7 @@
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
@@ -1287,7 +1288,8 @@
D8Command command = builder.build();
InternalOptions options = command.getInternalOptions();
if (optionsConsumer != null) {
- optionsConsumer.accept(options);
+ ExceptionUtils.withD8CompilationHandler(
+ options.reporter, () -> optionsConsumer.accept(options));
}
D8.runForTesting(command.getInputApp(), options);
return compatSink.build();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java
index 6e8f3e8..70ae6d6 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java
@@ -47,7 +47,7 @@
.addOptionsModification(
options ->
options.desugaredLibraryConfiguration =
- DesugaredLibraryConfiguration.withOnlyRewritePrefixForTesting(x))
+ DesugaredLibraryConfiguration.withOnlyRewritePrefixForTesting(x, options))
.compileWithExpectedDiagnostics(
diagnostics -> {
diagnostics.assertErrorMessageThatMatches(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
new file mode 100644
index 0000000..a9590a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
@@ -0,0 +1,730 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.android.tools.r8.utils.structural.Ordered;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Supplier;
+import org.hamcrest.Matcher;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class ObjectsTest extends DesugaredLibraryTestBase implements Opcodes {
+
+ private final TestParameters parameters;
+ private final boolean libraryDesugarJavaUtilObjects;
+ private final boolean shrinkDesugaredLibrary = false;
+ private final Path androidJar;
+
+ @Parameters(name = "{0}, libraryDesugarJavaUtilObjects: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+ BooleanUtils.values());
+ }
+
+ public ObjectsTest(TestParameters parameters, boolean libraryDesugarJavaUtilObjects) {
+ this.parameters = parameters;
+ this.libraryDesugarJavaUtilObjects = libraryDesugarJavaUtilObjects;
+ // Using desugared library require a compile SDK of 26 or higher.
+ this.androidJar =
+ ToolHelper.getAndroidJar(Ordered.max(parameters.getApiLevel(), AndroidApiLevel.O));
+ }
+
+ DesugaredLibraryConfiguration desugaredLibraryConfiguration(
+ InternalOptions options, boolean libraryCompilation, TestParameters parameters) {
+ return new DesugaredLibraryConfigurationParser(
+ options.dexItemFactory(),
+ options.reporter,
+ libraryCompilation,
+ parameters.getApiLevel().getLevel())
+ .parse(
+ StringResource.fromFile(
+ libraryDesugarJavaUtilObjects
+ ? ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING_ALTERNATIVE_3
+ : ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+ }
+
+ private void configurationForProgramCompilation(InternalOptions options) {
+ options.desugaredLibraryConfiguration =
+ desugaredLibraryConfiguration(options, false, parameters);
+ }
+
+ private void configurationForLibraryCompilation(InternalOptions options) {
+ options.desugaredLibraryConfiguration =
+ desugaredLibraryConfiguration(options, true, parameters);
+ }
+
+ private Matcher<MethodSubject> invokesObjectsCompare(String holder) {
+ return invokesMethod(
+ "int",
+ holder,
+ "compare",
+ ImmutableList.of("java.lang.Object", "java.lang.Object", "java.util.Comparator"));
+ }
+
+ private Matcher<MethodSubject> invokesObjectsEquals(String holder) {
+ return invokesMethod(
+ "boolean", holder, "equals", ImmutableList.of("java.lang.Object", "java.lang.Object"));
+ }
+
+ private Matcher<MethodSubject> invokesObjectsDeepEquals(String holder) {
+ return invokesMethod(
+ "boolean", holder, "deepEquals", ImmutableList.of("java.lang.Object", "java.lang.Object"));
+ }
+
+ private Matcher<MethodSubject> invokesObjectsHash(String holder) {
+ return invokesMethod("int", holder, "hash", ImmutableList.of("java.lang.Object[]"));
+ }
+
+ private Matcher<MethodSubject> invokesObjectsHashCode(String holder) {
+ return invokesMethod("int", holder, "hashCode", ImmutableList.of("java.lang.Object"));
+ }
+
+ private Matcher<MethodSubject> invokesObjectsRequireNonNull(String holder) {
+ return invokesMethod(
+ "java.lang.Object", holder, "requireNonNull", ImmutableList.of("java.lang.Object"));
+ }
+
+ private Matcher<MethodSubject> invokesObjectsRequireNonNullWithMessage(String holder) {
+ return invokesMethod(
+ "java.lang.Object",
+ holder,
+ "requireNonNull",
+ ImmutableList.of("java.lang.Object", "java.lang.String"));
+ }
+
+ private Matcher<MethodSubject> invokesObjectsRequireNonNullWithSupplier(
+ String holder, String Supplier) {
+ return invokesMethod(
+ "java.lang.Object",
+ holder,
+ "requireNonNull",
+ ImmutableList.of("java.lang.Object", Supplier));
+ }
+
+ private Matcher<MethodSubject> invokesObjectsToString(String holder) {
+ return invokesMethod(
+ "java.lang.String", holder, "toString", ImmutableList.of("java.lang.Object"));
+ }
+
+ private Matcher<MethodSubject> invokesObjectsToStringWithNullDefault(String holder) {
+ return invokesMethod(
+ "java.lang.String",
+ holder,
+ "toString",
+ ImmutableList.of("java.lang.Object", "java.lang.String"));
+ }
+
+ private Matcher<MethodSubject> invokesObjectsIsNull(String holder) {
+ return invokesMethod("boolean", holder, "isNull", ImmutableList.of("java.lang.Object"));
+ }
+
+ private Matcher<MethodSubject> invokesObjectsNonNull(String holder) {
+ return invokesMethod("boolean", holder, "nonNull", ImmutableList.of("java.lang.Object"));
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject testClass = inspector.clazz(TestClass.class);
+ assertThat(testClass, isPresent());
+
+ // Objects.equals as added in Android K, so when backporting, this is only backported below K.
+ // However, for library desugaring, the desugaring of Objects.equals happens all the way up to
+ // Android M, as that is grouped with other methods like Objects.requireNonNull which was
+ // added in Android N.
+ boolean invokeJavaUtilObjects =
+ !libraryDesugarJavaUtilObjects
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K)
+ || (libraryDesugarJavaUtilObjects
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+ boolean invokeJDollarUtilObjects =
+ libraryDesugarJavaUtilObjects && parameters.getApiLevel().isLessThan(AndroidApiLevel.N);
+ boolean invokeJavaUtilObjectsWithSupplier =
+ !libraryDesugarJavaUtilObjects || !parameters.getApiLevel().isLessThan(AndroidApiLevel.N);
+ boolean invokeJDollarUtilObjectsWithSupplier =
+ libraryDesugarJavaUtilObjects && parameters.getApiLevel().isLessThan(AndroidApiLevel.N);
+
+ assertThat(
+ testClass.uniqueMethodWithName("objectsCompare"),
+ onlyIf(invokeJavaUtilObjects, invokesObjectsCompare("java.util.Objects")));
+ assertThat(
+ testClass.uniqueMethodWithName("objectsCompare"),
+ onlyIf(invokeJDollarUtilObjects, invokesObjectsCompare("j$.util.Objects")));
+
+ assertThat(
+ testClass.uniqueMethodWithName("objectsDeepEquals"),
+ onlyIf(invokeJavaUtilObjects, invokesObjectsDeepEquals("java.util.Objects")));
+ assertThat(
+ testClass.uniqueMethodWithName("objectsDeepEquals"),
+ onlyIf(invokeJDollarUtilObjects, invokesObjectsDeepEquals("j$.util.Objects")));
+
+ assertThat(
+ testClass.uniqueMethodWithName("objectsEquals"),
+ onlyIf(invokeJavaUtilObjects, invokesObjectsEquals("java.util.Objects")));
+ assertThat(
+ testClass.uniqueMethodWithName("objectsEquals"),
+ onlyIf(invokeJDollarUtilObjects, invokesObjectsEquals("j$.util.Objects")));
+
+ assertThat(
+ testClass.uniqueMethodWithName("objectsHash"),
+ onlyIf(invokeJavaUtilObjects, invokesObjectsHash("java.util.Objects")));
+ assertThat(
+ testClass.uniqueMethodWithName("objectsHash"),
+ onlyIf(invokeJDollarUtilObjects, invokesObjectsHash("j$.util.Objects")));
+
+ assertThat(
+ testClass.uniqueMethodWithName("objectsHashCode"),
+ onlyIf(invokeJavaUtilObjects, invokesObjectsHashCode("java.util.Objects")));
+ assertThat(
+ testClass.uniqueMethodWithName("objectsHashCode"),
+ onlyIf(invokeJDollarUtilObjects, invokesObjectsHashCode("j$.util.Objects")));
+
+ assertThat(
+ testClass.uniqueMethodWithName("objectsRequireNonNull"),
+ onlyIf(invokeJavaUtilObjects, invokesObjectsRequireNonNull("java.util.Objects")));
+ assertThat(
+ testClass.uniqueMethodWithName("objectsRequireNonNull"),
+ onlyIf(invokeJDollarUtilObjects, invokesObjectsRequireNonNull("j$.util.Objects")));
+
+ assertThat(
+ testClass.uniqueMethodWithName("objectsRequireNonNullWithMessage"),
+ onlyIf(
+ invokeJavaUtilObjects, invokesObjectsRequireNonNullWithMessage("java.util.Objects")));
+ assertThat(
+ testClass.uniqueMethodWithName("objectsRequireNonNullWithMessage"),
+ onlyIf(
+ invokeJDollarUtilObjects, invokesObjectsRequireNonNullWithMessage("j$.util.Objects")));
+
+ assertThat(
+ testClass.uniqueMethodWithName("objectsRequireNonNullWithSupplier"),
+ onlyIf(
+ invokeJavaUtilObjectsWithSupplier,
+ invokesObjectsRequireNonNullWithSupplier(
+ "java.util.Objects", "java.util.function.Supplier")));
+ assertThat(
+ testClass.uniqueMethodWithName("objectsRequireNonNullWithSupplier"),
+ onlyIf(
+ invokeJDollarUtilObjectsWithSupplier,
+ invokesObjectsRequireNonNullWithSupplier(
+ "j$.util.Objects", "j$.util.function.Supplier")));
+
+ assertThat(
+ testClass.uniqueMethodWithName("objectsToString"),
+ onlyIf(invokeJavaUtilObjects, invokesObjectsToString("java.util.Objects")));
+ assertThat(
+ testClass.uniqueMethodWithName("objectsToString"),
+ onlyIf(invokeJDollarUtilObjects, invokesObjectsToString("j$.util.Objects")));
+
+ assertThat(
+ testClass.uniqueMethodWithName("objectsToStringWithNullDefault"),
+ onlyIf(invokeJavaUtilObjects, invokesObjectsToStringWithNullDefault("java.util.Objects")));
+ assertThat(
+ testClass.uniqueMethodWithName("objectsToStringWithNullDefault"),
+ onlyIf(invokeJDollarUtilObjects, invokesObjectsToStringWithNullDefault("j$.util.Objects")));
+
+ invokeJavaUtilObjects = parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
+
+ assertThat(
+ testClass.uniqueMethodWithName("objectsIsNull"),
+ onlyIf(invokeJavaUtilObjects, invokesObjectsIsNull("java.util.Objects")));
+ assertThat(
+ testClass.uniqueMethodWithName("objectsIsNull"),
+ onlyIf(invokeJDollarUtilObjects, invokesObjectsIsNull("j$.util.Objects")));
+
+ assertThat(
+ testClass.uniqueMethodWithName("objectsNonNull"),
+ onlyIf(invokeJavaUtilObjects, invokesObjectsNonNull("java.util.Objects")));
+ assertThat(
+ testClass.uniqueMethodWithName("objectsNonNull"),
+ onlyIf(invokeJDollarUtilObjects, invokesObjectsNonNull("j$.util.Objects")));
+ }
+
+ @Test
+ public void testD8Cf() throws Exception {
+ // Adjust API level if running on JDK 8. The java.util.Objects methods added in
+ // Android R where added in JDK 9, so setting the the API level to Android P will backport
+ // these methods for JDK 8.
+ AndroidApiLevel apiLevel = parameters.getApiLevel();
+ if (parameters.getRuntime().isCf()
+ && parameters.getRuntime().asCf().getVm() == CfVm.JDK8
+ && apiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.R)) {
+ apiLevel = AndroidApiLevel.P;
+ }
+
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ // Use D8 to desugar with Java classfile output.
+ Path jar =
+ testForD8(Backend.CF)
+ .addLibraryFiles(androidJar)
+ .addOptionsModification(this::configurationForProgramCompilation)
+ .addInnerClasses(ObjectsTest.class)
+ .addProgramClassFileData(dumpAndroidRUtilsObjectsMethods())
+ .setMinApi(apiLevel)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .inspect(this::inspect)
+ .writeToZip();
+
+ if (parameters.getRuntime().isDex()) {
+ // Collection keep rules is only implemented in the DEX writer.
+ String desugaredLibraryKeepRules = keepRuleConsumer.get();
+ if (desugaredLibraryKeepRules != null) {
+ assertEquals(0, desugaredLibraryKeepRules.length());
+ desugaredLibraryKeepRules = "-keep class * { *; }";
+ }
+
+ // Convert to DEX without desugaring and run.
+ testForD8()
+ .addLibraryFiles(androidJar)
+ .addProgramFiles(jar)
+ .setMinApi(apiLevel)
+ .disableDesugaring()
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ (apiLevel_, keepRules, shrink) ->
+ buildDesugaredLibrary(
+ apiLevel_,
+ keepRules,
+ shrink,
+ ImmutableList.of(),
+ this::configurationForLibraryCompilation),
+ parameters.getApiLevel(),
+ desugaredLibraryKeepRules,
+ shrinkDesugaredLibrary)
+ .run(
+ parameters.getRuntime(),
+ TestClass.class,
+ Boolean.toString(libraryDesugarJavaUtilObjects))
+ .assertSuccessWithOutput(expectedOutput(libraryDesugarJavaUtilObjects));
+ } else {
+ // Build the desugared library in class file format.
+ Path desugaredLib =
+ getDesugaredLibraryInCF(parameters, this::configurationForLibraryCompilation);
+
+ // Run on the JVM with desuagred library on classpath.
+ testForJvm()
+ .addProgramFiles(jar)
+ .addRunClasspathFiles(desugaredLib)
+ .run(
+ parameters.getRuntime(),
+ TestClass.class,
+ Boolean.toString(libraryDesugarJavaUtilObjects))
+ .assertSuccessWithOutput(expectedOutput(libraryDesugarJavaUtilObjects));
+ }
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ Assume.assumeTrue(parameters.getRuntime().isDex());
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8()
+ .addLibraryFiles(androidJar)
+ .addOptionsModification(this::configurationForProgramCompilation)
+ .addInnerClasses(ObjectsTest.class)
+ .addProgramClassFileData(dumpAndroidRUtilsObjectsMethods())
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ (apiLevel, keepRules, shrink) ->
+ buildDesugaredLibrary(
+ apiLevel,
+ keepRules,
+ shrink,
+ ImmutableList.of(),
+ this::configurationForLibraryCompilation),
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .inspect(this::inspect)
+ .run(
+ parameters.getRuntime(),
+ TestClass.class,
+ Boolean.toString(libraryDesugarJavaUtilObjects))
+ .assertSuccessWithOutput(expectedOutput(libraryDesugarJavaUtilObjects));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Assume.assumeTrue(parameters.getRuntime().isDex());
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(parameters.getBackend())
+ .addLibraryFiles(androidJar)
+ .addOptionsModification(this::configurationForProgramCompilation)
+ .addInnerClasses(ObjectsTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addProgramClassFileData(dumpAndroidRUtilsObjectsMethods())
+ .enableInliningAnnotations()
+ .noMinification()
+ .addKeepRules("-keep class AndroidRUtilsObjectsMethods { *; }")
+ .addKeepRules("-neverinline class AndroidRUtilsObjectsMethods { *; }")
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ (apiLevel, keepRules, shrink) ->
+ buildDesugaredLibrary(
+ apiLevel,
+ keepRules,
+ shrink,
+ ImmutableList.of(),
+ this::configurationForLibraryCompilation),
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .inspect(this::inspect)
+ .run(
+ parameters.getRuntime(),
+ TestClass.class,
+ Boolean.toString(libraryDesugarJavaUtilObjects))
+ .assertSuccessWithOutput(expectedOutput(libraryDesugarJavaUtilObjects));
+ }
+
+ private String expectedOutput(boolean objectsRequireNonNullWithSupplierSupported) {
+ return StringUtils.lines(
+ "1",
+ "true",
+ "true",
+ Objects.toString(Objects.hash(1, 2)),
+ "4",
+ "NPE",
+ "Was null",
+ objectsRequireNonNullWithSupplierSupported
+ ? "Supplier said was null"
+ : "Not supported (b/174840626)",
+ "5",
+ "6",
+ "true",
+ "false",
+ "1",
+ "2",
+ "3",
+ "4");
+ }
+
+ static class TestClass {
+ @NeverInline
+ private static void objectsCompare(String s1, String s2) {
+ Comparator<String> stringsNullLast =
+ (o1, o2) -> {
+ if (o1 == null) {
+ return o2 == null ? 0 : 1;
+ }
+ return o2 == null ? -1 : o1.compareTo(o2);
+ };
+ System.out.println(Objects.compare(s1, s2, stringsNullLast));
+ }
+
+ @NeverInline
+ private static void objectsDeepEquals(Object o1, Object o2) {
+ System.out.println(Objects.deepEquals(o1, o2));
+ }
+
+ @NeverInline
+ private static void objectsEquals(Object o1, Object o2) {
+ System.out.println(Objects.equals(o1, o2));
+ }
+
+ @NeverInline
+ private static void objectsHash(Object o1, Object o2) {
+ System.out.println(Objects.hash(o1, o2));
+ }
+
+ @NeverInline
+ private static void objectsHashCode(Object o) {
+ System.out.println(Objects.hashCode(o));
+ }
+
+ @NeverInline
+ private static void objectsRequireNonNull(Object o) {
+ try {
+ System.out.println(Objects.requireNonNull(o));
+ } catch (NullPointerException e) {
+ System.out.println("NPE");
+ }
+ }
+
+ @NeverInline
+ private static void objectsRequireNonNullWithMessage(Object o, String message) {
+ try {
+ System.out.println(Objects.requireNonNull(o, message));
+ } catch (NullPointerException e) {
+ System.out.println(e.getMessage());
+ }
+ }
+
+ @NeverInline
+ private static void objectsRequireNonNullWithSupplier(
+ Object o, Supplier<String> messageSupplier) {
+ try {
+ System.out.println(Objects.requireNonNull(o, messageSupplier));
+ } catch (NullPointerException e) {
+ System.out.println(e.getMessage());
+ }
+ }
+
+ @NeverInline
+ private static void objectsToString(Object o) {
+ System.out.println(Objects.toString(o));
+ }
+
+ @NeverInline
+ private static void objectsToStringWithNullDefault(Object o, String nullDefault) {
+ System.out.println(Objects.toString(o, nullDefault));
+ }
+
+ @NeverInline
+ private static void objectsIsNull(Object o) {
+ System.out.println(Objects.isNull(o));
+ }
+
+ @NeverInline
+ private static void objectsNonNull(Object o) {
+ System.out.println(Objects.nonNull(o));
+ }
+
+ public static void main(String[] args) throws Exception {
+ boolean objectsRequireNonNullWithSupplierSupported = Boolean.parseBoolean(args[0]);
+ // Android K methods.
+ objectsCompare("b", "a");
+ objectsDeepEquals(args, args);
+ objectsEquals(args, args);
+ objectsHash(1, 2);
+ objectsHashCode(4);
+ objectsRequireNonNull(null);
+ objectsRequireNonNullWithMessage(null, "Was null");
+ if (objectsRequireNonNullWithSupplierSupported) {
+ objectsRequireNonNullWithSupplier(null, () -> "Supplier said was null");
+ } else {
+ System.out.println("Not supported (b/174840626)");
+ }
+ objectsToString("5");
+ objectsToStringWithNullDefault(null, "6");
+
+ // Android N methods.
+ objectsIsNull(null);
+ objectsNonNull(null);
+
+ // Android R methods.
+ Class<?> c = Class.forName("AndroidRUtilsObjectsMethods");
+ c.getDeclaredMethod("checkFromIndexSize", int.class, int.class, int.class)
+ .invoke(null, 1, 2, 10);
+ c.getDeclaredMethod("checkFromToIndex", int.class, int.class, int.class)
+ .invoke(null, 2, 4, 10);
+ c.getDeclaredMethod("checkIndex", int.class, int.class).invoke(null, 3, 10);
+ c.getDeclaredMethod("requireNonNullElse", Object.class, Object.class).invoke(null, null, 4);
+ // TODO(b/174840626) Also support requireNonNullElseGet.
+ }
+ }
+
+ /*
+ Dump below is from this source:
+
+ import java.util.function.Supplier;
+ import java.util.Objects;
+
+ public class AndroidRUtilsObjectsMethods {
+ public static void checkFromIndexSize(int fromIndex, int size, int length) {
+ System.out.println(Objects.checkFromIndexSize(fromIndex, size, length));
+ }
+ public static void checkFromToIndex(int fromIndex, int toIndex, int length) {
+ System.out.println(Objects.checkFromToIndex(fromIndex, toIndex, length));
+ }
+ public static void checkIndex(int index, int length) {
+ System.out.println(Objects.checkIndex(index, length));
+ }
+ public static <T> void requireNonNullElse(T obj, T defaultObj) {
+ System.out.println(Objects.requireNonNullElse(obj, defaultObj));
+ }
+ public static <T> void requireNonNullElseGet(T obj, Supplier<? extends T> supplier) {
+ System.out.println(Objects.requireNonNullElse(obj, supplier));
+ }
+ }
+
+ This is added as a dump as it use APIs which are only abailable from JDK 9.
+ */
+ public static byte[] dumpAndroidRUtilsObjectsMethods() throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V9, ACC_PUBLIC | ACC_SUPER, "AndroidRUtilsObjectsMethods", null, "java/lang/Object", null);
+
+ classWriter.visitSource("AndroidRUtilsObjectsMethods.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(3, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "checkFromIndexSize", "(III)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(5, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitVarInsn(ILOAD, 0);
+ methodVisitor.visitVarInsn(ILOAD, 1);
+ methodVisitor.visitVarInsn(ILOAD, 2);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "java/util/Objects", "checkFromIndexSize", "(III)I", false);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(6, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(4, 3);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "checkFromToIndex", "(III)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(8, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitVarInsn(ILOAD, 0);
+ methodVisitor.visitVarInsn(ILOAD, 1);
+ methodVisitor.visitVarInsn(ILOAD, 2);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "java/util/Objects", "checkFromToIndex", "(III)I", false);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(9, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(4, 3);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "checkIndex", "(II)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(11, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitVarInsn(ILOAD, 0);
+ methodVisitor.visitVarInsn(ILOAD, 1);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "java/util/Objects", "checkIndex", "(II)I", false);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(12, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(3, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "requireNonNullElse",
+ "(Ljava/lang/Object;Ljava/lang/Object;)V",
+ "<T:Ljava/lang/Object;>(TT;TT;)V",
+ null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(14, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "java/util/Objects",
+ "requireNonNullElse",
+ "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
+ false);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(15, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(3, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "requireNonNullElseGet",
+ "(Ljava/lang/Object;Ljava/util/function/Supplier;)V",
+ "<T:Ljava/lang/Object;>(TT;Ljava/util/function/Supplier<+TT;>;)V",
+ null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(18, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "java/util/Objects",
+ "requireNonNullElse",
+ "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
+ false);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(19, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(3, 2);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassRecursionTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassRecursionTest.java
new file mode 100644
index 0000000..3ef9e29
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassRecursionTest.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokespecial;
+
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeSpecialOnSameClassRecursionTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeSpecialOnSameClassRecursionTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(Main.class, B.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A.foo -1");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, B.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A.foo -1");
+ }
+
+ private byte[] getClassWithTransformedInvoked() throws IOException {
+ return transformer(A.class)
+ .transformMethodInsnInMethod(
+ "foo",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ if (name.equals("foo")) {
+ continuation.visitMethodInsn(INVOKESPECIAL, owner, name, descriptor, isInterface);
+ } else {
+ continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .transform();
+ }
+
+ static class A {
+ private int i = 5;
+
+ public void foo() {
+ if (i-- > 0) {
+ foo();
+ } else {
+ System.out.println("A.foo " + i);
+ }
+ }
+ }
+
+ static class B extends A {
+ public void run() {
+ super.foo();
+ }
+
+ public void foo() {
+ throw new RuntimeException("never called");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new B().run();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java
index 48790e3..ffb5f37 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java
@@ -4,12 +4,8 @@
package com.android.tools.r8.graph.invokespecial;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -35,24 +31,15 @@
@Test
public void testRuntime() throws Exception {
- try {
- testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
- .addProgramClasses(Main.class)
- .addProgramClassFileData(getClassWithTransformedInvoked())
- .run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("Hello World!");
- // TODO(b/110175213): Remove when fixed.
- assertTrue(parameters.isCfRuntime());
- } catch (CompilationFailedException compilation) {
- assertThat(
- compilation.getCause().getMessage(),
- containsString("Failed to compile unsupported use of invokespecial"));
- }
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(Main.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
}
@Test
public void testR8() throws Exception {
- try {
testForR8(parameters.getBackend())
.addProgramClasses(Main.class)
.addProgramClassFileData(getClassWithTransformedInvoked())
@@ -60,13 +47,6 @@
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("Hello World!");
- // TODO(b/110175213): Remove when fixed.
- assertTrue(parameters.isCfRuntime());
- } catch (CompilationFailedException compilation) {
- assertThat(
- compilation.getCause().getMessage(),
- containsString("Failed to compile unsupported use of invokespecial"));
- }
}
private byte[] getClassWithTransformedInvoked() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameInterfaceTest.java
new file mode 100644
index 0000000..f57ce9b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameInterfaceTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokespecial;
+
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeSpecialOnSameInterfaceTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeSpecialOnSameInterfaceTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(Main.class, A.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, A.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ private byte[] getClassWithTransformedInvoked() throws IOException {
+ return transformer(AInterface.class)
+ .transformMethodInsnInMethod(
+ "bar",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ continuation.visitMethodInsn(INVOKESPECIAL, owner, name, descriptor, isInterface);
+ })
+ .transform();
+ }
+
+ public interface AInterface {
+ default void foo() {
+ System.out.println("Hello World!");
+ }
+
+ default void bar() {
+ foo(); // Will be rewritten to invoke-special AInterface.foo()
+ }
+ }
+
+ public static class A implements AInterface {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new A().bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialSuperVirtualTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialSuperVirtualTest.java
new file mode 100644
index 0000000..012dc0d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialSuperVirtualTest.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokespecial;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeSpecialSuperVirtualTest extends TestBase {
+
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines("A", "B", "A", "C", "A", "B", "A", "B", "A");
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeSpecialSuperVirtualTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(A.class, C.class, Main.class)
+ .addProgramClassFileData(getClassBWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, C.class, Main.class)
+ .addProgramClassFileData(getClassBWithTransformedInvoked())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ private byte[] getClassBWithTransformedInvoked() throws IOException {
+ return transformer(B.class)
+ .transformMethodInsnInMethod(
+ "printAll",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ if (name.equals("replace")) {
+ assertEquals(INVOKESPECIAL, opcode);
+ assertEquals(getBinaryNameFromJavaType(B.class.getTypeName()), owner);
+ continuation.visitMethodInsn(opcode, owner, "print", descriptor, isInterface);
+ } else {
+ continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .transform();
+ }
+
+ public static class A {
+
+ void print() {
+ System.out.println("A");
+ }
+ }
+
+ public static class B extends A {
+
+ void print() {
+ System.out.println("B");
+ super.print();
+ }
+
+ private void replace() {
+ System.out.println("Should not be called.");
+ }
+
+ void printAll() {
+ // Invoke-super.
+ super.print();
+ // Rewritten to Invoke-special to B::print.
+ replace();
+ // Invoke-virtual.
+ print();
+ }
+ }
+
+ public static class C extends B {
+
+ void print() {
+ System.out.println("C");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new C().printAll();
+ new B().printAll();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java
new file mode 100644
index 0000000..b439787
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokespecial;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeSpecialToSubclassTest extends TestBase {
+
+ private static final Set<Class<?>> CLASSES_TO_TEST =
+ ImmutableSet.of(C.class, EmptySubC.class, D.class);
+
+ private final TestParameters parameters;
+ private final Class<?> holder;
+
+ @Parameters(name = "{0} class: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), CLASSES_TO_TEST);
+ }
+
+ public InvokeSpecialToSubclassTest(TestParameters parameters, Class<?> holder) {
+ this.parameters = parameters;
+ this.holder = holder;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(EmptySubC.class, C.class, Main.class)
+ .addProgramClassFileData(getClassBWithTransformedInvoked(holder))
+ .run(parameters.getRuntime(), Main.class)
+ // The failures are very different from one back-end to another: verification error,
+ // invalid invoke-super, segmentation fault, NoSuchMethod, etc.
+ .assertFailure();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(EmptySubC.class, C.class, Main.class)
+ .addProgramClassFileData(getClassBWithTransformedInvoked(holder))
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ // The failures are very different from one back-end to another: verification error,
+ // invalid invoke-super, segmentation fault, NoSuchMethod, etc.
+ .assertFailure();
+ }
+
+ private byte[] getClassBWithTransformedInvoked(Class<?> holder) throws IOException {
+ return transformer(B.class)
+ .transformMethodInsnInMethod(
+ "callPrint",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ if (name.equals("replace")) {
+ assertEquals(INVOKESPECIAL, opcode);
+ assertEquals(getBinaryNameFromJavaType(B.class.getTypeName()), owner);
+ continuation.visitMethodInsn(
+ opcode,
+ getBinaryNameFromJavaType(holder.getTypeName()),
+ "print",
+ descriptor,
+ isInterface);
+ } else {
+ continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .transform();
+ }
+
+ public static class B {
+
+ void print() {
+ System.out.println("B");
+ }
+
+ void callPrint() {
+ ((C) this).replace(); // Replaced by C, EmptySubC and D invoke-special to print.
+ }
+ }
+
+ public static class C extends B {
+
+ private void replace() {
+ System.out.println("Should not be called.");
+ }
+
+ @Override
+ void print() {
+ System.out.println("C");
+ }
+ }
+
+ public static class EmptySubC extends C {}
+
+ public static class D extends EmptySubC {
+ @Override
+ void print() {
+ System.out.println("C");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new D().callPrint();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSuperclassTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSuperclassTest.java
new file mode 100644
index 0000000..90c61eb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSuperclassTest.java
@@ -0,0 +1,134 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokespecial;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeSpecialToSuperclassTest extends TestBase {
+
+ private static final Set<Class<?>> CLASSES_TO_TEST =
+ ImmutableSet.of(S.class, A.class, EmptySubA.class);
+
+ private final TestParameters parameters;
+ private final Class<?> holder;
+
+ @Parameters(name = "{0} class: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), CLASSES_TO_TEST);
+ }
+
+ public InvokeSpecialToSuperclassTest(TestParameters parameters, Class<?> holder) {
+ this.parameters = parameters;
+ this.holder = holder;
+ }
+
+ public String getExpectedResult(boolean r8) {
+ if (holder == S.class
+ && parameters.isDexRuntime()
+ && !r8
+ && parameters.getDexRuntimeVersion().isNewerThanOrEqual(ToolHelper.DexVm.Version.V5_1_1)
+ && parameters.getDexRuntimeVersion().isOlderThanOrEqual(ToolHelper.DexVm.Version.V6_0_1)) {
+ // TODO(b/175285016): Should be "A".
+ // On Android 5-6, the result for S.class is "S" instead of "A" with D8 compilation.
+ return "S";
+ }
+ return "A";
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(S.class, A.class, EmptySubA.class, Main.class)
+ .addProgramClassFileData(getClassBWithTransformedInvoked(holder))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(getExpectedResult(false));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(S.class, A.class, EmptySubA.class, Main.class)
+ .addProgramClassFileData(getClassBWithTransformedInvoked(holder))
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(getExpectedResult(true));
+ }
+
+ private byte[] getClassBWithTransformedInvoked(Class<?> holder) throws IOException {
+ return transformer(B.class)
+ .transformMethodInsnInMethod(
+ "callPrint",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ if (name.equals("replace")) {
+ assertEquals(INVOKESPECIAL, opcode);
+ assertEquals(getBinaryNameFromJavaType(B.class.getTypeName()), owner);
+ continuation.visitMethodInsn(
+ opcode,
+ getBinaryNameFromJavaType(holder.getTypeName()),
+ "print",
+ descriptor,
+ isInterface);
+ } else {
+ continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .transform();
+ }
+
+ public static class S {
+
+ void print() {
+ System.out.println("S");
+ }
+ }
+
+ public static class A extends S {
+
+ void print() {
+ System.out.println("A");
+ }
+ }
+
+ public static class EmptySubA extends A {}
+
+ public static class B extends EmptySubA {
+
+ void print() {
+ System.out.println("B");
+ }
+
+ private void replace() {
+ System.out.println("Should not be called.");
+ }
+
+ void callPrint() {
+ replace(); // Replaced by S, A, EmptySubA invoke-special to print.
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new B().callPrint();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToVirtualMethodTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToVirtualMethodTest.java
index 400e015..830283a 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToVirtualMethodTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToVirtualMethodTest.java
@@ -3,13 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph.invokespecial;
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -47,35 +45,26 @@
.assertSuccessWithOutput(EXPECTED);
}
-
- @Test(expected = CompilationFailedException.class)
+ @Test
public void testD8() throws Exception {
assumeTrue(parameters.isDexRuntime());
testForD8()
.addProgramClasses(Base.class, Bar.class, TestClass.class)
.addProgramClassFileData(getFooTransform())
.setMinApi(parameters.getApiLevel())
- .compileWithExpectedDiagnostics(
- diagnostics ->
- diagnostics
- .assertOnlyErrors()
- .assertErrorsMatch(
- diagnosticMessage(containsString("unsupported use of invokespecial"))));
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
}
- @Test(expected = CompilationFailedException.class)
+ @Test
public void testR8() throws Exception {
testForR8(parameters.getBackend())
.addProgramClasses(Base.class, Bar.class, TestClass.class)
.addProgramClassFileData(getFooTransform())
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(TestClass.class)
- .compileWithExpectedDiagnostics(
- diagnostics ->
- diagnostics
- .assertOnlyErrors()
- .assertErrorsMatch(
- diagnosticMessage(containsString("unsupported use of invokespecial"))));
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java b/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java
index 44dc04c..c329d52 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java
@@ -5,7 +5,6 @@
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.CompilationFailedException;
import com.google.common.collect.ImmutableList;
import org.junit.Rule;
import org.junit.Test;
@@ -71,10 +70,6 @@
String javaResult = runOnJava(builder, clazz.name);
assertEquals(expected, javaResult);
- thrown.expect(CompilationFailedException.class);
-
- // TODO(b/110175213): This will fail with a compilation exception since we cannot translate
- // an invoke-special to a member on the same class.
String artResult = runOnArtD8(builder, clazz.name);
assertEquals(expected, artResult);
}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java b/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java
index c81a2d6..3883528 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java
@@ -78,7 +78,8 @@
options ->
options.desugaredLibraryConfiguration =
DesugaredLibraryConfiguration.withOnlyRewritePrefixForTesting(
- ImmutableMap.of(packageName + ".runtime", packageName + ".library")))
+ ImmutableMap.of(packageName + ".runtime", packageName + ".library"),
+ options))
.compile();
testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithStringIdentifier.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithStringIdentifier.java
new file mode 100644
index 0000000..e589ab1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithStringIdentifier.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageWithStringIdentifier extends RepackageTestBase {
+
+ public final String EXPECTED = "Hello World!";
+
+ public RepackageWithStringIdentifier(
+ String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+ super(flattenPackageHierarchyOrRepackageClasses, parameters);
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(A.class, ACaller.class, Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(
+ transformer(A.class).removeInnerClasses().transform(),
+ transformer(ACaller.class).removeInnerClasses().transform(),
+ transformer(Main.class).removeInnerClasses().transform())
+ .apply(this::configureRepackaging)
+ .addKeepRules(
+ "-identifiernamestring class "
+ + Main.class.getTypeName()
+ + " { java.lang.String name; }")
+ .addKeepRules("-keepclassmembers,allowshrinking class **")
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(inspector -> assertThat(A.class, isRepackaged(inspector)))
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(IllegalAccessException.class);
+ }
+
+ @NeverClassInline
+ static class A {
+
+ @NeverInline
+ public void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class ACaller {
+
+ @NeverInline
+ public static void callA() {
+ new A().foo();
+ }
+ }
+
+ public static class Main {
+
+ private static String name;
+
+ static {
+ if (System.currentTimeMillis() > 0) {
+ name = "com.android.tools.r8.repackage.RepackageWithStringIdentifier$A";
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ Class.forName(name).getDeclaredConstructor().newInstance();
+ ACaller.callA();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 0c7dd7a..72221e3 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -34,10 +34,12 @@
import com.android.tools.r8.retrace.stacktraces.MemberFieldOverlapStackTrace;
import com.android.tools.r8.retrace.stacktraces.MultipleDotsInFileNameStackTrace;
import com.android.tools.r8.retrace.stacktraces.NamedModuleStackTrace;
+import com.android.tools.r8.retrace.stacktraces.NoObfuscationRangeMappingWithStackTrace;
import com.android.tools.r8.retrace.stacktraces.NullStackTrace;
import com.android.tools.r8.retrace.stacktraces.ObfucatedExceptionClassStackTrace;
import com.android.tools.r8.retrace.stacktraces.ObfuscatedRangeToSingleLineStackTrace;
import com.android.tools.r8.retrace.stacktraces.RetraceAssertionErrorStackTrace;
+import com.android.tools.r8.retrace.stacktraces.SourceFileWithNumberAndEmptyStackTrace;
import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
import com.android.tools.r8.retrace.stacktraces.SuppressedStackTrace;
import com.android.tools.r8.retrace.stacktraces.UnicodeInFileNameStackTrace;
@@ -93,6 +95,11 @@
}
@Test
+ public void testNoObfuscationRangeMappingWithStackTrace() {
+ runRetraceTest(new NoObfuscationRangeMappingWithStackTrace());
+ }
+
+ @Test
public void testNullLineTrace() {
TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
NullStackTrace nullStackTrace = new NullStackTrace();
@@ -217,6 +224,11 @@
inspectRetraceTest(stackTraceForTest, stackTraceForTest::inspectField);
}
+ @Test
+ public void testSourceFileWithNumberAndEmptyStackTrace() {
+ runRetraceTest(new SourceFileWithNumberAndEmptyStackTrace());
+ }
+
private void inspectRetraceTest(
StackTraceForTest stackTraceForTest, Consumer<Retracer> inspection) {
inspection.accept(
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java
index ddd4b92..4543fab 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java
@@ -32,9 +32,7 @@
return Arrays.asList(
"java.lang.IndexOutOfBoundsException",
"\tat java.util.ArrayList.get(ArrayList.java:411)",
- "\tat com.android.tools.r8.Internal.foo(Internal.java)",
- "\t<OR> at com.android.tools.r8.Internal.foo(Internal.java)",
- "\t<OR> at com.android.tools.r8.Internal.foo(Internal.java)");
+ "\tat com.android.tools.r8.Internal.foo(Internal.java)");
}
@Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureNonVerboseStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureNonVerboseStackTrace.java
index aa5946c..e4dc987 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureNonVerboseStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureNonVerboseStackTrace.java
@@ -33,10 +33,7 @@
return Arrays.asList(
"java.lang.IndexOutOfBoundsException",
"\tat java.util.ArrayList.get(ArrayList.java:411)",
- "\tat com.android.tools.r8.Internal.foo(Internal.java)",
- "\t<OR> at com.android.tools.r8.Internal.foo(Internal.java)",
- "\t<OR> at com.android.tools.r8.Internal.foo(Internal.java)",
- "\t<OR> at com.android.tools.r8.Internal.foo(Internal.java)");
+ "\tat com.android.tools.r8.Internal.foo(Internal.java)");
}
@Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java
new file mode 100644
index 0000000..4057a91
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class NoObfuscationRangeMappingWithStackTrace implements StackTraceForTest {
+
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat foo.a(Bar.dummy:0)",
+ "\tat foo.b(Foo.dummy:2)",
+ "\tat foo.c(Baz.dummy:8)",
+ "\tat foo.d(Qux.dummy:7)");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.foo(Main.dummy:1)",
+ "\tat com.android.tools.r8.naming.retrace.Main.bar(Main.dummy:3)",
+ "\tat com.android.tools.r8.naming.retrace.Main.baz(Main.dummy:8)",
+ "\tat com.android.tools.r8.naming.retrace.Main.main(Main.dummy:7)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "com.android.tools.r8.naming.retrace.Main -> foo:",
+ " void foo(long):1:1 -> a",
+ " void bar(int):3 -> b",
+ " void baz():0:0 -> c", // For 0:0 and 0 use the original line number
+ " void main(java.lang.String[]):0 -> d");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileWithNumberAndEmptyStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileWithNumberAndEmptyStackTrace.java
new file mode 100644
index 0000000..8a94018
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileWithNumberAndEmptyStackTrace.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class SourceFileWithNumberAndEmptyStackTrace implements StackTraceForTest {
+
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return Arrays.asList(
+ " at com.android.tools.r8.R8.a(R.java:34)", " at com.android.tools.r8.R8.a(:34)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "com.android.tools.r8.R8 -> com.android.tools.r8.R8:",
+ " 34:34:void com.android.tools.r8.utils.ExceptionUtils.withR8CompilationHandler("
+ + "com.android.tools.r8.utils.Reporter,"
+ + "com.android.tools.r8.utils.ExceptionUtils$CompileAction):59:59 -> a",
+ " 34:34:void runForTesting(com.android.tools.r8.utils.AndroidApp,"
+ + "com.android.tools.r8.utils.InternalOptions):261 -> a");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return Arrays.asList(
+ " at com.android.tools.r8.utils.ExceptionUtils.withR8CompilationHandler("
+ + "ExceptionUtils.java:59)",
+ " at com.android.tools.r8.R8.runForTesting(R.java:261)",
+ " at com.android.tools.r8.utils.ExceptionUtils.withR8CompilationHandler("
+ + "ExceptionUtils.java:59)",
+ " at com.android.tools.r8.R8.runForTesting(R8.java:261)");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java
index 3da5958..7254ee6 100644
--- a/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java
@@ -4,115 +4,97 @@
package com.android.tools.r8.shaking;
import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApp;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
-import java.util.List;
-import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
-interface GoingToBeMissed {
- void onSomeEvent(long soLong);
-}
-
-class TestClassForB112849320 {
- GoingToBeMissed instance;
- void foo(GoingToBeMissed instance) {
- System.out.println("B112849320");
- this.instance = instance;
- }
-
- void bar() {
- instance.onSomeEvent(8L);
- }
-
- public static void main(String[] args) {
- TestClassForB112849320 self = new TestClassForB112849320();
- self.foo(l -> {
- if (l > 0) {
- System.out.println(l);
- }
- });
- self.bar();
- }
-}
-
+@RunWith(Parameterized.class)
public class MissingInterfaceTest extends TestBase {
- private static String MAIN_NAME = TestClassForB112849320.class.getCanonicalName();
- private Path libJar;
- private Path libDex;
- @Before
- public void setUp() throws Exception {
- libJar = writeToJar(ImmutableList.of(ToolHelper.getClassAsBytes(GoingToBeMissed.class)));
- libDex = temp.getRoot().toPath().resolve("lib.zip");
- AndroidApp libApp = ToolHelper.runD8(readClasses(GoingToBeMissed.class));
- libApp.writeToZip(libDex, OutputMode.DexIndexed);
+ private final TestParameters parameters;
+ private final String[] EXPECTED = new String[] {"B112849320", "8"};
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public MissingInterfaceTest(TestParameters parameters) {
+ this.parameters = parameters;
}
@Test
public void test_missingInterface() throws Exception {
- List<String> config = ImmutableList.of(
- "-printmapping",
- "-keep class " + MAIN_NAME + " {",
- " public static void main(...);",
- "}"
- );
- R8Command.Builder builder = R8Command.builder();
- builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClassForB112849320.class));
- builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
- builder.addProguardConfiguration(config, Origin.unknown());
- AndroidApp processedApp = ToolHelper.runR8(builder.build(), options -> {
- options.enableInlining = false;
- });
-
- Path outDex = temp.getRoot().toPath().resolve("dex.zip");
- processedApp.writeToZip(outDex, OutputMode.DexIndexed);
- ProcessResult artResult = ToolHelper.runArtRaw(
- ImmutableList.of(outDex.toString(), libDex.toString()), MAIN_NAME, null);
- assertNotEquals(0, artResult.exitCode);
- assertThat(artResult.stdout, containsString("B112849320"));
- assertNotEquals(-1, artResult.stderr.indexOf("AbstractMethodError"));
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClassForB112849320.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(TestClassForB112849320.class)
+ .addOptionsModification(options -> options.enableInlining = false)
+ .allowDiagnosticWarningMessages(
+ parameters.isCfRuntime() || !parameters.canUseDefaultAndStaticInterfaceMethods())
+ .compile()
+ .addRunClasspathFiles(
+ buildOnDexRuntime(
+ parameters,
+ writeToJar(ImmutableList.of(ToolHelper.getClassAsBytes(GoingToBeMissed.class)))))
+ .run(parameters.getRuntime(), TestClassForB112849320.class)
+ .forCfRuntime(r -> r.assertSuccessWithOutputLines(EXPECTED))
+ .otherwise(
+ r -> {
+ r.assertStdoutMatches(containsString("B112849320"));
+ r.assertFailureWithErrorThatThrows(AbstractMethodError.class);
+ });
}
+
@Test
public void test_passingInterfaceAsLib() throws Exception {
- List<String> config = ImmutableList.of(
- "-printmapping",
- "-keep class " + MAIN_NAME + " {",
- " public static void main(...);",
- "}"
- );
- R8Command.Builder builder = R8Command.builder();
- builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClassForB112849320.class));
- builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar(), libJar);
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
- builder.addProguardConfiguration(config, Origin.unknown());
- AndroidApp processedApp = ToolHelper.runR8(builder.build(), options -> {
- options.enableInlining = false;
- });
-
- Path outDex = temp.getRoot().toPath().resolve("dex.zip");
- processedApp.writeToZip(outDex, OutputMode.DexIndexed);
- ProcessResult artResult = ToolHelper.runArtRaw(
- ImmutableList.of(outDex.toString(), libDex.toString()), MAIN_NAME, null);
- assertEquals(0, artResult.exitCode);
- assertThat(artResult.stdout, containsString("B112849320"));
- assertEquals(-1, artResult.stderr.indexOf("AbstractMethodError"));
+ Path lib = writeToJar(ImmutableList.of(ToolHelper.getClassAsBytes(GoingToBeMissed.class)));
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClassForB112849320.class)
+ .addLibraryFiles(lib)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(TestClassForB112849320.class)
+ .addOptionsModification(options -> options.enableInlining = false)
+ .addRunClasspathFiles(buildOnDexRuntime(parameters, lib))
+ .run(parameters.getRuntime(), TestClassForB112849320.class)
+ .assertSuccessWithOutputLines(EXPECTED);
}
+ interface GoingToBeMissed {
+ void onSomeEvent(long soLong);
+ }
+
+ public static class TestClassForB112849320 {
+ GoingToBeMissed instance;
+
+ void foo(GoingToBeMissed instance) {
+ System.out.println("B112849320");
+ this.instance = instance;
+ }
+
+ void bar() {
+ instance.onSomeEvent(8L);
+ }
+
+ public static void main(String[] args) {
+ TestClassForB112849320 self = new TestClassForB112849320();
+ self.foo(
+ l -> {
+ if (l > 0) {
+ System.out.println(l);
+ }
+ });
+ self.bar();
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java
index 983928b..f0152a6 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java
@@ -95,7 +95,7 @@
assertTrue(matchClassName("boobar", "!foobar", "?*<1>ar"));
assertFalse(matchClassName("foobar", "!foobar", "*bar"));
- assertFalse(matchClassName("foo", "!boo"));
+ assertTrue(matchClassName("foo", "!boo"));
assertFalse(matchClassName("foo", "baz,!boo"));
assertFalse(matchClassName("boo", "!boo", "**"));
@@ -105,7 +105,8 @@
assertTrue(matchClassName("boo",
ImmutableList.of(ImmutableList.of("!boo"), ImmutableList.of("**"))));
- assertFalse(matchClassName("boofoo", "!boo*,*foo,boofoo"));
+ // TODO(b/174824047): This parses as !(boo*,*foo,boofoo) and it should be !boo*,*foo,boofoo.
+ assertTrue(matchClassName("boofoo", "!boo*,*foo,boofoo"));
assertTrue(matchClassName("boofoo",
ImmutableList.of(ImmutableList.of("!boo*,*foo"), ImmutableList.of("boofoo"))));
}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
index acb1e86..3b9c067 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
@@ -51,13 +51,9 @@
// * REQUIRED: annotation for Test.field1
// * OPTIONAL: default value of WireField.label
// When generating class file the field values[] is also present as values() is kept.
- assertEquals(
- parameters.isCfRuntime() && defaultEnumValueInAnnotation ? 3 : 2,
- classSubject.allFields().size());
+ assertEquals(parameters.isCfRuntime() ? 3 : 2, classSubject.allFields().size());
// Methods <clinit>, <init> always present. values() present if generating class file.
- assertEquals(
- parameters.isCfRuntime() && defaultEnumValueInAnnotation ? 3 : 2,
- classSubject.allMethods().size());
+ assertEquals(parameters.isCfRuntime() ? 3 : 2, classSubject.allMethods().size());
}
@Test
@@ -254,7 +250,7 @@
return classWriter.toByteArray();
}
- public static byte[] classWireFieldLabel() throws Exception {
+ public static byte[] classWireFieldLabel() {
ClassWriter classWriter = new ClassWriter(0);
FieldVisitor fieldVisitor;
diff --git a/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java
new file mode 100644
index 0000000..d42d3ad
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.enums;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EnumInAnnotationTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public EnumInAnnotationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(EnumInAnnotationTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("TEST_ONE");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(EnumInAnnotationTest.class)
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepRuntimeVisibleAnnotations()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("TEST_ONE");
+ }
+
+ public enum Enum {
+ TEST_ONE,
+ TEST_TWO
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.TYPE})
+ public @interface MyAnnotation {
+
+ Enum value();
+ }
+
+ @MyAnnotation(value = Enum.TEST_ONE)
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(Main.class.getAnnotation(MyAnnotation.class).value());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/negatedrules/NegatedKeepRulesTest.java b/src/test/java/com/android/tools/r8/shaking/negatedrules/NegatedKeepRulesTest.java
new file mode 100644
index 0000000..a0f56b6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/negatedrules/NegatedKeepRulesTest.java
@@ -0,0 +1,295 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.negatedrules;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.Subject;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NegatedKeepRulesTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withCfRuntime(CfVm.JDK9).withDexRuntime(Version.DEFAULT).build();
+ }
+
+ public NegatedKeepRulesTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testPlainR8Compat() throws Exception {
+ testPlain(testForR8Compat(parameters.getBackend()));
+ }
+
+ @Test
+ public void testPlainR8Full() throws Exception {
+ testPlain(testForR8(parameters.getBackend()));
+ }
+
+ @Test
+ public void testPlainProguard() throws Exception {
+ testPlain(testForProguard(ProguardVersion.V7_0_0).addKeepRules("-dontwarn"));
+ }
+
+ public void testPlain(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+ run(testBuilder.addKeepRules("-keep class " + A.class.getTypeName() + " { *; }"))
+ .inspect(
+ inspector -> {
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), not(isPresent()));
+ assertThat(inspector.clazz(C.class), not(isPresent()));
+ assertThat(inspector.clazz(D.class), not(isPresent()));
+ assertThat(inspector.clazz(FooBar.class), not(isPresent()));
+ assertThat(inspector.clazz(BarBar.class), not(isPresent()));
+ });
+ }
+
+ @Test
+ public void testNegationR8Compat() throws Exception {
+ testNegation(testForR8Compat(parameters.getBackend()));
+ }
+
+ @Test
+ public void testNegationPlainR8Full() throws Exception {
+ testNegation(testForR8(parameters.getBackend()));
+ }
+
+ @Test
+ public void testNegationProguard() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testNegation(testForProguard(ProguardVersion.V7_0_0).addKeepRules("-dontwarn"));
+ }
+
+ public void testNegation(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+ run(testBuilder.addKeepRules("-keep class !" + B.class.getTypeName() + " { *; }"))
+ .inspect(
+ inspector -> {
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), not(isPresent()));
+ assertThat(inspector.clazz(C.class), isPresent());
+ assertThat(inspector.clazz(D.class), isPresent());
+ assertThat(inspector.clazz(FooBar.class), isPresent());
+ assertThat(inspector.clazz(BarBar.class), isPresent());
+ });
+ }
+
+ @Test
+ public void testExtendsR8Compat() throws Exception {
+ testExtends(testForR8Compat(parameters.getBackend()));
+ }
+
+ @Test
+ public void testExtendsR8Full() throws Exception {
+ testExtends(testForR8(parameters.getBackend()));
+ }
+
+ @Test
+ public void testExtendsProguard() throws Exception {
+ testExtends(testForProguard(ProguardVersion.V7_0_0).addKeepRules("-dontwarn"));
+ }
+
+ public void testExtends(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+ run(testBuilder.addKeepRules("-keep class ** extends " + A.class.getTypeName() + " { *; }"))
+ .inspect(
+ inspector -> {
+ // A is only kept in full-mode because we are keeping two sub-types. For full-mode,
+ // A could be removed. This is shown in the testNegatedExtends test.
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), isPresent());
+ assertThat(inspector.clazz(C.class), not(isPresent()));
+ assertThat(inspector.clazz(D.class), isPresent());
+ assertThat(inspector.clazz(FooBar.class), not(isPresent()));
+ assertThat(inspector.clazz(BarBar.class), not(isPresent()));
+ });
+ }
+
+ @Test
+ public void testNegatedExtendsR8Compat() throws Exception {
+ testNegatedExtends(testForR8Compat(parameters.getBackend()), isPresent());
+ }
+
+ @Test
+ public void testNegatedExtendsR8Full() throws Exception {
+ testNegatedExtends(testForR8(parameters.getBackend()), not(isPresent()));
+ }
+
+ @Test
+ public void testNegatedExtendsProguard() throws Exception {
+ testNegatedExtends(
+ testForProguard(ProguardVersion.V7_0_0).addKeepRules("-dontwarn"), isPresent());
+ }
+
+ public void testNegatedExtends(
+ TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder, Matcher<Subject> aMatcher) throws Exception {
+ // The negation binds closer than extends (at least for us).
+ run(testBuilder.addKeepRules("-keep class !**B extends " + A.class.getTypeName() + " { *; }"))
+ .inspect(
+ inspector -> {
+ assertThat(inspector.clazz(A.class), aMatcher);
+ assertThat(inspector.clazz(B.class), not(isPresent()));
+ assertThat(inspector.clazz(C.class), not(isPresent()));
+ assertThat(inspector.clazz(D.class), isPresent());
+ assertThat(inspector.clazz(FooBar.class), not(isPresent()));
+ assertThat(inspector.clazz(BarBar.class), not(isPresent()));
+ });
+ }
+
+ @Test
+ public void testNegatedWithStarsR8Compat() throws Exception {
+ testNegatedWithStars(testForR8Compat(parameters.getBackend()));
+ }
+
+ @Test
+ public void testNegatedWithStarsR8Full() throws Exception {
+ testNegatedWithStars(testForR8(parameters.getBackend()));
+ }
+
+ @Test
+ public void testNegatedWithStarsProguard() throws Exception {
+ testNegatedWithStars(testForProguard(ProguardVersion.V7_0_0).addKeepRules("-dontwarn"));
+ }
+
+ public void testNegatedWithStars(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder)
+ throws Exception {
+ run(testBuilder.addKeepRules("-keep class !" + B.class.getTypeName() + ", ** { *; }"))
+ .inspect(
+ inspector -> {
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), not(isPresent()));
+ assertThat(inspector.clazz(C.class), isPresent());
+ assertThat(inspector.clazz(D.class), isPresent());
+ assertThat(inspector.clazz(FooBar.class), isPresent());
+ assertThat(inspector.clazz(BarBar.class), isPresent());
+ });
+ }
+
+ @Test
+ public void testMultipleNegatedWithStarsR8Compat() throws Exception {
+ testMultipleNegatedWithStars(testForR8Compat(parameters.getBackend()));
+ }
+
+ @Test
+ public void testMultipleNegatedWithStarsR8Full() throws Exception {
+ testMultipleNegatedWithStars(testForR8(parameters.getBackend()));
+ }
+
+ @Test
+ public void testMultipleWithStarsProguard() throws Exception {
+ testMultipleNegatedWithStars(testForProguard(ProguardVersion.V7_0_0).addKeepRules("-dontwarn"));
+ }
+
+ public void testMultipleNegatedWithStars(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder)
+ throws Exception {
+ run(testBuilder.addKeepRules(
+ "-keep class !" + B.class.getTypeName() + ",!" + C.class.getTypeName() + ", ** { *; }"))
+ .inspect(
+ inspector -> {
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), not(isPresent()));
+ assertThat(inspector.clazz(C.class), not(isPresent()));
+ assertThat(inspector.clazz(D.class), isPresent());
+ assertThat(inspector.clazz(FooBar.class), isPresent());
+ assertThat(inspector.clazz(BarBar.class), isPresent());
+ });
+ }
+
+ @Test
+ public void testFooBarR8Compat() throws Exception {
+ testFooBar(testForR8Compat(parameters.getBackend()));
+ }
+
+ @Test
+ public void testFooBarR8Full() throws Exception {
+ testFooBar(testForR8(parameters.getBackend()));
+ }
+
+ @Test
+ public void testFooBarProguard() throws Exception {
+ testFooBar(testForProguard(ProguardVersion.V7_0_0).addKeepRules("-dontwarn"));
+ }
+
+ public void testFooBar(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+ run(testBuilder.addKeepRules("-keep class !" + FooBar.class.getTypeName() + ", **Bar { *; }"))
+ .inspect(
+ inspector -> {
+ assertThat(inspector.clazz(BarBar.class), isPresent());
+ assertThat(inspector.clazz(FooBar.class), not(isPresent()));
+ assertThat(inspector.clazz(A.class), not(isPresent()));
+ assertThat(inspector.clazz(B.class), not(isPresent()));
+ assertThat(inspector.clazz(C.class), not(isPresent()));
+ assertThat(inspector.clazz(D.class), not(isPresent()));
+ });
+ }
+
+ @Test
+ public void testMultipleNegatedR8Compat() throws Exception {
+ testMultipleNegated(testForR8Compat(parameters.getBackend()));
+ }
+
+ @Test
+ public void testMultipleNegatedR8Full() throws Exception {
+ testMultipleNegated(testForR8(parameters.getBackend()));
+ }
+
+ @Test
+ public void testMultipleNegatedProguard() throws Exception {
+ testMultipleNegated(testForProguard(ProguardVersion.V7_0_0).addKeepRules("-dontwarn"));
+ }
+
+ public void testMultipleNegated(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+ run(testBuilder.addKeepRules("-keep class !" + FooBar.class.getTypeName() + ", !**Bar { *; }"))
+ .inspect(
+ inspector -> {
+ assertThat(inspector.clazz(BarBar.class), not(isPresent()));
+ assertThat(inspector.clazz(FooBar.class), not(isPresent()));
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), isPresent());
+ assertThat(inspector.clazz(C.class), isPresent());
+ assertThat(inspector.clazz(D.class), isPresent());
+ });
+ }
+
+ private TestCompileResult<?, ?> run(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder)
+ throws Exception {
+ return testBuilder
+ .addProgramClasses(A.class, B.class, C.class, D.class, FooBar.class, BarBar.class)
+ .setMinApi(AndroidApiLevel.B)
+ .noMinification()
+ .compile();
+ }
+
+ public static class A {}
+
+ public static class B extends A {}
+
+ public static class C {}
+
+ public static class D extends A {}
+
+ public static class FooBar {}
+
+ public static class BarBar {}
+}
diff --git a/tools/retrace.py b/tools/retrace.py
index 70e3099..41b5a06 100755
--- a/tools/retrace.py
+++ b/tools/retrace.py
@@ -45,6 +45,11 @@
default=None,
action='store_true',
help='Disables diagnostics printing to stdout.')
+ parser.add_argument(
+ '--debug-agent',
+ default=None,
+ action='store_true',
+ help='Attach a debug-agent to the retracer java process.')
return parser.parse_args()
@@ -73,9 +78,11 @@
args.stacktrace,
args.commit_hash is not None,
args.no_r8lib,
- quiet=args.quiet)
+ quiet=args.quiet,
+ debug=args.debug_agent)
-def run(map_path, hash_or_version, stacktrace, is_hash, no_r8lib, quiet=False):
+def run(map_path, hash_or_version, stacktrace, is_hash, no_r8lib, quiet=False,
+ debug=False):
if hash_or_version:
download_path = archive.GetUploadDestination(
hash_or_version,
@@ -88,8 +95,12 @@
print('Could not find map file from argument: %s.' % hash_or_version)
return 1
- retrace_args = [
- jdk.GetJavaExecutable(),
+ retrace_args = [jdk.GetJavaExecutable()]
+
+ if debug:
+ retrace_args.append('-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005')
+
+ retrace_args += [
'-cp',
utils.R8_JAR if no_r8lib else utils.R8LIB_JAR,
'com.android.tools.r8.retrace.Retrace',