Merge commit '4a6cc7f317bd1b1968461b2688f51f6debbea91d' into dev-release
diff --git a/build.gradle b/build.gradle
index fba205d..9800595 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1822,6 +1822,7 @@
systemProperty 'test_git_HEAD_sha1', project.property('HEAD_sha1')
}
+ dependsOn buildLibraryDesugarConversions
dependsOn getJarsFromSupportLibs
// R8.jar is required for running bootstrap tests.
dependsOn R8
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json
index 3cfd647..7d2924f 100644
--- a/src/library_desugar/desugar_jdk_libs.json
+++ b/src/library_desugar/desugar_jdk_libs.json
@@ -1,6 +1,6 @@
{
"configuration_format_version": 3,
- "version": "0.9.0",
+ "version": "0.11.0",
"required_compilation_api_level": 26,
"synthesized_library_classes_package_prefix": "j$.",
"library_flags": [
@@ -16,6 +16,10 @@
"java.lang.Integer8": "java.lang.Integer",
"java.lang.Long8": "java.lang.Long",
"java.lang.Math8": "java.lang.Math"
+ },
+ "retarget_lib_member": {
+ "java.util.Date#toInstant": "java.util.DesugarDate",
+ "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar"
}
},
{
@@ -52,6 +56,7 @@
"java.util.Iterator#remove"
],
"emulate_interface": {
+ "java.lang.Iterable": "j$.lang.Iterable",
"java.util.Map$Entry": "j$.util.Map$Entry",
"java.util.Collection": "j$.util.Collection",
"java.util.Map": "j$.util.Map",
@@ -66,7 +71,10 @@
"java.util.Optional": "j$.util.OptionalConversions",
"java.util.OptionalDouble": "j$.util.OptionalConversions",
"java.util.OptionalInt": "j$.util.OptionalConversions",
- "java.util.OptionalLong": "j$.util.OptionalConversions"
+ "java.util.OptionalLong": "j$.util.OptionalConversions",
+ "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatisticsConversions",
+ "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatisticsConversions",
+ "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatisticsConversions"
}
}
],
@@ -124,12 +132,15 @@
"java.util.concurrent.atomic.AtomicReference#getAndUpdate": "java.util.concurrent.atomic.DesugarAtomicReference",
"java.util.concurrent.atomic.AtomicReference#updateAndGet": "java.util.concurrent.atomic.DesugarAtomicReference",
"java.util.concurrent.atomic.AtomicReference#getAndAccumulate": "java.util.concurrent.atomic.DesugarAtomicReference",
- "java.util.concurrent.atomic.AtomicReference#accumulateAndGet": "java.util.concurrent.atomic.DesugarAtomicReference"
+ "java.util.concurrent.atomic.AtomicReference#accumulateAndGet": "java.util.concurrent.atomic.DesugarAtomicReference",
+ "java.util.Collections#synchronizedMap": "java.util.DesugarCollections",
+ "java.util.Collections#synchronizedSortedMap": "java.util.DesugarCollections"
},
"dont_rewrite": [
"java.util.Iterator#remove"
],
"emulate_interface": {
+ "java.lang.Iterable": "j$.lang.Iterable",
"java.util.Map$Entry": "j$.util.Map$Entry",
"java.util.Collection": "j$.util.Collection",
"java.util.Map": "j$.util.Map",
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
index 054db58..2915114 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
@@ -4,14 +4,19 @@
package com.android.tools.r8;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
public class BaseCompilerCommandParser {
static void parseMinApi(BaseCompilerCommand.Builder builder, String minApiString, Origin origin) {
int minApi;
try {
- minApi = Integer.valueOf(minApiString);
+ minApi = Integer.parseInt(minApiString);
} catch (NumberFormatException e) {
builder.error(new StringDiagnostic("Invalid argument to --min-api: " + minApiString, origin));
return;
@@ -22,4 +27,40 @@
}
builder.setMinApiLevel(minApi);
}
+
+ /**
+ * This method must match the lookup in
+ * {@link com.android.tools.r8.JdkClassFileProvider#fromJdkHome}.
+ */
+ private static boolean isJdkHome(Path home) {
+ Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar");
+ if (Files.exists(jrtFsJar)) {
+ return true;
+ }
+ // JDK has rt.jar in jre/lib/rt.jar.
+ Path rtJar = home.resolve("jre").resolve("lib").resolve("rt.jar");
+ if (Files.exists(rtJar)) {
+ return true;
+ }
+ // JRE has rt.jar in lib/rt.jar.
+ rtJar = home.resolve("lib").resolve("rt.jar");
+ if (Files.exists(rtJar)) {
+ return true;
+ }
+ return false;
+ }
+
+ static void addLibraryArgument(BaseCommand.Builder builder, Origin origin, String arg) {
+ Path path = Paths.get(arg);
+ if (isJdkHome(path)) {
+ try {
+ builder
+ .addLibraryResourceProvider(JdkClassFileProvider.fromJdkHome(path));
+ } catch (IOException e) {
+ builder.error(new ExceptionDiagnostic(e, origin));
+ }
+ } else {
+ builder.addLibraryFiles(path);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 3985cd7..792f8b9 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -115,7 +115,7 @@
" --release # Compile without debugging information.",
" --output <file> # Output result in <outfile>.",
" # <file> must be an existing directory or a zip file.",
- " --lib <file> # Add <file> as a library resource.",
+ " --lib <file|jdk-home> # Add <file|jdk-home> as a library resource.",
" --classpath <file> # Add <file> as a classpath resource.",
" --min-api <number> # Minimum Android API level compatibility, default: "
+ AndroidApiLevel.getDefault().getLevel()
@@ -209,7 +209,7 @@
}
outputPath = Paths.get(nextArg);
} else if (arg.equals("--lib")) {
- builder.addLibraryFiles(Paths.get(nextArg));
+ addLibraryArgument(builder, origin, nextArg);
} else if (arg.equals("--classpath")) {
Path file = Paths.get(nextArg);
try {
diff --git a/src/main/java/com/android/tools/r8/JdkClassFileProvider.java b/src/main/java/com/android/tools/r8/JdkClassFileProvider.java
index 5e35cf0..9fcbd00 100644
--- a/src/main/java/com/android/tools/r8/JdkClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/JdkClassFileProvider.java
@@ -30,7 +30,9 @@
* <p>The descriptor index is built eagerly upon creating the provider and subsequent requests for
* resources in the descriptor set will then force the read of JDK content.
*
- * <p>Currently only JDK's of version 9 or higher with a lib/jrt-fs.jar file present is supported.
+ * <p>This supports all JDK versions. For JDK's of version 8 or lower classes in
+ * <code>lib/rt.jar</code> will be loaded. JDK's of version 9 or higher system module classes will
+ * be loaded using <code>lib/jrt-fs.jar/<code>.
*/
@Keep
public class JdkClassFileProvider implements ClassFileResourceProvider, Closeable {
@@ -65,11 +67,11 @@
}
/**
- * Creates a lazy class-file program-resource provider for the runtime of a JDK.
+ * Creates a lazy class-file program-resource provider for a JDK.
*
- * <p>This will load the program-resources form the system modules for JDK of version 9 or higher.
+ * <p>This will load the program resources form the system modules for JDK of version 9 or higher.
*
- * <p>This will load <code>rt.jar</code> for JDK of version 8 and lower.
+ * <p>This will load <code>lib/rt.jar</code> for JDK of version 8 and lower.
*
* @param home Location of the JDK to read the program-resources from.
*/
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 1ce3316..b4ae1c8 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinition;
+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.DexReference;
@@ -29,6 +30,7 @@
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.SourceDebugExtensionRewriter;
import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
import com.android.tools.r8.ir.optimize.EnumInfoMapCollector;
import com.android.tools.r8.ir.optimize.MethodPoolCollection;
@@ -318,6 +320,13 @@
assert appView.rootSet().verifyKeptTypesAreLive(appViewWithLiveness.appInfo());
appView.rootSet().checkAllRulesAreUsed(options);
+
+ if (appView.options().enableSourceDebugExtensionRewriter) {
+ appView.setSourceDebugExtensionRewriter(
+ new SourceDebugExtensionRewriter(appView)
+ .analyze(appView.withLiveness().appInfo()::isLiveProgramClass));
+ }
+
if (options.proguardSeedsConsumer != null) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
PrintStream out = new PrintStream(bytes);
@@ -363,7 +372,7 @@
}
assert appView.appInfo().hasLiveness();
-
+ assert verifyNoJarApplicationReaders(application.classes());
// Build conservative main dex content after first round of tree shaking. This is used
// by certain optimizations to avoid introducing additional class references into main dex
// classes, as that can cause the final number of main dex methods to grow.
@@ -883,6 +892,17 @@
}
}
+ private static boolean verifyNoJarApplicationReaders(List<DexProgramClass> classes) {
+ for (DexProgramClass clazz : classes) {
+ for (DexEncodedMethod method : clazz.methods()) {
+ if (method.getCode() != null) {
+ assert method.getCode().verifyNoInputReaders();
+ }
+ }
+ }
+ return true;
+ }
+
private static void run(String[] args) throws CompilationFailedException {
R8Command command = R8Command.parse(args, CommandLineOrigin.INSTANCE).build();
if (command.isPrintHelp()) {
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index fd4f570..b0acdb6 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -58,7 +58,7 @@
" --classfile # Compile program to Java classfile format.",
" --output <file> # Output result in <file>.",
" # <file> must be an existing directory or a zip file.",
- " --lib <file> # Add <file> as a library resource.",
+ " --lib <file|jdk-home> # Add <file|jdk-home> as a library resource.",
" --classpath <file> # Add <file> as a classpath resource.",
" --min-api <number> # Minimum Android API level compatibility, default: "
+ AndroidApiLevel.getDefault().getLevel()
@@ -178,7 +178,7 @@
}
state.outputPath = Paths.get(nextArg);
} else if (arg.equals("--lib")) {
- builder.addLibraryFiles(Paths.get(nextArg));
+ addLibraryArgument(builder, argsOrigin, nextArg);
} else if (arg.equals("--classpath")) {
builder.addClasspathFiles(Paths.get(nextArg));
} else if (arg.equals("--min-api")) {
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 0248418..e30a78d 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -493,7 +493,7 @@
newline();
instructionIndex();
builder.append(getLabel(label)).append(':');
- if (PRINT_INLINE_LOCALS) {
+ if (PRINT_INLINE_LOCALS && labelToIndex != null) {
int labelNumber = labelToIndex.getInt(label);
List<LocalVariableInfo> locals = localsAtLabel.get(labelNumber);
appendComment(
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 66f7051..2801ca8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -77,6 +77,14 @@
return false;
}
+ public CfPosition asPosition() {
+ return null;
+ }
+
+ public boolean isPosition() {
+ return false;
+ }
+
public CfLoad asLoad() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index 684ee26..9302711 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -50,6 +50,16 @@
}
@Override
+ public boolean isPosition() {
+ return true;
+ }
+
+ @Override
+ public CfPosition asPosition() {
+ return this;
+ }
+
+ @Override
public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
Position canonical = code.getCanonicalPosition(position);
state.setPosition(canonical);
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 1f95782..d549e28 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -179,6 +179,10 @@
return false;
}
+ public boolean isThrow() {
+ return false;
+ }
+
public int getPayloadOffset() {
return 0;
}
diff --git a/src/main/java/com/android/tools/r8/code/Throw.java b/src/main/java/com/android/tools/r8/code/Throw.java
index 247b475..40d69f1 100644
--- a/src/main/java/com/android/tools/r8/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/code/Throw.java
@@ -43,4 +43,9 @@
public boolean canThrow() {
return true;
}
+
+ @Override
+ public boolean isThrow() {
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
new file mode 100644
index 0000000..3b4d52f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -0,0 +1,69 @@
+// 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;
+
+/**
+ * Definitions of access control routines.
+ *
+ * <p>Follows SE 11, jvm spec, section 5.4.4 on "Access Control", except for aspects related to
+ * "run-time module", for which all items are assumed to be in the same single such module.
+ */
+public class AccessControl {
+
+ public static boolean isClassAccessible(DexClass clazz, DexProgramClass context) {
+ if (clazz.accessFlags.isPublic()) {
+ return true;
+ }
+ return clazz.getType().isSamePackage(context.getType());
+ }
+
+ public static boolean isMethodAccessible(
+ DexEncodedMethod method,
+ DexClass holder,
+ DexProgramClass context,
+ AppInfoWithSubtyping appInfo) {
+ return isMemberAccessible(method.accessFlags, holder, context, appInfo);
+ }
+
+ public static boolean isFieldAccessible(
+ DexEncodedField field,
+ DexClass holder,
+ DexProgramClass context,
+ AppInfoWithSubtyping appInfo) {
+ return isMemberAccessible(field.accessFlags, holder, context, appInfo);
+ }
+
+ private static boolean isMemberAccessible(
+ AccessFlags<?> memberFlags,
+ DexClass holder,
+ DexProgramClass context,
+ AppInfoWithSubtyping appInfo) {
+ if (!isClassAccessible(holder, context)) {
+ return false;
+ }
+ if (memberFlags.isPublic()) {
+ return true;
+ }
+ if (memberFlags.isPrivate()) {
+ return isNestMate(holder, context);
+ }
+ if (holder.getType().isSamePackage(context.getType())) {
+ return true;
+ }
+ if (!memberFlags.isProtected()) {
+ return false;
+ }
+ return appInfo.isSubtype(context.getType(), holder.getType());
+ }
+
+ private static boolean isNestMate(DexClass clazz, DexProgramClass context) {
+ if (clazz == context) {
+ return true;
+ }
+ if (!clazz.isInANest() || !context.isInANest()) {
+ return false;
+ }
+ return clazz.getNestHost() == context.getNestHost();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 3b51579..2390bc6 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import java.util.ArrayList;
@@ -19,6 +20,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
public class AppInfo implements DexDefinitionSupplier {
@@ -88,6 +90,7 @@
assert checkIfObsolete();
assert clazz.type.isD8R8SynthesizedClassType();
DexProgramClass previous = synthesizedClasses.put(clazz.type, clazz);
+ invalidateTypeCacheFor(clazz.type);
assert previous == null || previous == clazz;
}
@@ -98,7 +101,7 @@
private Map<Descriptor<?,?>, KeyedDexItem<?>> computeDefinitions(DexType type) {
Builder<Descriptor<?,?>, KeyedDexItem<?>> builder = ImmutableMap.builder();
- DexClass clazz = app.definitionFor(type);
+ DexClass clazz = definitionFor(type);
if (clazz != null) {
clazz.forEachMethod(method -> builder.put(method.getKey(), method));
clazz.forEachField(field -> builder.put(field.getKey(), field));
@@ -212,24 +215,7 @@
*/
public DexEncodedMethod lookupSuperTarget(DexMethod method, DexType invocationContext) {
assert checkIfObsolete();
- // Make sure we are not chasing NotFoundError.
- if (resolveMethod(method.holder, method).getSingleTarget() == null) {
- return null;
- }
- // According to
- // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial, use
- // the "symbolic reference" if the "symbolic reference" does not name a class.
- if (definitionFor(method.holder).isInterface()) {
- return resolveMethodOnInterface(method.holder, method).getSingleTarget();
- }
- // Then, resume on the search, but this time, starting from the holder of the caller.
- DexClass contextClass = definitionFor(invocationContext);
- if (contextClass == null || contextClass.superType == null) {
- return null;
- }
- ResolutionResult resolutionResult = resolveMethod(contextClass.superType, method);
- DexEncodedMethod target = resolutionResult.getSingleTarget();
- return target == null || !target.isStatic() ? target : null;
+ return resolveMethod(method.holder, method).lookupInvokeSuperTarget(invocationContext, this);
}
/**
@@ -355,50 +341,67 @@
assert checkIfObsolete();
assert !clazz.isInterface();
// Step 2:
- DexEncodedMethod singleTarget = resolveMethodOnClassStep2(clazz, method);
- if (singleTarget != null) {
- return new SingleResolutionResult(singleTarget);
+ SingleResolutionResult result = resolveMethodOnClassStep2(clazz, method, clazz);
+ if (result != null) {
+ return result;
}
// Finally Step 3:
return resolveMethodStep3(clazz, method);
}
/**
- * Implements step 2 of method resolution on classes as per
- * <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">
- * Section 5.4.3.3 of the JVM Spec</a>.
+ * Implements step 2 of method resolution on classes as per <a
+ * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">Section
+ * 5.4.3.3 of the JVM Spec</a>.
*/
- private DexEncodedMethod resolveMethodOnClassStep2(DexClass clazz, DexMethod method) {
+ private SingleResolutionResult resolveMethodOnClassStep2(
+ DexClass clazz, DexMethod method, DexClass initialResolutionHolder) {
// Pt. 1: Signature polymorphic method check.
// See also <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9">
// Section 2.9 of the JVM Spec</a>.
DexEncodedMethod result = clazz.lookupSignaturePolymorphicMethod(method.name, dexItemFactory);
if (result != null) {
- return result;
+ return new SingleResolutionResult(initialResolutionHolder, clazz, result);
}
// Pt 2: Find a method that matches the descriptor.
result = clazz.lookupMethod(method);
if (result != null) {
- return result;
+ return new SingleResolutionResult(initialResolutionHolder, clazz, result);
}
// Pt 3: Apply step two to direct superclass of holder.
if (clazz.superType != null) {
DexClass superClass = definitionFor(clazz.superType);
if (superClass != null) {
- return resolveMethodOnClassStep2(superClass, method);
+ return resolveMethodOnClassStep2(superClass, method, initialResolutionHolder);
}
}
return null;
}
/**
- * Implements step 3 of
- * <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">
- * Section 5.4.3.3 of the JVM Spec</a>. As this is the same for interfaces and classes, we share
- * one implementation.
+ * Helper method used for emulated interface resolution (not in JVM specifications). The result
+ * may be abstract.
+ */
+ public ResolutionResult resolveMaximallySpecificMethods(DexClass clazz, DexMethod method) {
+ assert !clazz.type.isArrayType();
+ if (clazz.isInterface()) {
+ // Look for exact method on interface.
+ DexEncodedMethod result = clazz.lookupMethod(method);
+ if (result != null) {
+ return new SingleResolutionResult(clazz, clazz, result);
+ }
+ }
+ return resolveMethodStep3(clazz, method);
+ }
+
+ /**
+ * Implements step 3 of <a
+ * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">Section
+ * 5.4.3.3 of the JVM Spec</a>. As this is the same for interfaces and classes, we share one
+ * implementation.
*/
private ResolutionResult resolveMethodStep3(DexClass clazz, DexMethod method) {
- MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
+ MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder(clazz);
resolveMethodStep3Helper(clazz, method, builder);
return builder.resolve();
}
@@ -475,7 +478,7 @@
// Step 2: Look for exact method on interface.
DexEncodedMethod result = definition.lookupMethod(desc);
if (result != null) {
- return new SingleResolutionResult(result);
+ return new SingleResolutionResult(definition, definition, result);
}
// Step 3: Look for matching method on object class.
DexClass objectClass = definitionFor(dexItemFactory.objectType);
@@ -484,7 +487,7 @@
}
result = objectClass.lookupMethod(desc);
if (result != null && result.accessFlags.isPublic() && !result.accessFlags.isAbstract()) {
- return new SingleResolutionResult(result);
+ return new SingleResolutionResult(definition, objectClass, result);
}
// Step 3: Look for maximally-specific superinterface methods or any interface definition.
// This is the same for classes and interfaces.
@@ -590,6 +593,8 @@
private static class MaximallySpecificMethodsBuilder {
+ private final DexClass initialResolutionHolder;
+
// The set of actual maximally specific methods.
// This set is linked map so that in the case where a number of methods remain a deterministic
// choice can be made. The map is from definition classes to their maximally specific method, or
@@ -598,6 +603,10 @@
// prior to writing.
LinkedHashMap<DexClass, DexEncodedMethod> maximallySpecificMethods = new LinkedHashMap<>();
+ public MaximallySpecificMethodsBuilder(DexClass initialResolutionHolder) {
+ this.initialResolutionHolder = initialResolutionHolder;
+ }
+
void addCandidate(DexClass holder, DexEncodedMethod method, AppInfo appInfo) {
// If this candidate is already a candidate or it is shadowed, then no need to continue.
if (maximallySpecificMethods.containsKey(holder)) {
@@ -640,32 +649,42 @@
}
// Fast path in the common case of a single method.
if (maximallySpecificMethods.size() == 1) {
- return new SingleResolutionResult(maximallySpecificMethods.values().iterator().next());
+ Entry<DexClass, DexEncodedMethod> first =
+ maximallySpecificMethods.entrySet().iterator().next();
+ return new SingleResolutionResult(
+ initialResolutionHolder, first.getKey(), first.getValue());
}
- DexEncodedMethod firstMaximallySpecificMethod = null;
- List<DexEncodedMethod> nonAbstractMethods = new ArrayList<>(maximallySpecificMethods.size());
- for (DexEncodedMethod method : maximallySpecificMethods.values()) {
+ Entry<DexClass, DexEncodedMethod> firstMaximallySpecificMethod = null;
+ List<Entry<DexClass, DexEncodedMethod>> nonAbstractMethods =
+ new ArrayList<>(maximallySpecificMethods.size());
+ for (Entry<DexClass, DexEncodedMethod> entry : maximallySpecificMethods.entrySet()) {
+ DexEncodedMethod method = entry.getValue();
if (method == null) {
// Ignore shadowed candidates.
continue;
}
if (firstMaximallySpecificMethod == null) {
- firstMaximallySpecificMethod = method;
+ firstMaximallySpecificMethod = entry;
}
if (method.isNonAbstractVirtualMethod()) {
- nonAbstractMethods.add(method);
+ nonAbstractMethods.add(entry);
}
}
// If there are no non-abstract methods, then any candidate will suffice as a target.
// For deterministic resolution, we return the first mapped method (of the linked map).
if (nonAbstractMethods.isEmpty()) {
- return new SingleResolutionResult(firstMaximallySpecificMethod);
+ return new SingleResolutionResult(
+ initialResolutionHolder,
+ firstMaximallySpecificMethod.getKey(),
+ firstMaximallySpecificMethod.getValue());
}
// If there is exactly one non-abstract method (a default method) it is the resolution target.
if (nonAbstractMethods.size() == 1) {
- return new SingleResolutionResult(nonAbstractMethods.get(0));
+ Entry<DexClass, DexEncodedMethod> entry = nonAbstractMethods.get(0);
+ return new SingleResolutionResult(
+ initialResolutionHolder, entry.getKey(), entry.getValue());
}
- return IncompatibleClassResult.create(nonAbstractMethods);
+ return IncompatibleClassResult.create(ListUtils.map(nonAbstractMethods, Entry::getValue));
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index e7ca421..0998136 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -5,10 +5,8 @@
import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
-import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
-import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.SetUtils;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
@@ -330,39 +328,6 @@
return true;
}
- /**
- * Lookup super method following the super chain from the holder of {@code method}.
- *
- * <p>This method will resolve the method on the holder of {@code method} and only return a
- * non-null value if the result of resolution was an instance (i.e. non-static) method.
- *
- * <p>Additionally, this will also verify that the invoke super is valid, i.e., it is on the same
- * type or a super type of the current context. The spec says that it has invoke super semantics,
- * if the type is a supertype of the current class. If it is the same or a subtype, it has invoke
- * direct semantics. The latter case is illegal, so we map it to a super call here. In R8, we
- * abort at a later stage (see. See also <a href=
- * "https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokespecial" </a>
- * for invokespecial dispatch and <a href="https://docs.oracle.com/javase/specs/jvms/"
- * "se7/html/jvms-4.html#jvms-4.10.1.9.invokespecial"</a> for verification requirements. In
- * particular, the requirement isAssignable(class(CurrentClassName, L), class(MethodClassName,
- * L)). com.android.tools.r8.cf.code.CfInvoke#isInvokeSuper(DexType)}.
- *
- * @param method the method to lookup
- * @param invocationContext the class the invoke is contained in, i.e., the holder of the caller.
- * @return The actual target for {@code method} or {@code null} if none found.
- */
- @Override
- public DexEncodedMethod lookupSuperTarget(DexMethod method, DexType invocationContext) {
- assert checkIfObsolete();
- if (!isSubtype(invocationContext, method.holder)) {
- DexClass contextClass = definitionFor(invocationContext);
- throw new CompilationError(
- "Illegal invoke-super to " + method.toSourceString() + " from class " + invocationContext,
- contextClass != null ? contextClass.getOrigin() : Origin.unknown());
- }
- return super.lookupSuperTarget(method, invocationContext);
- }
-
protected boolean hasAnyInstantiatedLambdas(DexProgramClass clazz) {
assert checkIfObsolete();
return true; // Don't know, there might be.
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 99a2a2b..91994d1 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteShrinker;
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.SourceDebugExtensionRewriter;
import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -57,6 +58,8 @@
private HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses;
private VerticallyMergedClasses verticallyMergedClasses;
+ private SourceDebugExtensionRewriter sourceDebugExtensionRewriter;
+
private AppView(
T appInfo, WholeProgramOptimizations wholeProgramOptimizations, InternalOptions options) {
this(
@@ -141,6 +144,14 @@
allCodeProcessed = true;
}
+ public void setSourceDebugExtensionRewriter(SourceDebugExtensionRewriter rewriter) {
+ this.sourceDebugExtensionRewriter = rewriter;
+ }
+
+ public SourceDebugExtensionRewriter getSourceDebugExtensionRewriter() {
+ return this.sourceDebugExtensionRewriter;
+ }
+
public AppServices appServices() {
return appServices;
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 003b3df..b9e8757 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -92,7 +92,7 @@
private final int maxStack;
private final int maxLocals;
- public final List<CfInstruction> instructions;
+ public List<CfInstruction> instructions;
private final List<CfTryCatch> tryCatchRanges;
private final List<LocalVariableInfo> localVariables;
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 242f953..0fbae38 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -95,4 +95,8 @@
}
public abstract boolean isEmptyVoidMethod();
+
+ public boolean verifyNoInputReaders() {
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 78ba461..358af04 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -968,6 +968,21 @@
return getKotlinInfo() != null;
}
+ public boolean hasInstanceFields() {
+ return instanceFields.length > 0;
+ }
+
+ public boolean hasInstanceFieldsDirectlyOrIndirectly(AppView<?> appView) {
+ if (superType == null || type == appView.dexItemFactory().objectType) {
+ return false;
+ }
+ if (hasInstanceFields()) {
+ return true;
+ }
+ DexClass superClass = appView.definitionFor(superType);
+ return superClass == null || superClass.hasInstanceFieldsDirectlyOrIndirectly(appView);
+ }
+
public boolean isValid(InternalOptions options) {
assert verifyNoAbstractMethodsOnNonAbstractClasses(virtualMethods(), options);
assert !isInterface() || virtualMethods().stream().noneMatch(DexEncodedMethod::isFinal);
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 62fc1a8..97da896 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -913,24 +913,22 @@
return builder.build();
}
- public DexEncodedMethod toEmulateInterfaceLibraryMethod(
+ public static DexEncodedMethod toEmulateDispatchLibraryMethod(
+ DexType interfaceType,
DexMethod newMethod,
DexMethod companionMethod,
DexMethod libraryMethod,
List<Pair<DexType, DexMethod>> extraDispatchCases,
AppView<?> appView) {
- assert isDefaultMethod() || isStatic();
- DexEncodedMethod.Builder builder = DexEncodedMethod.builder(this);
- builder.setMethod(newMethod);
- builder.accessFlags.setSynthetic();
- builder.accessFlags.setStatic();
- builder.accessFlags.unsetPrivate();
- builder.accessFlags.setPublic();
- builder.setCode(
+ MethodAccessFlags accessFlags =
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC, false);
+ CfCode code =
new EmulateInterfaceSyntheticCfCodeProvider(
- this.method.holder, companionMethod, libraryMethod, extraDispatchCases, appView)
- .generateCfCode());
- return builder.build();
+ interfaceType, companionMethod, libraryMethod, extraDispatchCases, appView)
+ .generateCfCode();
+ return new DexEncodedMethod(
+ newMethod, accessFlags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), code);
}
public DexEncodedMethod toStaticForwardingBridge(DexClass holder, DexMethod newMethod) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 40c60ac..faad0e6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -230,6 +230,7 @@
public final DexString consumerDescriptor = createString("Ljava/util/function/Consumer;");
public final DexString runnableDescriptor = createString("Ljava/lang/Runnable;");
public final DexString optionalDescriptor = createString("Ljava/util/Optional;");
+ public final DexString streamDescriptor = createString("Ljava/util/stream/Stream;");
public final DexString arraysDescriptor = createString("Ljava/util/Arrays;");
public final DexString throwableDescriptor = createString(throwableDescriptorString);
@@ -327,6 +328,7 @@
public final DexType consumerType = createType(consumerDescriptor);
public final DexType runnableType = createType(runnableDescriptor);
public final DexType optionalType = createType(optionalDescriptor);
+ public final DexType streamType = createType(streamDescriptor);
public final DexType runtimeExceptionType = createType(runtimeExceptionDescriptor);
public final DexType throwableType = createType(throwableDescriptor);
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index e3d78cf..cc39610 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -246,6 +246,11 @@
return isDoubleType() || isLongType();
}
+ public boolean isD8R8SynthesizedLambdaClassType() {
+ String name = toSourceString();
+ return name.contains(LAMBDA_CLASS_NAME_PREFIX);
+ }
+
public boolean isD8R8SynthesizedClassType() {
String name = toSourceString();
return name.contains(COMPANION_CLASS_NAME_SUFFIX)
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index cbce2db..f105f9c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -65,6 +65,14 @@
return null;
}
+ public DexValueString asDexValueString() {
+ return null;
+ }
+
+ public boolean isDexValueString() {
+ return false;
+ }
+
public boolean isDexValueType() {
return false;
}
@@ -779,6 +787,16 @@
}
@Override
+ public DexValueString asDexValueString() {
+ return this;
+ }
+
+ @Override
+ public boolean isDexValueString() {
+ return true;
+ }
+
+ @Override
public Object asAsmEncodedObject() {
return value.toString();
}
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 8feed9b..edec3a9 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -941,14 +941,20 @@
return parsingOptions;
}
+ @Override
+ public boolean verifyNoInputReaders() {
+ assert context == null && application == null;
+ return true;
+ }
+
private static boolean verifyNoReparseContext(DexClass owner) {
for (DexEncodedMethod method : owner.virtualMethods()) {
Code code = method.getCode();
- assert code == null || !(code instanceof LazyCfCode) || ((LazyCfCode) code).context == null;
+ assert code == null || code.verifyNoInputReaders();
}
for (DexEncodedMethod method : owner.directMethods()) {
Code code = method.getCode();
- assert code == null || !(code instanceof LazyCfCode) || ((LazyCfCode) code).context == null;
+ assert code == null || code.verifyNoInputReaders();
}
return true;
}
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index 1f8343e..bdea265 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -5,6 +5,8 @@
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.Sets;
@@ -15,128 +17,67 @@
public abstract class ResolutionResult {
+ /**
+ * Returns true if resolution succeeded *and* the resolved method has a known definition.
+ *
+ * <p>Note that {@code !isSingleResolution() && !isFailedResolution()} can be true. In that case
+ * that resolution has succeeded, but the definition of the resolved method is unknown. In
+ * particular this is the case for the clone() method on arrays.
+ */
+ public boolean isSingleResolution() {
+ return false;
+ }
+
+ /** Returns non-null if isSingleResolution() is true, otherwise null. */
+ public SingleResolutionResult asSingleResolution() {
+ return null;
+ }
+
+ /**
+ * Returns true if resolution failed.
+ *
+ * <p>Note the disclaimer in the doc of {@code isSingleResolution()}.
+ */
public boolean isFailedResolution() {
return false;
}
+ /** Returns non-null if isFailedResolution() is true, otherwise null. */
public FailedResolutionResult asFailedResolution() {
return null;
}
- public abstract DexEncodedMethod getSingleTarget();
+ /** Short-hand to get the single resolution method if resolution finds it, null otherwise. */
+ public final DexEncodedMethod getSingleTarget() {
+ return isSingleResolution() ? asSingleResolution().getResolvedMethod() : null;
+ }
- public abstract boolean hasSingleTarget();
+ public abstract boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo);
+
+ public abstract boolean isAccessibleForVirtualDispatchFrom(
+ DexProgramClass context, AppInfoWithSubtyping appInfo);
public abstract boolean isValidVirtualTarget(InternalOptions options);
public abstract boolean isValidVirtualTargetForDynamicDispatch();
- public Set<DexEncodedMethod> lookupVirtualDispatchTargets(
+ /** Lookup the single target of an invoke-super on this resolution result if possible. */
+ public abstract DexEncodedMethod lookupInvokeSuperTarget(DexType context, AppInfo appInfo);
+
+ public final Set<DexEncodedMethod> lookupVirtualDispatchTargets(
boolean isInterface, AppInfoWithSubtyping appInfo) {
return isInterface ? lookupInterfaceTargets(appInfo) : lookupVirtualTargets(appInfo);
}
- // TODO(b/140204899): Leverage refined receiver type if available.
- public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
- assert isValidVirtualTarget(appInfo.app().options);
- // First add the target for receiver type method.type.
- DexEncodedMethod encodedMethod = getSingleTarget();
- Set<DexEncodedMethod> result = SetUtils.newIdentityHashSet(encodedMethod);
- // Add all matching targets from the subclass hierarchy.
- DexMethod method = encodedMethod.method;
- // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined
- // receiver type if available.
- for (DexType type : appInfo.subtypes(method.holder)) {
- DexClass clazz = appInfo.definitionFor(type);
- if (!clazz.isInterface()) {
- ResolutionResult methods = appInfo.resolveMethodOnClass(clazz, method);
- DexEncodedMethod target = methods.getSingleTarget();
- if (target != null && target.isVirtualMethod()) {
- result.add(target);
- }
- }
- }
- return result;
- }
+ public abstract Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo);
- // TODO(b/140204899): Leverage refined receiver type if available.
- public Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
- assert isValidVirtualTarget(appInfo.app().options);
- Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
- if (hasSingleTarget()) {
- // Add default interface methods to the list of targets.
- //
- // This helps to make sure we take into account synthesized lambda classes
- // that we are not aware of. Like in the following example, we know that all
- // classes, XX in this case, override B::bar(), but there are also synthesized
- // classes for lambda which don't, so we still need default method to be live.
- //
- // public static void main(String[] args) {
- // X x = () -> {};
- // x.bar();
- // }
- //
- // interface X {
- // void foo();
- // default void bar() { }
- // }
- //
- // class XX implements X {
- // public void foo() { }
- // public void bar() { }
- // }
- //
- DexEncodedMethod singleTarget = getSingleTarget();
- if (singleTarget.hasCode()) {
- DexProgramClass holder =
- asProgramClassOrNull(appInfo.definitionFor(singleTarget.method.holder));
- if (appInfo.hasAnyInstantiatedLambdas(holder)) {
- result.add(singleTarget);
- }
- }
- }
+ public abstract Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo);
- DexEncodedMethod encodedMethod = getSingleTarget();
- DexMethod method = encodedMethod.method;
- Consumer<DexEncodedMethod> addIfNotAbstract =
- m -> {
- if (!m.accessFlags.isAbstract()) {
- result.add(m);
- }
- };
- // Default methods are looked up when looking at a specific subtype that does not override
- // them.
- // Otherwise, we would look up default methods that are actually never used. However, we have
- // to
- // add bridge methods, otherwise we can remove a bridge that will be used.
- Consumer<DexEncodedMethod> addIfNotAbstractAndBridge =
- m -> {
- if (!m.accessFlags.isAbstract() && m.accessFlags.isBridge()) {
- result.add(m);
- }
- };
-
- // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined
- // receiver type if available.
- for (DexType type : appInfo.subtypes(method.holder)) {
- DexClass clazz = appInfo.definitionFor(type);
- if (clazz.isInterface()) {
- ResolutionResult targetMethods = appInfo.resolveMethodOnInterface(clazz, method);
- if (targetMethods.hasSingleTarget()) {
- addIfNotAbstractAndBridge.accept(targetMethods.getSingleTarget());
- }
- } else {
- ResolutionResult targetMethods = appInfo.resolveMethodOnClass(clazz, method);
- if (targetMethods.hasSingleTarget()) {
- addIfNotAbstract.accept(targetMethods.getSingleTarget());
- }
- }
- }
- return result;
- }
-
+ /** Result for a resolution that succeeds with a known declaration/definition. */
public static class SingleResolutionResult extends ResolutionResult {
- final DexEncodedMethod resolutionTarget;
+ private final DexClass initialResolutionHolder;
+ private final DexClass resolvedHolder;
+ private final DexEncodedMethod resolvedMethod;
public static boolean isValidVirtualTarget(InternalOptions options, DexEncodedMethod target) {
return options.canUseNestBasedAccess()
@@ -144,55 +85,242 @@
: target.isVirtualMethod();
}
- public SingleResolutionResult(DexEncodedMethod resolutionTarget) {
- assert resolutionTarget != null;
- this.resolutionTarget = resolutionTarget;
+ public SingleResolutionResult(
+ DexClass initialResolutionHolder,
+ DexClass resolvedHolder,
+ DexEncodedMethod resolvedMethod) {
+ assert initialResolutionHolder != null;
+ assert resolvedHolder != null;
+ assert resolvedMethod != null;
+ assert resolvedHolder.type == resolvedMethod.method.holder;
+ this.resolvedHolder = resolvedHolder;
+ this.resolvedMethod = resolvedMethod;
+ this.initialResolutionHolder = initialResolutionHolder;
+ }
+
+ public DexClass getResolvedHolder() {
+ return resolvedHolder;
+ }
+
+ public DexEncodedMethod getResolvedMethod() {
+ return resolvedMethod;
+ }
+
+ @Override
+ public boolean isSingleResolution() {
+ return true;
+ }
+
+ @Override
+ public SingleResolutionResult asSingleResolution() {
+ return this;
+ }
+
+ @Override
+ public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo) {
+ return AccessControl.isMethodAccessible(
+ resolvedMethod, initialResolutionHolder, context, appInfo);
+ }
+
+ @Override
+ public boolean isAccessibleForVirtualDispatchFrom(
+ DexProgramClass context, AppInfoWithSubtyping appInfo) {
+ // If a private method is accessible (which implies it is via its nest), then it is a valid
+ // virtual dispatch target if non-static.
+ return isAccessibleFrom(context, appInfo)
+ && (resolvedMethod.isVirtualMethod()
+ || (resolvedMethod.isPrivateMethod() && !resolvedMethod.isStatic()));
}
@Override
public boolean isValidVirtualTarget(InternalOptions options) {
- return isValidVirtualTarget(options, resolutionTarget);
+ return isValidVirtualTarget(options, resolvedMethod);
}
@Override
public boolean isValidVirtualTargetForDynamicDispatch() {
- return resolutionTarget.isVirtualMethod();
+ return resolvedMethod.isVirtualMethod();
+ }
+
+ /**
+ * Lookup super method following the super chain from the holder of {@code method}.
+ *
+ * <p>This method will resolve the method on the holder of {@code method} and only return a
+ * non-null value if the result of resolution was an instance (i.e. non-static) method.
+ *
+ * <p>Additionally, this will also verify that the invoke super is valid, i.e., it is on the
+ * same type or a super type of the current context. The spec says that it has invoke super
+ * semantics, if the type is a supertype of the current class. If it is the same or a subtype,
+ * it has invoke direct semantics. The latter case is illegal, so we map it to a super call
+ * here. In R8, we abort at a later stage (see. See also <a href=
+ * "https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokespecial" </a>
+ * for invokespecial dispatch and <a href="https://docs.oracle.com/javase/specs/jvms/"
+ * "se7/html/jvms-4.html#jvms-4.10.1.9.invokespecial"</a> for verification requirements. In
+ * particular, the requirement isAssignable(class(CurrentClassName, L), class(MethodClassName,
+ * L)). com.android.tools.r8.cf.code.CfInvoke#isInvokeSuper(DexType)}.
+ *
+ * @param context the class the invoke is contained in, i.e., the holder of the caller.
+ * @param appInfo Application info.
+ * @return The actual target for the invoke-super or {@code null} if none found.
+ */
+ @Override
+ public DexEncodedMethod lookupInvokeSuperTarget(DexType context, AppInfo appInfo) {
+ DexMethod method = resolvedMethod.method;
+ // TODO(b/145775365): Check the requirements for an invoke-special to a protected method.
+ // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial
+
+ if (appInfo.hasSubtyping()
+ && !appInfo.withSubtyping().isSubtype(context, initialResolutionHolder.type)) {
+ DexClass contextClass = appInfo.definitionFor(context);
+ throw new CompilationError(
+ "Illegal invoke-super to " + method.toSourceString() + " from class " + context,
+ contextClass != null ? contextClass.getOrigin() : Origin.unknown());
+ }
+
+ // According to
+ // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial, use
+ // the "symbolic reference" if the "symbolic reference" does not name a class.
+ // TODO(b/145775365): This looks like the exact opposite of what the spec says, second item is
+ // - is-class(sym-ref) => is-super(sym-ref, current-class)
+ // this implication trivially holds for !is-class(sym-ref) == is-inteface(sym-ref), thus
+ // the resolution should specifically *not* use the "symbolic reference".
+ if (initialResolutionHolder.isInterface()) {
+ // TODO(b/145775365): This does not consider a static method!
+ return appInfo.resolveMethodOnInterface(initialResolutionHolder, method).getSingleTarget();
+ }
+ // Then, resume on the search, but this time, starting from the holder of the caller.
+ DexClass contextClass = appInfo.definitionFor(context);
+ if (contextClass == null || contextClass.superType == null) {
+ return null;
+ }
+ SingleResolutionResult resolution =
+ appInfo.resolveMethodOnClass(contextClass.superType, method).asSingleResolution();
+ return resolution != null && !resolution.resolvedMethod.isStatic()
+ ? resolution.resolvedMethod
+ : null;
}
@Override
- public DexEncodedMethod getSingleTarget() {
- return resolutionTarget;
- }
-
- @Override
- public boolean hasSingleTarget() {
- return true;
- }
- }
-
- public abstract static class EmptyResult extends ResolutionResult {
-
- @Override
- public DexEncodedMethod getSingleTarget() {
- return null;
- }
-
- @Override
- public boolean hasSingleTarget() {
- return false;
- }
-
- @Override
+ // TODO(b/140204899): Leverage refined receiver type if available.
public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
+ assert isValidVirtualTarget(appInfo.app().options);
+ // First add the target for receiver type method.type.
+ DexEncodedMethod encodedMethod = getSingleTarget();
+ Set<DexEncodedMethod> result = SetUtils.newIdentityHashSet(encodedMethod);
+ // Add all matching targets from the subclass hierarchy.
+ DexMethod method = encodedMethod.method;
+ // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined
+ // receiver type if available.
+ for (DexType type : appInfo.subtypes(method.holder)) {
+ DexClass clazz = appInfo.definitionFor(type);
+ if (!clazz.isInterface()) {
+ ResolutionResult methods = appInfo.resolveMethodOnClass(clazz, method);
+ DexEncodedMethod target = methods.getSingleTarget();
+ if (target != null && target.isVirtualMethod()) {
+ result.add(target);
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ // TODO(b/140204899): Leverage refined receiver type if available.
+ public Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
+ assert isValidVirtualTarget(appInfo.app().options);
+ Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
+ if (isSingleResolution()) {
+ // Add default interface methods to the list of targets.
+ //
+ // This helps to make sure we take into account synthesized lambda classes
+ // that we are not aware of. Like in the following example, we know that all
+ // classes, XX in this case, override B::bar(), but there are also synthesized
+ // classes for lambda which don't, so we still need default method to be live.
+ //
+ // public static void main(String[] args) {
+ // X x = () -> {};
+ // x.bar();
+ // }
+ //
+ // interface X {
+ // void foo();
+ // default void bar() { }
+ // }
+ //
+ // class XX implements X {
+ // public void foo() { }
+ // public void bar() { }
+ // }
+ //
+ DexEncodedMethod singleTarget = getSingleTarget();
+ if (singleTarget.hasCode()) {
+ DexProgramClass holder =
+ asProgramClassOrNull(appInfo.definitionFor(singleTarget.method.holder));
+ if (appInfo.hasAnyInstantiatedLambdas(holder)) {
+ result.add(singleTarget);
+ }
+ }
+ }
+
+ DexEncodedMethod encodedMethod = getSingleTarget();
+ DexMethod method = encodedMethod.method;
+ Consumer<DexEncodedMethod> addIfNotAbstract =
+ m -> {
+ if (!m.accessFlags.isAbstract()) {
+ result.add(m);
+ }
+ };
+ // Default methods are looked up when looking at a specific subtype that does not override
+ // them.
+ // Otherwise, we would look up default methods that are actually never used. However, we have
+ // to
+ // add bridge methods, otherwise we can remove a bridge that will be used.
+ Consumer<DexEncodedMethod> addIfNotAbstractAndBridge =
+ m -> {
+ if (!m.accessFlags.isAbstract() && m.accessFlags.isBridge()) {
+ result.add(m);
+ }
+ };
+
+ // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined
+ // receiver type if available.
+ for (DexType type : appInfo.subtypes(method.holder)) {
+ DexClass clazz = appInfo.definitionFor(type);
+ if (clazz.isInterface()) {
+ ResolutionResult targetMethods = appInfo.resolveMethodOnInterface(clazz, method);
+ if (targetMethods.isSingleResolution()) {
+ addIfNotAbstractAndBridge.accept(targetMethods.getSingleTarget());
+ }
+ } else {
+ ResolutionResult targetMethods = appInfo.resolveMethodOnClass(clazz, method);
+ if (targetMethods.isSingleResolution()) {
+ addIfNotAbstract.accept(targetMethods.getSingleTarget());
+ }
+ }
+ }
+ return result;
+ }
+ }
+
+ abstract static class EmptyResult extends ResolutionResult {
+
+ @Override
+ public final DexEncodedMethod lookupInvokeSuperTarget(DexType context, AppInfo appInfo) {
return null;
}
@Override
- public Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
+ public final Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
+ return null;
+ }
+
+ @Override
+ public final Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
return null;
}
}
+ /** Singleton result for the special case resolving the array clone() method. */
public static class ArrayCloneMethodResult extends EmptyResult {
static final ArrayCloneMethodResult INSTANCE = new ArrayCloneMethodResult();
@@ -202,6 +330,17 @@
}
@Override
+ public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo) {
+ return true;
+ }
+
+ @Override
+ public boolean isAccessibleForVirtualDispatchFrom(
+ DexProgramClass context, AppInfoWithSubtyping appInfo) {
+ return true;
+ }
+
+ @Override
public boolean isValidVirtualTarget(InternalOptions options) {
return true;
}
@@ -212,6 +351,7 @@
}
}
+ /** Base class for all types of failed resolutions. */
public abstract static class FailedResolutionResult extends EmptyResult {
@Override
@@ -229,6 +369,17 @@
}
@Override
+ public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo) {
+ return false;
+ }
+
+ @Override
+ public boolean isAccessibleForVirtualDispatchFrom(
+ DexProgramClass context, AppInfoWithSubtyping appInfo) {
+ return false;
+ }
+
+ @Override
public boolean isValidVirtualTarget(InternalOptions options) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
index e5ace97..1049fb8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -336,7 +336,7 @@
DexMethod method = instruction.getInvokedMethod();
ResolutionResult resolutionResult =
appView.appInfo().resolveMethodOnInterface(method.holder, method);
- if (!resolutionResult.hasSingleTarget()) {
+ if (!resolutionResult.isSingleResolution()) {
return false;
}
DexType holder = resolutionResult.getSingleTarget().method.holder;
@@ -394,7 +394,7 @@
}
ResolutionResult resolutionResult =
appView.appInfo().resolveMethod(superType, method, instruction.itf);
- if (!resolutionResult.hasSingleTarget()) {
+ if (!resolutionResult.isSingleResolution()) {
return false;
}
DexType holder = resolutionResult.getSingleTarget().method.holder;
@@ -430,7 +430,7 @@
DexMethod method = instruction.getInvokedMethod();
ResolutionResult resolutionResult =
appView.appInfo().resolveMethodOnClass(method.holder, method);
- if (!resolutionResult.hasSingleTarget()) {
+ if (!resolutionResult.isSingleResolution()) {
return false;
}
DexType holder = resolutionResult.getSingleTarget().method.holder;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index 0605623..9fcee4a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -4,9 +4,12 @@
package com.android.tools.r8.ir.analysis;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.IRCode;
@@ -16,6 +19,7 @@
import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewArrayFilledData;
+import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
@@ -284,6 +288,12 @@
return false;
}
+ NewInstance newInstance = value.definition.asNewInstance();
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(newInstance.clazz));
+ if (clazz == null) {
+ return false;
+ }
+
// Find the single constructor invocation.
InvokeMethod constructorInvoke = null;
for (Instruction instruction : value.uniqueUsers()) {
@@ -319,24 +329,26 @@
return false;
}
- InstanceInitializerInfo initializerInfo =
- constructor.getOptimizationInfo().getInstanceInitializerInfo();
- if (initializerInfo.instanceFieldInitializationMayDependOnEnvironment()) {
- return false;
- }
-
- // Check that none of the arguments to the constructor depend on the environment.
- for (int i = 1; i < constructorInvoke.arguments().size(); i++) {
- Value argument = constructorInvoke.arguments().get(i);
- if (valueMayDependOnEnvironment(argument, assumedNotToDependOnEnvironment)) {
+ if (clazz.hasInstanceFieldsDirectlyOrIndirectly(appView)) {
+ InstanceInitializerInfo initializerInfo =
+ constructor.getOptimizationInfo().getInstanceInitializerInfo();
+ if (initializerInfo.instanceFieldInitializationMayDependOnEnvironment()) {
return false;
}
- }
- // Finally, check that the object does not escape.
- if (valueMayBeMutatedBeforeMethodExit(
- value, assumedNotToDependOnEnvironment, ImmutableSet.of(constructorInvoke))) {
- return false;
+ // Check that none of the arguments to the constructor depend on the environment.
+ for (int i = 1; i < constructorInvoke.arguments().size(); i++) {
+ Value argument = constructorInvoke.arguments().get(i);
+ if (valueMayDependOnEnvironment(argument, assumedNotToDependOnEnvironment)) {
+ return false;
+ }
+ }
+
+ // Finally, check that the object does not escape.
+ if (valueMayBeMutatedBeforeMethodExit(
+ value, assumedNotToDependOnEnvironment, ImmutableSet.of(constructorInvoke))) {
+ return false;
+ }
}
if (assumedNotToDependOnEnvironment.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
index 0ac6de7..bc13aa1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
@@ -27,8 +27,9 @@
fields.add(field);
}
- public void addAll(ConcreteMutableFieldSet other) {
+ public ConcreteMutableFieldSet addAll(ConcreteMutableFieldSet other) {
fields.addAll(other.fields);
+ return this;
}
Set<DexEncodedField> getFields() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 484c4e3..e7d2ab1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -110,7 +110,7 @@
if (refinedReceiverType != staticReceiverType) {
ResolutionResult refinedResolution =
appView.appInfo().resolveMethod(refinedReceiverType, method);
- if (refinedResolution.hasSingleTarget()) {
+ if (refinedResolution.isSingleResolution()) {
DexEncodedMethod refinedTarget = refinedResolution.getSingleTarget();
Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
for (DexEncodedMethod target : targets) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index d9d65e5..407742e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -175,7 +175,7 @@
DexItemFactory dexItemFactory = appView.dexItemFactory();
ResolutionResult finalizeResolutionResult =
appView.appInfo().resolveMethod(clazz, dexItemFactory.objectMethods.finalize);
- if (finalizeResolutionResult.hasSingleTarget()) {
+ if (finalizeResolutionResult.isSingleResolution()) {
DexMethod finalizeMethod = finalizeResolutionResult.getSingleTarget().method;
if (finalizeMethod != dexItemFactory.enumMethods.finalize
&& finalizeMethod != dexItemFactory.objectMethods.finalize) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 2f0911d..16fc789 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.InvalidDebugInfoException;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexMethod;
@@ -16,11 +17,14 @@
import com.android.tools.r8.ir.conversion.TypeConstraintResolver;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.CfgPrinter;
+import com.android.tools.r8.utils.DequeUtils;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.Sets;
import java.util.ArrayList;
+import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -395,4 +399,31 @@
}
return result;
}
+
+ @Override
+ public TypeLatticeElement getDynamicUpperBoundType(
+ AppView<? extends AppInfoWithSubtyping> appView) {
+ Set<Phi> reachablePhis = SetUtils.newIdentityHashSet(this);
+ Deque<Phi> worklist = DequeUtils.newArrayDeque(this);
+ while (!worklist.isEmpty()) {
+ Phi phi = worklist.removeFirst();
+ assert reachablePhis.contains(phi);
+ for (Value operand : phi.getOperands()) {
+ Phi candidate = operand.getAliasedValue().asPhi();
+ if (candidate != null && reachablePhis.add(candidate)) {
+ worklist.addLast(candidate);
+ }
+ }
+ }
+ Set<Value> visitedOperands = Sets.newIdentityHashSet();
+ TypeLatticeElement result = TypeLatticeElement.BOTTOM;
+ for (Phi phi : reachablePhis) {
+ for (Value operand : phi.getOperands()) {
+ if (!operand.getAliasedValue().isPhi() && visitedOperands.add(operand)) {
+ result = result.join(operand.getDynamicUpperBoundType(appView), appView);
+ }
+ }
+ }
+ return result;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index dca6174..d6372b5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -4,6 +4,13 @@
package com.android.tools.r8.ir.code;
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
+import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING;
+import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_OF;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
@@ -888,6 +895,27 @@
return isArgument;
}
+ public boolean onlyDependsOnArgument() {
+ if (isPhi()) {
+ return false;
+ }
+ switch (definition.opcode()) {
+ case ARGUMENT:
+ case CONST_CLASS:
+ case CONST_NUMBER:
+ case CONST_STRING:
+ case DEX_ITEM_BASED_CONST_STRING:
+ // Constants don't depend on anything.
+ return true;
+ case CHECK_CAST:
+ return definition.asCheckCast().object().onlyDependsOnArgument();
+ case INSTANCE_OF:
+ return definition.asInstanceOf().value().onlyDependsOnArgument();
+ default:
+ return false;
+ }
+ }
+
public int computeArgumentPosition(IRCode code) {
assert isArgument;
int position = 0;
@@ -1086,6 +1114,14 @@
public TypeLatticeElement getDynamicUpperBoundType(
AppView<? extends AppInfoWithSubtyping> appView) {
+ Value root = getAliasedValue();
+ if (root.isPhi()) {
+ assert getSpecificAliasedValue(
+ value -> !value.isPhi() && value.definition.isAssumeDynamicType())
+ == null;
+ return root.getDynamicUpperBoundType(appView);
+ }
+
// Try to find an alias of the receiver, which is defined by an instruction of the type
// Assume<DynamicTypeAssumption>.
Value aliasedValue =
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index c4f9072..06f1f58 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -9,8 +9,12 @@
import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator.CycleEliminationResult;
import com.android.tools.r8.ir.conversion.CallSiteInformation.CallGraphBasedCallSiteInformation;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
+import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
/**
* Call graph representation.
@@ -172,4 +176,32 @@
? new CallGraphBasedCallSiteInformation(appView, this)
: CallSiteInformation.empty();
}
+
+ public boolean isEmpty() {
+ return nodes.isEmpty();
+ }
+
+ public Set<DexEncodedMethod> extractLeaves() {
+ return extractNodes(Node::isLeaf, Node::cleanCallersForRemoval);
+ }
+
+ public Set<DexEncodedMethod> extractRoots() {
+ return extractNodes(Node::isRoot, Node::cleanCalleesForRemoval);
+ }
+
+ private Set<DexEncodedMethod> extractNodes(Predicate<Node> predicate, Consumer<Node> clean) {
+ Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
+ Set<Node> removed = Sets.newIdentityHashSet();
+ Iterator<Node> nodeIterator = nodes.iterator();
+ while (nodeIterator.hasNext()) {
+ Node node = nodeIterator.next();
+ if (predicate.test(node)) {
+ result.add(node.method);
+ nodeIterator.remove();
+ removed.add(node);
+ }
+ }
+ removed.forEach(clean);
+ return result;
+ }
}
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 45be243..5041e1d 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
@@ -296,7 +296,7 @@
: null;
this.lambdaMerger =
options.enableLambdaMerging ? new LambdaMerger(appViewWithLiveness) : null;
- this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness, lambdaRewriter);
+ this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness);
this.inliner =
new Inliner(appViewWithLiveness, mainDexClasses, lambdaMerger, lensCodeRewriter);
this.outliner = new Outliner(appViewWithLiveness);
@@ -807,11 +807,13 @@
return builder.build();
}
- private void waveStart(Collection<DexEncodedMethod> wave) {
+ private void waveStart(Collection<DexEncodedMethod> wave, ExecutorService executorService)
+ throws ExecutionException {
onWaveDoneActions = Collections.synchronizedList(new ArrayList<>());
if (lambdaRewriter != null) {
- wave.forEach(method -> lambdaRewriter.synthesizeLambdaClassesFor(method, lensCodeRewriter));
+ lambdaRewriter.synthesizeLambdaClassesForWave(
+ wave, executorService, delayedOptimizationFeedback, lensCodeRewriter);
}
}
@@ -975,10 +977,25 @@
for (DexProgramClass clazz : classes) {
clazz.forEachMethod(methods::add);
}
- // Process the generated class, but don't apply any outlining.
processMethodsConcurrently(methods, executorService);
}
+ public void optimizeSynthesizedLambdaClasses(
+ Collection<DexProgramClass> classes, ExecutorService executorService)
+ throws ExecutionException {
+ assert appView.enableWholeProgramOptimizations();
+ Set<DexEncodedMethod> methods = Sets.newIdentityHashSet();
+ for (DexProgramClass clazz : classes) {
+ clazz.forEachMethod(methods::add);
+ }
+ LambdaMethodProcessor processor =
+ new LambdaMethodProcessor(appView.withLiveness(), methods, executorService, timing);
+ processor.forEachMethod(
+ method -> processMethod(method, delayedOptimizationFeedback, processor),
+ delayedOptimizationFeedback::updateVisibleOptimizationInfo,
+ executorService);
+ }
+
public void optimizeSynthesizedMethod(DexEncodedMethod method) {
if (!method.isProcessed()) {
// Process the generated method, but don't apply any outlining.
@@ -1111,10 +1128,11 @@
lensCodeRewriter.rewrite(code, method);
} else {
assert appView.graphLense().isIdentityLense();
- if (lambdaRewriter != null && options.testing.desugarLambdasThroughLensCodeRewriter()) {
- lambdaRewriter.desugarLambdas(method, code);
- assert code.isConsistentSSA();
- }
+ }
+
+ if (lambdaRewriter != null) {
+ lambdaRewriter.desugarLambdas(method, code);
+ assert code.isConsistentSSA();
}
}
@@ -1277,12 +1295,6 @@
stringConcatRewriter.desugarStringConcats(method.method, code);
- if (options.testing.desugarLambdasThroughLensCodeRewriter()) {
- assert !options.enableDesugaring || lambdaRewriter.verifyNoLambdasToDesugar(code);
- } else if (lambdaRewriter != null) {
- lambdaRewriter.desugarLambdas(method, code);
- assert code.isConsistentSSA();
- }
previous = printMethod(code, "IR after lambda desugaring (SSA)", previous);
assert code.verifyTypes(appView);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LambdaMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/LambdaMethodProcessor.java
new file mode 100644
index 0000000..a6a9b94
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LambdaMethodProcessor.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.IROrdering;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.Timing;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+class LambdaMethodProcessor implements MethodProcessor {
+
+ private final Deque<Collection<DexEncodedMethod>> waves;
+ private Collection<DexEncodedMethod> wave;
+
+ LambdaMethodProcessor(
+ AppView<AppInfoWithLiveness> appView,
+ Set<DexEncodedMethod> methods,
+ ExecutorService executorService,
+ Timing timing)
+ throws ExecutionException {
+ CallGraph callGraph =
+ new PartialCallGraphBuilder(appView, methods).build(executorService, timing);
+ this.waves = createWaves(appView, callGraph);
+ }
+
+ @Override
+ public Phase getPhase() {
+ return Phase.LAMBDA_PROCESSING;
+ }
+
+ private Deque<Collection<DexEncodedMethod>> createWaves(AppView<?> appView, CallGraph callGraph) {
+ IROrdering shuffle = appView.options().testing.irOrdering;
+ Deque<Collection<DexEncodedMethod>> waves = new ArrayDeque<>();
+ while (!callGraph.isEmpty()) {
+ waves.addLast(shuffle.order(callGraph.extractLeaves()));
+ }
+ return waves;
+ }
+
+ @Override
+ public boolean isProcessedConcurrently(DexEncodedMethod method) {
+ return wave.contains(method);
+ }
+
+ <E extends Exception> void forEachMethod(
+ ThrowingConsumer<DexEncodedMethod, E> consumer,
+ Action waveDone,
+ ExecutorService executorService)
+ throws ExecutionException {
+ while (!waves.isEmpty()) {
+ wave = waves.removeFirst();
+ assert wave.size() > 0;
+ ThreadUtils.processItems(wave, consumer, executorService);
+ waveDone.execute();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 21f800c..bf556c8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -60,7 +60,6 @@
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.logging.Log;
import com.google.common.collect.Sets;
import java.util.ArrayList;
@@ -77,11 +76,9 @@
private final AppView<? extends AppInfoWithSubtyping> appView;
private final Map<DexProto, DexProto> protoFixupCache = new ConcurrentHashMap<>();
- private final LambdaRewriter lambdaRewriter;
- LensCodeRewriter(AppView<? extends AppInfoWithSubtyping> appView, LambdaRewriter lambdaRewriter) {
+ LensCodeRewriter(AppView<? extends AppInfoWithSubtyping> appView) {
this.appView = appView;
- this.lambdaRewriter = lambdaRewriter;
}
private Value makeOutValue(Instruction insn, IRCode code) {
@@ -128,13 +125,6 @@
affectedPhis.addAll(newOutValue.uniquePhiUsers());
}
}
- if (lambdaRewriter != null
- && appView.options().testing.desugarLambdasThroughLensCodeRewriter()) {
- Instruction previous = iterator.peekPrevious();
- assert previous.isInvokeCustom();
- lambdaRewriter.desugarLambda(
- method.method.holder, iterator, previous.asInvokeCustom(), code);
- }
} else if (current.isConstMethodHandle()) {
DexMethodHandle handle = current.asConstMethodHandle().getValue();
DexMethodHandle newHandle = rewriteDexMethodHandle(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 0f6bd4f..7d4a203 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.BitSet;
import java.util.Set;
@@ -59,7 +59,8 @@
void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility);
- void setInitializerInfo(DexEncodedMethod method, InitializerInfo info);
+ void setInstanceInitializerInfo(
+ DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo);
void setInitializerEnablingJavaAssertions(DexEncodedMethod method);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index b2bf648..d6e2ff3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -9,6 +9,7 @@
enum Phase {
ONE_TIME,
+ LAMBDA_PROCESSING,
PRIMARY,
POST
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 5107b18..9ecc62d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -7,7 +7,6 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.conversion.CallGraph.Node;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.logging.Log;
@@ -17,17 +16,14 @@
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
-import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
class PostMethodProcessor implements MethodProcessor {
@@ -106,11 +102,9 @@
IROrdering shuffle = appView.options().testing.irOrdering;
Deque<Collection<DexEncodedMethod>> waves = new ArrayDeque<>();
- Set<Node> nodes = callGraph.nodes;
int waveCount = 1;
- while (!nodes.isEmpty()) {
- Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
- extractRoots(nodes, n -> wave.add(n.method));
+ while (!callGraph.isEmpty()) {
+ Set<DexEncodedMethod> wave = callGraph.extractRoots();
waves.addLast(shuffle.order(wave));
if (Log.ENABLED && Log.isLoggingEnabledFor(PostMethodProcessor.class)) {
Log.info(getClass(), "Wave #%d: %d", waveCount++, wave.size());
@@ -120,25 +114,6 @@
return waves;
}
- /**
- * Extract the next set of roots (nodes with an incoming call degree of 0) if any.
- *
- * <p>All nodes in the graph are extracted if called repeatedly until null is returned.
- */
- static void extractRoots(Set<Node> nodes, Consumer<Node> fn) {
- Set<Node> removed = Sets.newIdentityHashSet();
- Iterator<Node> nodeIterator = nodes.iterator();
- while (nodeIterator.hasNext()) {
- Node node = nodeIterator.next();
- if (node.isRoot()) {
- fn.accept(node);
- nodeIterator.remove();
- removed.add(node);
- }
- }
- removed.forEach(Node::cleanCalleesForRemoval);
- }
-
@Override
public boolean isProcessedConcurrently(DexEncodedMethod method) {
return wave != null && wave.contains(method);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index 1787587..be529f4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -31,6 +31,12 @@
*/
class PrimaryMethodProcessor implements MethodProcessor {
+ interface WaveStartAction {
+
+ void notifyWaveStart(Collection<DexEncodedMethod> wave, ExecutorService executorService)
+ throws ExecutionException;
+ }
+
private final CallSiteInformation callSiteInformation;
private final PostMethodProcessor.Builder postMethodProcessorBuilder;
private final Deque<Collection<DexEncodedMethod>> waves;
@@ -130,14 +136,14 @@
*/
<E extends Exception> void forEachMethod(
ThrowingConsumer<DexEncodedMethod, E> consumer,
- Consumer<Collection<DexEncodedMethod>> waveStart,
+ WaveStartAction waveStartAction,
Action waveDone,
ExecutorService executorService)
throws ExecutionException {
while (!waves.isEmpty()) {
wave = waves.removeFirst();
assert wave.size() > 0;
- waveStart.accept(wave);
+ waveStartAction.notifyWaveStart(wave, executorService);
ThreadUtils.processItems(wave, consumer, executorService);
waveDone.execute();
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SourceDebugExtensionRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/SourceDebugExtensionRewriter.java
new file mode 100644
index 0000000..20fc11c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SourceDebugExtensionRewriter.java
@@ -0,0 +1,205 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser;
+import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Result;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.Multimaps;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+import java.util.function.Predicate;
+
+public class SourceDebugExtensionRewriter {
+
+ private static final String SYNTHETIC_INLINE_FUNCTION_NAME_PREFIX = "$i$f$";
+
+ private final AppView<?> appView;
+ private final DexItemFactory factory;
+
+ public SourceDebugExtensionRewriter(AppView<?> appView) {
+ this.appView = appView;
+ this.factory = appView.dexItemFactory();
+ }
+
+ public SourceDebugExtensionRewriter analyze(Predicate<DexProgramClass> shouldProcess) {
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ if (!shouldProcess.test(clazz)) {
+ continue;
+ }
+ DexAnnotation sourceDebug =
+ clazz.annotations.getFirstMatching(factory.annotationSourceDebugExtension);
+ if (sourceDebug == null || sourceDebug.annotation.elements.length != 1) {
+ continue;
+ }
+ DexValueString dexValueString = sourceDebug.annotation.elements[0].value.asDexValueString();
+ if (dexValueString == null) {
+ continue;
+ }
+ Result parsedData = KotlinSourceDebugExtensionParser.parse(dexValueString.value.toString());
+ if (parsedData == null) {
+ continue;
+ }
+ for (DexEncodedMethod method : clazz.methods()) {
+ if (method.getCode().isCfCode()) {
+ processMethod(method, parsedData);
+ }
+ }
+ }
+ return this;
+ }
+
+ private static class Context {
+
+ private Position currentPosition = null;
+ private LocalVariableInfo localVariableInliningInfoEntry = null;
+ private final Stack<CfLabel> endRangeLabels = new Stack<>();
+ private final List<CfInstruction> resultingList;
+ private final ImmutableListMultimap<CfLabel, LocalVariableInfo> localVariableInfoStartMap;
+ private int lastPosition = -1;
+
+ Context(
+ int initialSize,
+ ImmutableListMultimap<CfLabel, LocalVariableInfo> localVariableInfoStartMap) {
+ this.resultingList = new ArrayList<>(initialSize);
+ this.localVariableInfoStartMap = localVariableInfoStartMap;
+ }
+
+ String getInlinedFunctionName() {
+ return localVariableInliningInfoEntry
+ .getLocal()
+ .name
+ .toString()
+ .substring(SYNTHETIC_INLINE_FUNCTION_NAME_PREFIX.length());
+ }
+ }
+
+ private void processMethod(DexEncodedMethod method, Result parsedData) {
+ CfCode cfCode = method.getCode().asCfCode();
+ Context context =
+ new Context(
+ cfCode.getInstructions().size() + parsedData.getPositions().size(),
+ Multimaps.index(cfCode.getLocalVariables(), LocalVariableInfo::getStart));
+ for (CfInstruction instruction : cfCode.getInstructions()) {
+ if (instruction.isLabel()) {
+ handleLabel(context, instruction.asLabel());
+ } else if (instruction.isPosition()
+ && (context.currentPosition != null || context.localVariableInliningInfoEntry != null)) {
+ handlePosition(context, instruction.asPosition(), parsedData);
+ } else {
+ context.resultingList.add(instruction);
+ }
+ }
+ cfCode.instructions = context.resultingList;
+ }
+
+ private void handleLabel(Context context, CfLabel label) {
+ ImmutableList<LocalVariableInfo> localVariableInfos =
+ context.localVariableInfoStartMap.get(label);
+ if (localVariableInfos != null) {
+ LocalVariableInfo newLocalVariableInliningInfo = null;
+ for (LocalVariableInfo localVariableInfo : localVariableInfos) {
+ String localVariableName = localVariableInfo.getLocal().name.toString();
+ if (!localVariableName.startsWith(SYNTHETIC_INLINE_FUNCTION_NAME_PREFIX)) {
+ continue;
+ }
+ // Only one synthetic inlining label for a position should exist.
+ assert newLocalVariableInliningInfo == null;
+ newLocalVariableInliningInfo = localVariableInfo;
+ }
+ context.localVariableInliningInfoEntry = newLocalVariableInliningInfo;
+ }
+ while (!context.endRangeLabels.empty() && context.endRangeLabels.peek() == label) {
+ // The inlined range is ending here. Multiple inline ranges can end at the same label.
+ assert context.currentPosition != null;
+ context.currentPosition = context.currentPosition.callerPosition;
+ context.endRangeLabels.pop();
+ }
+ // Ensure endRangeLabels are in sync with the current position.
+ assert !context.endRangeLabels.empty() || context.currentPosition == null;
+ context.resultingList.add(label);
+ }
+
+ private void handlePosition(Context context, CfPosition position, Result parsedData) {
+ if (context.localVariableInliningInfoEntry != null) {
+ // This is potentially a new inlining frame.
+ KotlinSourceDebugExtensionParser.Position parsedInlinePosition =
+ parsedData.getPositions().get(position.getPosition().line);
+ if (parsedInlinePosition != null) {
+ String descriptor = "L" + parsedInlinePosition.getSource().getPath() + ";";
+ if (DescriptorUtils.isClassDescriptor(descriptor)) {
+ // This is a new inline function. Build up the inlining information from the parsed data
+ // and the local variable table.
+ DexType sourceHolder = factory.createType(descriptor);
+ final String inlinee = context.getInlinedFunctionName();
+ // TODO(b/145904809): See if we can find the inline function.
+ DexMethod syntheticExistingMethod =
+ factory.createMethod(
+ sourceHolder,
+ factory.createProto(factory.voidType),
+ factory.createString(inlinee));
+ context.currentPosition =
+ new Position(
+ parsedInlinePosition.getRange().from,
+ null,
+ syntheticExistingMethod,
+ context.currentPosition);
+ context.endRangeLabels.push(context.localVariableInliningInfoEntry.getEnd());
+ context.lastPosition = position.getPosition().line;
+ }
+ }
+ context.localVariableInliningInfoEntry = null;
+ }
+ if (context.currentPosition != null) {
+ // We have a line-entry in a mapped range. Make sure to increment the index according to
+ // the delta in the inlined source.
+ Position currentPosition = context.currentPosition;
+ assert context.lastPosition > -1;
+ int delta = position.getPosition().line - context.lastPosition;
+ context.currentPosition =
+ new Position(
+ context.currentPosition.line + delta,
+ null,
+ currentPosition.method,
+ currentPosition.callerPosition);
+ // Append the original line index as the current caller context.
+ context.resultingList.add(
+ new CfPosition(
+ position.getLabel(),
+ appendAsOuterMostCaller(context.currentPosition, position.getPosition())));
+ } else {
+ context.resultingList.add(position);
+ }
+ }
+
+ private Position appendAsOuterMostCaller(Position position, Position callerPosition) {
+ if (position == null) {
+ return callerPosition;
+ } else {
+ return new Position(
+ position.line,
+ position.file,
+ position.method,
+ appendAsOuterMostCaller(position.callerPosition, callerPosition));
+ }
+ }
+}
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 41995ce..939d33a 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
@@ -15,6 +15,7 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexLibraryClass;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
@@ -24,9 +25,11 @@
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.conversion.IRConverter;
@@ -38,16 +41,19 @@
import com.android.tools.r8.ir.desugar.backports.LongMethodRewrites;
import com.android.tools.r8.ir.desugar.backports.NumericMethodRewrites;
import com.android.tools.r8.ir.desugar.backports.ObjectsMethodRewrites;
+import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
+import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -105,30 +111,44 @@
continue;
}
// We need to force resolution, even on d8, to know if the invoke has to be rewritten.
- DexEncodedMethod dexEncodedMethod =
- quickLookUp(invoke.getInvokedMethod());
- if (dexEncodedMethod == null) {
+ ResolutionResult resolutionResult =
+ appView
+ .appInfo()
+ .resolveMethod(invoke.getInvokedMethod().holder, invoke.getInvokedMethod());
+ if (resolutionResult.isFailedResolution()) {
continue;
}
- provider = getMethodProviderOrNull(dexEncodedMethod.method);
+ DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
+ assert singleTarget != null;
+ provider = getMethodProviderOrNull(singleTarget.method);
if (provider == null) {
continue;
}
+ }
- // Since we are rewriting a virtual method into a static invoke in this case, the look-up
- // logic gets confused. Final methods rewritten in such a way are always or invokes from a
- // library class are rewritten into the static invoke, which is correct. However,
- // overrides of the programmer are currently disabled. We still rewrite everything to make
- // basic cases work.
- // TODO(b/142846107): Support overrides of retarget virtual methods by uncommenting the
- // following and implementing doSomethingSmart().
-
- // DexClass receiverType = appView.definitionFor(invoke.getInvokedMethod().holder);
- // if (!(dexEncodedMethod.isFinal()
- // || (receiverType != null && receiverType.isLibraryClass()))) {
- // doSomethingSmart();
- // continue;
- // }
+ // Due to emulated dispatch, we have to rewrite invoke-super differently or we end up in
+ // infinite loops. We do direct resolution. This is a very uncommon case.
+ if (invoke.isInvokeSuper()) {
+ DexEncodedMethod dexEncodedMethod =
+ appView
+ .appInfo()
+ .lookupSuperTarget(invoke.getInvokedMethod(), code.method.method.holder);
+ if (!dexEncodedMethod.isFinal()) { // Final methods can be rewritten as a normal invoke.
+ Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+ appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
+ Map<DexType, DexType> typeMap = retargetCoreLibMember.get(dexEncodedMethod.method.name);
+ if (typeMap != null && typeMap.containsKey(dexEncodedMethod.method.holder)) {
+ DexMethod retargetMethod =
+ factory.createMethod(
+ typeMap.get(dexEncodedMethod.method.holder),
+ factory.prependTypeToProto(
+ dexEncodedMethod.method.holder, dexEncodedMethod.method.proto),
+ dexEncodedMethod.method.name);
+ iterator.replaceCurrentInstruction(
+ new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
+ }
+ continue;
+ }
}
provider.rewriteInvoke(invoke, iterator, code, appView);
@@ -141,31 +161,6 @@
}
}
- private DexEncodedMethod quickLookUp(DexMethod method) {
- // Since retargeting cannot be on interface, we do a quick look-up excluding interfaces.
- // On R8 resolution is immediate, on d8 it may look-up.
- DexClass current = appView.definitionFor(method.holder);
- if (current == null) {
- return null;
- }
- DexEncodedMethod dexEncodedMethod = current.lookupVirtualMethod(method);
- if (dexEncodedMethod != null) {
- return dexEncodedMethod;
- }
- while (current.superType != factory.objectType) {
- DexType superType = current.superType;
- current = appView.definitionFor(superType);
- if (current == null) {
- return null;
- }
- dexEncodedMethod = current.lookupVirtualMethod(method);
- if (dexEncodedMethod != null) {
- return dexEncodedMethod;
- }
- }
- return null;
- }
-
private Collection<DexProgramClass> findSynthesizedFrom(Builder<?> builder, DexType holder) {
for (DexProgramClass synthesizedClass : builder.getSynthesizedClasses()) {
if (holder == synthesizedClass.getType()) {
@@ -181,6 +176,11 @@
public void synthesizeUtilityClasses(Builder<?> builder, ExecutorService executorService)
throws ExecutionException {
+ if (appView.options().isDesugaredLibraryCompilation()) {
+ synthesizeEmulatedDispatchMethods(builder);
+ } else {
+ addInterfacesAndForwardingMethods(executorService);
+ }
if (holders.isEmpty()) {
return;
}
@@ -248,6 +248,214 @@
}
}
+ private void addInterfacesAndForwardingMethods(ExecutorService executorService)
+ throws ExecutionException {
+ assert !appView.options().isDesugaredLibraryCompilation();
+ Map<DexType, List<DexMethod>> map = Maps.newIdentityHashMap();
+ for (DexMethod emulatedDispatchMethod : rewritableMethods.getEmulatedDispatchMethods()) {
+ map.putIfAbsent(emulatedDispatchMethod.holder, new ArrayList<>(1));
+ map.get(emulatedDispatchMethod.holder).add(emulatedDispatchMethod);
+ }
+ List<DexEncodedMethod> addedMethods = new ArrayList<>();
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ if (clazz.superType == null) {
+ assert clazz.type == appView.dexItemFactory().objectType : clazz.type.toSourceString();
+ continue;
+ }
+ DexClass dexClass = appView.definitionFor(clazz.superType);
+ // Only performs computation if superclass is a library class, but not object to filter out
+ // the most common case.
+ if (dexClass != null
+ && dexClass.isLibraryClass()
+ && dexClass.type != appView.dexItemFactory().objectType) {
+ for (DexType dexType : map.keySet()) {
+ if (inherit(dexClass.asLibraryClass(), dexType)) {
+ addedMethods.addAll(addInterfacesAndForwardingMethods(clazz, map.get(dexType)));
+ }
+ }
+ }
+ }
+ if (addedMethods.isEmpty()) {
+ return;
+ }
+ converter.processMethodsConcurrently(addedMethods, executorService);
+ }
+
+ private boolean inherit(DexLibraryClass clazz, DexType typeToInherit) {
+ DexLibraryClass current = clazz;
+ while (current.type != appView.dexItemFactory().objectType) {
+ if (current.type == typeToInherit) {
+ return true;
+ }
+ current = appView.definitionFor(current.superType).asLibraryClass();
+ }
+ return false;
+ }
+
+ private List<DexEncodedMethod> addInterfacesAndForwardingMethods(
+ DexProgramClass clazz, List<DexMethod> dexMethods) {
+ // BackportedMethodRewriter emulate dispatch: insertion of a marker interface & forwarding
+ // methods.
+ // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
+ // applies up to 24.
+ List<DexEncodedMethod> newForwardingMethods = new ArrayList<>();
+ for (DexMethod dexMethod : dexMethods) {
+ DexType[] newInterfaces = Arrays.copyOf(clazz.interfaces.values, clazz.interfaces.size() + 1);
+ newInterfaces[newInterfaces.length - 1] =
+ BackportedMethodRewriter.dispatchInterfaceTypeFor(appView, dexMethod);
+ clazz.interfaces = new DexTypeList(newInterfaces);
+ DexEncodedMethod dexEncodedMethod = clazz.lookupVirtualMethod(dexMethod);
+ if (dexEncodedMethod == null) {
+ DexEncodedMethod newMethod = createForwardingMethod(dexMethod, clazz);
+ clazz.addVirtualMethod(newMethod);
+ newForwardingMethods.add(newMethod);
+ }
+ }
+ return newForwardingMethods;
+ }
+
+ private DexEncodedMethod createForwardingMethod(DexMethod target, DexClass clazz) {
+ // NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
+ // even if this results in invalid code, these classes are never desugared.
+ // In desugared library, emulated interface methods can be overridden by retarget lib members.
+ DexMethod forwardMethod = ClassProcessor.retargetMethod(appView, target);
+ // New method will have the same name, proto, and also all the flags of the
+ // default method, including bridge flag.
+ DexMethod newMethod =
+ appView.dexItemFactory().createMethod(clazz.type, target.proto, target.name);
+ DexEncodedMethod dexEncodedMethod = appView.definitionFor(target);
+ MethodAccessFlags newFlags = dexEncodedMethod.accessFlags.copy();
+ newFlags.setSynthetic();
+ ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
+ ForwardMethodSourceCode.builder(newMethod);
+ forwardSourceCodeBuilder
+ .setReceiver(clazz.type)
+ .setTarget(forwardMethod)
+ .setInvokeType(Invoke.Type.STATIC)
+ .setIsInterface(false);
+ return new DexEncodedMethod(
+ newMethod,
+ newFlags,
+ dexEncodedMethod.annotations,
+ dexEncodedMethod.parameterAnnotationsList,
+ new SynthesizedCode(forwardSourceCodeBuilder::build));
+ }
+
+ private void synthesizeEmulatedDispatchMethods(Builder<?> builder) {
+ assert appView.options().isDesugaredLibraryCompilation();
+ if (rewritableMethods.getEmulatedDispatchMethods().isEmpty()) {
+ return;
+ }
+ ClassAccessFlags itfAccessFlags =
+ ClassAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC
+ | Constants.ACC_SYNTHETIC
+ | Constants.ACC_ABSTRACT
+ | Constants.ACC_INTERFACE);
+ ClassAccessFlags holderAccessFlags =
+ ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
+ for (DexMethod emulatedDispatchMethod : rewritableMethods.getEmulatedDispatchMethods()) {
+ // Dispatch interface.
+ DexType interfaceType = dispatchInterfaceTypeFor(appView, emulatedDispatchMethod);
+ DexEncodedMethod itfMethod =
+ generateInterfaceDispatchMethod(emulatedDispatchMethod, interfaceType);
+ DexProgramClass dispatchInterface =
+ new DexProgramClass(
+ interfaceType,
+ null,
+ new SynthesizedOrigin("desugared library interface dispatch", getClass()),
+ itfAccessFlags,
+ factory.objectType,
+ DexTypeList.empty(),
+ null,
+ null,
+ Collections.emptyList(),
+ null,
+ Collections.emptyList(),
+ DexAnnotationSet.empty(),
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ new DexEncodedMethod[] {itfMethod},
+ factory.getSkipNameValidationForTesting(),
+ getChecksumSupplier(itfMethod));
+ appView.appInfo().addSynthesizedClass(dispatchInterface);
+ builder.addSynthesizedClass(dispatchInterface, false);
+ // Dispatch holder.
+ DexType holderType = dispatchHolderTypeFor(appView, emulatedDispatchMethod);
+ DexEncodedMethod dispatchMethod =
+ generateHolderDispatchMethod(emulatedDispatchMethod, holderType, itfMethod.method);
+ DexProgramClass dispatchHolder =
+ new DexProgramClass(
+ holderType,
+ null,
+ new SynthesizedOrigin("desugared library dispatch holder class", getClass()),
+ holderAccessFlags,
+ factory.objectType,
+ DexTypeList.empty(),
+ null,
+ null,
+ Collections.emptyList(),
+ null,
+ Collections.emptyList(),
+ DexAnnotationSet.empty(),
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedField.EMPTY_ARRAY,
+ new DexEncodedMethod[] {dispatchMethod},
+ DexEncodedMethod.EMPTY_ARRAY,
+ factory.getSkipNameValidationForTesting(),
+ getChecksumSupplier(dispatchMethod));
+ appView.appInfo().addSynthesizedClass(dispatchHolder);
+ builder.addSynthesizedClass(dispatchHolder, false);
+ }
+ }
+
+ private DexEncodedMethod generateInterfaceDispatchMethod(
+ DexMethod emulatedDispatchMethod, DexType interfaceType) {
+ MethodAccessFlags flags =
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT | Constants.ACC_SYNTHETIC, false);
+ DexMethod newMethod =
+ factory.createMethod(
+ interfaceType, emulatedDispatchMethod.proto, emulatedDispatchMethod.name);
+ return new DexEncodedMethod(
+ newMethod, flags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), null);
+ }
+
+ private DexEncodedMethod generateHolderDispatchMethod(
+ DexMethod emulatedDispatchMethod, DexType dispatchHolder, DexMethod itfMethod) {
+ // The method should look like:
+ // static foo(rcvr, arg0, arg1) {
+ // if (rcvr instanceof interfaceType) {
+ // return invoke-interface receiver.foo(arg0, arg1);
+ // } else {
+ // return DesugarX.foo(rcvr, arg0, arg1)
+ // }
+ // We do not deal with complex cases (multiple retargeting of the same signature in the
+ // same inheritance tree, etc., since they do not happen in the most common desugared library.
+ DexItemFactory factory = appView.dexItemFactory();
+ DexProto newProto =
+ factory.prependTypeToProto(emulatedDispatchMethod.holder, emulatedDispatchMethod.proto);
+ DexMethod newMethod =
+ factory.createMethod(dispatchHolder, newProto, emulatedDispatchMethod.name);
+ DexType desugarType =
+ appView
+ .options()
+ .desugaredLibraryConfiguration
+ .getRetargetCoreLibMember()
+ .get(emulatedDispatchMethod.name)
+ .get(emulatedDispatchMethod.holder);
+ DexMethod desugarMethod =
+ factory.createMethod(desugarType, newProto, emulatedDispatchMethod.name);
+ return DexEncodedMethod.toEmulateDispatchLibraryMethod(
+ emulatedDispatchMethod.holder,
+ newMethod,
+ desugarMethod,
+ itfMethod,
+ Collections.emptyList(),
+ appView);
+ }
+
private ChecksumSupplier getChecksumSupplier(DexEncodedMethod method) {
if (!appView.options().encodeChecksums) {
return DexProgramClass::invalidChecksumRequest;
@@ -255,6 +463,31 @@
return c -> method.method.hashCode();
}
+ public static DexType dispatchInterfaceTypeFor(AppView<?> appView, DexMethod method) {
+ return dispatchTypeFor(appView, method, "dispatchInterface");
+ }
+
+ static DexType dispatchHolderTypeFor(AppView<?> appView, DexMethod method) {
+ return dispatchTypeFor(appView, method, "dispatchHolder");
+ }
+
+ private static DexType dispatchTypeFor(AppView<?> appView, DexMethod method, String suffix) {
+ String desugaredLibPrefix =
+ appView.options().desugaredLibraryConfiguration.getSynthesizedLibraryClassesPackagePrefix();
+ String descriptor =
+ "L"
+ + desugaredLibPrefix
+ + UTILITY_CLASS_NAME_PREFIX
+ + '$'
+ + method.holder.getName()
+ + '$'
+ + method.name
+ + '$'
+ + suffix
+ + ';';
+ return appView.dexItemFactory().createType(descriptor);
+ }
+
private MethodProvider getMethodProviderOrNull(DexMethod method) {
DexMethod original = appView.graphLense().getOriginalMethodSignature(method);
assert original != null;
@@ -285,6 +518,8 @@
// rewritten while the holder is non final but no superclass implement the method. In this case
// d8 needs to force resolution of given methods to see if the invoke needs to be rewritten.
private final Map<DexString, List<DexMethod>> virtualRewrites = new IdentityHashMap<>();
+ // non final virtual library methods requiring generation of emulated dispatch.
+ private final Set<DexMethod> emulatedDispatchMethods = Sets.newHashSet();
RewritableMethods(InternalOptions options, AppView<?> appView) {
DexItemFactory factory = options.itemFactory;
@@ -299,14 +534,18 @@
initializeAndroidOMethodProviders(factory);
}
+ // The following providers are currently not implemented at any API level in Android.
+ // They however require the Optional/Stream class to be present, either through
+ // desugared libraries or natively. If Optional/Stream class is not present,
+ // we do not desugar to avoid confusion in error messages.
if (appView.rewritePrefix.hasRewrittenType(factory.optionalType)
|| options.minApiLevel >= AndroidApiLevel.N.getLevel()) {
- // These are currently not implemented at any API level in Android.
- // They however require the Optional class to be present, either through
- // desugared libraries or natively. If Optional class is not present,
- // we do not desugar to avoid confusion in error messages.
initializeOptionalMethodProviders(factory);
}
+ if (appView.rewritePrefix.hasRewrittenType(factory.streamType)
+ || options.minApiLevel >= AndroidApiLevel.N.getLevel()) {
+ initializeStreamMethodProviders(factory);
+ }
// These are currently not implemented at any API level in Android.
initializeJava9MethodProviders(factory);
@@ -330,6 +569,10 @@
return false;
}
+ public Set<DexMethod> getEmulatedDispatchMethods() {
+ return emulatedDispatchMethods;
+ }
+
boolean isEmpty() {
return rewritable.isEmpty();
}
@@ -1169,7 +1412,7 @@
// Optional.stream()
name = factory.createString("stream");
- proto = factory.createProto(factory.createType("Ljava/util/stream/Stream;"));
+ proto = factory.createProto(factory.streamType);
method = factory.createMethod(optionalType, proto, name);
addProvider(
new StatifyingMethodGenerator(
@@ -1208,6 +1451,19 @@
}
}
+ private void initializeStreamMethodProviders(DexItemFactory factory) {
+ // Stream
+ DexType streamType = factory.streamType;
+
+ // Stream.ofNullable(object)
+ DexString name = factory.createString("ofNullable");
+ DexProto proto = factory.createProto(factory.streamType, factory.objectType);
+ DexMethod method = factory.createMethod(streamType, proto, name);
+ addProvider(
+ new MethodGenerator(
+ method, BackportedMethods::StreamMethods_ofNullable, "ofNullable") {});
+ }
+
private void warnMissingRetargetCoreLibraryMember(DexType type, AppView<?> appView) {
StringDiagnostic warning =
new StringDiagnostic(
@@ -1232,9 +1488,14 @@
if (!encodedMethod.isStatic()) {
virtualRewrites.putIfAbsent(encodedMethod.method.name, new ArrayList<>());
virtualRewrites.get(encodedMethod.method.name).add(encodedMethod.method);
- if (isEmulatedInterfaceDispatch(appView, encodedMethod)) {
+ if (InterfaceMethodRewriter.isEmulatedInterfaceDispatch(appView, encodedMethod)) {
// In this case interface method rewriter takes care of it.
continue;
+ } else if (!encodedMethod.isFinal()) {
+ // Virtual rewrites require emulated dispatch for inheritance.
+ // The call is rewritten to the dispatch holder class instead.
+ handleEmulateDispatch(appView, encodedMethod.method);
+ newHolder = dispatchHolderTypeFor(appView, encodedMethod.method);
}
}
DexProto proto = encodedMethod.method.proto;
@@ -1248,37 +1509,6 @@
}
}
- private boolean isEmulatedInterfaceDispatch(AppView<?> appView, DexEncodedMethod method) {
- // Answers true if this method is already managed through emulated interface dispatch.
- Map<DexType, DexType> emulateLibraryInterface =
- appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
- if (emulateLibraryInterface.isEmpty()) {
- return false;
- }
- DexMethod methodToFind = method.method;
-
- // Look-up all superclass and interfaces, if an emulated interface is found, and it implements
- // the method, answers true.
- LinkedList<DexType> workList = new LinkedList<>();
- workList.add(methodToFind.holder);
- while (!workList.isEmpty()) {
- DexType dexType = workList.removeFirst();
- DexClass dexClass = appView.definitionFor(dexType);
- assert dexClass != null; // It is a library class, or we are doing L8 compilation.
- if (dexClass.isInterface() && emulateLibraryInterface.containsKey(dexType)) {
- DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(methodToFind);
- if (dexEncodedMethod != null) {
- return true;
- }
- }
- Collections.addAll(workList, dexClass.interfaces.values);
- if (dexClass.superType != appView.dexItemFactory().objectType) {
- workList.add(dexClass.superType);
- }
- }
- return false;
- }
-
private List<DexEncodedMethod> findDexEncodedMethodsWithName(
DexString methodName, DexClass clazz) {
List<DexEncodedMethod> found = new ArrayList<>();
@@ -1291,6 +1521,17 @@
return found;
}
+ private void handleEmulateDispatch(AppView<?> appView, DexMethod method) {
+ emulatedDispatchMethods.add(method);
+ if (!appView.options().isDesugaredLibraryCompilation()) {
+ // Add rewrite rules so keeps rules are correctly generated in the program.
+ DexType dispatchInterfaceType = dispatchInterfaceTypeFor(appView, method);
+ appView.rewritePrefix.rewriteType(dispatchInterfaceType, dispatchInterfaceType);
+ DexType dispatchHolderType = dispatchHolderTypeFor(appView, method);
+ appView.rewritePrefix.rewriteType(dispatchHolderType, dispatchHolderType);
+ }
+ }
+
private void addProvider(MethodProvider generator) {
MethodProvider replaced = rewritable.put(generator.method, generator);
assert replaced == null;
@@ -1457,8 +1698,8 @@
}
// Specific subclass to transform virtual methods into static desugared methods.
- // To be correct, the method has to be on a final class, and be implemented directly
- // on the class (no overrides).
+ // To be correct, the method has to be on a final class or be a final method, and to be
+ // implemented directly on the class (no overrides).
private static class StatifyingMethodGenerator extends MethodGenerator {
private final DexType receiverType;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 11c9239..c2f5ae9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -4,151 +4,398 @@
package com.android.tools.r8.ir.desugar;
-import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexLibraryClass;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.IncompatibleClassResult;
import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.desugar.DefaultMethodsHelper.DefaultMethodCandidates;
import com.android.tools.r8.ir.synthetic.ExceptionThrowingSourceCode;
import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
-import com.google.common.collect.Sets;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.IdentityHashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.objectweb.asm.Opcodes;
-// Default and static method interface desugaring processor for classes.
-// Adds default interface methods into the class when needed.
+/**
+ * Default and static method interface desugaring processor for classes.
+ *
+ * <p>The core algorithm of the class processing is to ensure that for any type, all of its super
+ * and implements hierarchy is computed first, and based on the summaries of these types the summary
+ * of the class can be computed and the required forwarding methods on that type can be generated.
+ * In other words, the traversal is in top-down (edges from type to its subtypes) topological order.
+ * The traversal is lazy, starting from the unordered set of program classes.
+ */
final class ClassProcessor {
+ // Collection for method signatures that may cause forwarding methods to be created.
+ private static class MethodSignatures {
+ static final MethodSignatures EMPTY = new MethodSignatures(Collections.emptySet());
+
+ static MethodSignatures create(Set<Wrapper<DexMethod>> signatures) {
+ return signatures.isEmpty() ? EMPTY : new MethodSignatures(signatures);
+ }
+
+ final Set<Wrapper<DexMethod>> signatures;
+
+ MethodSignatures(Set<Wrapper<DexMethod>> signatures) {
+ this.signatures = Collections.unmodifiableSet(signatures);
+ }
+
+ MethodSignatures merge(MethodSignatures other) {
+ if (isEmpty()) {
+ return other;
+ }
+ if (other.isEmpty()) {
+ return this;
+ }
+ Set<Wrapper<DexMethod>> merged = new HashSet<>(signatures);
+ merged.addAll(other.signatures);
+ return signatures.size() == merged.size() ? this : new MethodSignatures(merged);
+ }
+
+ MethodSignatures merge(List<MethodSignatures> others) {
+ MethodSignatures merged = this;
+ for (MethodSignatures other : others) {
+ merged = merged.merge(others);
+ }
+ return merged;
+ }
+
+ boolean isEmpty() {
+ return signatures.isEmpty();
+ }
+ }
+
+ // Collection of information known at the point of a given (non-library) class.
+ // This info is immutable and shared as it is often the same on a significant part of the
+ // class hierarchy. Thus, in the case of additions the parent pointer will contain prior info.
+ private static class ClassInfo {
+
+ static final ClassInfo EMPTY = new ClassInfo(null, ImmutableList.of());
+
+ final ClassInfo parent;
+
+ // List of methods that are known to be forwarded to by a forwarding method at this point in the
+ // class hierarchy. This set consists of the default interface methods, i.e., the targets of the
+ // forwarding methods, *not* the forwarding methods themselves.
+ final ImmutableList<DexEncodedMethod> forwardedMethodTargets;
+
+ ClassInfo(ClassInfo parent, ImmutableList<DexEncodedMethod> forwardedMethodTargets) {
+ this.parent = parent;
+ this.forwardedMethodTargets = forwardedMethodTargets;
+ }
+
+ static ClassInfo create(
+ ClassInfo parent, ImmutableList<DexEncodedMethod> forwardedMethodTargets) {
+ return forwardedMethodTargets.isEmpty()
+ ? parent
+ : new ClassInfo(parent, forwardedMethodTargets);
+ }
+
+ public boolean isEmpty() {
+ return this == EMPTY;
+ }
+
+ boolean isTargetedByForwards(DexEncodedMethod method) {
+ return forwardedMethodTargets.contains(method)
+ || (parent != null && parent.isTargetedByForwards(method));
+ }
+ }
+
+ // Helper to keep track of the direct active subclass and nearest program subclass for reporting.
+ private static class ReportingContext {
+ final DexClass directSubClass;
+ final DexProgramClass closestProgramSubClass;
+
+ public ReportingContext(DexClass directSubClass, DexProgramClass closestProgramSubClass) {
+ this.directSubClass = directSubClass;
+ this.closestProgramSubClass = closestProgramSubClass;
+ }
+
+ ReportingContext forClass(DexClass directSubClass) {
+ return new ReportingContext(
+ directSubClass,
+ directSubClass.isProgramClass()
+ ? directSubClass.asProgramClass()
+ : closestProgramSubClass);
+ }
+
+ public void reportDependency(DexClass clazz, AppView<?> appView) {
+ // If the direct subclass is in the compilation unit, report its dependencies.
+ if (clazz != directSubClass && directSubClass.isProgramClass()) {
+ InterfaceMethodRewriter.reportDependencyEdge(
+ clazz, directSubClass.asProgramClass(), appView);
+ }
+ }
+
+ public void reportMissingType(DexType missingType, InterfaceMethodRewriter rewriter) {
+ rewriter.warnMissingInterface(closestProgramSubClass, closestProgramSubClass, missingType);
+ }
+ }
+
+ // Specialized context to disable reporting when traversing the library strucure.
+ private static class LibraryReportingContext extends ReportingContext {
+ static final LibraryReportingContext LIBRARY_CONTEXT = new LibraryReportingContext();
+
+ LibraryReportingContext() {
+ super(null, null);
+ }
+
+ @Override
+ ReportingContext forClass(DexClass directSubClass) {
+ return this;
+ }
+
+ @Override
+ public void reportDependency(DexClass clazz, AppView<?> appView) {
+ // Don't report dependencies in the library.
+ }
+
+ @Override
+ public void reportMissingType(DexType missingType, InterfaceMethodRewriter rewriter) {
+ // Ignore missing types in the library.
+ }
+ }
+
private final AppView<?> appView;
private final DexItemFactory dexItemFactory;
private final InterfaceMethodRewriter rewriter;
- // Set of already processed classes.
- private final Set<DexClass> processedClasses = Sets.newIdentityHashSet();
- // Maps already created methods into default methods they were generated based on.
- private final Map<DexEncodedMethod, DexEncodedMethod> createdMethods = new IdentityHashMap<>();
+ private final Consumer<DexEncodedMethod> newSynthesizedMethodConsumer;
+ private final MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
+ private final boolean needsLibraryInfo;
- ClassProcessor(AppView<?> appView, InterfaceMethodRewriter rewriter) {
+ // Mapping from program and classpath classes to their information summary.
+ private final Map<DexClass, ClassInfo> classInfo = new IdentityHashMap<>();
+
+ // Mapping from library classes to their information summary.
+ private final Map<DexLibraryClass, MethodSignatures> libraryClassInfo = new IdentityHashMap<>();
+
+ // Mapping from arbitrary interfaces to an information summary.
+ private final Map<DexClass, MethodSignatures> interfaceInfo = new IdentityHashMap<>();
+
+ // Mapping from actual program classes to the synthesized forwarding methods to be created.
+ private final Map<DexProgramClass, List<DexEncodedMethod>> newSyntheticMethods =
+ new IdentityHashMap<>();
+
+ ClassProcessor(
+ AppView<?> appView,
+ InterfaceMethodRewriter rewriter,
+ Consumer<DexEncodedMethod> newSynthesizedMethodConsumer) {
this.appView = appView;
this.dexItemFactory = appView.dexItemFactory();
this.rewriter = rewriter;
+ this.newSynthesizedMethodConsumer = newSynthesizedMethodConsumer;
+ needsLibraryInfo =
+ !appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface().isEmpty()
+ || !appView
+ .options()
+ .desugaredLibraryConfiguration
+ .getRetargetCoreLibMember()
+ .isEmpty();
}
- final Set<DexEncodedMethod> getForwardMethods() {
- return createdMethods.keySet();
+ private boolean needsLibraryInfo() {
+ return needsLibraryInfo;
}
- final void process(DexProgramClass clazz) {
- assert !clazz.isInterface();
- if (!processedClasses.add(clazz)) {
- return; // Has already been processed.
- }
+ private boolean ignoreLibraryInfo() {
+ return !needsLibraryInfo;
+ }
- // Ensure superclasses are processed first. We need it since we use information
- // about methods added to superclasses when we decide if we want to add a default
- // method to class `clazz`.
- DexType superType = clazz.superType;
- // If superClass definition is missing, just skip this part and let real processing of its
- // subclasses report the error if it is required.
- DexClass superClass = superType == null ? null : appView.definitionFor(superType);
- if (superClass != null && superType != dexItemFactory.objectType) {
- if (superClass.isInterface()) {
- throw new CompilationError("Interface `" + superClass.toSourceString()
- + "` used as super class of `" + clazz.toSourceString() + "`.");
- }
- // We assume that library classes don't need to be processed, since they
- // are provided by a runtime not supporting default interface methods. We
- // also skip classpath classes, which results in sub-optimal behavior in
- // case classpath superclass when processed adds a default method which
- // could have been reused in this class otherwise.
- if (superClass.isProgramClass()) {
- process(superClass.asProgramClass());
+ public void processClass(DexProgramClass clazz) {
+ visitClassInfo(clazz, new ReportingContext(clazz, clazz));
+ }
+
+ final void addSyntheticMethods() {
+ for (DexProgramClass clazz : newSyntheticMethods.keySet()) {
+ List<DexEncodedMethod> newForwardingMethods = newSyntheticMethods.get(clazz);
+ if (newForwardingMethods != null) {
+ clazz.appendVirtualMethods(newForwardingMethods);
+ newForwardingMethods.forEach(newSynthesizedMethodConsumer);
}
}
+ }
- // When inheriting from a library class, the library class may implement interfaces to
- // desugar. We therefore need to look at the interfaces of the library classes.
- boolean desugaredLibraryLookup =
- superClass != null
- && superClass.isLibraryClass()
- && appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface().size()
- > 0;
+ // Computes the set of method signatures that may need forwarding methods on derived classes.
+ private MethodSignatures computeInterfaceInfo(DexClass iface, MethodSignatures signatures) {
+ assert iface.isInterface();
+ assert iface.superType == dexItemFactory.objectType;
+ // Add non-library default methods as well as those for desugared library classes.
+ if (!iface.isLibraryClass() || (needsLibraryInfo() && rewriter.isInDesugaredLibrary(iface))) {
+ List<DexEncodedMethod> methods = iface.virtualMethods();
+ List<Wrapper<DexMethod>> additions = new ArrayList<>(methods.size());
+ for (DexEncodedMethod method : methods) {
+ if (method.isDefaultMethod()) {
+ additions.add(equivalence.wrap(method.method));
+ }
+ }
+ if (!additions.isEmpty()) {
+ signatures = signatures.merge(MethodSignatures.create(new HashSet<>(additions)));
+ }
+ }
+ return signatures;
+ }
- if (clazz.interfaces.isEmpty() && !desugaredLibraryLookup) {
- // Since superclass has already been processed and it has all missing methods
- // added, these methods will be inherited by `clazz`, and only need to be revised
- // in case this class has *additional* interfaces implemented, which may change
- // the entire picture of the default method selection in runtime.
+ // Computes the set of signatures of that may need forwarding methods on classes that derive
+ // from a library class.
+ private MethodSignatures computeLibraryClassInfo(
+ DexLibraryClass clazz, MethodSignatures signatures) {
+ // The result is the identity as the library class does not itself contribute to the set.
+ return signatures;
+ }
+
+ // The computation of a class information and the insertions of forwarding methods.
+ private ClassInfo computeClassInfo(
+ DexClass clazz, ClassInfo superInfo, MethodSignatures signatures) {
+ Builder<DexEncodedMethod> additionalForwards = ImmutableList.builder();
+ for (Wrapper<DexMethod> wrapper : signatures.signatures) {
+ resolveForwardForSignature(
+ clazz,
+ wrapper.get(),
+ (targetHolder, target) -> {
+ if (!superInfo.isTargetedByForwards(target)) {
+ additionalForwards.add(target);
+ addForwardingMethod(targetHolder, target, clazz);
+ }
+ });
+ }
+ return ClassInfo.create(superInfo, additionalForwards.build());
+ }
+
+ // Resolves a method signature from the point of 'clazz', if it must target a default method
+ // the 'addForward' call-back is called with the target of the forward.
+ private void resolveForwardForSignature(
+ DexClass clazz, DexMethod method, BiConsumer<DexClass, DexEncodedMethod> addForward) {
+ ResolutionResult resolution = appView.appInfo().resolveMethod(clazz, method);
+ // If resolution fails, install a method throwing IncompatibleClassChangeError.
+ if (resolution.isFailedResolution()) {
+ assert resolution instanceof IncompatibleClassResult;
+ addICCEThrowingMethod(method, clazz);
+ return;
+ }
+ DexEncodedMethod target = resolution.getSingleTarget();
+ DexClass targetHolder = appView.definitionFor(target.method.holder);
+ // Don-t forward if the target is explicitly marked as 'dont-rewrite'
+ if (targetHolder == null || dontRewrite(targetHolder, target)) {
return;
}
- // Collect the default interface methods to be added to this class.
- DefaultMethodCandidates methodsToImplement =
- collectMethodsToImplement(clazz, desugaredLibraryLookup);
- if (methodsToImplement.isEmpty()) {
+ // If resolution targets a default interface method, forward it.
+ if (targetHolder.isInterface() && target.isDefaultMethod()) {
+ addForward.accept(targetHolder, target);
return;
}
- // Add the methods.
- List<DexEncodedMethod> newForwardingMethods = new ArrayList<>(methodsToImplement.size());
- for (DexEncodedMethod method : methodsToImplement.candidates) {
- assert method.accessFlags.isPublic() && !method.accessFlags.isAbstract();
- DexEncodedMethod newMethod = addForwardingMethod(method, clazz);
- newForwardingMethods.add(newMethod);
- createdMethods.put(newMethod, method);
+ // Remaining edge cases only pertain to desugaring of library methods.
+ DexLibraryClass libraryHolder = targetHolder.asLibraryClass();
+ if (libraryHolder == null || ignoreLibraryInfo()) {
+ return;
}
- for (DexEncodedMethod conflict : methodsToImplement.conflicts.keySet()) {
- assert conflict.accessFlags.isPublic() && !conflict.accessFlags.isAbstract();
- DexEncodedMethod newMethod = addICCEThrowingMethod(conflict, clazz);
- newForwardingMethods.add(newMethod);
- createdMethods.put(newMethod, conflict);
+
+ if (isRetargetMethod(libraryHolder, target)) {
+ addForward.accept(targetHolder, target);
+ return;
}
- clazz.appendVirtualMethods(newForwardingMethods);
+
+ // If target is a non-interface library class it may be an emulated interface.
+ if (!libraryHolder.isInterface()) {
+ // Here we use step-3 of resolution to find a maximally specific default interface method.
+ target =
+ appView
+ .appInfo()
+ .resolveMaximallySpecificMethods(libraryHolder, method)
+ .getSingleTarget();
+ if (target != null && rewriter.isEmulatedInterface(target.method.holder)) {
+ targetHolder = appView.definitionFor(target.method.holder);
+ addForward.accept(targetHolder, target);
+ }
+ }
}
- private DexEncodedMethod addICCEThrowingMethod(DexEncodedMethod method, DexClass clazz) {
- DexMethod newMethod =
- dexItemFactory.createMethod(clazz.type, method.method.proto, method.method.name);
- return new DexEncodedMethod(
- newMethod,
- method.accessFlags.copy(),
- DexAnnotationSet.empty(),
- ParameterAnnotationsList.empty(),
- new SynthesizedCode(
- callerPosition ->
- new ExceptionThrowingSourceCode(
- clazz.type, method.method, callerPosition, dexItemFactory.icceType)));
+ private boolean isRetargetMethod(DexLibraryClass holder, DexEncodedMethod method) {
+ assert needsLibraryInfo();
+ assert holder.type == method.method.holder;
+ assert method.isVirtualMethod();
+ if (method.isFinal()) {
+ return false;
+ }
+ Map<DexType, DexType> typeMap =
+ appView
+ .options()
+ .desugaredLibraryConfiguration
+ .getRetargetCoreLibMember()
+ .get(method.method.name);
+ return typeMap != null && typeMap.containsKey(holder.type);
}
- private DexEncodedMethod addForwardingMethod(DexEncodedMethod defaultMethod, DexClass clazz) {
- DexMethod method = defaultMethod.method;
- DexClass target = appView.definitionFor(method.holder);
+ private boolean dontRewrite(DexClass clazz, DexEncodedMethod method) {
+ return needsLibraryInfo() && clazz.isLibraryClass() && rewriter.dontRewrite(method.method);
+ }
+
+ // Construction of actual forwarding methods.
+
+ private void addSyntheticMethod(DexProgramClass clazz, DexEncodedMethod newMethod) {
+ newSyntheticMethods.computeIfAbsent(clazz, key -> new ArrayList<>()).add(newMethod);
+ }
+
+ private void addICCEThrowingMethod(DexMethod method, DexClass clazz) {
+ if (!clazz.isProgramClass()) {
+ return;
+ }
+ DexMethod newMethod = dexItemFactory.createMethod(clazz.type, method.proto, method.name);
+ DexEncodedMethod newEncodedMethod =
+ new DexEncodedMethod(
+ newMethod,
+ MethodAccessFlags.fromCfAccessFlags(Opcodes.ACC_PUBLIC, false),
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ new SynthesizedCode(
+ callerPosition ->
+ new ExceptionThrowingSourceCode(
+ clazz.type, method, callerPosition, dexItemFactory.icceType)));
+ addSyntheticMethod(clazz.asProgramClass(), newEncodedMethod);
+ }
+
+ // Note: The parameter 'target' may be a public method on a class in case of desugared
+ // library retargeting (See below target.isInterface check).
+ private void addForwardingMethod(DexClass targetHolder, DexEncodedMethod target, DexClass clazz) {
+ assert targetHolder != null;
+ if (!clazz.isProgramClass()) {
+ return;
+ }
+ DexMethod method = target.method;
// NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
// even if this results in invalid code, these classes are never desugared.
- assert target != null;
// In desugared library, emulated interface methods can be overridden by retarget lib members.
DexMethod forwardMethod =
- target.isInterface()
+ targetHolder.isInterface()
? rewriter.defaultAsMethodOfCompanionClass(method)
- : retargetMethod(method);
+ : retargetMethod(appView, method);
// New method will have the same name, proto, and also all the flags of the
// default method, including bridge flag.
DexMethod newMethod = dexItemFactory.createMethod(clazz.type, method.proto, method.name);
- MethodAccessFlags newFlags = defaultMethod.accessFlags.copy();
+ MethodAccessFlags newFlags = target.accessFlags.copy();
// Some debuggers (like IntelliJ) automatically skip synthetic methods on single step.
newFlags.setSynthetic();
ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
@@ -158,185 +405,116 @@
.setTarget(forwardMethod)
.setInvokeType(Invoke.Type.STATIC)
.setIsInterface(false); // Holder is companion class, not an interface.
- return new DexEncodedMethod(
- newMethod,
- newFlags,
- defaultMethod.annotations,
- defaultMethod.parameterAnnotationsList,
- new SynthesizedCode(forwardSourceCodeBuilder::build));
+ DexEncodedMethod newEncodedMethod =
+ new DexEncodedMethod(
+ newMethod,
+ newFlags,
+ target.annotations,
+ target.parameterAnnotationsList,
+ new SynthesizedCode(forwardSourceCodeBuilder::build));
+ addSyntheticMethod(clazz.asProgramClass(), newEncodedMethod);
}
- private DexMethod retargetMethod(DexMethod method) {
+ static DexMethod retargetMethod(AppView<?> appView, DexMethod method) {
Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
Map<DexType, DexType> typeMap = retargetCoreLibMember.get(method.name);
assert typeMap != null;
assert typeMap.get(method.holder) != null;
- return dexItemFactory.createMethod(
- typeMap.get(method.holder),
- dexItemFactory.prependTypeToProto(method.holder, method.proto),
- method.name);
+ return appView
+ .dexItemFactory()
+ .createMethod(
+ typeMap.get(method.holder),
+ appView.dexItemFactory().prependTypeToProto(method.holder, method.proto),
+ method.name);
}
- // For a given class `clazz` inspects all interfaces it implements directly or
- // indirectly and collect a set of all default methods to be implemented
- // in this class.
- private DefaultMethodCandidates collectMethodsToImplement(
- DexClass clazz, boolean desugaredLibraryLookup) {
- DefaultMethodsHelper helper = new DefaultMethodsHelper();
- DexClass current = clazz;
- List<DexEncodedMethod> accumulatedVirtualMethods = new ArrayList<>();
- // Collect candidate default methods by inspecting interfaces implemented
- // by this class as well as its superclasses.
- //
- // We assume here that interfaces implemented by java.lang.Object don't
- // have default methods to desugar since they are library interfaces. And we assume object
- // methods don't hide any default interface methods. Default interface method matching Object's
- // methods is supposed to fail with a compilation error.
- // Note that this last assumption will be broken if Object API is augmented with a new method in
- // the future.
- while (current.type != dexItemFactory.objectType) {
- for (DexType type : current.interfaces.values) {
- helper.merge(rewriter.getOrCreateInterfaceInfo(clazz, current, type));
- }
+ // Topological order traversal and its helpers.
- // TODO(anyone): Using clazz here instead of current looks suspicious, should this be hoisted
- // out of the loop or changed to current?
- accumulatedVirtualMethods.addAll(clazz.virtualMethods());
-
- List<DexEncodedMethod> defaultMethodsInDirectInterface = helper.createFullList();
-
- List<DexEncodedMethod> toBeImplementedFromDirectInterface =
- new ArrayList<>(defaultMethodsInDirectInterface.size());
- hideCandidates(accumulatedVirtualMethods,
- defaultMethodsInDirectInterface,
- toBeImplementedFromDirectInterface);
- // toBeImplementedFromDirectInterface are those that we know for sure we need to implement by
- // looking at the already desugared super classes.
- // Remaining methods in defaultMethodsInDirectInterface are those methods we need to look at
- // the hierarchy to know how they should be handled.
- if (toBeImplementedFromDirectInterface.isEmpty()
- && defaultMethodsInDirectInterface.isEmpty()
- && !desugaredLibraryLookup) {
- // No interface with default in direct hierarchy, nothing to do: super already has all that
- // is needed.
- return DefaultMethodCandidates.empty();
- }
-
- if (current.superType == null) {
- // TODO(anyone): Can this ever happen? It seems the loop stops on Object.
- break;
- } else {
- DexClass superClass = appView.definitionFor(current.superType);
- if (superClass != null) {
- // TODO(b/138988172): Can we avoid traversing the full hierarchy for each type?
- InterfaceMethodRewriter.reportDependencyEdge(superClass, current, appView);
- current = superClass;
- } else {
- String message = "Default method desugaring of `" + clazz.toSourceString() + "` failed";
- if (current == clazz) {
- message += " because its super class `" +
- clazz.superType.toSourceString() + "` is missing";
- } else {
- message +=
- " because it's hierarchy is incomplete. The class `"
- + current.superType.toSourceString()
- + "` is missing and it is the declared super class of `"
- + current.toSourceString() + "`";
- }
- throw new CompilationError(message);
- }
- }
+ private DexClass definitionOrNull(DexType type, ReportingContext context) {
+ // No forwards at the top of the class hierarchy (assuming java.lang.Object is never amended).
+ if (type == null || type == dexItemFactory.objectType) {
+ return null;
}
-
- DefaultMethodCandidates candidateSet = helper.createCandidatesList();
- if (candidateSet.isEmpty()) {
- return candidateSet;
+ DexClass clazz = appView.definitionFor(type);
+ if (clazz == null) {
+ context.reportMissingType(type, rewriter);
+ return null;
}
-
- // Remove from candidates methods defined in class or any of its superclasses.
- List<DexEncodedMethod> candidates = candidateSet.candidates;
- List<DexEncodedMethod> toBeImplemented = new ArrayList<>(candidates.size());
- current = clazz;
- Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
- appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
- while (true) {
- // In desugared library look-up, methods from library classes cannot hide methods from
- // emulated interfaces (the method being desugared implied the implementation is not
- // present in the library class), except through retarget core lib member.
- if (desugaredLibraryLookup && current.isLibraryClass()) {
- Iterator<DexEncodedMethod> iterator = candidates.iterator();
- while (iterator.hasNext()) {
- DexEncodedMethod candidate = iterator.next();
- if (rewriter.isEmulatedInterface(candidate.method.holder)
- && current.lookupVirtualMethod(candidate.method) != null) {
- // A library class overrides an emulated interface method. This override is valid
- // only if it goes through retarget core lib member, else it needs to be implemented.
- Map<DexType, DexType> typeMap = retargetCoreLibMember.get(candidate.method.name);
- if (typeMap != null && typeMap.containsKey(current.type)) {
- // A rewrite needs to be performed, but instead of rewriting to the companion class,
- // D8/R8 needs to rewrite to the retarget member.
- toBeImplemented.add(current.lookupVirtualMethod(candidate.method));
- } else {
- toBeImplemented.add(candidate);
- iterator.remove();
- }
- }
- }
- }
- // Hide candidates by virtual method of the class.
- hideCandidates(current.virtualMethods(), candidates, toBeImplemented);
- if (candidates.isEmpty()) {
- return new DefaultMethodCandidates(toBeImplemented, candidateSet.conflicts);
- }
-
- DexType superType = current.superType;
- DexClass superClass = null;
- if (superType != null) {
- superClass = appView.definitionFor(superType);
- // It's available or we would have failed while analyzing the hierarchy for interfaces.
- assert superClass != null;
- }
- if (superClass == null || superType == dexItemFactory.objectType) {
- // Note that default interface methods must never have same
- // name/signature as any method in java.lang.Object (JLS §9.4.1.2).
-
- // Everything still in candidate list is not hidden.
- toBeImplemented.addAll(candidates);
-
- return new DefaultMethodCandidates(toBeImplemented, candidateSet.conflicts);
- }
- current = superClass;
- }
+ return clazz;
}
- private void hideCandidates(
- List<DexEncodedMethod> virtualMethods,
- Collection<DexEncodedMethod> candidates,
- List<DexEncodedMethod> toBeImplemented) {
- Iterator<DexEncodedMethod> it = candidates.iterator();
- while (it.hasNext()) {
- DexEncodedMethod candidate = it.next();
- for (DexEncodedMethod encoded : virtualMethods) {
- if (candidate.method.match(encoded)) {
- // Found a methods hiding the candidate.
- DexEncodedMethod basedOnCandidate = createdMethods.get(encoded);
- if (basedOnCandidate != null) {
- // The method we found is a method we have generated for a default interface
- // method in a superclass. If the method is based on the same candidate we don't
- // need to re-generate this method again since it is going to be inherited.
- if (basedOnCandidate != candidate) {
- // Need to re-generate since the inherited version is
- // based on a different candidate.
- toBeImplemented.add(candidate);
- }
- }
+ private ClassInfo visitClassInfo(DexType type, ReportingContext context) {
+ DexClass clazz = definitionOrNull(type, context);
+ return clazz == null ? ClassInfo.EMPTY : visitClassInfo(clazz, context);
+ }
- // Done with this candidate.
- it.remove();
- break;
- }
- }
+ private ClassInfo visitClassInfo(DexClass clazz, ReportingContext context) {
+ assert !clazz.isInterface();
+ if (clazz.isLibraryClass()) {
+ return ClassInfo.EMPTY;
}
+ context.reportDependency(clazz, appView);
+ return classInfo.computeIfAbsent(clazz, key -> visitClassInfoRaw(key, context));
+ }
+
+ private ClassInfo visitClassInfoRaw(DexClass clazz, ReportingContext context) {
+ // We compute both library and class information, but one of them is empty, since a class is
+ // a library class or is not, but cannot be both.
+ ReportingContext thisContext = context.forClass(clazz);
+ ClassInfo superInfo = visitClassInfo(clazz.superType, thisContext);
+ MethodSignatures signatures = visitLibraryClassInfo(clazz.superType);
+ assert superInfo.isEmpty() || signatures.isEmpty();
+ for (DexType iface : clazz.interfaces.values) {
+ signatures = signatures.merge(visitInterfaceInfo(iface, thisContext));
+ }
+ return computeClassInfo(clazz, superInfo, signatures);
+ }
+
+ private MethodSignatures visitLibraryClassInfo(DexType type) {
+ // No desugaring required, no library class analysis.
+ if (ignoreLibraryInfo()) {
+ return MethodSignatures.EMPTY;
+ }
+ DexClass clazz = definitionOrNull(type, LibraryReportingContext.LIBRARY_CONTEXT);
+ return clazz == null ? MethodSignatures.EMPTY : visitLibraryClassInfo(clazz);
+ }
+
+ private MethodSignatures visitLibraryClassInfo(DexClass clazz) {
+ assert !clazz.isInterface();
+ return clazz.isLibraryClass()
+ ? libraryClassInfo.computeIfAbsent(clazz.asLibraryClass(), this::visitLibraryClassInfoRaw)
+ : MethodSignatures.EMPTY;
+ }
+
+ private MethodSignatures visitLibraryClassInfoRaw(DexLibraryClass clazz) {
+ MethodSignatures signatures = visitLibraryClassInfo(clazz.superType);
+ for (DexType iface : clazz.interfaces.values) {
+ signatures =
+ signatures.merge(visitInterfaceInfo(iface, LibraryReportingContext.LIBRARY_CONTEXT));
+ }
+ return computeLibraryClassInfo(clazz, signatures);
+ }
+
+ private MethodSignatures visitInterfaceInfo(DexType iface, ReportingContext context) {
+ DexClass definition = definitionOrNull(iface, context);
+ return definition == null ? MethodSignatures.EMPTY : visitInterfaceInfo(definition, context);
+ }
+
+ private MethodSignatures visitInterfaceInfo(DexClass iface, ReportingContext context) {
+ if (iface.isLibraryClass() && ignoreLibraryInfo()) {
+ return MethodSignatures.EMPTY;
+ }
+ context.reportDependency(iface, appView);
+ return interfaceInfo.computeIfAbsent(iface, key -> visitInterfaceInfoRaw(key, context));
+ }
+
+ private MethodSignatures visitInterfaceInfoRaw(DexClass iface, ReportingContext context) {
+ ReportingContext thisContext = context.forClass(iface);
+ MethodSignatures signatures = MethodSignatures.EMPTY;
+ for (DexType superiface : iface.interfaces.values) {
+ signatures = signatures.merge(visitInterfaceInfo(superiface, thisContext));
+ }
+ return computeInterfaceInfo(iface, signatures);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
index 2484113..07277bc 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
@@ -6,66 +6,15 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
-import com.google.common.base.Equivalence;
-import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
-import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Objects;
import java.util.Set;
// Helper class implementing bunch of default interface method handling operations.
final class DefaultMethodsHelper {
-
- // Collection of default methods that need to have generated forwarding methods.
- public static class DefaultMethodCandidates {
- final List<DexEncodedMethod> candidates;
- final Map<DexEncodedMethod, List<DexEncodedMethod>> conflicts;
-
- private static final DefaultMethodCandidates EMPTY =
- new DefaultMethodCandidates(Collections.emptyList(), Collections.emptyMap());
-
- public static DefaultMethodCandidates empty() {
- return EMPTY;
- }
-
- public DefaultMethodCandidates(
- List<DexEncodedMethod> candidates,
- Map<DexEncodedMethod, List<DexEncodedMethod>> conflicts) {
- this.candidates = candidates;
- this.conflicts = conflicts;
- }
-
- public int size() {
- return candidates.size() + conflicts.size();
- }
-
- public boolean isEmpty() {
- return candidates.isEmpty() && conflicts.isEmpty();
- }
- }
-
- // Equivalence wrapper for comparing two method signatures modulo holder type.
- private static class SignatureEquivalence extends Equivalence<DexEncodedMethod> {
-
- @Override
- protected boolean doEquivalent(DexEncodedMethod method1, DexEncodedMethod method2) {
- return method1.method.match(method2.method);
- }
-
- @Override
- protected int doHash(DexEncodedMethod method) {
- return Objects.hash(method.method.name, method.method.proto);
- }
- }
-
// Current set of default interface methods, may overlap with `hidden`.
private final Set<DexEncodedMethod> candidates = Sets.newIdentityHashSet();
// Current set of known hidden default interface methods.
@@ -127,63 +76,6 @@
candidates.add(encoded);
}
- final DefaultMethodCandidates createCandidatesList() {
- // The common cases is for no default methods or a single one.
- if (candidates.isEmpty()) {
- return DefaultMethodCandidates.empty();
- }
- if (candidates.size() == 1 && hidden.isEmpty()) {
- return new DefaultMethodCandidates(new ArrayList<>(candidates), Collections.emptyMap());
- }
- // In case there are more we need to check for potential duplicates and treat them specially
- // to preserve the IncompatibleClassChangeError that would arise at runtime.
- int maxSize = candidates.size();
- SignatureEquivalence equivalence = new SignatureEquivalence();
- Map<Wrapper<DexEncodedMethod>, List<DexEncodedMethod>> groups = new HashMap<>(maxSize);
- boolean foundConflicts = false;
- for (DexEncodedMethod candidate : candidates) {
- if (hidden.contains(candidate)) {
- continue;
- }
- Wrapper<DexEncodedMethod> key = equivalence.wrap(candidate);
- List<DexEncodedMethod> conflicts = groups.get(key);
- if (conflicts != null) {
- foundConflicts = true;
- } else {
- conflicts = new ArrayList<>(maxSize);
- groups.put(key, conflicts);
- }
- conflicts.add(candidate);
- }
- // In the fast path we don't expect any conflicts or hidden candidates.
- if (!foundConflicts && hidden.isEmpty()) {
- return new DefaultMethodCandidates(new ArrayList<>(candidates), Collections.emptyMap());
- }
- // Slow case in the case of conflicts or hidden candidates build the result.
- List<DexEncodedMethod> actualCandidates = new ArrayList<>(groups.size());
- Map<DexEncodedMethod, List<DexEncodedMethod>> conflicts = new IdentityHashMap<>();
- for (Entry<Wrapper<DexEncodedMethod>, List<DexEncodedMethod>> entry : groups.entrySet()) {
- if (entry.getValue().size() == 1) {
- actualCandidates.add(entry.getKey().get());
- } else {
- conflicts.put(entry.getKey().get(), entry.getValue());
- }
- }
- return new DefaultMethodCandidates(actualCandidates, conflicts);
- }
-
- final List<DexEncodedMethod> createFullList() {
- if (candidates.isEmpty() && hidden.isEmpty()) {
- return Collections.emptyList();
- }
-
- List<DexEncodedMethod> fullList =
- new ArrayList<DexEncodedMethod>(candidates.size() + hidden.size());
- fullList.addAll(candidates);
- fullList.addAll(hidden);
- return fullList;
- }
-
// Create default interface collection based on collected information.
final Collection wrapInCollection() {
candidates.removeAll(hidden);
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 1c11d78..fd954e5 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
@@ -114,6 +114,10 @@
: "";
}
+ public String getSynthesizedLibraryClassesPackagePrefix() {
+ return synthesizedLibraryClassesPackagePrefix;
+ }
+
public Map<String, String> getRewritePrefix() {
return rewritePrefix;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index d75b84c..c9bd364 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -381,7 +381,7 @@
appView
.options()
.reporter
- .info(
+ .warning(
new StringDiagnostic(
"Desugared library API conversion: cannot wrap final methods "
+ Arrays.toString(methodArray)
@@ -440,17 +440,6 @@
workList.add(superClass);
}
}
- // 10 is large enough to avoid warnings on Clock/Function, but not on Stream.
- if (implementedMethods.size() > 10) {
- appView
- .options()
- .reporter
- .info(
- new StringDiagnostic(
- "Desugared library API conversion: Generating a large wrapper for "
- + libraryClass.type
- + ". Is that the intended behavior?"));
- }
return implementedMethods;
}
@@ -585,7 +574,7 @@
factory.createString(
"Unsupported conversion for "
+ type
- + ". See compilation time infos for more details."),
+ + ". See compilation time warnings for more details."),
holder)
.generateCfCode();
} else {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index eb855d1..679f927 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -27,6 +27,7 @@
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -60,6 +61,7 @@
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
//
// Default and static interface method desugaring rewriter (note that lambda
@@ -103,8 +105,6 @@
private final Map<DexType, DexType> emulatedInterfaces = new IdentityHashMap<>();
// The emulatedMethod set is there to avoid doing the emulated look-up too often.
private final Set<DexString> emulatedMethods = Sets.newIdentityHashSet();
- private ConcurrentHashMap<DexMethod, DexType> nearestEmulatedInterfaceCache =
- new ConcurrentHashMap<>();
// All forwarding methods generated during desugaring. We don't synchronize access
// to this collection since it is only filled in ClassProcessor running synchronously.
@@ -272,7 +272,7 @@
new InvokeStatic(defaultAsMethodOfCompanionClass(amendedMethod),
invokeSuper.outValue(), invokeSuper.arguments()));
} else {
- DexType dexType = nearestEmulatedInterfaceOrNull(invokedMethod);
+ DexType dexType = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
if (dexType != null) {
// That invoke super may not resolve since the super method may not be present
// since it's in the emulated interface. We need to force resolution. If it resolves
@@ -373,7 +373,7 @@
if (instruction.isInvokeVirtual() || instruction.isInvokeInterface()) {
InvokeMethod invokeMethod = instruction.asInvokeMethod();
DexMethod invokedMethod = invokeMethod.getInvokedMethod();
- DexType dexType = nearestEmulatedInterfaceOrNull(invokedMethod);
+ DexType dexType = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
if (dexType != null) {
rewriteCurrentInstructionToEmulatedInterfaceCall(
dexType, invokedMethod, invokeMethod, instructions);
@@ -383,7 +383,7 @@
}
}
- private DexType nearestEmulatedInterfaceOrNull(DexMethod invokedMethod) {
+ private DexType maximallySpecificEmulatedInterfaceOrNull(DexMethod invokedMethod) {
// Here we try to avoid doing the expensive look-up on all invokes.
if (!emulatedMethods.contains(invokedMethod.name)) {
return null;
@@ -393,20 +393,30 @@
if (dexClass == null) {
return null;
}
- // TODO(b/120884788): Make sure program class are looked up before library class for
- // CoreLib compilation or look again into all desugared library emulation.
- // Outside of core libraries, only library classes are rewritten. In core libraries,
- // some classes are present both as program and library class, and definitionFor
- // answers the program class so this is not true.
+ // TODO(b/120884788): Make sure program class are looked up before library class.
+ // Since program classes are desugared, no need to rewrite invokes which can target only
+ // program types.
if (!appView.options().isDesugaredLibraryCompilation() && !dexClass.isLibraryClass()) {
return null;
}
- // We always rewrite interfaces, but classes are rewritten only if they are not already
- // desugared (CoreLibrary classes efficient implementation).
- if (!dexClass.isInterface() && isInDesugaredLibrary(dexClass)) {
+ // Since desugared library classes are desugared, no need to rewrite invokes which can target
+ // only such classes program types.
+ if (appView.rewritePrefix.hasRewrittenType(dexClass.type)) {
return null;
}
- return nearestEmulatedInterfaceImplementingWithCache(invokedMethod);
+ ResolutionResult resolutionResult =
+ appView.appInfo().resolveMaximallySpecificMethods(dexClass, invokedMethod);
+ if (!resolutionResult.isSingleResolution()) {
+ // At this point we are in a library class. Failures can happen with NoSuchMethod if a
+ // library class implement a method with same signature but not related to emulated
+ // interfaces.
+ return null;
+ }
+ DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
+ if (!singleTarget.isAbstract() && isEmulatedInterface(singleTarget.method.holder)) {
+ return singleTarget.method.holder;
+ }
+ return null;
}
private void rewriteCurrentInstructionToEmulatedInterfaceCall(
@@ -425,137 +435,11 @@
}
}
- private DexType nearestEmulatedInterfaceImplementingWithCache(DexMethod method) {
- DexType sentinel = DexItemFactory.nullValueType;
- if (nearestEmulatedInterfaceCache.containsKey(method)) {
- DexType dexType = nearestEmulatedInterfaceCache.get(method);
- if (dexType == sentinel) {
- return null;
- }
- return dexType;
- } else {
- DexType value = nearestEmulatedInterfaceImplementing(method);
- DexType putValue = value == null ? sentinel : value;
- nearestEmulatedInterfaceCache.put(method, putValue);
- return value;
- }
- }
-
- private DexType nearestEmulatedInterfaceImplementing(DexMethod method) {
- // Find the nearest emulated interface implementing method in a non abstract way.
- // Answers null if none.
- if (!method.holder.isClassType()) {
- return null;
- }
- // 1. Direct match against the interface for invokeInterface.
- if (isMatchingEmulatedInterface(method.holder, method)) {
- return method.holder;
- }
- List<DexType> foundInterfaces = new ArrayList<>();
- Set<DexType> foundEmulatedInterfaces = Sets.newIdentityHashSet();
- // 2. Walk superclass hierarchy to find implemented interfaces, pick the minimal of them.
- DexType current = method.holder;
- DexClass currentClass = appView.definitionFor(current);
- while (currentClass != null) {
- for (DexType itf : currentClass.interfaces.values) {
- if (isMatchingEmulatedInterface(itf, method)) {
- foundEmulatedInterfaces.add(itf);
- } else if (foundEmulatedInterfaces.isEmpty()) {
- foundInterfaces.add(itf);
- }
- }
- current = currentClass.superType;
- currentClass = current == null ? null : appView.definitionFor(current);
- }
- if (!foundEmulatedInterfaces.isEmpty()) {
- return minimalInterfaceOf(foundEmulatedInterfaces);
- }
- // 3. Walk the interfaces hierachies to find implemented interfaces, pick the minimal of them.
- LinkedList<DexType> workList = new LinkedList<>(foundInterfaces);
- while (!workList.isEmpty()) {
- currentClass = appView.definitionFor(workList.removeFirst());
- if (currentClass != null) {
- for (DexType itf : currentClass.interfaces.values) {
- if (isMatchingEmulatedInterface(itf, method)) {
- foundEmulatedInterfaces.add(itf);
- } else if (!foundInterfaces.contains(itf)) {
- foundInterfaces.add(itf);
- workList.add(itf);
- }
- }
- }
- }
- if (!foundEmulatedInterfaces.isEmpty()) {
- return minimalInterfaceOf(foundEmulatedInterfaces);
- }
- return null;
- }
-
- private boolean isMatchingEmulatedInterface(DexType itf, DexMethod method) {
- DexClass dexClass = appView.definitionFor(itf);
- DexEncodedMethod encodedMethod = dexClass == null ? null : dexClass.lookupMethod(method);
- return emulatedInterfaces.containsKey(itf)
- && encodedMethod != null
- && !encodedMethod.isAbstract();
- }
-
- private DexType minimalInterfaceOf(Set<DexType> interfaces) {
- assert interfaces.size() > 0;
- if (interfaces.size() == 1) {
- return interfaces.iterator().next();
- }
- // We may have two classes which appears unrelated due to a missing interface in the list,
- // i.e., A implements B implements C, but B is not implementing the method.
- // We look up interface hierarchy here for all interfaces to determine interfaces with children.
- // The unique interface without children is returned (nearest interface).
- final ArrayList<DexType> hasChildren = new ArrayList<>();
- for (DexType anInterface : interfaces) {
- LinkedList<DexType> workList = new LinkedList<>();
- workList.add(anInterface);
- while (!workList.isEmpty()) {
- DexType itf = workList.removeFirst();
- DexClass itfClass = appView.definitionFor(itf);
- if (itfClass == null) {
- continue;
- }
- for (DexType superItf : itfClass.interfaces.values) {
- if (interfaces.contains(superItf)) {
- hasChildren.add(superItf);
- } else {
- workList.add(superItf);
- }
- }
- }
- }
- DexType result = null;
- for (DexType anInterface : interfaces) {
- if (!hasChildren.contains(anInterface)) {
- if (result != null) {
- throw new CompilationError(
- "Multiple emulated interfaces, non related to each other, "
- + "implementing the same default method ("
- + anInterface
- + ","
- + result
- + ")");
- }
- result = anInterface;
- }
- }
- if (result == null) {
- throw new CompilationError(
- "All emulated interfaces "
- + Arrays.toString(interfaces.toArray())
- + " inherit from each other.");
- }
- return result;
- }
-
- boolean isNonDesugaredLibraryClass(DexClass clazz) {
+ private boolean isNonDesugaredLibraryClass(DexClass clazz) {
return clazz.isLibraryClass() && !isInDesugaredLibrary(clazz);
}
- private boolean isInDesugaredLibrary(DexClass clazz) {
+ boolean isInDesugaredLibrary(DexClass clazz) {
assert clazz.isLibraryClass() || options.isDesugaredLibraryCompilation();
if (emulatedInterfaces.containsKey(clazz.type)) {
return true;
@@ -563,7 +447,7 @@
return appView.rewritePrefix.hasRewrittenType(clazz.type);
}
- private boolean dontRewrite(DexMethod method) {
+ boolean dontRewrite(DexMethod method) {
for (Pair<DexType, DexString> dontRewrite :
options.desugaredLibraryConfiguration.getDontRewriteInvocation()) {
if (method.holder == dontRewrite.getFirst() && method.name == dontRewrite.getSecond()) {
@@ -726,7 +610,8 @@
}
}
emulationMethods.add(
- method.toEmulateInterfaceLibraryMethod(
+ DexEncodedMethod.toEmulateDispatchLibraryMethod(
+ method.method.holder,
emulateInterfaceLibraryMethod(method.method, method.method.holder, factory),
companionMethod,
libraryMethod,
@@ -1041,7 +926,7 @@
// Process all classes first. Add missing forwarding methods to
// replace desugared default interface methods.
- synthesizedMethods.addAll(processClasses(builder, flavour));
+ processClasses(builder, flavour, synthesizedMethods::add);
// Process interfaces, create companion or dispatch class if needed, move static
// methods to companion class, copy default interface methods to companion classes,
@@ -1095,14 +980,17 @@
return processor.syntheticClasses;
}
- private Set<DexEncodedMethod> processClasses(Builder<?> builder, Flavor flavour) {
- ClassProcessor processor = new ClassProcessor(appView, this);
+ private void processClasses(
+ Builder<?> builder, Flavor flavour, Consumer<DexEncodedMethod> newSynthesizedMethodConsumer) {
+ ClassProcessor processor = new ClassProcessor(appView, this, newSynthesizedMethodConsumer);
+ // First we compute all desugaring *without* introducing forwarding methods.
for (DexProgramClass clazz : builder.getProgramClasses()) {
if (shouldProcess(clazz, flavour, false)) {
- processor.process(clazz);
+ processor.processClass(clazz);
}
}
- return processor.getForwardMethods();
+ // Then we introduce forwarding methods.
+ processor.addSyntheticMethods();
}
final boolean isDefaultMethod(DexEncodedMethod method) {
@@ -1125,6 +1013,37 @@
return true;
}
+ public static boolean isEmulatedInterfaceDispatch(AppView<?> appView, DexEncodedMethod method) {
+ // Answers true if this method is already managed through emulated interface dispatch.
+ Map<DexType, DexType> emulateLibraryInterface =
+ appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
+ if (emulateLibraryInterface.isEmpty()) {
+ return false;
+ }
+ DexMethod methodToFind = method.method;
+
+ // Look-up all superclass and interfaces, if an emulated interface is found, and it implements
+ // the method, answers true.
+ LinkedList<DexType> workList = new LinkedList<>();
+ workList.add(methodToFind.holder);
+ while (!workList.isEmpty()) {
+ DexType dexType = workList.removeFirst();
+ DexClass dexClass = appView.definitionFor(dexType);
+ assert dexClass != null; // It is a library class, or we are doing L8 compilation.
+ if (dexClass.isInterface() && emulateLibraryInterface.containsKey(dexType)) {
+ DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(methodToFind);
+ if (dexEncodedMethod != null) {
+ return true;
+ }
+ }
+ Collections.addAll(workList, dexClass.interfaces.values);
+ if (dexClass.superType != appView.dexItemFactory().objectType) {
+ workList.add(dexClass.superType);
+ }
+ }
+ return false;
+ }
+
public void warnMissingInterface(
DexClass classToDesugar, DexClass implementing, DexType missing) {
// We use contains() on non hashed collection, but we know it's a 8 cases collection.
@@ -1138,6 +1057,7 @@
// Companion/Emulated interface/Conversion classes for desugared library won't be missing,
// they are in the desugared library.
if (appView.rewritePrefix.hasRewrittenType(missing)
+ || DesugaredLibraryWrapperSynthesizer.isSynthesizedWrapper(missing)
|| appView
.options()
.desugaredLibraryConfiguration
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 8b8ef05..1c20983 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -62,7 +62,6 @@
void process(DexProgramClass iface, NestedGraphLense.Builder graphLensBuilder) {
assert iface.isInterface();
-
// The list of methods to be created in companion class.
List<DexEncodedMethod> companionMethods = new ArrayList<>();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSynthesizedCode.java
new file mode 100644
index 0000000..6e109dd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSynthesizedCode.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.function.Consumer;
+
+class LambdaBridgeMethodSynthesizedCode extends LambdaSynthesizedCode {
+
+ private final DexMethod mainMethod;
+ private final DexMethod bridgeMethod;
+
+ LambdaBridgeMethodSynthesizedCode(
+ LambdaClass lambda, DexMethod mainMethod, DexMethod bridgeMethod) {
+ super(lambda);
+ this.mainMethod = mainMethod;
+ this.bridgeMethod = bridgeMethod;
+ }
+
+ @Override
+ public SourceCodeProvider getSourceCodeProvider() {
+ return callerPosition ->
+ new LambdaBridgeMethodSourceCode(lambda, mainMethod, bridgeMethod, callerPosition);
+ }
+
+ @Override
+ public Consumer<UseRegistry> getRegistryCallback() {
+ return registry -> {
+ registry.registerInvokeVirtual(mainMethod);
+
+ DexType bridgeMethodReturnType = bridgeMethod.proto.returnType;
+ if (!bridgeMethodReturnType.isVoidType()
+ && bridgeMethodReturnType != mainMethod.proto.returnType
+ && bridgeMethodReturnType != dexItemFactory().objectType) {
+ registry.registerCheckCast(bridgeMethodReturnType);
+ }
+ };
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 9998c94..e7ba0b5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -62,7 +62,6 @@
final LambdaDescriptor descriptor;
final DexMethod constructor;
final DexMethod classConstructor;
- final DexMethod createInstanceMethod;
final DexField lambdaField;
final Target target;
final AtomicBoolean addToMainDexList = new AtomicBoolean(false);
@@ -98,13 +97,6 @@
!stateless
? null
: factory.createField(lambdaClassType, lambdaClassType, rewriter.instanceFieldName);
- this.createInstanceMethod =
- stateless
- ? null
- : factory.createMethod(
- lambdaClassType,
- factory.createProto(lambdaClassType, descriptor.captures.values),
- rewriter.createInstanceMethodName);
}
// Generate unique lambda class type for lambda descriptor and instantiation point context.
@@ -137,11 +129,6 @@
return lazyDexClass.get();
}
- DexMethod getCreateInstanceMethod() {
- assert createInstanceMethod != null;
- return createInstanceMethod;
- }
-
private DexProgramClass synthesizeLambdaClass() {
DexMethod mainMethod =
rewriter.factory.createMethod(type, descriptor.erasedProto, descriptor.name);
@@ -169,9 +156,7 @@
synthesizeVirtualMethods(mainMethod),
rewriter.factory.getSkipNameValidationForTesting(),
LambdaClass::computeChecksumForSynthesizedClass);
- // Optimize main method.
rewriter.converter.appView.appInfo().addSynthesizedClass(clazz);
- rewriter.converter.optimizeSynthesizedMethod(clazz.lookupVirtualMethod(mainMethod));
// The method addSynthesizedFrom() may be called concurrently. To avoid a Concurrent-
// ModificationException we must use synchronization.
@@ -232,9 +217,7 @@
Constants.ACC_PUBLIC | Constants.ACC_FINAL, false),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
- new SynthesizedCode(
- callerPosition ->
- new LambdaMainMethodSourceCode(this, mainMethod, callerPosition)));
+ new LambdaMainMethodSynthesizedCode(this, mainMethod));
// Synthesize bridge methods.
for (DexProto bridgeProto : descriptor.bridges) {
@@ -250,10 +233,7 @@
false),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
- new SynthesizedCode(
- callerPosition ->
- new LambdaBridgeMethodSourceCode(
- this, mainMethod, bridgeMethod, callerPosition)));
+ new LambdaBridgeMethodSynthesizedCode(this, mainMethod, bridgeMethod));
}
return methods;
}
@@ -261,10 +241,7 @@
// Synthesize direct methods.
private DexEncodedMethod[] synthesizeDirectMethods() {
boolean stateless = isStateless();
- boolean enableStatefulLambdaCreateInstanceMethod =
- rewriter.converter.appView.options().testing.enableStatefulLambdaCreateInstanceMethod;
- DexEncodedMethod[] methods =
- new DexEncodedMethod[(stateless || enableStatefulLambdaCreateInstanceMethod) ? 2 : 1];
+ DexEncodedMethod[] methods = new DexEncodedMethod[stateless ? 2 : 1];
// Constructor.
methods[0] =
@@ -276,8 +253,7 @@
true),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
- new SynthesizedCode(
- callerPosition -> new LambdaConstructorSourceCode(this, callerPosition)));
+ new LambdaConstructorSynthesizedCode(this));
// Class constructor for stateless lambda classes.
if (stateless) {
@@ -288,18 +264,7 @@
Constants.ACC_SYNTHETIC | Constants.ACC_STATIC, true),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
- new SynthesizedCode(
- callerPosition -> new LambdaClassConstructorSourceCode(this, callerPosition)));
- } else if (enableStatefulLambdaCreateInstanceMethod) {
- methods[1] =
- new DexEncodedMethod(
- createInstanceMethod,
- MethodAccessFlags.fromSharedAccessFlags(
- Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC, false),
- DexAnnotationSet.empty(),
- ParameterAnnotationsList.empty(),
- new SynthesizedCode(
- callerPosition -> new LambdaCreateInstanceSourceCode(this, callerPosition)));
+ new LambdaClassConstructorSynthesizedCode(this));
}
return methods;
}
@@ -312,7 +277,7 @@
for (int i = 0; i < fieldCount; i++) {
FieldAccessFlags accessFlags =
FieldAccessFlags.fromSharedAccessFlags(
- Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PRIVATE);
+ Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC);
fields[i] = new DexEncodedField(
getCaptureField(i), accessFlags, DexAnnotationSet.empty(), null);
}
@@ -518,7 +483,7 @@
}
// Ensure access of the referenced symbol(s).
- abstract boolean ensureAccessibility();
+ abstract void ensureAccessibility();
DexClass definitionFor(DexType type) {
return rewriter.converter.appView.appInfo().app().definitionFor(type);
@@ -563,9 +528,7 @@
}
@Override
- boolean ensureAccessibility() {
- return true;
- }
+ void ensureAccessibility() {}
}
// Used for static private lambda$ methods. Only needs access relaxation.
@@ -576,7 +539,7 @@
}
@Override
- boolean ensureAccessibility() {
+ void ensureAccessibility() {
// We already found the static method to be called, just relax its accessibility.
assert descriptor.getAccessibility() != null;
descriptor.getAccessibility().unsetPrivate();
@@ -584,7 +547,6 @@
if (implMethodHolder.isInterface()) {
descriptor.getAccessibility().setPublic();
}
- return true;
}
}
@@ -597,7 +559,7 @@
}
@Override
- boolean ensureAccessibility() {
+ void ensureAccessibility() {
// For all instantiation points for which the compiler creates lambda$
// methods, it creates these methods in the same class/interface.
DexMethod implMethod = descriptor.implHandle.asMethod();
@@ -628,12 +590,11 @@
DexEncodedMethod.setDebugInfoWithFakeThisParameter(
newMethod.getCode(), callTarget.getArity(), rewriter.converter.appView);
implMethodHolder.setDirectMethod(i, newMethod);
- return true;
+ return;
}
}
assert false
: "Unexpected failure to find direct lambda target for: " + implMethod.qualifiedName();
- return false;
}
}
@@ -645,7 +606,7 @@
}
@Override
- boolean ensureAccessibility() {
+ void ensureAccessibility() {
// For all instantiation points for which the compiler creates lambda$
// methods, it creates these methods in the same class/interface.
DexMethod implMethod = descriptor.implHandle.asMethod();
@@ -672,10 +633,9 @@
// Move the method from the direct methods to the virtual methods set.
implMethodHolder.removeDirectMethod(i);
implMethodHolder.appendVirtualMethod(newMethod);
- return true;
+ return;
}
}
- return false;
}
}
@@ -688,7 +648,7 @@
}
@Override
- boolean ensureAccessibility() {
+ void ensureAccessibility() {
// Create a static accessor with proper accessibility.
DexProgramClass accessorClass = programDefinitionFor(callTarget.holder);
assert accessorClass != null;
@@ -715,7 +675,6 @@
}
rewriter.converter.optimizeSynthesizedMethod(accessorEncodedMethod);
- return true;
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSynthesizedCode.java
new file mode 100644
index 0000000..2dbac91
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSynthesizedCode.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.function.Consumer;
+
+class LambdaClassConstructorSynthesizedCode extends LambdaSynthesizedCode {
+
+ LambdaClassConstructorSynthesizedCode(LambdaClass lambda) {
+ super(lambda);
+ }
+
+ @Override
+ public SourceCodeProvider getSourceCodeProvider() {
+ return callerPosition -> new LambdaClassConstructorSourceCode(lambda, callerPosition);
+ }
+
+ @Override
+ public Consumer<UseRegistry> getRegistryCallback() {
+ return registry -> {
+ registry.registerNewInstance(lambda.type);
+ registry.registerInvokeDirect(lambda.constructor);
+ registry.registerStaticFieldWrite(lambda.lambdaField);
+ };
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java
new file mode 100644
index 0000000..29fb5c8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.function.Consumer;
+
+class LambdaConstructorSynthesizedCode extends LambdaSynthesizedCode {
+
+ LambdaConstructorSynthesizedCode(LambdaClass lambda) {
+ super(lambda);
+ }
+
+ @Override
+ public SourceCodeProvider getSourceCodeProvider() {
+ return callerPosition -> new LambdaConstructorSourceCode(lambda, callerPosition);
+ }
+
+ @Override
+ public Consumer<UseRegistry> getRegistryCallback() {
+ return registry -> {
+ registry.registerInvokeDirect(lambda.rewriter.objectInitMethod);
+ DexType[] capturedTypes = captures();
+ for (int i = 0; i < capturedTypes.length; i++) {
+ registry.registerInstanceFieldWrite(lambda.getCaptureField(i));
+ }
+ };
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaCreateInstanceSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaCreateInstanceSourceCode.java
deleted file mode 100644
index eacad03..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaCreateInstanceSourceCode.java
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.desugar;
-
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueType;
-import java.util.ArrayList;
-import java.util.List;
-
-// Source code for the static method in a stateful lambda class which creates and initializes
-// a new instance.
-final class LambdaCreateInstanceSourceCode extends SynthesizedLambdaSourceCode {
-
- LambdaCreateInstanceSourceCode(LambdaClass lambda, Position callerPosition) {
- super(lambda, lambda.createInstanceMethod, callerPosition, null);
- }
-
- @Override
- protected void prepareInstructions() {
- // Create and initialize an instance.
- int instance = nextRegister(ValueType.OBJECT);
- add(builder -> builder.addNewInstance(instance, lambda.type));
- int paramCount = proto.parameters.values.length;
- List<ValueType> types = new ArrayList<>(paramCount + 1);
- List<Integer> registers = new ArrayList<>(paramCount + 1);
- types.add(ValueType.OBJECT);
- registers.add(instance);
- for (int i = 0; i < paramCount; ++i) {
- types.add(ValueType.fromDexType(proto.parameters.values[i]));
- registers.add(getParamRegister(i));
- }
- add(
- builder ->
- builder.addInvoke(
- Invoke.Type.DIRECT,
- lambda.constructor,
- lambda.constructor.proto,
- types,
- registers,
- false /* isInterface */));
- add(builder -> builder.addReturn(instance));
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java
new file mode 100644
index 0000000..3e7e3f5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.code.Invoke;
+import java.util.function.Consumer;
+
+class LambdaMainMethodSynthesizedCode extends LambdaSynthesizedCode {
+
+ private final DexMethod mainMethod;
+
+ LambdaMainMethodSynthesizedCode(LambdaClass lambda, DexMethod mainMethod) {
+ super(lambda);
+ this.mainMethod = mainMethod;
+ }
+
+ @Override
+ public SourceCodeProvider getSourceCodeProvider() {
+ return callerPosition -> new LambdaMainMethodSourceCode(lambda, mainMethod, callerPosition);
+ }
+
+ @Override
+ public Consumer<UseRegistry> getRegistryCallback() {
+ return registry -> {
+ LambdaClass.Target target = lambda.target;
+ assert target.invokeType == Invoke.Type.STATIC
+ || target.invokeType == Invoke.Type.VIRTUAL
+ || target.invokeType == Invoke.Type.DIRECT
+ || target.invokeType == Invoke.Type.INTERFACE;
+
+ registry.registerNewInstance(target.callTarget.holder);
+
+ DexType[] capturedTypes = captures();
+ for (int i = 0; i < capturedTypes.length; i++) {
+ registry.registerInstanceFieldRead(lambda.getCaptureField(i));
+ }
+
+ switch (target.invokeType) {
+ case DIRECT:
+ registry.registerInvokeDirect(target.callTarget);
+ break;
+ case INTERFACE:
+ registry.registerInvokeInterface(target.callTarget);
+ break;
+ case STATIC:
+ registry.registerInvokeStatic(target.callTarget);
+ break;
+ case VIRTUAL:
+ registry.registerInvokeVirtual(target.callTarget);
+ break;
+ default:
+ throw new Unreachable();
+ }
+ };
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 8cc6d15..ed49900 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.DefaultUseRegistry;
import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -27,18 +28,20 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeCustom;
import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.LensCodeRewriter;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.ListIterator;
@@ -46,6 +49,7 @@
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
/**
* Lambda desugaring rewriter.
@@ -59,8 +63,7 @@
public static final String LAMBDA_CLASS_NAME_PREFIX = "-$$Lambda$";
public static final String LAMBDA_GROUP_CLASS_NAME_PREFIX = "-$$LambdaGroup$";
static final String EXPECTED_LAMBDA_METHOD_PREFIX = "lambda$";
- static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
- static final String LAMBDA_CREATE_INSTANCE_METHOD_NAME = "$$createInstance";
+ private static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
private final AppView<?> appView;
final IRConverter converter;
@@ -71,7 +74,6 @@
final DexString constructorName;
final DexString classConstructorName;
final DexString instanceFieldName;
- final DexString createInstanceMethodName;
final BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
@@ -102,11 +104,47 @@
this.objectInitMethod = factory.createMethod(factory.objectType, initProto, constructorName);
this.classConstructorName = factory.createString(Constants.CLASS_INITIALIZER_NAME);
this.instanceFieldName = factory.createString(LAMBDA_INSTANCE_FIELD_NAME);
- this.createInstanceMethodName = factory.createString(LAMBDA_CREATE_INSTANCE_METHOD_NAME);
}
- public void synthesizeLambdaClassesFor(
- DexEncodedMethod method, LensCodeRewriter lensCodeRewriter) {
+ public void synthesizeLambdaClassesForWave(
+ Collection<DexEncodedMethod> wave,
+ ExecutorService executorService,
+ OptimizationFeedbackDelayed feedback,
+ LensCodeRewriter lensCodeRewriter)
+ throws ExecutionException {
+ Set<DexProgramClass> synthesizedLambdaClasses = Sets.newIdentityHashSet();
+ for (DexEncodedMethod method : wave) {
+ synthesizeLambdaClassesForMethod(method, synthesizedLambdaClasses::add, lensCodeRewriter);
+ }
+
+ if (synthesizedLambdaClasses.isEmpty()) {
+ return;
+ }
+
+ // Record that the static fields on each lambda class are only written inside the static
+ // initializer of the lambdas.
+ Map<DexEncodedField, Set<DexEncodedMethod>> writesWithContexts = new IdentityHashMap<>();
+ for (DexProgramClass synthesizedLambdaClass : synthesizedLambdaClasses) {
+ DexEncodedMethod clinit = synthesizedLambdaClass.getClassInitializer();
+ if (clinit != null) {
+ for (DexEncodedField field : synthesizedLambdaClass.staticFields()) {
+ writesWithContexts.put(field, ImmutableSet.of(clinit));
+ }
+ }
+ }
+
+ AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+ appViewWithLiveness.setAppInfo(
+ appViewWithLiveness.appInfo().withStaticFieldWrites(writesWithContexts));
+
+ converter.optimizeSynthesizedLambdaClasses(synthesizedLambdaClasses, executorService);
+ feedback.updateVisibleOptimizationInfo();
+ }
+
+ public void synthesizeLambdaClassesForMethod(
+ DexEncodedMethod method,
+ Consumer<DexProgramClass> consumer,
+ LensCodeRewriter lensCodeRewriter) {
if (!method.hasCode() || method.isProcessed()) {
// Nothing to desugar.
return;
@@ -129,7 +167,9 @@
LambdaDescriptor descriptor =
inferLambdaDescriptor(lensCodeRewriter.rewriteCallSite(callSite, method));
if (descriptor != LambdaDescriptor.MATCH_FAILED) {
- getOrCreateLambdaClass(descriptor, method.method.holder);
+ consumer.accept(
+ getOrCreateLambdaClass(descriptor, method.method.holder)
+ .getOrCreateLambdaClass());
}
}
});
@@ -177,34 +217,6 @@
assert code.isConsistentSSA();
}
- public void desugarLambda(
- DexType currentType,
- InstructionListIterator instructions,
- InvokeCustom lenseRewrittenInvokeCustom,
- IRCode code) {
- LambdaDescriptor descriptor = inferLambdaDescriptor(lenseRewrittenInvokeCustom.getCallSite());
- if (descriptor == LambdaDescriptor.MATCH_FAILED) {
- return;
- }
-
- // We have a descriptor, get or create lambda class.
- LambdaClass lambdaClass = getOrCreateLambdaClass(descriptor, currentType);
- assert lambdaClass != null;
-
- // We rely on patch performing its work in a way which
- // keeps `instructions` iterator in valid state so that we can continue iteration.
- patchInstructionSimple(lambdaClass, code, instructions, lenseRewrittenInvokeCustom);
- }
-
- public boolean verifyNoLambdasToDesugar(IRCode code) {
- for (Instruction instruction : code.instructions()) {
- assert !instruction.isInvokeCustom()
- || inferLambdaDescriptor(instruction.asInvokeCustom().getCallSite())
- == LambdaDescriptor.MATCH_FAILED;
- }
- return true;
- }
-
/** Remove lambda deserialization methods. */
public boolean removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
for (DexProgramClass clazz : classes) {
@@ -400,108 +412,44 @@
return;
}
- if (!converter.appView.options().testing.enableStatefulLambdaCreateInstanceMethod) {
- // For stateful lambdas we always create a new instance since we need to pass
- // captured values to the constructor.
- //
- // We replace InvokeCustom instruction with a new NewInstance instruction
- // instantiating lambda followed by InvokeDirect instruction calling a
- // constructor on it.
- //
- // original:
- // Invoke-Custom rResult <- { rArg0, rArg1, ... }; call site: ...
- //
- // result:
- // NewInstance rResult <- LambdaClass
- // Invoke-Direct { rResult, rArg0, rArg1, ... }; method: void LambdaClass.<init>(...)
- lambdaInstanceValue.setTypeLattice(
- lambdaInstanceValue
- .getTypeLattice()
- .asReferenceTypeLatticeElement()
- .asDefinitelyNotNull());
- NewInstance newInstance = new NewInstance(lambdaClass.type, lambdaInstanceValue);
- instructions.replaceCurrentInstruction(newInstance);
-
- List<Value> arguments = new ArrayList<>();
- arguments.add(lambdaInstanceValue);
- arguments.addAll(invoke.arguments()); // Optional captures.
- InvokeDirect constructorCall =
- new InvokeDirect(lambdaClass.constructor, null /* no return value */, arguments);
- instructions.add(constructorCall);
- constructorCall.setPosition(newInstance.getPosition());
-
- // If we don't have catch handlers we are done.
- if (!constructorCall.getBlock().hasCatchHandlers()) {
- return;
- }
-
- // Move the iterator back to position it between the two instructions, split
- // the block between the two instructions, and copy the catch handlers.
- instructions.previous();
- assert instructions.peekNext().isInvokeDirect();
- BasicBlock currentBlock = newInstance.getBlock();
- BasicBlock nextBlock = instructions.split(code, blocks);
- assert !instructions.hasNext();
- nextBlock.copyCatchHandlers(code, blocks, currentBlock, appView.options());
- } else {
- // For stateful lambdas we call the createInstance method.
- //
- // original:
- // Invoke-Custom rResult <- { rArg0, rArg1, ... }; call site: ...
- //
- // result:
- // Invoke-Static rResult <- { rArg0, rArg1, ... }; method void
- // LambdaClass.createInstance(...)
- InvokeStatic invokeStatic =
- new InvokeStatic(
- lambdaClass.getCreateInstanceMethod(), lambdaInstanceValue, invoke.arguments());
- instructions.replaceCurrentInstruction(invokeStatic);
- }
- }
-
- // Patches invoke-custom instruction to create or get an instance
- // of the generated lambda class. Assumes that for stateful lambdas the createInstance method
- // is enabled so invokeCustom is always replaced by a single instruction.
- private void patchInstructionSimple(
- LambdaClass lambdaClass,
- IRCode code,
- InstructionListIterator instructions,
- InvokeCustom invoke) {
- assert lambdaClass != null;
- assert instructions != null;
-
- // The value representing new lambda instance: we reuse the value from the original
- // invoke-custom instruction, and thus all its usages.
- Value lambdaInstanceValue = invoke.outValue();
- if (lambdaInstanceValue == null) {
- // The out value might be empty in case it was optimized out.
- lambdaInstanceValue =
- code.createValue(
- TypeLatticeElement.fromDexType(lambdaClass.type, Nullability.maybeNull(), appView));
- }
-
- // For stateless lambdas we replace InvokeCustom instruction with StaticGet reading the value of
- // INSTANCE field created for singleton lambda class.
- if (lambdaClass.isStateless()) {
- instructions.replaceCurrentInstruction(
- new StaticGet(lambdaInstanceValue, lambdaClass.lambdaField));
- // Note that since we replace one throwing operation with another we don't need
- // to have any special handling for catch handlers.
- return;
- }
-
- assert appView.options().testing.enableStatefulLambdaCreateInstanceMethod;
- // For stateful lambdas we call the createInstance method.
+ // For stateful lambdas we always create a new instance since we need to pass
+ // captured values to the constructor.
+ //
+ // We replace InvokeCustom instruction with a new NewInstance instruction
+ // instantiating lambda followed by InvokeDirect instruction calling a
+ // constructor on it.
//
// original:
// Invoke-Custom rResult <- { rArg0, rArg1, ... }; call site: ...
//
// result:
- // Invoke-Static rResult <- { rArg0, rArg1, ... }; method void
- // LambdaClass.createInstance(...)
- InvokeStatic invokeStatic =
- new InvokeStatic(
- lambdaClass.getCreateInstanceMethod(), lambdaInstanceValue, invoke.arguments());
- instructions.replaceCurrentInstruction(invokeStatic);
+ // NewInstance rResult <- LambdaClass
+ // Invoke-Direct { rResult, rArg0, rArg1, ... }; method: void LambdaClass.<init>(...)
+ lambdaInstanceValue.setTypeLattice(
+ lambdaInstanceValue.getTypeLattice().asReferenceTypeLatticeElement().asDefinitelyNotNull());
+ NewInstance newInstance = new NewInstance(lambdaClass.type, lambdaInstanceValue);
+ instructions.replaceCurrentInstruction(newInstance);
+
+ List<Value> arguments = new ArrayList<>();
+ arguments.add(lambdaInstanceValue);
+ arguments.addAll(invoke.arguments()); // Optional captures.
+ InvokeDirect constructorCall =
+ new InvokeDirect(lambdaClass.constructor, null /* no return value */, arguments);
+ instructions.add(constructorCall);
+ constructorCall.setPosition(newInstance.getPosition());
+
+ // If we don't have catch handlers we are done.
+ if (!constructorCall.getBlock().hasCatchHandlers()) {
+ return;
+ }
+
+ // Move the iterator back to position it between the two instructions, split
+ // the block between the two instructions, and copy the catch handlers.
+ instructions.previous();
+ assert instructions.peekNext().isInvokeDirect();
+ BasicBlock currentBlock = newInstance.getBlock();
+ BasicBlock nextBlock = instructions.split(code, blocks);
+ assert !instructions.hasNext();
+ nextBlock.copyCatchHandlers(code, blocks, currentBlock, appView.options());
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaSynthesizedCode.java
new file mode 100644
index 0000000..2565687
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaSynthesizedCode.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.synthetic.SynthesizedCode;
+import java.util.function.Consumer;
+
+abstract class LambdaSynthesizedCode extends SynthesizedCode {
+
+ final LambdaClass lambda;
+
+ LambdaSynthesizedCode(LambdaClass lambda) {
+ super(null);
+ this.lambda = lambda;
+ }
+
+ final DexItemFactory dexItemFactory() {
+ return lambda.rewriter.factory;
+ }
+
+ final LambdaDescriptor descriptor() {
+ return lambda.descriptor;
+ }
+
+ final DexType[] captures() {
+ DexTypeList captures = descriptor().captures;
+ assert captures != null;
+ return captures.values;
+ }
+
+ @Override
+ public abstract SourceCodeProvider getSourceCodeProvider();
+
+ @Override
+ public abstract Consumer<UseRegistry> getRegistryCallback();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 864099b..ddfb1c5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -4888,6 +4888,46 @@
ImmutableList.of());
}
+ public static CfCode StreamMethods_ofNullable(InternalOptions options, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ CfLabel label2 = new CfLabel();
+ CfLabel label3 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 1,
+ 1,
+ ImmutableList.of(
+ label0,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfIf(If.Type.NE, ValueType.OBJECT, label1),
+ new CfInvoke(
+ 184,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/util/stream/Stream;"),
+ options.itemFactory.createProto(
+ options.itemFactory.createType("Ljava/util/stream/Stream;")),
+ options.itemFactory.createString("empty")),
+ true),
+ new CfGoto(label2),
+ label1,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInvoke(
+ 184,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/util/stream/Stream;"),
+ options.itemFactory.createProto(
+ options.itemFactory.createType("Ljava/util/stream/Stream;"),
+ options.itemFactory.createType("Ljava/lang/Object;")),
+ options.itemFactory.createString("of")),
+ true),
+ label2,
+ new CfReturn(ValueType.OBJECT),
+ label3),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
public static CfCode StringMethods_joinArray(InternalOptions options, DexMethod method) {
CfLabel label0 = new CfLabel();
CfLabel label1 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Assumer.java b/src/main/java/com/android/tools/r8/ir/optimize/Assumer.java
index dfabec5..334a606 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Assumer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Assumer.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
-import com.android.tools.r8.Keep;
import com.android.tools.r8.ir.code.Assume;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
@@ -14,9 +13,6 @@
/**
* One that assumes. Inherited tracker/optimization insert necessary variants of {@link Assume}.
*/
-// TODO(b/143590191): should not need an explicit keep annotation to prevent the default interface
-// method from being shrunk.
-@Keep
public interface Assumer {
default void insertAssumeInstructions(IRCode code) {
insertAssumeInstructionsInBlocks(code, code.listIterator(), Predicates.alwaysTrue());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 49b11a5..e83a17a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -93,7 +93,7 @@
}
// If the resolution ended up with a single target, check if it is a library override.
// And if so, bail out early (to avoid expensive target lookup).
- if (resolutionResult.hasSingleTarget()
+ if (resolutionResult.isSingleResolution()
&& isLibraryMethodOrLibraryMethodOverride(resolutionResult.getSingleTarget())) {
continue;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 6cae242..8c07d3d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -6,7 +6,6 @@
import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -29,8 +28,6 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
@@ -138,9 +135,6 @@
&& optimizationInfo.getDynamicUpperBoundType().isDefinitelyNotNull()) {
knownToBeNonNullValues.add(outValue);
}
-
- assert verifyCompanionClassInstanceIsKnownToBeNonNull(
- fieldInstruction, encodedField, knownToBeNonNullValues);
}
}
}
@@ -246,33 +240,6 @@
}
}
- private boolean verifyCompanionClassInstanceIsKnownToBeNonNull(
- FieldInstruction instruction,
- DexEncodedField encodedField,
- Set<Value> knownToBeNonNullValues) {
- if (!appView.appInfo().hasLiveness()) {
- return true;
- }
- if (instruction.isStaticGet()) {
- AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
- DexField field = encodedField.field;
- DexClass clazz = appViewWithLiveness.definitionFor(field.holder);
- assert clazz != null;
- if (clazz.accessFlags.isFinal()
- && !clazz.initializationOfParentTypesMayHaveSideEffects(appViewWithLiveness)) {
- DexEncodedMethod classInitializer = clazz.getClassInitializer();
- if (classInitializer != null) {
- ClassInitializerInfo info =
- classInitializer.getOptimizationInfo().getClassInitializerInfo();
- boolean expectedToBeNonNull =
- info != null && info.field == field && !appViewWithLiveness.appInfo().isPinned(field);
- assert !expectedToBeNonNull || knownToBeNonNullValues.contains(instruction.outValue());
- }
- }
- }
- return true;
- }
-
private void addNonNullForValues(
IRCode code,
ListIterator<BasicBlock> blockIterator,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index ddb7e99..18c56e6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.InliningOracle;
+import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.IllegalClassInlinerStateException;
import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
import com.android.tools.r8.ir.optimize.string.StringOptimizer;
import com.android.tools.r8.logging.Log;
@@ -35,9 +36,11 @@
enum EligibilityStatus {
// Used by InlineCandidateProcessor#isInstanceEligible
- UNUSED_INSTANCE,
NON_CLASS_TYPE,
+ NOT_A_SINGLETON_FIELD,
+ RETRIEVAL_MAY_HAVE_SIDE_EFFECTS,
UNKNOWN_TYPE,
+ UNUSED_INSTANCE,
// Used by isClassEligible
NON_PROGRAM_CLASS,
@@ -235,7 +238,19 @@
}
// Inline the class instance.
- anyInlinedMethods |= processor.processInlining(code, defaultOracle, inliningIRProvider);
+ try {
+ anyInlinedMethods |= processor.processInlining(code, defaultOracle, inliningIRProvider);
+ } catch (IllegalClassInlinerStateException e) {
+ // We introduced a user that we cannot handle in the class inliner as a result of force
+ // inlining. Abort gracefully from class inlining without removing the instance.
+ //
+ // Alternatively we would need to collect additional information about the behavior of
+ // methods (which is bad for memory), or we would need to analyze the called methods
+ // before inlining them. The latter could be good solution, since we are going to build IR
+ // for the methods that need to be inlined anyway.
+ assert appView.options().testing.allowClassInlinerGracefulExit;
+ anyInlinedMethods = true;
+ }
assert inliningIRProvider.verifyIRCacheIsEmpty();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 436f81b..9afba98 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -11,10 +11,12 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.code.Assume;
import com.android.tools.r8.ir.code.BasicBlock;
@@ -29,6 +31,7 @@
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.optimize.Inliner;
@@ -37,9 +40,9 @@
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.InliningOracle;
import com.android.tools.r8.ir.optimize.classinliner.ClassInliner.EligibilityStatus;
+import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
@@ -136,9 +139,23 @@
if (eligibleInstance == null) {
return EligibilityStatus.UNUSED_INSTANCE;
}
-
- eligibleClass =
- root.isNewInstance() ? root.asNewInstance().clazz : root.asStaticGet().getField().type;
+ if (root.isNewInstance()) {
+ eligibleClass = root.asNewInstance().clazz;
+ } else {
+ assert root.isStaticGet();
+ StaticGet staticGet = root.asStaticGet();
+ if (staticGet.instructionMayHaveSideEffects(appView, method.method.holder)) {
+ return EligibilityStatus.RETRIEVAL_MAY_HAVE_SIDE_EFFECTS;
+ }
+ DexEncodedField field = appView.appInfo().resolveField(staticGet.getField());
+ FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
+ ClassTypeLatticeElement dynamicLowerBoundType = optimizationInfo.getDynamicLowerBoundType();
+ if (dynamicLowerBoundType == null
+ || !dynamicLowerBoundType.equals(optimizationInfo.getDynamicUpperBoundType())) {
+ return EligibilityStatus.NOT_A_SINGLETON_FIELD;
+ }
+ eligibleClass = dynamicLowerBoundType.getClassType();
+ }
if (!eligibleClass.isClassType()) {
return EligibilityStatus.NON_CLASS_TYPE;
}
@@ -180,11 +197,13 @@
// TrivialInstanceInitializer. This will be checked in areInstanceUsersEligible(...).
// There must be no static initializer on the class itself.
- if (eligibleClassDefinition.hasClassInitializer()) {
+ if (eligibleClassDefinition.classInitializationMayHaveSideEffects(
+ appView,
+ // Types that are a super type of the current context are guaranteed to be initialized.
+ type -> appView.isSubtype(method.method.holder, type).isTrue())) {
return EligibilityStatus.HAS_CLINIT;
- } else {
- return EligibilityStatus.ELIGIBLE;
}
+ return EligibilityStatus.ELIGIBLE;
}
assert root.isStaticGet();
@@ -244,34 +263,10 @@
// of class inlining
//
- if (eligibleClassDefinition.instanceFields().size() > 0) {
+ if (!eligibleClassDefinition.instanceFields().isEmpty()) {
return EligibilityStatus.HAS_INSTANCE_FIELDS;
}
- if (appView.appInfo().hasSubtypes(eligibleClassDefinition.type)) {
- assert !eligibleClassDefinition.accessFlags.isFinal();
- return EligibilityStatus.NON_FINAL_TYPE;
- }
-
- // Singleton instance must be initialized in class constructor.
- DexEncodedMethod classInitializer = eligibleClassDefinition.getClassInitializer();
- if (classInitializer == null || isProcessedConcurrently.test(classInitializer)) {
- return EligibilityStatus.NOT_INITIALIZED_AT_INIT;
- }
-
- ClassInitializerInfo initializerInfo =
- classInitializer.getOptimizationInfo().getClassInitializerInfo();
- DexField instanceField = root.asStaticGet().getField();
- // Singleton instance field must NOT be pinned.
- AppInfoWithLiveness appInfo = appView.appInfo();
- boolean notPinned =
- initializerInfo != null
- && initializerInfo.field == instanceField
- && !appInfo.isPinned(eligibleClassDefinition.lookupStaticField(instanceField).field);
- if (notPinned) {
- return EligibilityStatus.ELIGIBLE;
- } else {
- return EligibilityStatus.PINNED_FIELD;
- }
+ return EligibilityStatus.ELIGIBLE;
}
/**
@@ -340,6 +335,8 @@
continue;
}
}
+ assert !isExtraMethodCall(invoke);
+ return user; // Not eligible.
}
}
@@ -397,7 +394,8 @@
//
// Returns `true` if at least one method was inlined.
boolean processInlining(
- IRCode code, Supplier<InliningOracle> defaultOracle, InliningIRProvider inliningIRProvider) {
+ IRCode code, Supplier<InliningOracle> defaultOracle, InliningIRProvider inliningIRProvider)
+ throws IllegalClassInlinerStateException {
// Verify that `eligibleInstance` is not aliased.
assert eligibleInstance == eligibleInstance.getAliasedValue();
replaceUsagesAsUnusedArgument(code);
@@ -413,15 +411,7 @@
// Repeat user analysis
InstructionOrPhi ineligibleUser = areInstanceUsersEligible(defaultOracle);
if (ineligibleUser != null) {
- // We introduced a user that we cannot handle in the class inliner as a result of force
- // inlining. Abort gracefully from class inlining without removing the instance.
- //
- // Alternatively we would need to collect additional information about the behavior of
- // methods (which is bad for memory), or we would need to analyze the called methods before
- // inlining them. The latter could be good solution, since we are going to build IR for the
- // methods that need to be inlined anyway.
- assert appView.options().testing.allowClassInlinerGracefulExit;
- return true;
+ throw new IllegalClassInlinerStateException();
}
assert extraMethodCalls.isEmpty()
: "Remaining extra method calls: " + StringUtils.join(extraMethodCalls.entrySet(), ", ");
@@ -465,7 +455,7 @@
}
private boolean forceInlineDirectMethodInvocations(
- IRCode code, InliningIRProvider inliningIRProvider) {
+ IRCode code, InliningIRProvider inliningIRProvider) throws IllegalClassInlinerStateException {
if (methodCallsOnInstance.isEmpty()) {
return false;
}
@@ -485,20 +475,33 @@
for (Instruction instruction : eligibleInstance.uniqueUsers()) {
if (instruction.isInvokeDirect()) {
InvokeDirect invoke = instruction.asInvokeDirect();
- Value receiver = invoke.getReceiver();
- if (receiver == eligibleInstance) {
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (appView.dexItemFactory().isConstructor(invokedMethod)
- && invokedMethod != appView.dexItemFactory().objectMethods.constructor) {
- methodCallsOnInstance.put(
- invoke,
- new InliningInfo(
- appView.definitionFor(invokedMethod), root.asNewInstance().clazz));
- break;
- }
- } else {
- assert receiver.getAliasedValue() != eligibleInstance;
+ Value receiver = invoke.getReceiver().getAliasedValue();
+ if (receiver != eligibleInstance) {
+ continue;
}
+
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ if (invokedMethod == appView.dexItemFactory().objectMethods.constructor) {
+ continue;
+ }
+
+ if (!appView.dexItemFactory().isConstructor(invokedMethod)) {
+ throw new IllegalClassInlinerStateException();
+ }
+
+ DexEncodedMethod singleTarget = appView.definitionFor(invokedMethod);
+ if (singleTarget == null
+ || !singleTarget.isInliningCandidate(
+ method,
+ Reason.SIMPLE,
+ appView.appInfo(),
+ NopWhyAreYouNotInliningReporter.getInstance())) {
+ throw new IllegalClassInlinerStateException();
+ }
+
+ methodCallsOnInstance.put(
+ invoke, new InliningInfo(singleTarget, root.asNewInstance().clazz));
+ break;
}
}
if (!methodCallsOnInstance.isEmpty()) {
@@ -704,29 +707,25 @@
return new InliningInfo(singleTarget, eligibleClass);
}
- // If the superclass of the initializer is NOT java.lang.Object, the super class initializer
- // being called must be classified as TrivialInstanceInitializer.
- //
- // NOTE: since we already classified the class as eligible, it does not have
- // any class initializers in superclass chain or in superinterfaces, see
- // details in ClassInliner::computeClassEligible(...).
- if (eligibleClassDefinition.superType != appView.dexItemFactory().objectType) {
- DexClass superClass = appView.definitionFor(eligibleClassDefinition.superType);
- if (superClass == null || !superClass.isProgramClass()) {
+ // Check that the entire constructor chain can be inlined into the current context.
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ DexMethod parent = singleTarget.getOptimizationInfo().getInstanceInitializerInfo().getParent();
+ while (parent != dexItemFactory.objectMethods.constructor) {
+ if (parent == null) {
return null;
}
-
- // At this point, we don't know which constructor in the super type that is invoked from the
- // method. Therefore, we just check if all of the constructors in the super type are trivial.
- for (DexEncodedMethod method : superClass.directMethods()) {
- if (method.isInstanceInitializer()) {
- InstanceInitializerInfo initializerInfo =
- method.getOptimizationInfo().getInstanceInitializerInfo();
- if (initializerInfo.receiverMayEscapeOutsideConstructorChain()) {
- return null;
- }
- }
+ DexEncodedMethod encodedParent = appView.definitionFor(parent);
+ if (encodedParent == null) {
+ return null;
}
+ if (!encodedParent.isInliningCandidate(
+ method,
+ Reason.SIMPLE,
+ appView.appInfo(),
+ NopWhyAreYouNotInliningReporter.getInstance())) {
+ return null;
+ }
+ parent = encodedParent.getOptimizationInfo().getInstanceInitializerInfo().getParent();
}
return singleTarget.getOptimizationInfo().getClassInlinerEligibility() != null
@@ -857,7 +856,7 @@
// We should not inline a method if the invocation has type interface or virtual and the
// signature of the invocation resolves to a private or static method.
ResolutionResult resolutionResult = appView.appInfo().resolveMethod(callee.holder, callee);
- if (resolutionResult.hasSingleTarget()
+ if (resolutionResult.isSingleResolution()
&& !resolutionResult.getSingleTarget().isVirtualMethod()) {
return null;
}
@@ -869,13 +868,7 @@
return null; // Don't inline itself.
}
- if (isDesugaredLambda && !singleTarget.accessFlags.isBridge()) {
- markSizeForInlining(invoke, singleTarget);
- return new InliningInfo(singleTarget, eligibleClass);
- }
-
MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
-
ClassInlinerEligibilityInfo eligibility = optimizationInfo.getClassInlinerEligibility();
if (eligibility == null) {
return null;
@@ -1146,4 +1139,6 @@
instruction.inValues().forEach(v -> v.removeUser(instruction));
instruction.getBlock().removeInstruction(instruction);
}
+
+ static class IllegalClassInlinerStateException extends Exception {}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 7ecbf18..06a94a6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.google.common.collect.ImmutableSet;
@@ -83,11 +82,6 @@
}
@Override
- public ClassInitializerInfo getClassInitializerInfo() {
- return null;
- }
-
- @Override
public InstanceInitializerInfo getInstanceInitializerInfo() {
return DefaultInstanceInitializerInfo.getInstance();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index d9c4083..fd8078d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import java.util.BitSet;
import java.util.Set;
@@ -59,8 +58,6 @@
Set<DexType> getInitializedClassesOnNormalExit();
- ClassInitializerInfo getClassInitializerInfo();
-
InstanceInitializerInfo getInstanceInitializerInfo();
boolean isInitializerEnablingJavaAssertions();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index eac8791..614258b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -7,14 +7,24 @@
import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
+import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS;
import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER;
import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING;
import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING;
import static com.android.tools.r8.ir.code.Opcodes.GOTO;
+import static com.android.tools.r8.ir.code.Opcodes.IF;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_OF;
import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.NEW_INSTANCE;
import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
+import static com.android.tools.r8.ir.code.Opcodes.THROW;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
@@ -36,22 +46,23 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.DominatorTree;
+import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Return;
-import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerReceiverAnalysis;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder;
-import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.NonTrivialInstanceInitializerInfo;
import com.android.tools.r8.kotlin.Kotlin;
@@ -94,7 +105,7 @@
}
computeDynamicReturnType(dynamicTypeOptimization, feedback, method, code);
computeInitializedClassesOnNormalExit(feedback, method, code);
- computeInitializerInfo(method, code, feedback);
+ computeInstanceInitializerInfo(method, code, feedback);
computeMayHaveSideEffects(feedback, method, code);
computeReturnValueOnlyDependsOnArguments(feedback, method, code);
computeNonNullParamOrThrow(feedback, method, code);
@@ -157,8 +168,8 @@
}
}
DexField field = insn.asFieldInstruction().getField();
- if (field.holder == clazz.type && clazz.lookupInstanceField(field) != null) {
- // Require only accessing instance fields of the *current* class.
+ if (appView.appInfo().resolveFieldOn(clazz, field) != null) {
+ // Require only accessing direct or indirect instance fields of the current class.
continue;
}
return;
@@ -274,11 +285,11 @@
}
}
- private void computeInitializerInfo(
+ private void computeInstanceInitializerInfo(
DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
assert !appView.appInfo().isPinned(method.method);
- if (!method.isInitializer()) {
+ if (!method.isInstanceInitializer()) {
return;
}
@@ -296,90 +307,12 @@
return;
}
- feedback.setInitializerInfo(
+ InstanceInitializerInfo instanceInitializerInfo = analyzeInstanceInitializer(code, clazz);
+ feedback.setInstanceInitializerInfo(
method,
- method.isInstanceInitializer()
- ? computeInstanceInitializerInfo(code, clazz)
- : computeClassInitializerInfo(code, clazz));
- }
-
- // This method defines trivial class initializer as follows:
- //
- // ** The initializer may only instantiate an instance of the same class,
- // initialize it with a call to a trivial constructor *without* arguments,
- // and assign this instance to a static final field of the same class.
- //
- private ClassInitializerInfo computeClassInitializerInfo(IRCode code, DexClass clazz) {
- Value createdSingletonInstance = null;
- DexField singletonField = null;
- for (Instruction insn : code.instructions()) {
- if (insn.isConstNumber()) {
- continue;
- }
-
- if (insn.isConstString()) {
- if (insn.instructionInstanceCanThrow()) {
- return null;
- }
- continue;
- }
-
- if (insn.isReturn()) {
- continue;
- }
-
- if (insn.isAssume()) {
- continue;
- }
-
- if (insn.isNewInstance()) {
- NewInstance newInstance = insn.asNewInstance();
- if (createdSingletonInstance != null
- || newInstance.clazz != clazz.type
- || insn.outValue() == null) {
- return null;
- }
- createdSingletonInstance = insn.outValue();
- continue;
- }
-
- if (insn.isInvokeDirect()) {
- InvokeDirect invokedDirect = insn.asInvokeDirect();
- if (createdSingletonInstance == null
- || invokedDirect.getReceiver() != createdSingletonInstance) {
- return null;
- }
- DexEncodedMethod callTarget = clazz.lookupDirectMethod(invokedDirect.getInvokedMethod());
- if (callTarget == null
- || !callTarget.isInstanceInitializer()
- || !callTarget.method.proto.parameters.isEmpty()
- || callTarget.getOptimizationInfo().getInstanceInitializerInfo().isDefaultInfo()) {
- return null;
- }
- continue;
- }
-
- if (insn.isStaticPut()) {
- StaticPut staticPut = insn.asStaticPut();
- if (singletonField != null
- || createdSingletonInstance == null
- || staticPut.value() != createdSingletonInstance) {
- return null;
- }
- DexEncodedField field = clazz.lookupStaticField(staticPut.getField());
- if (field == null
- || !field.accessFlags.isStatic()
- || !field.accessFlags.isFinal()) {
- return null;
- }
- singletonField = field.field;
- continue;
- }
-
- // Other instructions make the class initializer not eligible.
- return null;
- }
- return singletonField == null ? null : new ClassInitializerInfo(singletonField);
+ instanceInitializerInfo != null
+ ? instanceInitializerInfo
+ : DefaultInstanceInitializerInfo.getInstance());
}
// This method defines trivial instance initializer as follows:
@@ -397,82 +330,190 @@
// ** Assigns arguments or non-throwing constants to fields of this class.
//
// (Note that this initializer does not have to have zero arguments.)
- private InstanceInitializerInfo computeInstanceInitializerInfo(IRCode code, DexClass clazz) {
+ private InstanceInitializerInfo analyzeInstanceInitializer(IRCode code, DexClass clazz) {
if (clazz.definesFinalizer(options.itemFactory)) {
// Defining a finalize method can observe the side-effect of Object.<init> GC registration.
return null;
}
+
NonTrivialInstanceInitializerInfo.Builder builder = NonTrivialInstanceInitializerInfo.builder();
Value receiver = code.getThis();
- for (Instruction instruction : code.instructions()) {
- switch (instruction.opcode()) {
- case ARGUMENT:
- case ASSUME:
- case CONST_NUMBER:
- case RETURN:
- break;
+ boolean hasCatchHandler = false;
+ for (BasicBlock block : code.blocks) {
+ if (block.hasCatchHandlers()) {
+ hasCatchHandler = true;
+ }
- case CONST_CLASS:
- case CONST_STRING:
- case DEX_ITEM_BASED_CONST_STRING:
- if (instruction.instructionMayTriggerMethodInvocation(appView, clazz.type)) {
- return null;
- }
- break;
+ for (Instruction instruction : block.getInstructions()) {
+ switch (instruction.opcode()) {
+ case ARGUMENT:
+ case ASSUME:
+ case CONST_NUMBER:
+ case GOTO:
+ case RETURN:
+ break;
- case GOTO:
- // Trivial goto to the next block.
- if (!instruction.asGoto().isTrivialGotoToTheNextBlock(code)) {
- return null;
- }
- break;
+ case IF:
+ builder.setInstanceFieldInitializationMayDependOnEnvironment();
+ break;
- case INSTANCE_PUT:
- {
- InstancePut instancePut = instruction.asInstancePut();
- DexEncodedField field = appView.appInfo().resolveFieldOn(clazz, instancePut.getField());
- if (field == null
- || instancePut.object() != receiver
- || (instancePut.value() != receiver && !instancePut.value().isArgument())) {
- return null;
+ case CHECK_CAST:
+ case CONST_CLASS:
+ case CONST_STRING:
+ case DEX_ITEM_BASED_CONST_STRING:
+ case INSTANCE_OF:
+ case THROW:
+ // These instructions types may raise an exception, which is a side effect. None of the
+ // instructions can trigger class initialization side effects, hence it is not necessary
+ // to mark all fields as potentially being read. Also, none of the instruction types
+ // can cause the receiver to escape.
+ if (instruction.instructionMayHaveSideEffects(appView, clazz.type)) {
+ builder.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
}
- }
- break;
+ break;
- case INVOKE_DIRECT:
- {
- InvokeDirect invoke = instruction.asInvokeDirect();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (!dexItemFactory.isConstructor(invokedMethod)) {
- return null;
- }
- if (invokedMethod.holder != clazz.superType) {
- return null;
- }
- // java.lang.Enum.<init>() and java.lang.Object.<init>() are considered trivial.
- if (invokedMethod == dexItemFactory.enumMethods.constructor
- || invokedMethod == dexItemFactory.objectMethods.constructor) {
- break;
- }
- DexEncodedMethod singleTarget = appView.definitionFor(invokedMethod);
- if (singleTarget == null
- || singleTarget.getOptimizationInfo().getInstanceInitializerInfo().isDefaultInfo()
- || invoke.getReceiver() != receiver) {
- return null;
- }
- for (Value value : invoke.inValues()) {
- if (value != receiver && !(value.isConstant() || value.isArgument())) {
+ case INSTANCE_GET:
+ case STATIC_GET:
+ {
+ FieldInstruction fieldGet = instruction.asFieldInstruction();
+ DexEncodedField field = appView.appInfo().resolveField(fieldGet.getField());
+ if (field == null) {
return null;
}
+ builder.markFieldAsRead(field);
+ if (fieldGet.instructionMayHaveSideEffects(appView, clazz.type)) {
+ builder.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+ if (fieldGet.isStaticGet()) {
+ // It could trigger a class initializer.
+ builder.markAllFieldsAsRead();
+ }
+ }
}
- }
- break;
+ break;
- default:
- // Other instructions make the instance initializer not eligible.
- return null;
+ case INSTANCE_PUT:
+ {
+ InstancePut instancePut = instruction.asInstancePut();
+ DexEncodedField field = appView.appInfo().resolveField(instancePut.getField());
+ if (field == null) {
+ return null;
+ }
+ if (instancePut.object().getAliasedValue() != receiver
+ || instancePut.instructionInstanceCanThrow(appView, clazz.type).isThrowing()) {
+ builder.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+ }
+
+ Value value = instancePut.value().getAliasedValue();
+ // TODO(b/142762134): Replace the use of onlyDependsOnArgument() by
+ // ValueMayDependOnEnvironmentAnalysis.
+ if (!value.onlyDependsOnArgument()) {
+ builder.setInstanceFieldInitializationMayDependOnEnvironment();
+ }
+ if (value == receiver) {
+ builder.setReceiverMayEscapeOutsideConstructorChain();
+ }
+ }
+ break;
+
+ case INVOKE_DIRECT:
+ {
+ InvokeDirect invoke = instruction.asInvokeDirect();
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ DexEncodedMethod singleTarget = appView.definitionFor(invokedMethod);
+ if (singleTarget == null) {
+ return null;
+ }
+ if (singleTarget.isInstanceInitializer() && invoke.getReceiver() == receiver) {
+ if (builder.hasParent()) {
+ return null;
+ }
+ // java.lang.Enum.<init>() and java.lang.Object.<init>() are considered trivial.
+ if (invokedMethod == dexItemFactory.enumMethods.constructor
+ || invokedMethod == dexItemFactory.objectMethods.constructor) {
+ builder.setParent(invokedMethod);
+ break;
+ }
+ builder.merge(singleTarget.getOptimizationInfo().getInstanceInitializerInfo());
+ for (int i = 1; i < invoke.arguments().size(); i++) {
+ Value argument = invoke.arguments().get(i).getAliasedValue();
+ if (argument == receiver) {
+ // In the analysis of the parent constructor, we don't consider the non-receiver
+ // arguments as being aliases of the receiver. Therefore, we explicitly mark
+ // that the receiver escapes from this constructor.
+ builder.setReceiverMayEscapeOutsideConstructorChain();
+ }
+ if (!argument.onlyDependsOnArgument()) {
+ // If the parent constructor assigns this argument into a field, then the value
+ // of the field may depend on the environment.
+ builder.setInstanceFieldInitializationMayDependOnEnvironment();
+ }
+ }
+ builder.setParent(invokedMethod);
+ } else {
+ builder
+ .markAllFieldsAsRead()
+ .setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+ for (Value inValue : invoke.inValues()) {
+ if (inValue.getAliasedValue() == receiver) {
+ builder.setReceiverMayEscapeOutsideConstructorChain();
+ break;
+ }
+ }
+ }
+ }
+ break;
+
+ case INVOKE_INTERFACE:
+ case INVOKE_STATIC:
+ case INVOKE_VIRTUAL:
+ InvokeMethod invoke = instruction.asInvokeMethod();
+ builder.markAllFieldsAsRead().setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+ for (Value inValue : invoke.inValues()) {
+ if (inValue.getAliasedValue() == receiver) {
+ builder.setReceiverMayEscapeOutsideConstructorChain();
+ break;
+ }
+ }
+ break;
+
+ case NEW_INSTANCE:
+ {
+ NewInstance newInstance = instruction.asNewInstance();
+ if (newInstance.instructionMayHaveSideEffects(appView, clazz.type)) {
+ // It could trigger a class initializer.
+ builder
+ .markAllFieldsAsRead()
+ .setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+ }
+ }
+ break;
+
+ default:
+ builder
+ .markAllFieldsAsRead()
+ .setInstanceFieldInitializationMayDependOnEnvironment()
+ .setMayHaveOtherSideEffectsThanInstanceFieldAssignments()
+ .setReceiverMayEscapeOutsideConstructorChain();
+ break;
+ }
}
}
+
+ // In presence of exceptional control flow, the assignments to the instance fields could depend
+ // on the environment, if there is an instruction that could throw.
+ //
+ // Example:
+ // void <init>() {
+ // try {
+ // throwIfTrue(Environment.STATIC_FIELD);
+ // } catch (Exception e) {
+ // this.f = 42;
+ // }
+ // }
+ if (hasCatchHandler && builder.mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
+ builder.setInstanceFieldInitializationMayDependOnEnvironment();
+ }
+
return builder.build();
}
@@ -780,7 +821,7 @@
if (!options.enableSideEffectAnalysis) {
return;
}
- if (appView.appInfo().withLiveness().mayHaveSideEffects.containsKey(method.method)) {
+ if (appView.appInfo().mayHaveSideEffects.containsKey(method.method)) {
return;
}
DexType context = method.method.holder;
@@ -794,6 +835,11 @@
feedback.classInitializerMayBePostponed(method);
} else if (classInitializerSideEffect.canBePostponed()) {
feedback.classInitializerMayBePostponed(method);
+ } else {
+ assert !context.isD8R8SynthesizedLambdaClassType()
+ || options.debug
+ || appView.appInfo().hasPinnedInstanceInitializer(context)
+ : "Unexpected observable side effects from lambda `" + context.toSourceString() + "`";
}
return;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index a0828e9..330eba6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.IteratorUtils;
import com.android.tools.r8.utils.StringUtils;
@@ -233,8 +233,10 @@
}
@Override
- public synchronized void setInitializerInfo(DexEncodedMethod method, InitializerInfo info) {
- getMethodOptimizationInfoForUpdating(method).setInitializerInfo(info);
+ public synchronized void setInstanceInitializerInfo(
+ DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo) {
+ getMethodOptimizationInfoForUpdating(method)
+ .setInstanceInitializerInfo(instanceInitializerInfo);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index e106dc8..2495e58 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.BitSet;
import java.util.Set;
@@ -112,7 +112,8 @@
DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {}
@Override
- public void setInitializerInfo(DexEncodedMethod method, InitializerInfo info) {}
+ public void setInstanceInitializerInfo(
+ DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo) {}
@Override
public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 0178a2d..34a1fb8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.BitSet;
import java.util.Set;
@@ -161,7 +161,8 @@
}
@Override
- public void setInitializerInfo(DexEncodedMethod method, InitializerInfo info) {
+ public void setInstanceInitializerInfo(
+ DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo) {
// Ignored.
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index 74dd886..d599a38 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -12,9 +12,7 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import java.util.BitSet;
import java.util.Set;
@@ -51,7 +49,8 @@
// class inliner, null value indicates that the method is not eligible.
private ClassInlinerEligibilityInfo classInlinerEligibility =
DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY;
- private InitializerInfo initializerInfo = null;
+ private InstanceInitializerInfo instanceInitializerInfo =
+ DefaultInstanceInitializerInfo.getInstance();
private boolean initializerEnablingJavaAssertions =
DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
private ParameterUsagesInfo parametersUsages =
@@ -101,7 +100,7 @@
checksNullReceiverBeforeAnySideEffect = template.checksNullReceiverBeforeAnySideEffect;
triggersClassInitBeforeAnySideEffect = template.triggersClassInitBeforeAnySideEffect;
classInlinerEligibility = template.classInlinerEligibility;
- initializerInfo = template.initializerInfo;
+ instanceInitializerInfo = template.instanceInitializerInfo;
initializerEnablingJavaAssertions = template.initializerEnablingJavaAssertions;
parametersUsages = template.parametersUsages;
nonNullParamOrThrow = template.nonNullParamOrThrow;
@@ -172,16 +171,8 @@
}
@Override
- public ClassInitializerInfo getClassInitializerInfo() {
- return initializerInfo != null ? initializerInfo.asClassInitializerInfo() : null;
- }
-
- @Override
public InstanceInitializerInfo getInstanceInitializerInfo() {
- if (initializerInfo != null) {
- return initializerInfo.asInstanceInitializerInfo();
- }
- return DefaultInstanceInitializerInfo.getInstance();
+ return instanceInitializerInfo;
}
@Override
@@ -304,8 +295,8 @@
this.classInlinerEligibility = eligibility;
}
- void setInitializerInfo(InitializerInfo initializerInfo) {
- this.initializerInfo = initializerInfo;
+ void setInstanceInitializerInfo(InstanceInitializerInfo instanceInitializerInfo) {
+ this.instanceInitializerInfo = instanceInitializerInfo;
}
void setInitializerEnablingJavaAssertions() {
@@ -458,7 +449,7 @@
// classInlinerEligibility: chances are the method is not an instance method anymore.
classInlinerEligibility = DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY;
// initializerInfo: the computed initializer info may become invalid.
- initializerInfo = null;
+ instanceInitializerInfo = null;
// initializerEnablingJavaAssertions: `this` could trigger <clinit> of the previous holder.
initializerEnablingJavaAssertions =
DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ClassInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ClassInitializerInfo.java
deleted file mode 100644
index 01d1bb0..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ClassInitializerInfo.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.optimize.info.initializer;
-
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoCollector;
-
-/**
- * Defines class trivial initialized, see details in comments {@link
- * MethodOptimizationInfoCollector#computeClassInitializerInfo}.
- */
-public final class ClassInitializerInfo extends InitializerInfo {
-
- public final DexField field;
-
- public ClassInitializerInfo(DexField field) {
- this.field = field;
- }
-
- @Override
- public ClassInitializerInfo asClassInitializerInfo() {
- return this;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
index e93cda7..ef3ca08 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize.info.initializer;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
@@ -24,6 +25,11 @@
}
@Override
+ public DexMethod getParent() {
+ return null;
+ }
+
+ @Override
public AbstractFieldSet readSet() {
return UnknownFieldSet.getInstance();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InitializerInfo.java
deleted file mode 100644
index a4a2ad9..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InitializerInfo.java
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.optimize.info.initializer;
-
-public class InitializerInfo {
-
- InitializerInfo() {}
-
- public ClassInitializerInfo asClassInitializerInfo() {
- return null;
- }
-
- public InstanceInitializerInfo asInstanceInitializerInfo() {
- return null;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
index 24cb9af..5452bce 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
@@ -4,9 +4,12 @@
package com.android.tools.r8.ir.optimize.info.initializer;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
-public abstract class InstanceInitializerInfo extends InitializerInfo {
+public abstract class InstanceInitializerInfo {
+
+ public abstract DexMethod getParent();
/**
* Returns an abstraction of the set of fields that may be as a result of executing this
@@ -50,9 +53,4 @@
public boolean isDefaultInfo() {
return false;
}
-
- @Override
- public InstanceInitializerInfo asInstanceInitializerInfo() {
- return this;
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
index 7802e12..70017cc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.optimize.info.initializer;
import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.ConcreteMutableFieldSet;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
@@ -18,11 +19,13 @@
private final int data;
private final AbstractFieldSet readSet;
+ private final DexMethod parent;
- private NonTrivialInstanceInitializerInfo(int data, AbstractFieldSet readSet) {
+ private NonTrivialInstanceInitializerInfo(int data, AbstractFieldSet readSet, DexMethod parent) {
assert verifyNoUnknownBits(data);
this.data = data;
this.readSet = readSet;
+ this.parent = parent;
}
private static boolean verifyNoUnknownBits(int data) {
@@ -39,6 +42,11 @@
}
@Override
+ public DexMethod getParent() {
+ return parent;
+ }
+
+ @Override
public AbstractFieldSet readSet() {
return readSet;
}
@@ -65,9 +73,10 @@
| NO_OTHER_SIDE_EFFECTS_THAN_INSTANCE_FIELD_ASSIGNMENTS
| RECEIVER_NEVER_ESCAPE_OUTSIDE_CONSTRUCTOR_CHAIN;
private AbstractFieldSet readSet = EmptyFieldSet.getInstance();
+ private DexMethod parent;
private boolean isTrivial() {
- return data == 0 && readSet.isTop();
+ return data == 0 && readSet.isTop() && parent == null;
}
public Builder markFieldAsRead(DexEncodedField field) {
@@ -82,16 +91,50 @@
return this;
}
+ public Builder markFieldsAsRead(AbstractFieldSet otherReadSet) {
+ if (readSet.isTop() || otherReadSet.isBottom()) {
+ return this;
+ }
+ if (otherReadSet.isTop()) {
+ return markAllFieldsAsRead();
+ }
+ ConcreteMutableFieldSet otherConcreteReadSet = otherReadSet.asConcreteFieldSet();
+ if (readSet.isBottom()) {
+ readSet = new ConcreteMutableFieldSet().addAll(otherConcreteReadSet);
+ } else {
+ readSet.asConcreteFieldSet().addAll(otherConcreteReadSet);
+ }
+ return this;
+ }
+
public Builder markAllFieldsAsRead() {
readSet = UnknownFieldSet.getInstance();
return this;
}
+ public Builder merge(InstanceInitializerInfo instanceInitializerInfo) {
+ markFieldsAsRead(instanceInitializerInfo.readSet());
+ if (instanceInitializerInfo.instanceFieldInitializationMayDependOnEnvironment()) {
+ setInstanceFieldInitializationMayDependOnEnvironment();
+ }
+ if (instanceInitializerInfo.mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
+ setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+ }
+ if (instanceInitializerInfo.receiverMayEscapeOutsideConstructorChain()) {
+ setReceiverMayEscapeOutsideConstructorChain();
+ }
+ return this;
+ }
+
public Builder setInstanceFieldInitializationMayDependOnEnvironment() {
data &= ~INSTANCE_FIELD_INITIALIZATION_INDEPENDENT_OF_ENVIRONMENT;
return this;
}
+ public boolean mayHaveOtherSideEffectsThanInstanceFieldAssignments() {
+ return (data & ~NO_OTHER_SIDE_EFFECTS_THAN_INSTANCE_FIELD_ASSIGNMENTS) == 0;
+ }
+
public Builder setMayHaveOtherSideEffectsThanInstanceFieldAssignments() {
data &= ~NO_OTHER_SIDE_EFFECTS_THAN_INSTANCE_FIELD_ASSIGNMENTS;
return this;
@@ -102,10 +145,20 @@
return this;
}
+ public boolean hasParent() {
+ return parent != null;
+ }
+
+ public Builder setParent(DexMethod parent) {
+ assert !hasParent();
+ this.parent = parent;
+ return this;
+ }
+
public InstanceInitializerInfo build() {
return isTrivial()
? DefaultInstanceInitializerInfo.getInstance()
- : new NonTrivialInstanceInitializerInfo(data, readSet);
+ : new NonTrivialInstanceInitializerInfo(data, readSet, parent);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index acb6c55..9f77f7a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -4,17 +4,16 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmType;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Box;
-import com.android.tools.r8.utils.DescriptorUtils;
-import java.util.ListIterator;
+import java.util.List;
import kotlinx.metadata.KmClass;
import kotlinx.metadata.KmType;
-import kotlinx.metadata.KmTypeVisitor;
import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -42,35 +41,18 @@
@Override
void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
- ListIterator<KmType> superTypeIterator = kmClass.getSupertypes().listIterator();
- while (superTypeIterator.hasNext()) {
- KmType kmType = superTypeIterator.next();
- Box<Boolean> isLive = new Box<>(false);
- Box<DexType> renamed = new Box<>(null);
- kmType.accept(new KmTypeVisitor() {
- @Override
- public void visitClass(String name) {
- String descriptor = DescriptorUtils.getDescriptorFromKotlinClassifier(name);
- DexType type = appView.dexItemFactory().createType(descriptor);
- isLive.set(appView.appInfo().isLiveProgramType(type));
- DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
- if (renamedType != type) {
- renamed.set(renamedType);
- }
- }
- });
- if (!isLive.get()) {
- superTypeIterator.remove();
- continue;
+ List<KmType> superTypes = kmClass.getSupertypes();
+ superTypes.clear();
+ for (DexType itfType : clazz.interfaces.values) {
+ KmType kmType = toRenamedKmType(itfType, appView, lens);
+ if (kmType != null) {
+ superTypes.add(kmType);
}
- if (renamed.get() != null) {
- // TODO(b/70169921): need a general util to convert the current clazz's access flag.
- KmType renamedKmType = new KmType(kmType.getFlags());
- renamedKmType.visitClass(
- DescriptorUtils.descriptorToInternalName(renamed.get().toDescriptorString()));
- superTypeIterator.remove();
- superTypeIterator.add(renamedKmType);
- }
+ }
+ assert clazz.superType != null;
+ KmType kmTypeForSupertype = toRenamedKmType(clazz.superType, appView, lens);
+ if (kmTypeForSupertype != null) {
+ superTypes.add(kmTypeForSupertype);
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
index 6429a50..a24055e 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import kotlinx.metadata.jvm.KotlinClassHeader;
@@ -13,15 +14,16 @@
public final class KotlinClassFacade extends KotlinInfo<KotlinClassMetadata.MultiFileClassFacade> {
- static KotlinClassFacade fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) {
+ static KotlinClassFacade fromKotlinClassMetadata(
+ KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
assert kotlinClassMetadata instanceof KotlinClassMetadata.MultiFileClassFacade;
KotlinClassMetadata.MultiFileClassFacade multiFileClassFacade =
(KotlinClassMetadata.MultiFileClassFacade) kotlinClassMetadata;
- return new KotlinClassFacade(multiFileClassFacade);
+ return new KotlinClassFacade(multiFileClassFacade, clazz);
}
- private KotlinClassFacade(KotlinClassMetadata.MultiFileClassFacade metadata) {
- super(metadata);
+ private KotlinClassFacade(KotlinClassMetadata.MultiFileClassFacade metadata, DexClass clazz) {
+ super(metadata, clazz);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index dc3161c..c05b48f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueArray;
@@ -30,7 +31,7 @@
DexAnnotation meta = clazz.annotations.getFirstMatching(kotlin.metadata.kotlinMetadataType);
if (meta != null) {
try {
- return createKotlinInfo(kotlin, clazz, meta);
+ return createKotlinInfo(kotlin, clazz, meta.annotation);
} catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) {
reporter.info(
new StringDiagnostic("Class " + clazz.type.toSourceString()
@@ -44,12 +45,10 @@
return null;
}
- private static KotlinInfo createKotlinInfo(
- Kotlin kotlin,
- DexClass clazz,
- DexAnnotation meta) {
+ private static KotlinClassMetadata toKotlinClassMetadata(
+ Kotlin kotlin, DexEncodedAnnotation metadataAnnotation) {
Map<DexString, DexAnnotationElement> elementMap = new IdentityHashMap<>();
- for (DexAnnotationElement element : meta.annotation.elements) {
+ for (DexAnnotationElement element : metadataAnnotation.elements) {
elementMap.put(element.name, element);
}
@@ -74,20 +73,25 @@
Integer xi = extraInt == null ? null : (Integer) extraInt.value.getBoxedValue();
KotlinClassHeader header = new KotlinClassHeader(k, mv, bv, d1, d2, xs, pn, xi);
- KotlinClassMetadata kMetadata = KotlinClassMetadata.read(header);
+ return KotlinClassMetadata.read(header);
+ }
+
+ private static KotlinInfo createKotlinInfo(
+ Kotlin kotlin, DexClass clazz, DexEncodedAnnotation metadataAnnotation) {
+ KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, metadataAnnotation);
if (kMetadata instanceof KotlinClassMetadata.Class) {
return KotlinClass.fromKotlinClassMetadata(kMetadata, clazz);
} else if (kMetadata instanceof KotlinClassMetadata.FileFacade) {
return KotlinFile.fromKotlinClassMetadata(kMetadata, clazz);
} else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
- return KotlinClassFacade.fromKotlinClassMetadata(kMetadata);
+ return KotlinClassFacade.fromKotlinClassMetadata(kMetadata, clazz);
} else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) {
- return KotlinClassPart.fromKotlinClassMetadata(kMetadata);
+ return KotlinClassPart.fromKotlinClassMetadata(kMetadata, clazz);
} else if (kMetadata instanceof KotlinClassMetadata.SyntheticClass) {
return KotlinSyntheticClass.fromKotlinClassMetadata(kMetadata, kotlin, clazz);
} else {
- throw new MetadataError("unsupported 'k' value: " + k);
+ throw new MetadataError("unsupported 'k' value: " + kMetadata.getHeader().getKind());
}
}
@@ -129,5 +133,4 @@
super(cause);
}
}
-
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index 96c1022..2b1f9bc 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.kotlin;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import kotlinx.metadata.KmPackage;
@@ -15,15 +16,16 @@
private KmPackage kmPackage;
- static KotlinClassPart fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) {
+ static KotlinClassPart fromKotlinClassMetadata(
+ KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
assert kotlinClassMetadata instanceof KotlinClassMetadata.MultiFileClassPart;
KotlinClassMetadata.MultiFileClassPart multiFileClassPart =
(KotlinClassMetadata.MultiFileClassPart) kotlinClassMetadata;
- return new KotlinClassPart(multiFileClassPart);
+ return new KotlinClassPart(multiFileClassPart, clazz);
}
- private KotlinClassPart(KotlinClassMetadata.MultiFileClassPart metadata) {
- super(metadata);
+ private KotlinClassPart(KotlinClassMetadata.MultiFileClassPart metadata, DexClass clazz) {
+ super(metadata, clazz);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index ac69eff..bd8d4c0 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -17,11 +17,8 @@
final DexClass clazz;
boolean isProcessed;
- KotlinInfo(MetadataKind metadata) {
- this(metadata, null);
- }
-
KotlinInfo(MetadataKind metadata, DexClass clazz) {
+ assert clazz != null;
this.metadata = metadata;
this.clazz = clazz;
processMetadata();
@@ -83,6 +80,7 @@
@Override
public String toString() {
- return clazz.toSourceString() + ": " + metadata.toString();
+ return (clazz != null ? clazz.toSourceString() : "<null class?!>")
+ + ": " + metadata.toString();
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
new file mode 100644
index 0000000..0b061f3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -0,0 +1,37 @@
+// 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.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import kotlinx.metadata.KmType;
+
+class KotlinMetadataSynthesizer {
+ static KmType toRenamedKmType(
+ DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ DexClass clazz = appView.definitionFor(type);
+ if (clazz == null) {
+ return null;
+ }
+ // For library or classpath class, synthesize @Metadata always.
+ if (clazz.isNotProgramClass()) {
+ KmType kmType = new KmType(clazz.accessFlags.getAsCfAccessFlags());
+ assert type == lens.lookupType(type, appView.dexItemFactory());
+ kmType.visitClass(DescriptorUtils.descriptorToInternalName(type.toDescriptorString()));
+ return kmType;
+ }
+ // From now on, it is a program class. First, make sure it is live.
+ if (!appView.appInfo().isLiveProgramType(type)) {
+ return null;
+ }
+ KmType kmType = new KmType(clazz.accessFlags.getAsCfAccessFlags());
+ DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
+ kmType.visitClass(DescriptorUtils.descriptorToInternalName(renamedType.toDescriptorString()));
+ return kmType;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
index fcf1453..a0d4df2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
@@ -152,7 +152,7 @@
"Unexpected EOF - no debug line positions");
}
// Iterate over the debug line number positions:
- // <from>#<file>,<to>:<debug-line-position>
+ // <from>#<file>,<range>:<debug-line-position>
// or
// <from>#<file>:<debug-line-position>
reader.readUntil(
@@ -223,18 +223,18 @@
// The range may have a different end than start.
String fileAndEndRange = original.substring(fileIndexSplit + 1);
int endRangeCharPosition = fileAndEndRange.indexOf(',');
- int originalEnd = originalStart;
+ int size = originalStart;
if (endRangeCharPosition > -1) {
// The file should be at least one number wide.
assert endRangeCharPosition > 0;
- originalEnd = Integer.parseInt(fileAndEndRange.substring(endRangeCharPosition + 1));
+ size = Integer.parseInt(fileAndEndRange.substring(endRangeCharPosition + 1));
} else {
endRangeCharPosition = fileAndEndRange.length();
}
int fileIndex = Integer.parseInt(fileAndEndRange.substring(0, endRangeCharPosition));
Source thisFileSource = builder.files.get(fileIndex);
if (thisFileSource != null) {
- Range range = new Range(originalStart, originalEnd);
+ Range range = new Range(originalStart, originalStart + size);
Position position = new Position(thisFileSource, range);
Position existingPosition = builder.positions.put(target, position);
assert existingPosition == null
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 1a1d4ef..147c251 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -419,6 +419,16 @@
}
}
+ public int getFirstLineNumberOfOriginalRange() {
+ if (originalRange == null) {
+ return 0;
+ } else if (originalRange instanceof Integer) {
+ return (int) originalRange;
+ } else {
+ return ((Range) originalRange).from;
+ }
+ }
+
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index 95fbcc8..84f726d 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -124,6 +124,16 @@
return name.indexOf(JAVA_PACKAGE_SEPARATOR) != -1;
}
+ public String toUnqualifiedName() {
+ assert isQualified();
+ return name.substring(name.lastIndexOf(JAVA_PACKAGE_SEPARATOR) + 1);
+ }
+
+ public String toHolderFromQualified() {
+ assert isQualified();
+ return name.substring(0, name.lastIndexOf(JAVA_PACKAGE_SEPARATOR));
+ }
+
public boolean isMethodSignature() {
return false;
}
@@ -286,16 +296,6 @@
return new MethodSignature(toUnqualifiedName(), type, parameters);
}
- public String toUnqualifiedName() {
- assert isQualified();
- return name.substring(name.lastIndexOf(JAVA_PACKAGE_SEPARATOR) + 1);
- }
-
- public String toHolderFromQualified() {
- assert isQualified();
- return name.substring(0, name.lastIndexOf(JAVA_PACKAGE_SEPARATOR));
- }
-
public DexMethod toDexMethod(DexItemFactory factory, DexType clazz) {
DexType[] paramTypes = new DexType[parameters.length];
for (int i = 0; i < parameters.length; i++) {
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index 7d93488..8ea3d93 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -100,7 +100,7 @@
}
// If the method does not have a direct renaming, return the resolutions mapping.
ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
- if (resolutionResult.hasSingleTarget()) {
+ if (resolutionResult.isSingleResolution()) {
return renaming.getOrDefault(resolutionResult.getSingleTarget().method, method.name);
}
// If resolution fails, the method must be renamed consistently with the targets that give rise
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 92df84f..fab5f50 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -262,11 +262,10 @@
DexType type, Map<DexReference, MemberNaming> nonPrivateMembers, DexType[] interfaces) {
for (DexType iface : interfaces) {
ClassNamingForMapApplier interfaceNaming = seedMapper.getClassNaming(iface);
- if (interfaceNaming == null) {
- continue;
+ if (interfaceNaming != null) {
+ interfaceNaming.forAllMemberNaming(
+ memberNaming -> addMemberNamings(type, memberNaming, nonPrivateMembers, true));
}
- interfaceNaming.forAllMemberNaming(
- memberNaming -> addMemberNamings(type, memberNaming, nonPrivateMembers, true));
DexClass ifaceClass = appView.definitionFor(iface);
if (ifaceClass != null) {
addNonPrivateInterfaceMappings(type, nonPrivateMembers, ifaceClass.interfaces.values);
diff --git a/src/main/java/com/android/tools/r8/naming/Range.java b/src/main/java/com/android/tools/r8/naming/Range.java
index 231a143..c91ae99 100644
--- a/src/main/java/com/android/tools/r8/naming/Range.java
+++ b/src/main/java/com/android/tools/r8/naming/Range.java
@@ -12,6 +12,7 @@
public Range(int from, int to) {
this.from = from;
this.to = to;
+ // TODO(b/145897713): Seems like we should be able to assert from <= to.
}
public boolean contains(int value) {
diff --git a/src/main/java/com/android/tools/r8/retrace/Result.java b/src/main/java/com/android/tools/r8/retrace/Result.java
index 2940b12..12e92d3 100644
--- a/src/main/java/com/android/tools/r8/retrace/Result.java
+++ b/src/main/java/com/android/tools/r8/retrace/Result.java
@@ -6,9 +6,12 @@
import com.android.tools.r8.Keep;
import java.util.function.Consumer;
+import java.util.stream.Stream;
@Keep
public abstract class Result<R, RR extends Result<R, RR>> {
+ public abstract Stream<R> stream();
+
public abstract RR forEach(Consumer<R> resultConsumer);
}
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 b485868..79d9721 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.retrace.RetraceCommand.Builder;
import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
-import com.android.tools.r8.retrace.RetraceStackTrace.RetraceResult;
import com.android.tools.r8.utils.OptionsParsing;
import com.android.tools.r8.utils.OptionsParsing.ParseContext;
import com.android.tools.r8.utils.StringDiagnostic;
@@ -117,11 +116,22 @@
try {
ClassNameMapper classNameMapper =
ClassNameMapper.mapperFromString(command.proguardMapProducer.get());
- RetraceBase retraceBase = new RetraceBaseImpl(classNameMapper);
- RetraceResult result =
- new RetraceStackTrace(retraceBase, command.stackTrace, command.diagnosticsHandler)
- .retrace();
- command.retracedStackTraceConsumer.accept(result.toListOfStrings());
+ RetraceBase retraceBase = RetraceBaseImpl.create(classNameMapper);
+ RetraceCommandLineResult result;
+ if (command.regularExpression != null) {
+ result =
+ new RetraceRegularExpression(
+ retraceBase,
+ command.stackTrace,
+ command.diagnosticsHandler,
+ command.regularExpression)
+ .retrace();
+ } else {
+ result =
+ new RetraceStackTrace(retraceBase, command.stackTrace, command.diagnosticsHandler)
+ .retrace();
+ }
+ command.retracedStackTraceConsumer.accept(result.getNodes());
} catch (IOException ex) {
command.diagnosticsHandler.error(
new StringDiagnostic("Could not open mapping input stream: " + ex.getMessage()));
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java b/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
index c60cd91..9a68706 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
@@ -21,10 +21,14 @@
private final ClassNameMapper classNameMapper;
- RetraceBaseImpl(ClassNameMapper classNameMapper) {
+ private RetraceBaseImpl(ClassNameMapper classNameMapper) {
this.classNameMapper = classNameMapper;
}
+ public static RetraceBase create(ClassNameMapper classNameMapper) {
+ return new RetraceBaseImpl(classNameMapper);
+ }
+
@Override
public RetraceMethodResult retrace(MethodReference methodReference) {
return retrace(methodReference.getHolderClass()).lookupMethod(methodReference.getMethodName());
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
index ee21fd2..25505fe 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
@@ -80,7 +80,8 @@
return mapper != null;
}
- Stream<Element> stream() {
+ @Override
+ public Stream<Element> stream() {
return Stream.of(
new Element(
this,
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
index 5385b09..b2692a2 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
@@ -13,6 +13,7 @@
public class RetraceCommand {
final boolean isVerbose;
+ final String regularExpression;
final DiagnosticsHandler diagnosticsHandler;
final ProguardMapProducer proguardMapProducer;
final List<String> stackTrace;
@@ -20,11 +21,13 @@
private RetraceCommand(
boolean isVerbose,
+ String regularExpression,
DiagnosticsHandler diagnosticsHandler,
ProguardMapProducer proguardMapProducer,
List<String> stackTrace,
Consumer<List<String>> retracedStackTraceConsumer) {
this.isVerbose = isVerbose;
+ this.regularExpression = regularExpression;
this.diagnosticsHandler = diagnosticsHandler;
this.proguardMapProducer = proguardMapProducer;
this.stackTrace = stackTrace;
@@ -55,6 +58,7 @@
private boolean isVerbose;
private DiagnosticsHandler diagnosticsHandler;
private ProguardMapProducer proguardMapProducer;
+ private String regularExpression;
private List<String> stackTrace;
private Consumer<List<String>> retracedStackTraceConsumer;
@@ -79,6 +83,17 @@
}
/**
+ * Set a regular expression for parsing the incoming text. The Regular expression must not use
+ * naming groups and has special wild cards according to proguard retrace.
+ *
+ * @param regularExpression The regular expression to use.
+ */
+ public Builder setRegularExpression(String regularExpression) {
+ this.regularExpression = regularExpression;
+ return this;
+ }
+
+ /**
* Set the obfuscated stack trace that is to be retraced.
*
* @param stackTrace Stack trace having the top entry(the closest stack to the error) as the
@@ -114,6 +129,7 @@
}
return new RetraceCommand(
isVerbose,
+ regularExpression,
diagnosticsHandler,
proguardMapProducer,
stackTrace,
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceCommandLineResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceCommandLineResult.java
new file mode 100644
index 0000000..00f1857
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceCommandLineResult.java
@@ -0,0 +1,20 @@
+// 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.retrace;
+
+import java.util.List;
+
+public class RetraceCommandLineResult {
+
+ private final List<String> nodes;
+
+ RetraceCommandLineResult(List<String> nodes) {
+ this.nodes = nodes;
+ }
+
+ public List<String> getNodes() {
+ return nodes;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
index 69a561a..d02913e 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
@@ -7,9 +7,11 @@
import com.android.tools.r8.Keep;
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.FieldReference.UnknownFieldReference;
import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.DescriptorUtils;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
@@ -46,7 +48,8 @@
return memberNamings.size() > 1;
}
- Stream<Element> stream() {
+ @Override
+ public Stream<Element> stream() {
if (!hasRetraceResult()) {
return Stream.of(
new Element(
@@ -61,12 +64,20 @@
assert memberNaming.isFieldNaming();
FieldSignature fieldSignature =
memberNaming.getOriginalSignature().asFieldSignature();
+ ClassReference holder =
+ fieldSignature.isQualified()
+ ? Reference.classFromDescriptor(
+ DescriptorUtils.javaTypeToDescriptor(
+ fieldSignature.toHolderFromQualified()))
+ : classElement.getClassReference();
return new Element(
this,
classElement,
Reference.field(
- classElement.getClassReference(),
- fieldSignature.name,
+ holder,
+ fieldSignature.isQualified()
+ ? fieldSignature.toUnqualifiedName()
+ : fieldSignature.name,
Reference.typeFromTypeName(fieldSignature.type)));
});
}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
index 77b4761..d537f03 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
@@ -38,6 +38,10 @@
assert classElement != null;
}
+ public UnknownMethodReference getUnknownReference() {
+ return new UnknownMethodReference(classElement.getClassReference(), obfuscatedName);
+ }
+
private boolean hasRetraceResult() {
return mappedRanges != null && mappedRanges.getMappedRanges().size() > 0;
}
@@ -86,14 +90,10 @@
classElement, new MappedRangesOfName(narrowedRanges), obfuscatedName);
}
- Stream<Element> stream() {
+ @Override
+ public Stream<Element> stream() {
if (!hasRetraceResult()) {
- return Stream.of(
- new Element(
- this,
- classElement,
- new UnknownMethodReference(classElement.getClassReference(), obfuscatedName),
- null));
+ return Stream.of(new Element(this, classElement, getUnknownReference(), null));
}
return mappedRanges.getMappedRanges().stream()
.map(
@@ -161,5 +161,12 @@
public int getOriginalLineNumber(int linePosition) {
return mappedRange != null ? mappedRange.getOriginalLineNumber(linePosition) : linePosition;
}
+
+ public int getFirstLineNumberOfOriginalRange() {
+ if (mappedRange == null) {
+ return 0;
+ }
+ return mappedRange.getFirstLineNumberOfOriginalRange();
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
new file mode 100644
index 0000000..d6b4cb9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
@@ -0,0 +1,746 @@
+// 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.retrace;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.retrace.RetraceClassResult.Element;
+import com.android.tools.r8.retrace.RetraceRegularExpression.RetraceString.RetraceStringBuilder;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.Lists;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+public class RetraceRegularExpression {
+
+ private final RetraceBase retraceBase;
+ private final List<String> stackTrace;
+ private final DiagnosticsHandler diagnosticsHandler;
+ private final String regularExpression;
+
+ private static final int NO_MATCH = -1;
+
+ private final RegularExpressionGroup[] groups =
+ new RegularExpressionGroup[] {
+ new TypeNameGroup(),
+ new BinaryNameGroup(),
+ new MethodNameGroup(),
+ new FieldNameGroup(),
+ new SourceFileGroup(),
+ new LineNumberGroup(),
+ new FieldOrReturnTypeGroup(),
+ new MethodArgumentsGroup()
+ };
+
+ private static final String CAPTURE_GROUP_PREFIX = "captureGroup";
+
+ RetraceRegularExpression(
+ RetraceBase retraceBase,
+ List<String> stackTrace,
+ DiagnosticsHandler diagnosticsHandler,
+ String regularExpression) {
+ this.retraceBase = retraceBase;
+ this.stackTrace = stackTrace;
+ this.diagnosticsHandler = diagnosticsHandler;
+ this.regularExpression = regularExpression;
+ }
+
+ public RetraceCommandLineResult retrace() {
+ List<RegularExpressionGroupHandler> handlers = new ArrayList<>();
+ String regularExpression = registerGroups(this.regularExpression, handlers);
+ Pattern compiledPattern = Pattern.compile(regularExpression);
+ List<String> result = new ArrayList<>();
+ for (String string : stackTrace) {
+ Matcher matcher = compiledPattern.matcher(string);
+ List<RetraceString> retracedStrings =
+ Lists.newArrayList(RetraceStringBuilder.create(string).build());
+ if (matcher.matches()) {
+ for (RegularExpressionGroupHandler handler : handlers) {
+ retracedStrings = handler.handleMatch(retracedStrings, matcher, retraceBase);
+ }
+ }
+ if (retracedStrings.isEmpty()) {
+ // We could not find a match. Output the identity.
+ result.add(string);
+ } else {
+ for (RetraceString retracedString : retracedStrings) {
+ result.add(retracedString.getRetracedString());
+ }
+ }
+ }
+ return new RetraceCommandLineResult(result);
+ }
+
+ private String registerGroups(
+ String regularExpression, List<RegularExpressionGroupHandler> handlers) {
+ int currentIndex = 0;
+ int captureGroupIndex = 0;
+ while (currentIndex < regularExpression.length()) {
+ RegularExpressionGroup firstGroup = null;
+ int firstIndexFromCurrent = regularExpression.length();
+ for (RegularExpressionGroup group : groups) {
+ int nextIndexOf = regularExpression.indexOf(group.shortName(), currentIndex);
+ if (nextIndexOf > NO_MATCH && nextIndexOf < firstIndexFromCurrent) {
+ // Check if previous character in the regular expression is not \\ to ensure not
+ // overriding a matching on shortName.
+ if (nextIndexOf > 0 && regularExpression.charAt(nextIndexOf - 1) == '\\') {
+ continue;
+ }
+ firstGroup = group;
+ firstIndexFromCurrent = nextIndexOf;
+ }
+ }
+ if (firstGroup != null) {
+ String captureGroupName = CAPTURE_GROUP_PREFIX + (captureGroupIndex++);
+ String patternToInsert = "(?<" + captureGroupName + ">" + firstGroup.subExpression() + ")";
+ regularExpression =
+ regularExpression.substring(0, firstIndexFromCurrent)
+ + patternToInsert
+ + regularExpression.substring(
+ firstIndexFromCurrent + firstGroup.shortName().length());
+ handlers.add(firstGroup.createHandler(captureGroupName));
+ firstIndexFromCurrent += patternToInsert.length();
+ }
+ currentIndex = firstIndexFromCurrent;
+ }
+ return regularExpression;
+ }
+
+ static class RetraceString {
+
+ private final Element classContext;
+ private final ClassNameGroup classNameGroup;
+ private final ClassReference qualifiedContext;
+ private final RetraceMethodResult.Element methodContext;
+ private final TypeReference typeOrReturnTypeContext;
+ private final boolean hasTypeOrReturnTypeContext;
+ private final String retracedString;
+ private final int adjustedIndex;
+
+ private RetraceString(
+ Element classContext,
+ ClassNameGroup classNameGroup,
+ ClassReference qualifiedContext,
+ RetraceMethodResult.Element methodContext,
+ TypeReference typeOrReturnTypeContext,
+ boolean hasTypeOrReturnTypeContext,
+ String retracedString,
+ int adjustedIndex) {
+ this.classContext = classContext;
+ this.classNameGroup = classNameGroup;
+ this.qualifiedContext = qualifiedContext;
+ this.methodContext = methodContext;
+ this.typeOrReturnTypeContext = typeOrReturnTypeContext;
+ this.hasTypeOrReturnTypeContext = hasTypeOrReturnTypeContext;
+ this.retracedString = retracedString;
+ this.adjustedIndex = adjustedIndex;
+ }
+
+ String getRetracedString() {
+ return retracedString;
+ }
+
+ boolean hasTypeOrReturnTypeContext() {
+ return hasTypeOrReturnTypeContext;
+ }
+
+ Element getClassContext() {
+ return classContext;
+ }
+
+ RetraceMethodResult.Element getMethodContext() {
+ return methodContext;
+ }
+
+ TypeReference getTypeOrReturnTypeContext() {
+ return typeOrReturnTypeContext;
+ }
+
+ public ClassReference getQualifiedContext() {
+ return qualifiedContext;
+ }
+
+ RetraceStringBuilder transform() {
+ return RetraceStringBuilder.create(this);
+ }
+
+ static class RetraceStringBuilder {
+
+ private Element classContext;
+ private ClassNameGroup classNameGroup;
+ private ClassReference qualifiedContext;
+ private RetraceMethodResult.Element methodContext;
+ private TypeReference typeOrReturnTypeContext;
+ private boolean hasTypeOrReturnTypeContext;
+ private String retracedString;
+ private int adjustedIndex;
+
+ private int maxReplaceStringIndex = NO_MATCH;
+
+ private RetraceStringBuilder(
+ Element classContext,
+ ClassNameGroup classNameGroup,
+ ClassReference qualifiedContext,
+ RetraceMethodResult.Element methodContext,
+ TypeReference typeOrReturnTypeContext,
+ boolean hasTypeOrReturnTypeContext,
+ String retracedString,
+ int adjustedIndex) {
+ this.classContext = classContext;
+ this.classNameGroup = classNameGroup;
+ this.qualifiedContext = qualifiedContext;
+ this.methodContext = methodContext;
+ this.typeOrReturnTypeContext = typeOrReturnTypeContext;
+ this.hasTypeOrReturnTypeContext = hasTypeOrReturnTypeContext;
+ this.retracedString = retracedString;
+ this.adjustedIndex = adjustedIndex;
+ }
+
+ static RetraceStringBuilder create(String string) {
+ return new RetraceStringBuilder(null, null, null, null, null, false, string, 0);
+ }
+
+ static RetraceStringBuilder create(RetraceString string) {
+ return new RetraceStringBuilder(
+ string.classContext,
+ string.classNameGroup,
+ string.qualifiedContext,
+ string.methodContext,
+ string.typeOrReturnTypeContext,
+ string.hasTypeOrReturnTypeContext,
+ string.retracedString,
+ string.adjustedIndex);
+ }
+
+ RetraceStringBuilder setClassContext(Element classContext, ClassNameGroup classNameGroup) {
+ this.classContext = classContext;
+ this.classNameGroup = classNameGroup;
+ return this;
+ }
+
+ RetraceStringBuilder setMethodContext(RetraceMethodResult.Element methodContext) {
+ this.methodContext = methodContext;
+ return this;
+ }
+
+ RetraceStringBuilder setTypeOrReturnTypeContext(TypeReference typeOrReturnTypeContext) {
+ hasTypeOrReturnTypeContext = true;
+ this.typeOrReturnTypeContext = typeOrReturnTypeContext;
+ return this;
+ }
+
+ RetraceStringBuilder setQualifiedContext(ClassReference qualifiedContext) {
+ this.qualifiedContext = qualifiedContext;
+ return this;
+ }
+
+ RetraceStringBuilder replaceInString(String oldString, String newString) {
+ int oldStringStartIndex = retracedString.indexOf(oldString);
+ assert oldStringStartIndex > NO_MATCH;
+ int oldStringEndIndex = oldStringStartIndex + oldString.length();
+ return replaceInStringRaw(newString, oldStringStartIndex, oldStringEndIndex);
+ }
+
+ RetraceStringBuilder replaceInString(String newString, int originalFrom, int originalTo) {
+ return replaceInStringRaw(
+ newString, originalFrom + adjustedIndex, originalTo + adjustedIndex);
+ }
+
+ RetraceStringBuilder replaceInStringRaw(String newString, int from, int to) {
+ assert from <= to;
+ assert from > maxReplaceStringIndex;
+ String prefix = retracedString.substring(0, from);
+ String postFix = retracedString.substring(to);
+ this.retracedString = prefix + newString + postFix;
+ this.adjustedIndex = adjustedIndex + newString.length() - (to - from);
+ maxReplaceStringIndex = prefix.length() + newString.length();
+ return this;
+ }
+
+ RetraceString build() {
+ return new RetraceString(
+ classContext,
+ classNameGroup,
+ qualifiedContext,
+ methodContext,
+ typeOrReturnTypeContext,
+ hasTypeOrReturnTypeContext,
+ retracedString,
+ adjustedIndex);
+ }
+ }
+ }
+
+ private interface RegularExpressionGroupHandler {
+
+ List<RetraceString> handleMatch(
+ List<RetraceString> strings, Matcher matcher, RetraceBase retraceBase);
+ }
+
+ private abstract static class RegularExpressionGroup {
+
+ abstract String shortName();
+
+ abstract String subExpression();
+
+ abstract RegularExpressionGroupHandler createHandler(String captureGroup);
+ }
+
+ // TODO(b/145731185): Extend support for identifiers with strings inside back ticks.
+ private static final String javaIdentifierSegment = "[\\p{L}\\p{N}_\\p{Sc}]+";
+
+ private abstract static class ClassNameGroup extends RegularExpressionGroup {
+
+ abstract String getClassName(ClassReference classReference);
+
+ abstract ClassReference classFromMatch(String match);
+
+ @Override
+ RegularExpressionGroupHandler createHandler(String captureGroup) {
+ return (strings, matcher, retraceBase) -> {
+ if (matcher.start(captureGroup) == NO_MATCH) {
+ return strings;
+ }
+ String typeName = matcher.group(captureGroup);
+ RetraceClassResult retraceResult = retraceBase.retrace(classFromMatch(typeName));
+ List<RetraceString> retracedStrings = new ArrayList<>();
+ for (RetraceString retraceString : strings) {
+ retraceResult.forEach(
+ element -> {
+ retracedStrings.add(
+ retraceString
+ .transform()
+ .setClassContext(element, this)
+ .setMethodContext(null)
+ .replaceInString(
+ getClassName(element.getClassReference()),
+ matcher.start(captureGroup),
+ matcher.end(captureGroup))
+ .build());
+ });
+ }
+ return retracedStrings;
+ };
+ }
+ }
+
+ private static class TypeNameGroup extends ClassNameGroup {
+
+ @Override
+ String shortName() {
+ return "%c";
+ }
+
+ @Override
+ String subExpression() {
+ return "(" + javaIdentifierSegment + "\\.)*" + javaIdentifierSegment;
+ }
+
+ @Override
+ String getClassName(ClassReference classReference) {
+ return classReference.getTypeName();
+ }
+
+ @Override
+ ClassReference classFromMatch(String match) {
+ return Reference.classFromTypeName(match);
+ }
+ }
+
+ private static class BinaryNameGroup extends ClassNameGroup {
+
+ @Override
+ String shortName() {
+ return "%C";
+ }
+
+ @Override
+ String subExpression() {
+ return "(?:" + javaIdentifierSegment + "\\/)*" + javaIdentifierSegment;
+ }
+
+ @Override
+ String getClassName(ClassReference classReference) {
+ return classReference.getBinaryName();
+ }
+
+ @Override
+ ClassReference classFromMatch(String match) {
+ return Reference.classFromBinaryName(match);
+ }
+ }
+
+ private static class MethodNameGroup extends RegularExpressionGroup {
+
+ @Override
+ String shortName() {
+ return "%m";
+ }
+
+ @Override
+ String subExpression() {
+ return javaIdentifierSegment;
+ }
+
+ @Override
+ RegularExpressionGroupHandler createHandler(String captureGroup) {
+ return (strings, matcher, retraceBase) -> {
+ if (matcher.start(captureGroup) == NO_MATCH) {
+ return strings;
+ }
+ String methodName = matcher.group(captureGroup);
+ List<RetraceString> retracedStrings = new ArrayList<>();
+ for (RetraceString retraceString : strings) {
+ if (retraceString.classContext == null) {
+ retracedStrings.add(retraceString);
+ continue;
+ }
+ retraceString
+ .getClassContext()
+ .lookupMethod(methodName)
+ .forEach(
+ element -> {
+ MethodReference methodReference = element.getMethodReference();
+ if (retraceString.hasTypeOrReturnTypeContext()) {
+ if (methodReference.getReturnType() == null
+ && retraceString.getTypeOrReturnTypeContext() != null) {
+ return;
+ } else if (methodReference.getReturnType() != null
+ && !methodReference
+ .getReturnType()
+ .equals(retraceString.getTypeOrReturnTypeContext())) {
+ return;
+ }
+ }
+ RetraceStringBuilder newRetraceString = retraceString.transform();
+ ClassReference existingClass =
+ retraceString.getClassContext().getClassReference();
+ ClassReference holder = methodReference.getHolderClass();
+ if (holder != existingClass) {
+ // The element is defined on another holder.
+ newRetraceString
+ .replaceInString(
+ newRetraceString.classNameGroup.getClassName(existingClass),
+ newRetraceString.classNameGroup.getClassName(holder))
+ .setQualifiedContext(holder);
+ }
+ newRetraceString
+ .setMethodContext(element)
+ .replaceInString(
+ methodReference.getMethodName(),
+ matcher.start(captureGroup),
+ matcher.end(captureGroup));
+ retracedStrings.add(newRetraceString.build());
+ });
+ }
+ return retracedStrings;
+ };
+ }
+ }
+
+ private static class FieldNameGroup extends RegularExpressionGroup {
+
+ @Override
+ String shortName() {
+ return "%f";
+ }
+
+ @Override
+ String subExpression() {
+ return javaIdentifierSegment;
+ }
+
+ @Override
+ RegularExpressionGroupHandler createHandler(String captureGroup) {
+ return (strings, matcher, retraceBase) -> {
+ if (matcher.start(captureGroup) == NO_MATCH) {
+ return strings;
+ }
+ String methodName = matcher.group(captureGroup);
+ List<RetraceString> retracedStrings = new ArrayList<>();
+ for (RetraceString retraceString : strings) {
+ if (retraceString.getClassContext() == null) {
+ retracedStrings.add(retraceString);
+ continue;
+ }
+ retraceString
+ .getClassContext()
+ .lookupField(methodName)
+ .forEach(
+ element -> {
+ RetraceStringBuilder newRetraceString = retraceString.transform();
+ ClassReference existingClass =
+ retraceString.getClassContext().getClassReference();
+ ClassReference holder = element.getFieldReference().getHolderClass();
+ if (holder != existingClass) {
+ // The element is defined on another holder.
+ newRetraceString
+ .replaceInString(
+ newRetraceString.classNameGroup.getClassName(existingClass),
+ newRetraceString.classNameGroup.getClassName(holder))
+ .setQualifiedContext(holder);
+ }
+ newRetraceString.replaceInString(
+ element.getFieldReference().getFieldName(),
+ matcher.start(captureGroup),
+ matcher.end(captureGroup));
+ retracedStrings.add(newRetraceString.build());
+ });
+ }
+ return retracedStrings;
+ };
+ }
+ }
+
+ private static class SourceFileGroup extends RegularExpressionGroup {
+
+ @Override
+ String shortName() {
+ return "%s";
+ }
+
+ @Override
+ String subExpression() {
+ return "(?:\\w+\\.)*\\w+";
+ }
+
+ @Override
+ RegularExpressionGroupHandler createHandler(String captureGroup) {
+ return (strings, matcher, retraceBase) -> {
+ if (matcher.start(captureGroup) == NO_MATCH) {
+ return strings;
+ }
+ String fileName = matcher.group(captureGroup);
+ List<RetraceString> retracedStrings = new ArrayList<>();
+ for (RetraceString retraceString : strings) {
+ if (retraceString.classContext == null) {
+ retracedStrings.add(retraceString);
+ continue;
+ }
+ String newSourceFile =
+ retraceString.getQualifiedContext() != null
+ ? retraceBase.retraceSourceFile(
+ retraceString.classContext.getClassReference(),
+ fileName,
+ retraceString.getQualifiedContext(),
+ true)
+ : retraceString.classContext.retraceSourceFile(fileName, retraceBase);
+ retracedStrings.add(
+ retraceString
+ .transform()
+ .replaceInString(
+ newSourceFile, matcher.start(captureGroup), matcher.end(captureGroup))
+ .build());
+ }
+ return retracedStrings;
+ };
+ }
+ }
+
+ private class LineNumberGroup extends RegularExpressionGroup {
+
+ @Override
+ String shortName() {
+ return "%l";
+ }
+
+ @Override
+ String subExpression() {
+ return "\\d*";
+ }
+
+ @Override
+ RegularExpressionGroupHandler createHandler(String captureGroup) {
+ return (strings, matcher, retraceBase) -> {
+ if (matcher.start(captureGroup) == NO_MATCH) {
+ return strings;
+ }
+ String lineNumberAsString = matcher.group(captureGroup);
+ int lineNumber =
+ lineNumberAsString.isEmpty() ? NO_MATCH : Integer.parseInt(lineNumberAsString);
+ List<RetraceString> retracedStrings = new ArrayList<>();
+ for (RetraceString retraceString : strings) {
+ RetraceMethodResult.Element methodContext = retraceString.methodContext;
+ if (methodContext == null) {
+ retracedStrings.add(retraceString);
+ continue;
+ }
+ Set<MethodReference> narrowedSet =
+ methodContext.getRetraceMethodResult().narrowByLine(lineNumber).stream()
+ .map(RetraceMethodResult.Element::getMethodReference)
+ .collect(Collectors.toSet());
+ if (!narrowedSet.contains(methodContext.getMethodReference())) {
+ // Prune the retraceString since we now have line number information and this is not
+ // a part of the result.
+ diagnosticsHandler.info(
+ new StringDiagnostic(
+ "Pruning "
+ + retraceString.getRetracedString()
+ + " from result because line number "
+ + lineNumber
+ + " does not match."));
+ continue;
+ }
+ String newLineNumber =
+ lineNumber > NO_MATCH
+ ? methodContext.getOriginalLineNumber(lineNumber) + ""
+ : lineNumberAsString;
+ retracedStrings.add(
+ retraceString
+ .transform()
+ .replaceInString(
+ newLineNumber, matcher.start(captureGroup), matcher.end(captureGroup))
+ .build());
+ }
+ return retracedStrings;
+ };
+ }
+ }
+
+ private static final String JAVA_TYPE_REGULAR_EXPRESSION =
+ "(" + javaIdentifierSegment + "\\.)*" + javaIdentifierSegment + "[\\[\\]]*";
+
+ private static class FieldOrReturnTypeGroup extends RegularExpressionGroup {
+
+ @Override
+ String shortName() {
+ return "%t";
+ }
+
+ @Override
+ String subExpression() {
+ return JAVA_TYPE_REGULAR_EXPRESSION;
+ }
+
+ @Override
+ RegularExpressionGroupHandler createHandler(String captureGroup) {
+ return (strings, matcher, retraceBase) -> {
+ if (matcher.start(captureGroup) == NO_MATCH) {
+ return strings;
+ }
+ String typeName = matcher.group(captureGroup);
+ String descriptor = DescriptorUtils.javaTypeToDescriptor(typeName);
+ if (!DescriptorUtils.isDescriptor(descriptor) && !"V".equals(descriptor)) {
+ return strings;
+ }
+ TypeReference typeReference = Reference.returnTypeFromDescriptor(descriptor);
+ List<RetraceString> retracedStrings = new ArrayList<>();
+ RetraceTypeResult retracedType = retraceBase.retrace(typeReference);
+ for (RetraceString retraceString : strings) {
+ retracedType.forEach(
+ element -> {
+ TypeReference retracedReference = element.getTypeReference();
+ retracedStrings.add(
+ retraceString
+ .transform()
+ .setTypeOrReturnTypeContext(retracedReference)
+ .replaceInString(
+ retracedReference == null ? "void" : retracedReference.getTypeName(),
+ matcher.start(captureGroup),
+ matcher.end(captureGroup))
+ .build());
+ });
+ }
+ return retracedStrings;
+ };
+ }
+ }
+
+ private class MethodArgumentsGroup extends RegularExpressionGroup {
+
+ @Override
+ String shortName() {
+ return "%a";
+ }
+
+ @Override
+ String subExpression() {
+ return "((" + JAVA_TYPE_REGULAR_EXPRESSION + "\\,)*" + JAVA_TYPE_REGULAR_EXPRESSION + ")?";
+ }
+
+ @Override
+ RegularExpressionGroupHandler createHandler(String captureGroup) {
+ return (strings, matcher, retraceBase) -> {
+ if (matcher.start(captureGroup) == NO_MATCH) {
+ return strings;
+ }
+ Set<List<TypeReference>> initialValue = new LinkedHashSet<>();
+ initialValue.add(new ArrayList<>());
+ Set<List<TypeReference>> allRetracedReferences =
+ Arrays.stream(matcher.group(captureGroup).split(","))
+ .map(String::trim)
+ .reduce(
+ initialValue,
+ (acc, typeName) -> {
+ String descriptor = DescriptorUtils.javaTypeToDescriptor(typeName);
+ if (!DescriptorUtils.isDescriptor(descriptor) && !"V".equals(descriptor)) {
+ return acc;
+ }
+ TypeReference typeReference = Reference.returnTypeFromDescriptor(descriptor);
+ Set<List<TypeReference>> retracedTypes = new LinkedHashSet<>();
+ retraceBase
+ .retrace(typeReference)
+ .forEach(
+ element -> {
+ for (List<TypeReference> currentReferences : acc) {
+ ArrayList<TypeReference> newList =
+ new ArrayList<>(currentReferences);
+ newList.add(element.getTypeReference());
+ retracedTypes.add(newList);
+ }
+ });
+ return retracedTypes;
+ },
+ (l1, l2) -> {
+ l1.addAll(l2);
+ return l1;
+ });
+ List<RetraceString> retracedStrings = new ArrayList<>();
+ for (RetraceString retraceString : strings) {
+ if (retraceString.getMethodContext() != null
+ && !allRetracedReferences.contains(
+ retraceString.getMethodContext().getMethodReference().getFormalTypes())) {
+ // Prune the string since we now know the formals.
+ String formals =
+ retraceString.getMethodContext().getMethodReference().getFormalTypes().stream()
+ .map(TypeReference::getTypeName)
+ .collect(Collectors.joining(","));
+ diagnosticsHandler.info(
+ new StringDiagnostic(
+ "Pruning "
+ + retraceString.getRetracedString()
+ + " from result because formals ("
+ + formals
+ + ") do not match result set."));
+ continue;
+ }
+ for (List<TypeReference> retracedReferences : allRetracedReferences) {
+ retracedStrings.add(
+ retraceString
+ .transform()
+ .replaceInString(
+ retracedReferences.stream()
+ .map(TypeReference::getTypeName)
+ .collect(Collectors.joining(",")),
+ matcher.start(captureGroup),
+ matcher.end(captureGroup))
+ .build());
+ }
+ }
+ return retracedStrings;
+ };
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
index 1f4fa43..7c899a7 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
@@ -79,23 +79,6 @@
}
}
- static class RetraceResult {
-
- private final List<StackTraceNode> nodes;
-
- RetraceResult(List<StackTraceNode> nodes) {
- this.nodes = nodes;
- }
-
- List<String> toListOfStrings() {
- List<String> strings = new ArrayList<>(nodes.size());
- for (StackTraceNode node : nodes) {
- node.append(strings);
- }
- return strings;
- }
- }
-
private final RetraceBase retraceBase;
private final List<String> stackTrace;
private final DiagnosticsHandler diagnosticsHandler;
@@ -107,10 +90,14 @@
this.diagnosticsHandler = diagnosticsHandler;
}
- public RetraceResult retrace() {
+ public RetraceCommandLineResult retrace() {
ArrayList<StackTraceNode> result = new ArrayList<>();
retraceLine(stackTrace, 0, result);
- return new RetraceResult(result);
+ List<String> retracedStrings = new ArrayList<>();
+ for (StackTraceNode node : result) {
+ node.append(retracedStrings);
+ }
+ return new RetraceCommandLineResult(retracedStrings);
}
private void retraceLine(List<String> stackTrace, int index, List<StackTraceNode> result) {
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
index b04d9a8..2ab72a7 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
@@ -20,6 +20,7 @@
this.retraceBase = retraceBase;
}
+ @Override
public Stream<Element> stream() {
// Handle void and primitive types as single element results.
if (obfuscatedType == null || obfuscatedType.isPrimitive()) {
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 69e1930..347ae0f 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -29,7 +29,6 @@
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.optimize.NestUtils;
import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.ImmutableList;
@@ -630,6 +629,28 @@
return constClassReferences.contains(type);
}
+ public AppInfoWithLiveness withStaticFieldWrites(
+ Map<DexEncodedField, Set<DexEncodedMethod>> writesWithContexts) {
+ assert checkIfObsolete();
+ if (writesWithContexts.isEmpty()) {
+ return this;
+ }
+ AppInfoWithLiveness result = new AppInfoWithLiveness(this);
+ writesWithContexts.forEach(
+ (encodedField, contexts) -> {
+ DexField field = encodedField.field;
+ FieldAccessInfoImpl fieldAccessInfo = result.fieldAccessInfoCollection.get(field);
+ if (fieldAccessInfo == null) {
+ fieldAccessInfo = new FieldAccessInfoImpl(field);
+ result.fieldAccessInfoCollection.extend(field, fieldAccessInfo);
+ }
+ for (DexEncodedMethod context : contexts) {
+ fieldAccessInfo.recordWrite(field, context);
+ }
+ });
+ return result;
+ }
+
public AppInfoWithLiveness withoutStaticFieldsWrites(Set<DexField> noLongerWrittenFields) {
assert checkIfObsolete();
if (noLongerWrittenFields.isEmpty()) {
@@ -838,6 +859,19 @@
return pinnedItems.contains(reference);
}
+ public boolean hasPinnedInstanceInitializer(DexType type) {
+ assert type.isClassType();
+ DexProgramClass clazz = asProgramClassOrNull(definitionFor(type));
+ if (clazz != null) {
+ for (DexEncodedMethod method : clazz.directMethods()) {
+ if (method.isInstanceInitializer() && isPinned(method.method)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private boolean canVirtualMethodBeImplementedInExtraSubclass(
DexProgramClass clazz, DexMethod method) {
// For functional interfaces that are instantiated by lambdas, we may not have synthesized all
@@ -853,7 +887,7 @@
// overrides the kept method.
if (isPinned(clazz.type)) {
ResolutionResult resolutionResult = resolveMethod(clazz, method);
- if (resolutionResult.hasSingleTarget()) {
+ if (resolutionResult.isSingleResolution()) {
DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
return !resolutionTarget.isProgramMethod(this)
|| resolutionTarget.isLibraryMethodOverride().isPossiblyTrue()
@@ -982,9 +1016,24 @@
DexType refinedReceiverType,
ClassTypeLatticeElement receiverLowerBoundType) {
assert checkIfObsolete();
- DexEncodedMethod directResult = nestAccessLookup(method, invocationContext);
- if (directResult != null) {
- return directResult;
+ // TODO: replace invocationContext by a DexProgramClass typed formal.
+ DexProgramClass invocationClass = asProgramClassOrNull(definitionFor(invocationContext));
+ assert invocationClass != null;
+
+ ResolutionResult resolutionResult = resolveMethodOnClass(method.holder, method);
+ if (!resolutionResult.isAccessibleForVirtualDispatchFrom(invocationClass, this)) {
+ return null;
+ }
+
+ DexEncodedMethod topTarget = resolutionResult.getSingleTarget();
+ if (topTarget == null) {
+ // A null target represents a valid target without a known defintion, ie, array clone().
+ return null;
+ }
+
+ // If the target is a private method, then the invocation is a direct access to a nest member.
+ if (topTarget.isPrivateMethod()) {
+ return topTarget;
}
// If the lower-bound on the receiver type is the same as the upper-bound, then we have exact
@@ -992,11 +1041,10 @@
// from the runtime type of the receiver.
if (receiverLowerBoundType != null) {
if (receiverLowerBoundType.getClassType() == refinedReceiverType) {
- ResolutionResult resolutionResult = resolveMethod(method.holder, method, false);
- if (resolutionResult.hasSingleTarget()
+ if (resolutionResult.isSingleResolution()
&& resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
- if (refinedResolutionResult.hasSingleTarget()
+ if (refinedResolutionResult.isSingleResolution()
&& refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) {
return validateSingleVirtualTarget(
refinedResolutionResult.getSingleTarget(), resolutionResult.getSingleTarget());
@@ -1014,14 +1062,13 @@
assert method != null;
assert isSubtype(refinedReceiverType, method.holder);
if (method.holder.isArrayType()) {
- // For javac output this will only be clone(), but in general the methods from Object can
- // be invoked with an array type holder.
return null;
}
DexClass holder = definitionFor(method.holder);
- if (holder == null || holder.isNotProgramClass() || holder.isInterface()) {
+ if (holder == null || holder.isNotProgramClass()) {
return null;
}
+ assert !holder.isInterface();
boolean refinedReceiverIsStrictSubType = refinedReceiverType != method.holder;
DexProgramClass refinedHolder =
(refinedReceiverIsStrictSubType ? definitionFor(refinedReceiverType) : holder)
@@ -1036,7 +1083,7 @@
// First get the target for the holder type.
ResolutionResult topMethod = resolveMethodOnClass(holder, method);
// We might hit none or multiple targets. Both make this fail at runtime.
- if (!topMethod.hasSingleTarget() || !topMethod.isValidVirtualTarget(options())) {
+ if (!topMethod.isSingleResolution() || !topMethod.isValidVirtualTarget(options())) {
method.setSingleVirtualMethodCache(refinedReceiverType, null);
return null;
}
@@ -1063,21 +1110,6 @@
return result;
}
- private DexEncodedMethod nestAccessLookup(DexMethod method, DexType invocationContext) {
- if (method.holder == invocationContext || !definitionFor(invocationContext).isInANest()) {
- return null;
- }
- DexEncodedMethod directTarget = lookupDirectTarget(method);
- assert directTarget == null || directTarget.method.holder == method.holder;
- if (directTarget != null
- && directTarget.isPrivateMethod()
- && NestUtils.sameNest(method.holder, invocationContext, this)) {
- return directTarget;
- }
-
- return null;
- }
-
/**
* Computes which methods overriding <code>method</code> are visible for the subtypes of type.
*
@@ -1183,10 +1215,9 @@
DexType refinedReceiverType,
ClassTypeLatticeElement receiverLowerBoundType) {
assert checkIfObsolete();
- DexEncodedMethod directResult = nestAccessLookup(method, invocationContext);
- if (directResult != null) {
- return directResult;
- }
+ // Replace DexType invocationContext by DexProgramClass throughout.
+ DexProgramClass invocationClass = asProgramClassOrNull(definitionFor(invocationContext));
+ assert invocationClass != null;
// If the lower-bound on the receiver type is the same as the upper-bound, then we have exact
// runtime type information. In this case, the invoke will dispatch to the resolution result
@@ -1194,10 +1225,10 @@
if (receiverLowerBoundType != null) {
if (receiverLowerBoundType.getClassType() == refinedReceiverType) {
ResolutionResult resolutionResult = resolveMethod(method.holder, method, true);
- if (resolutionResult.hasSingleTarget()
+ if (resolutionResult.isSingleResolution()
&& resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
- if (refinedResolutionResult.hasSingleTarget()
+ if (refinedResolutionResult.isSingleResolution()
&& refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) {
return validateSingleVirtualTarget(
refinedResolutionResult.getSingleTarget(), resolutionResult.getSingleTarget());
@@ -1214,13 +1245,24 @@
if (holder == null || !holder.accessFlags.isInterface()) {
return null;
}
- // First check that there is a target for this invoke-interface to hit. If there is none,
- // this will fail at runtime.
- DexEncodedMethod topTarget = resolveMethodOnInterface(holder, method).getSingleTarget();
- if (topTarget == null || !SingleResolutionResult.isValidVirtualTarget(options(), topTarget)) {
+ // First check that there is a visible and valid target for this invoke-interface to hit.
+ // If there is none, this will fail at runtime.
+ ResolutionResult topResolution = resolveMethodOnInterface(holder, method);
+ if (!topResolution.isAccessibleForVirtualDispatchFrom(invocationClass, this)) {
return null;
}
+ DexEncodedMethod topTarget = topResolution.getSingleTarget();
+ if (topTarget == null) {
+ // An null target represents a valid target with no known defintion, eg, array clone().
+ return null;
+ }
+
+ // If the target is a private method, then the invocation is a direct access to a nest member.
+ if (topTarget.isPrivateMethod()) {
+ return topTarget;
+ }
+
// If the invoke could target a method in a class that is not visible to R8, then give up.
if (canVirtualMethodBeImplementedInExtraSubclass(holder, method)) {
return null;
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 a9f60ec..3548caf 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2022,7 +2022,7 @@
assert interfaceInvoke == holder.isInterface();
Set<DexEncodedMethod> possibleTargets =
// TODO(b/140214802): Call on the resolution once proper resolution and lookup is resolved.
- new SingleResolutionResult(resolution.method)
+ new SingleResolutionResult(holder, resolution.holder, resolution.method)
.lookupVirtualDispatchTargets(interfaceInvoke, appInfo);
if (possibleTargets == null || possibleTargets.isEmpty()) {
return;
@@ -2173,25 +2173,26 @@
// See <a
// href="https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokespecial">
// the JVM spec for invoke-special.
- DexEncodedMethod resolutionTarget =
- appInfo.resolveMethod(method.holder, method).getSingleTarget();
- if (resolutionTarget == null) {
+ SingleResolutionResult resolution =
+ appInfo.resolveMethod(method.holder, method).asSingleResolution();
+ if (resolution == null) {
brokenSuperInvokes.add(method);
reportMissingMethod(method);
return;
}
+ DexEncodedMethod resolutionTarget = resolution.getResolvedMethod();
if (resolutionTarget.accessFlags.isPrivate() || resolutionTarget.accessFlags.isStatic()) {
brokenSuperInvokes.add(resolutionTarget.method);
}
- DexProgramClass resolutionTargetClass = getProgramClassOrNull(resolutionTarget.method.holder);
+ DexProgramClass resolutionTargetClass = resolution.getResolvedHolder().asProgramClass();
if (resolutionTargetClass != null) {
markMethodAsTargeted(
resolutionTargetClass, resolutionTarget, KeepReason.targetedBySuperFrom(from));
}
// Now we need to compute the actual target in the context.
- DexEncodedMethod target = appInfo.lookupSuperTarget(method, from.method.holder);
+ DexEncodedMethod target = resolution.lookupInvokeSuperTarget(from.method.holder, appInfo);
if (target == null) {
// The actual implementation in the super class is missing.
reportMissingMethod(method);
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index fee13db..24b5004 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -533,7 +533,7 @@
ResolutionResult resolutionResult =
appView.appInfo().resolveMethod(originalClazz, method.method);
if (!resolutionResult.isValidVirtualTarget(appView.options())
- || !resolutionResult.hasSingleTarget()) {
+ || !resolutionResult.isSingleResolution()) {
return;
}
DexEncodedMethod methodToKeep = resolutionResult.getSingleTarget();
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 9f4b3e3..77de8e2 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1244,7 +1244,7 @@
// Returns the method that shadows the given method, or null if method is not shadowed.
private DexEncodedMethod findMethodInTarget(DexEncodedMethod method) {
ResolutionResult resolutionResult = appInfo.resolveMethod(target, method.method);
- if (!resolutionResult.hasSingleTarget()) {
+ if (!resolutionResult.isSingleResolution()) {
// May happen in case of missing classes, or if multiple implementations were found.
abortMerge = true;
return null;
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 e815737..40633b6 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -223,6 +223,7 @@
public boolean enablePropagationOfConstantsAtCallSites = false;
public boolean encodeChecksums = false;
public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
+ public boolean enableSourceDebugExtensionRewriter = false;
public int callGraphLikelySpuriousCallEdgeThreshold = 50;
@@ -1013,8 +1014,6 @@
public boolean forceNameReflectionOptimization = false;
public boolean enableNarrowingChecksInD8 = false;
public Consumer<IRCode> irModifier = null;
- // TODO(b/129458850) When fixed, remove this and change all usages to "true".
- public boolean enableStatefulLambdaCreateInstanceMethod = false;
public int basicBlockMuncherIterationLimit = NO_LIMIT;
public boolean dontReportFailingCheckDiscarded = false;
public boolean deterministicSortingBasedOnDexType = true;
@@ -1033,10 +1032,6 @@
// deleted. Useful to check that our dump functionality does not cause compilation failure.
public boolean dumpAll = false;
- public boolean desugarLambdasThroughLensCodeRewriter() {
- return enableStatefulLambdaCreateInstanceMethod;
- }
-
public boolean readInputStackMaps = false;
// Option for testing outlining with interface array arguments, see b/132420510.
diff --git a/src/test/examplesJava9/backport/StreamBackportJava9Main.java b/src/test/examplesJava9/backport/StreamBackportJava9Main.java
new file mode 100644
index 0000000..b8c8f28
--- /dev/null
+++ b/src/test/examplesJava9/backport/StreamBackportJava9Main.java
@@ -0,0 +1,28 @@
+// 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 backport;
+
+import java.util.stream.Stream;
+
+public final class StreamBackportJava9Main {
+
+ public static void main(String[] args) {
+ testOfNullable();
+ }
+
+ public static void testOfNullable() {
+ Object guineaPig = new Object();
+ Stream<Object> streamNonEmpty = Stream.ofNullable(guineaPig);
+ assertTrue(streamNonEmpty.count() == 1);
+ Stream<Object> streamEmpty = Stream.ofNullable(null);
+ assertTrue(streamEmpty.count() == 0);
+ }
+
+ private static void assertTrue(boolean value) {
+ if (!value) {
+ throw new AssertionError("Expected <true> but was <false>");
+ }
+ }
+}
diff --git a/src/test/examplesJava9/desugaredlib/SynchronizedCollectionMain.java b/src/test/examplesJava9/desugaredlib/SynchronizedCollectionMain.java
new file mode 100644
index 0000000..d600c63
--- /dev/null
+++ b/src/test/examplesJava9/desugaredlib/SynchronizedCollectionMain.java
@@ -0,0 +1,90 @@
+// 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 desugaredlib;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+public class SynchronizedCollectionMain {
+
+ public static void main(String[] args) {
+ // Test 3 maps.
+ // Test keySet entrySet values in syncMap
+ // test navigableKeySet in syncNavigMap
+ // Test rewriting of removeIf, replaceAll, sort.
+ // Retarget removeIf.
+ ArrayList<Integer> list = new ArrayList<>();
+ list.add(1);
+ list.add(2);
+ Collection<Integer> syncCol = Collections.synchronizedCollection(list);
+ syncCol.removeIf(x -> x == 2);
+ System.out.println(syncCol);
+
+ // Retarget sort, replaceAll.
+ ArrayList<Integer> list2 = new ArrayList<>();
+ list2.add(2);
+ list2.add(1);
+ List<Integer> syncList = Collections.synchronizedList(list2);
+ syncList.replaceAll(x -> x + 1);
+ System.out.println(syncList.size());
+ syncList.sort(Comparator.naturalOrder());
+ System.out.println(syncList);
+
+ // Retarget synchronizedMap.
+ Map<Integer, Double> map = new IdentityHashMap<>();
+ map.put(1, 1.1);
+ map.put(2, 2.2);
+ Map<Integer, Double> synchronizedMap = Collections.synchronizedMap(map);
+ // Reflective instantiation.
+ System.out.println(synchronizedMap.keySet().contains(1));
+ System.out.println(synchronizedMap.entrySet().size());
+ System.out.println(synchronizedMap.values().size());
+
+ // Retarget synchronizedSortedMap
+ SortedMap<Integer, Double> sortedMap = new ConcurrentSkipListMap<>();
+ sortedMap.put(1, 1.1);
+ sortedMap.put(2, 2.2);
+ SortedMap<Integer, Double> synchronizedSortedMap = Collections.synchronizedSortedMap(sortedMap);
+ System.out.println(synchronizedSortedMap.size());
+
+ testSynchronization();
+ }
+
+ public static void testSynchronization() {
+ int LIST_SIZE = 10000;
+ // Different thread mutate the same collection. Without synchronization,
+ // some of the integers should be concurrently incremented leading to an invalid result.
+ for (int numThreads : new int[] {4, 5, 8, 9, 15, 16}) {
+ ArrayList<Integer> list = new ArrayList<>();
+ for (int i = 0; i < LIST_SIZE; i++) {
+ list.add(i);
+ }
+ List<Integer> syncList = Collections.synchronizedList(list);
+ try {
+ ExecutorService executor = Executors.newFixedThreadPool(numThreads);
+ for (int i = 0; i < numThreads; i++) {
+ executor.submit(() -> syncList.replaceAll(x -> x + 1));
+ }
+ executor.shutdown();
+ executor.awaitTermination(5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ for (int i = 0; i < LIST_SIZE; i++) {
+ assert i + numThreads == list.get(i);
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/Collectors.java b/src/test/java/com/android/tools/r8/Collectors.java
new file mode 100644
index 0000000..0e05ce0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/Collectors.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import java.util.stream.Collector;
+
+public abstract class Collectors {
+
+ public static <T> Collector<T, ?, T> toSingle() {
+ return java.util.stream.Collectors.collectingAndThen(
+ java.util.stream.Collectors.toList(),
+ items -> {
+ assert items.size() == 1;
+ return items.get(0);
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTestJdkLib.java b/src/test/java/com/android/tools/r8/D8CommandTestJdkLib.java
new file mode 100644
index 0000000..553452e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/D8CommandTestJdkLib.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.origin.EmbeddedOrigin;
+import com.android.tools.r8.utils.AndroidApp;
+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 D8CommandTestJdkLib extends TestBase {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withCfRuntimes().build();
+ }
+
+ private final TestParameters parameters;
+
+ public D8CommandTestJdkLib(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void jdkLib() throws Throwable {
+ CfRuntime runtime = parameters.getRuntime().asCf();
+ D8Command command = parse("--lib", runtime.getJavaHome().toString());
+ AndroidApp inputApp = ToolHelper.getApp(command);
+ assertEquals(1, inputApp.getLibraryResourceProviders().size());
+ if (runtime.isNewerThan(CfVm.JDK8)) {
+ assertTrue(inputApp.getLibraryResourceProviders().get(0) instanceof JdkClassFileProvider);
+ } else {
+ assertTrue(inputApp.getLibraryResourceProviders().get(0) instanceof ArchiveClassFileProvider);
+ }
+ }
+
+ private D8Command parse(String... args) throws CompilationFailedException {
+ return D8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
index 6ccf086..1bf86e3 100644
--- a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
@@ -4,7 +4,7 @@
package com.android.tools.r8;
-import static org.junit.Assert.assertTrue;
+import static org.hamcrest.CoreMatchers.containsString;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.OffOrAuto;
@@ -346,20 +346,23 @@
// implementation from B.
// test is compiled with incomplete classpath: lib3 is missing so
// ImplementMethodsWithDefault is missing its super class.
+
D8TestRunner test =
- test("desugaringwithmissingclasstest2", "desugaringwithmissingclasstest2", "N/A")
- .withInterfaceMethodDesugaring(OffOrAuto.Auto)
- .withClasspath(lib1.getInputJar())
- .withClasspath(lib2.getInputJar())
- .withMinApiLevel(minApi);
- try {
- test.build();
- Assert.fail("Expected build to fail with CompilationFailedException");
- } catch (CompilationFailedException e) {
- String message = e.getCause().getMessage();
- assertTrue(message.contains("desugaringwithmissingclasstest2.ImplementMethodsWithDefault"));
- assertTrue(message.contains("desugaringwithmissingclasslib3.C"));
- }
+ test("desugaringwithmissingclasstest2", "desugaringwithmissingclasstest2", "N/A");
+
+ TestBase.testForD8(temp)
+ .addProgramFiles(test.getInputJar())
+ .addClasspathFiles(lib1.getInputJar())
+ .addClasspathFiles(lib2.getInputJar())
+ .setMinApi(minApi)
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyWarnings();
+ diagnostics.assertWarningMessageThatMatches(
+ containsString("desugaringwithmissingclasstest2.ImplementMethodsWithDefault"));
+ diagnostics.assertWarningMessageThatMatches(
+ containsString("desugaringwithmissingclasslib3.C"));
+ });
}
@Test
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
index 6eba851..049cdb7 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
@@ -7,7 +7,6 @@
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.graph.invokesuper.Consumer;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.position.Position;
@@ -17,6 +16,7 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
// Helper to check that a particular error occurred.
public class DiagnosticsChecker implements DiagnosticsHandler {
diff --git a/src/test/java/com/android/tools/r8/KotlinTestBase.java b/src/test/java/com/android/tools/r8/KotlinTestBase.java
index 87f946c..a470bb3 100644
--- a/src/test/java/com/android/tools/r8/KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/KotlinTestBase.java
@@ -4,8 +4,7 @@
package com.android.tools.r8;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -17,7 +16,9 @@
+ "java.lang.Object, java.lang.String)";
protected static final String throwParameterIsNotNullExceptionSignature =
"void kotlin.jvm.internal.Intrinsics.throwParameterIsNullException(java.lang.String)";
- protected static final String METADATA_DESCRIPTOR = "Lkotlin/Metadata;";
+ public static final String METADATA_DESCRIPTOR = "Lkotlin/Metadata;";
+ public static final String METADATA_TYPE =
+ DescriptorUtils.descriptorToJavaType(METADATA_DESCRIPTOR);
private static final String RSRC = "kotlinR8TestResources";
@@ -48,13 +49,4 @@
protected Path getMappingfile(String folder, String mappingFileName) {
return Paths.get(ToolHelper.TESTS_DIR, RSRC, folder, mappingFileName);
}
-
- protected DexAnnotation retrieveMetadata(DexClass dexClass) {
- for (DexAnnotation annotation : dexClass.annotations.annotations) {
- if (annotation.annotation.type.toDescriptorString().equals(METADATA_DESCRIPTOR)) {
- return annotation;
- }
- }
- return null;
- }
}
diff --git a/src/test/java/com/android/tools/r8/R8CommandTestJdkLib.java b/src/test/java/com/android/tools/r8/R8CommandTestJdkLib.java
new file mode 100644
index 0000000..f5cd550
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8CommandTestJdkLib.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.origin.EmbeddedOrigin;
+import com.android.tools.r8.utils.AndroidApp;
+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 R8CommandTestJdkLib extends TestBase {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withCfRuntimes().build();
+ }
+
+ private final TestParameters parameters;
+
+ public R8CommandTestJdkLib(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void jdkLib() throws Throwable {
+ CfRuntime runtime = parameters.getRuntime().asCf();
+ R8Command command = parse("--lib", runtime.getJavaHome().toString());
+ AndroidApp inputApp = ToolHelper.getApp(command);
+ assertEquals(1, inputApp.getLibraryResourceProviders().size());
+ if (runtime.isNewerThan(CfVm.JDK8)) {
+ assertTrue(inputApp.getLibraryResourceProviders().get(0) instanceof JdkClassFileProvider);
+ } else {
+ assertTrue(inputApp.getLibraryResourceProviders().get(0) instanceof ArchiveClassFileProvider);
+ }
+ }
+
+ private R8Command parse(String... args) throws CompilationFailedException {
+ return R8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 5f328d3..8154dfc 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -102,7 +102,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 180, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 137, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -110,10 +110,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- // TODO(b/120814598): Should be 24. Some lambdas are not class inlined because parameter
- // usages for lambda methods are not present for the class inliner.
- // TODO(b/141719453): Also, some are not inined due to instruction limits.
- .withDexCheck(inspector -> checkLambdaCount(inspector, 39, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
.run();
}
@@ -137,37 +134,6 @@
.run();
}
- @Override
- @Test
- public void lambdaDesugaringCreateMethod() throws Throwable {
- test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
- .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
- .withOptionConsumer(
- opts -> {
- opts.enableClassInlining = false;
- opts.testing.enableStatefulLambdaCreateInstanceMethod = true;
- })
- .withBuilderTransformation(
- b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 180, "lambdadesugaring"))
- .run();
-
- test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
- .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
- .withOptionConsumer(
- opts -> {
- opts.enableClassInlining = true;
- opts.testing.enableStatefulLambdaCreateInstanceMethod = true;
- })
- .withBuilderTransformation(
- b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- // TODO(b/120814598): Should be 24. Some lambdas are not class inlined because parameter
- // usages for lambda methods are not present for the class inliner.
- // TODO(b/141719453): Also, some are not inined due to instruction limits.
- .withDexCheck(inspector -> checkLambdaCount(inspector, 39, "lambdadesugaring"))
- .run();
- }
-
@Test
@IgnoreIfVmOlderThan(Version.V7_0_0)
public void lambdaDesugaringWithDefaultMethods() throws Throwable {
@@ -176,7 +142,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 180, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 137, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -184,10 +150,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- // TODO(b/120814598): Should be 24. Some lambdas are not class inlined because parameter
- // usages for lambda methods are not present for the class inliner.
- // TODO(b/141719453): Also, some are not inined due to instruction limits.
- .withDexCheck(inspector -> checkLambdaCount(inspector, 39, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
.run();
}
@@ -201,7 +164,7 @@
.withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 31, "lambdadesugaringnplus"))
.run();
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -211,10 +174,7 @@
.withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
- // TODO(b/120814598): Should be 5. Some lambdas are not class inlined because parameter
- // usages for lambda methods are not present for the class inliner.
- // TODO(b/141719453): Also, some are not inined due to instruction limits.
- .withDexCheck(inspector -> checkLambdaCount(inspector, 24, "lambdadesugaringnplus"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
.run();
}
@@ -228,7 +188,7 @@
.withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 31, "lambdadesugaringnplus"))
.run();
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -238,10 +198,7 @@
.withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
- // TODO(b/120814598): Should be 5. Some lambdas are not class inlined because parameter
- // usages for lambda methods are not present for the class inliner.
- // TODO(b/141719453): Also, some are not inined due to instruction limits.
- .withDexCheck(inspector -> checkLambdaCount(inspector, 24, "lambdadesugaringnplus"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
.run();
}
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 415abb1..50eb3be 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -8,15 +8,12 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.naming.retrace.StackTrace;
-import com.android.tools.r8.retrace.Retrace;
-import com.android.tools.r8.retrace.RetraceCommand;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingBiConsumer;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.graphinspector.GraphInspector;
import java.io.IOException;
-import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
@@ -73,27 +70,20 @@
return graphInspector.get();
}
- public R8TestRunResult inspectGraph(Consumer<GraphInspector> consumer)
+ public R8TestRunResult inspectGraph(Consumer<GraphInspector> consumer)
throws IOException, ExecutionException {
consumer.accept(graphInspector());
return self();
}
- public String proguardMap() {
- return proguardMap;
+ public <E extends Throwable> R8TestRunResult inspectStackTrace(
+ ThrowingBiConsumer<StackTrace, CodeInspector, E> consumer)
+ throws E, IOException, ExecutionException {
+ consumer.accept(getStackTrace(), new CodeInspector(app, proguardMap));
+ return self();
}
- public List<String> retrace() {
- class Box {
- List<String> result;
- }
- Box box = new Box();
- Retrace.run(
- RetraceCommand.builder()
- .setProguardMapProducer(() -> proguardMap)
- .setStackTrace(StringUtils.splitLines(getStdErr()))
- .setRetracedStackTraceConsumer(retraced -> box.result = retraced)
- .build());
- return box.result;
+ public String proguardMap() {
+ return proguardMap;
}
}
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index a6522b1..daab81d 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -369,15 +369,6 @@
}
@Test
- public void lambdaDesugaringCreateMethod() throws Throwable {
- test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
- .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
- .withKeepAll()
- .withOptionConsumer(o -> o.testing.enableStatefulLambdaCreateInstanceMethod = true)
- .run();
- }
-
- @Test
public void lambdaDesugaringNPlus() throws Throwable {
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
.withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 57eae6b..00d991a 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -27,6 +27,7 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
@@ -34,6 +35,7 @@
import com.android.tools.r8.graph.SmaliWriter;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.references.TypeReference;
@@ -75,6 +77,7 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
@@ -208,6 +211,19 @@
return JavaCompilerTool.create(jdk, temp);
}
+ public static KotlinCompilerTool kotlinc(
+ CfRuntime jdk,
+ TemporaryFolder temp,
+ KotlinCompiler kotlinCompiler,
+ KotlinTargetVersion kotlinTargetVersion) {
+ return KotlinCompilerTool.create(jdk, temp, kotlinCompiler, kotlinTargetVersion);
+ }
+
+ public static KotlinCompilerTool kotlinc(
+ KotlinCompiler kotlinCompiler, KotlinTargetVersion kotlinTargetVersion) {
+ return kotlinc(TestRuntime.getCheckedInJdk9(), staticTemp, kotlinCompiler, kotlinTargetVersion);
+ }
+
public KotlinCompilerTool kotlinc(
CfRuntime jdk, KotlinCompiler kotlinCompiler, KotlinTargetVersion kotlinTargetVersion) {
return KotlinCompilerTool.create(jdk, temp, kotlinCompiler, kotlinTargetVersion);
@@ -588,6 +604,17 @@
return factory.createType(type.getDescriptor());
}
+ protected static DexField buildField(Field field, DexItemFactory factory) {
+ return buildField(Reference.fieldFromField(field), factory);
+ }
+
+ protected static DexField buildField(FieldReference field, DexItemFactory factory) {
+ return factory.createField(
+ buildType(field.getHolderClass(), factory),
+ buildType(field.getFieldType(), factory),
+ field.getFieldName());
+ }
+
protected static DexMethod buildMethod(Method method, DexItemFactory factory) {
return buildMethod(Reference.methodFromMethod(method), factory);
}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
index 249e39e..c188c87 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
@@ -6,7 +6,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -27,7 +26,9 @@
@Parameters(name = "{1}, minification: {0}")
public static List<Object[]> data() {
- return buildParameters(BooleanUtils.values(), getTestParameters().withDexRuntimes().build());
+ return buildParameters(
+ BooleanUtils.values(),
+ getTestParameters().withDexRuntimes().withAllRuntimesAndApiLevels().build());
}
public KeepNonVisibilityBridgeMethodsTest(boolean minification, TestParameters parameters) {
@@ -50,12 +51,13 @@
" synthetic void registerObserver(...);",
"}")
.allowAccessModification()
+ .enableClassInliningAnnotations()
// TODO(b/120764902): MemberSubject.getOriginalName() is not working without the @NeverMerge
// annotation on DataAdapter.Observer.
.enableMergeAnnotations()
.enableProguardTestOptions()
.minification(minification)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(
inspector -> {
@@ -63,7 +65,7 @@
assertThat(classSubject, isPresent());
MethodSubject subject = classSubject.uniqueMethodWithName("registerObserver");
- assertTrue(subject.isPresent());
+ assertThat(subject, isPresent());
})
.run(parameters.getRuntime(), Main.class)
.assertSuccess();
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleDataAdapter.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleDataAdapter.java
index 148a61e..cc60012 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleDataAdapter.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleDataAdapter.java
@@ -4,8 +4,10 @@
package com.android.tools.r8.bridgeremoval.bridgestokeep;
-public class SimpleDataAdapter
- extends SimpleObservableList<DataAdapter.Observer>
+import com.android.tools.r8.NeverClassInline;
+
+@NeverClassInline
+public class SimpleDataAdapter extends SimpleObservableList<DataAdapter.Observer>
implements DataAdapter {
public SimpleDataAdapter() {
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index 82385a2..fc3f22f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -338,11 +338,15 @@
"classmerging.LambdaRewritingTest",
"classmerging.LambdaRewritingTest$Function",
"classmerging.LambdaRewritingTest$InterfaceImpl");
- runTest(
+ runTestOnInput(
main,
- programFiles,
+ readProgramFiles(programFiles),
name -> preservedClassNames.contains(name) || name.contains("$Lambda$"),
- getProguardConfig(JAVA8_EXAMPLE_KEEP));
+ getProguardConfig(JAVA8_EXAMPLE_KEEP),
+ options -> {
+ this.configure(options);
+ options.enableClassInlining = false;
+ });
}
@Test
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/StreamBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/StreamBackportJava9Test.java
new file mode 100644
index 0000000..0f08ff9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/StreamBackportJava9Test.java
@@ -0,0 +1,42 @@
+// 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.desugar.backports;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.stream.Stream;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public final class StreamBackportJava9Test extends AbstractBackportTest {
+ @Parameters(name = "{0}")
+ public static Iterable<?> data() {
+ return getTestParameters()
+ .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
+ .withCfRuntimesStartingFromIncluding(CfVm.JDK9)
+ .build();
+ }
+
+ private static final Path TEST_JAR =
+ Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR).resolve("backport" + JAR_EXTENSION);
+
+ public StreamBackportJava9Test(TestParameters parameters) {
+ super(parameters, Stream.class, TEST_JAR, "backport.StreamBackportJava9Main");
+ // Available since N as part of library desugaring.
+ ignoreInvokes("of");
+ ignoreInvokes("empty");
+ ignoreInvokes("count");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
new file mode 100644
index 0000000..edaf321
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
@@ -0,0 +1,254 @@
+// 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.desugar.desugaredlibrary;
+
+import static junit.framework.TestCase.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Spliterator;
+import org.jetbrains.annotations.NotNull;
+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 CustomCollectionForwardingTest extends DesugaredLibraryTestBase {
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+ }
+
+ public CustomCollectionForwardingTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testCustomCollectionD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8()
+ .addInnerClasses(CustomCollectionForwardingTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .inspect(this::assertForwardingMethods)
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(
+ StringUtils.lines("false", "false", "false", "false", "false", "false"));
+ }
+
+ private void assertForwardingMethods(CodeInspector inspector) {
+ if (parameters.getApiLevel().getLevel() >= AndroidApiLevel.N.getLevel()) {
+ return;
+ }
+ ClassSubject cal = inspector.clazz(CustomArrayList.class);
+ MethodSubject spliteratorCal = cal.method("j$.util.Spliterator", "spliterator");
+ assertTrue(spliteratorCal.isPresent());
+ assertTrue(
+ spliteratorCal
+ .streamInstructions()
+ .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("List$-CC")));
+ MethodSubject streamCal = cal.method("j$.util.stream.Stream", "stream");
+ assertTrue(streamCal.isPresent());
+ assertTrue(
+ streamCal
+ .streamInstructions()
+ .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("Collection$-CC")));
+
+ ClassSubject clhs = inspector.clazz(CustomLinkedHashSet.class);
+ MethodSubject spliteratorClhs = clhs.method("j$.util.Spliterator", "spliterator");
+ assertTrue(spliteratorClhs.isPresent());
+ assertTrue(
+ spliteratorClhs
+ .streamInstructions()
+ .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("DesugarLinkedHashSet")));
+ MethodSubject streamClhs = clhs.method("j$.util.stream.Stream", "stream");
+ assertTrue(streamClhs.isPresent());
+ assertTrue(
+ streamClhs
+ .streamInstructions()
+ .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("Collection$-CC")));
+
+ ClassSubject cl = inspector.clazz(CustomList.class);
+ MethodSubject spliteratorCl = cl.method("j$.util.Spliterator", "spliterator");
+ assertTrue(spliteratorCl.isPresent());
+ assertTrue(
+ spliteratorCl
+ .streamInstructions()
+ .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("List$-CC")));
+ MethodSubject streamCl = cl.method("j$.util.stream.Stream", "stream");
+ assertTrue(streamCl.isPresent());
+ assertTrue(
+ streamCl
+ .streamInstructions()
+ .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("Collection$-CC")));
+ }
+
+ static class Executor {
+
+ public static void main(String[] args) {
+ CustomArrayList<Object> objects = new CustomArrayList<>();
+ System.out.println(objects.spliterator().hasCharacteristics(Spliterator.CONCURRENT));
+ System.out.println(objects.stream().spliterator().hasCharacteristics(Spliterator.CONCURRENT));
+
+ CustomLinkedHashSet<Object> objects2 = new CustomLinkedHashSet<>();
+ System.out.println(objects2.spliterator().hasCharacteristics(Spliterator.CONCURRENT));
+ System.out.println(
+ objects2.stream().spliterator().hasCharacteristics(Spliterator.CONCURRENT));
+
+ CustomList<Object> objects3 = new CustomList<>();
+ System.out.println(objects3.spliterator().hasCharacteristics(Spliterator.CONCURRENT));
+ System.out.println(
+ objects3.stream().spliterator().hasCharacteristics(Spliterator.CONCURRENT));
+ }
+ }
+
+ static class CustomArrayList<E> extends ArrayList<E> implements Collection<E> {}
+
+ static class CustomLinkedHashSet<E> extends LinkedHashSet<E> implements Collection<E> {}
+
+ static class CustomList<E> implements Collection<E>, List<E> {
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return false;
+ }
+
+ @NotNull
+ @Override
+ public Iterator<E> iterator() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Object[] toArray() {
+ return new Object[0];
+ }
+
+ @NotNull
+ @Override
+ public <T> T[] toArray(@NotNull T[] a) {
+ return null;
+ }
+
+ @Override
+ public boolean add(E e) {
+ return false;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsAll(@NotNull Collection<?> c) {
+ return false;
+ }
+
+ @Override
+ public boolean addAll(@NotNull Collection<? extends E> c) {
+ return false;
+ }
+
+ @Override
+ public boolean addAll(int index, @NotNull Collection<? extends E> c) {
+ return false;
+ }
+
+ @Override
+ public boolean removeAll(@NotNull Collection<?> c) {
+ return false;
+ }
+
+ @Override
+ public boolean retainAll(@NotNull Collection<?> c) {
+ return false;
+ }
+
+ @Override
+ public void clear() {}
+
+ @Override
+ public E get(int index) {
+ return null;
+ }
+
+ @Override
+ public E set(int index, E element) {
+ return null;
+ }
+
+ @Override
+ public void add(int index, E element) {}
+
+ @Override
+ public E remove(int index) {
+ return null;
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ return 0;
+ }
+
+ @Override
+ public int lastIndexOf(Object o) {
+ return 0;
+ }
+
+ @NotNull
+ @Override
+ public ListIterator<E> listIterator() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public ListIterator<E> listIterator(int index) {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public List<E> subList(int fromIndex, int toIndex) {
+ return null;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionInterfaceSuperTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionInterfaceSuperTest.java
new file mode 100644
index 0000000..d00811f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionInterfaceSuperTest.java
@@ -0,0 +1,328 @@
+// 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.desugar.desugaredlibrary;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Predicate;
+import org.jetbrains.annotations.NotNull;
+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 CustomCollectionInterfaceSuperTest extends DesugaredLibraryTestBase {
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimes().withAllApiLevels().build());
+ }
+
+ public CustomCollectionInterfaceSuperTest(
+ boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ private static final String EXPECTED_OUTPUT =
+ StringUtils.lines(
+ "removeIf from MyCol1",
+ "removeIf from MyCol1",
+ "removeIf from MyCol2",
+ "removeIf from MyCol1",
+ "removeIf from MyCol2",
+ "removeIf from MyCol1");
+
+ @Test
+ public void testCustomCollectionD8() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addInnerClasses(CustomCollectionInterfaceSuperTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ return;
+ }
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8()
+ .addInnerClasses(CustomCollectionInterfaceSuperTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .assertNoMessages()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ static class Main {
+
+ @SuppressWarnings({
+ "MismatchedQueryAndUpdateOfCollection",
+ "RedundantOperationOnEmptyContainer"
+ })
+ public static void main(String[] args) {
+ Col<Integer> ints = new Col<>();
+ Col1<Integer> ints1 = new Col1<>();
+ Col2<Integer> ints2 = new Col2<>();
+ ints.removeIf(x -> x == 1);
+ ints.superRemoveIf(x -> x == 2);
+ ints1.removeIf(x -> x == 3);
+ ints1.superRemoveIf(x -> x == 4);
+ ints2.removeIf(x -> x == 5);
+ ints2.superRemoveIf(x -> x == 6);
+ }
+ }
+
+ interface MyCol1<E> extends Collection<E> {
+
+ @Override
+ default boolean removeIf(Predicate<? super E> filter) {
+ System.out.println("removeIf from MyCol1");
+ return Collection.super.removeIf(filter);
+ }
+ }
+
+ interface MyCol2<E> extends MyCol1<E> {
+
+ @Override
+ default boolean removeIf(Predicate<? super E> filter) {
+ System.out.println("removeIf from MyCol2");
+ return MyCol1.super.removeIf(filter);
+ }
+ }
+
+ static class Col<E> implements Collection<E> {
+
+ public boolean superRemoveIf(Predicate<? super E> filter) {
+ return Collection.super.removeIf(filter);
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return false;
+ }
+
+ @NotNull
+ @Override
+ public Iterator<E> iterator() {
+ return Collections.emptyIterator();
+ }
+
+ @NotNull
+ @Override
+ public Object[] toArray() {
+ return new Object[0];
+ }
+
+ @NotNull
+ @Override
+ public <T> T[] toArray(@NotNull T[] a) {
+ return a;
+ }
+
+ @Override
+ public boolean add(E e) {
+ return false;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsAll(@NotNull Collection<?> c) {
+ return false;
+ }
+
+ @Override
+ public boolean addAll(@NotNull Collection<? extends E> c) {
+ return false;
+ }
+
+ @Override
+ public boolean removeAll(@NotNull Collection<?> c) {
+ return false;
+ }
+
+ @Override
+ public boolean retainAll(@NotNull Collection<?> c) {
+ return false;
+ }
+
+ @Override
+ public void clear() {}
+ }
+
+ static class Col1<E> implements MyCol1<E> {
+
+ public boolean superRemoveIf(Predicate<? super E> filter) {
+ return MyCol1.super.removeIf(filter);
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return false;
+ }
+
+ @NotNull
+ @Override
+ public Iterator<E> iterator() {
+ return Collections.emptyIterator();
+ }
+
+ @NotNull
+ @Override
+ public Object[] toArray() {
+ return new Object[0];
+ }
+
+ @NotNull
+ @Override
+ public <T> T[] toArray(@NotNull T[] a) {
+ return a;
+ }
+
+ @Override
+ public boolean add(E e) {
+ return false;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsAll(@NotNull Collection<?> c) {
+ return false;
+ }
+
+ @Override
+ public boolean addAll(@NotNull Collection<? extends E> c) {
+ return false;
+ }
+
+ @Override
+ public boolean removeAll(@NotNull Collection<?> c) {
+ return false;
+ }
+
+ @Override
+ public boolean retainAll(@NotNull Collection<?> c) {
+ return false;
+ }
+
+ @Override
+ public void clear() {}
+ }
+
+ static class Col2<E> implements MyCol2<E> {
+
+ public boolean superRemoveIf(Predicate<? super E> filter) {
+ return MyCol2.super.removeIf(filter);
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return false;
+ }
+
+ @NotNull
+ @Override
+ public Iterator<E> iterator() {
+ return Collections.emptyIterator();
+ }
+
+ @NotNull
+ @Override
+ public Object[] toArray() {
+ return new Object[0];
+ }
+
+ @NotNull
+ @Override
+ public <T> T[] toArray(@NotNull T[] a) {
+ return a;
+ }
+
+ @Override
+ public boolean add(E e) {
+ return false;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsAll(@NotNull Collection<?> c) {
+ return false;
+ }
+
+ @Override
+ public boolean addAll(@NotNull Collection<? extends E> c) {
+ return false;
+ }
+
+ @Override
+ public boolean removeAll(@NotNull Collection<?> c) {
+ return false;
+ }
+
+ @Override
+ public boolean retainAll(@NotNull Collection<?> c) {
+ return false;
+ }
+
+ @Override
+ public void clear() {}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
index 6085be7..cd3e666 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
@@ -59,6 +59,7 @@
.setMinApi(parameters.getApiLevel())
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
+ .assertNoMessages()
.inspect(
inspector -> {
this.assertCustomCollectionCallsCorrect(inspector, false);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java
index 220147f..b7181de 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java
@@ -81,7 +81,6 @@
.setMinApi(parameters.getApiLevel())
.enableCoreLibraryDesugaring(parameters.getApiLevel())
.compile()
- .disassemble()
.addDesugaredCoreLibraryRunClassPath(
this::buildDesugaredLibrary, parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
index 7716c96..ec4ba98 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.desugar.desugaredlibrary;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -78,22 +77,9 @@
}
private void checkResult(D8TestRunResult result) {
- // TODO(b/145506767): Default method desugaring fails to generate a library forwarding method.
- if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
- result.assertFailureWithErrorThatMatches(
- containsString(
- parameters
- .getRuntime()
- .asDex()
- .getVm()
- .getVersion()
- .isOlderThanOrEqual(Version.V4_4_4)
- ? "VerifyError"
- : AbstractMethodError.class.getName()));
- return;
- }
- // TODO(b/145504401): Execution on Art 7.0.0 has the wrong runtime behavior.
+ // TODO(b/145504401): Execution on Art 7.0.0 has the wrong runtime behavior (non-desugared).
if (parameters.isDexRuntime()
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)
&& parameters.getRuntime().asDex().getVm().getVersion().equals(Version.V7_0_0)) {
result.assertSuccessWithOutputLines("42", "42");
return;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index 17b2a61..61296c9 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -110,7 +110,10 @@
String[] lines = stdOut.split("\n");
assert lines.length % 2 == 0;
for (int i = 0; i < lines.length; i += 2) {
- assertEquals(lines[i], lines[i + 1]);
+ assertEquals(
+ "Different lines: " + lines[i] + " || " + lines[i + 1] + "\n" + stdOut,
+ lines[i],
+ lines[i + 1]);
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmulatedInterfacesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmulatedInterfacesTest.java
index 5851353..451a255 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmulatedInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmulatedInterfacesTest.java
@@ -58,7 +58,7 @@
private void assertEmulateInterfaceClassesPresentWithDispatchMethods(CodeInspector inspector) {
List<FoundClassSubject> emulatedInterfaces = getEmulatedInterfaces(inspector);
- int numDispatchClasses = 8;
+ int numDispatchClasses = 9;
assertThat(inspector.clazz("j$.util.Map$Entry$-EL"), not(isPresent()));
assertEquals(numDispatchClasses, emulatedInterfaces.size());
for (FoundClassSubject clazz : emulatedInterfaces) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
new file mode 100644
index 0000000..f6e0d0a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
@@ -0,0 +1,89 @@
+// 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.desugar.desugaredlibrary;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+import org.jetbrains.annotations.NotNull;
+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 IterableTest extends DesugaredLibraryTestBase {
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+ private static final String EXPECTED_OUTPUT =
+ StringUtils.lines("1", "2", "3", "4", "5", "Count: 4");
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimes().withAllApiLevels().build());
+ }
+
+ public IterableTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testIterable() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addInnerClasses(IterableTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ return;
+ }
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8()
+ .addInnerClasses(IterableTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Iterable<Integer> iterable = new MyIterable<>(Arrays.asList(1, 2, 3, 4, 5));
+ iterable.forEach(System.out::println);
+ Stream<Integer> stream = StreamSupport.stream(iterable.spliterator(), false);
+ System.out.println("Count: " + stream.filter(x -> x != 3).count());
+ }
+ }
+
+ static class MyIterable<E> implements Iterable<E> {
+
+ private Collection<E> collection;
+
+ public MyIterable(Collection<E> collection) {
+ this.collection = collection;
+ }
+
+ @NotNull
+ @Override
+ public Iterator<E> iterator() {
+ return collection.iterator();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
index eebea28..04a7258 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
@@ -5,11 +5,12 @@
package com.android.tools.r8.desugar.desugaredlibrary;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.GregorianCalendar;
+import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntUnaryOperator;
import org.junit.Test;
@@ -21,26 +22,33 @@
public class RetargetOverrideTest extends DesugaredLibraryTestBase {
private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
}
- public RetargetOverrideTest(TestParameters parameters) {
+ public RetargetOverrideTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
this.parameters = parameters;
}
@Test
public void testRetargetOverrideD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
String stdout =
testForD8()
.addInnerClasses(RetargetOverrideTest.class)
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.setMinApi(parameters.getApiLevel())
.compile()
.addDesugaredCoreLibraryRunClassPath(
- this::buildDesugaredLibrary, parameters.getApiLevel())
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
.run(parameters.getRuntime(), Executor.class)
.assertSuccess()
.getStdOut();
@@ -49,15 +57,19 @@
@Test
public void testRetargetOverrideR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
String stdout =
testForR8(Backend.DEX)
.addKeepMainRule(Executor.class)
.addInnerClasses(RetargetOverrideTest.class)
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.setMinApi(parameters.getApiLevel())
.compile()
.addDesugaredCoreLibraryRunClassPath(
- this::buildDesugaredLibrary, parameters.getApiLevel())
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
.run(parameters.getRuntime(), Executor.class)
.assertSuccess()
.getStdOut();
@@ -67,22 +79,18 @@
static class Executor {
public static void main(String[] args) {
- java.sql.Date date = new java.sql.Date(123456789);
- // The following one is not working on JVMs, but works on Android...
- System.out.println(date.toInstant());
- System.out.println("1970-01-02T10:17:36.789Z");
+ directTypes();
+ polyTypes();
+ baseTypes();
+ }
- GregorianCalendar gregCal = new GregorianCalendar(1990, 2, 22);
- System.out.println(gregCal.toInstant());
+ public static void directTypes() {
+ MyCalendarOverride myCal = new MyCalendarOverride(1990, 2, 22);
+ System.out.println(myCal.toZonedDateTime());
+ System.out.println("1990-11-22T00:00Z[GMT]");
+ System.out.println(myCal.toInstant());
System.out.println("1990-03-22T00:00:00Z");
- // TODO(b/142846107): Enable overrides of retarget core members.
- // MyCalendarOverride myCal = new MyCalendarOverride(1990, 2, 22);
- // System.out.println(myCal.toZonedDateTime());
- // System.out.println("1990-11-22T00:00Z[GMT]");
- // System.out.println(myCal.toInstant());
- // System.out.println("1990-03-22T00:00:00Z");
-
MyCalendarNoOverride myCalN = new MyCalendarNoOverride(1990, 2, 22);
System.out.println(myCalN.toZonedDateTime());
System.out.println("1990-03-22T00:00Z[GMT]");
@@ -93,10 +101,13 @@
System.out.println(myCalN.superToInstant());
System.out.println("1990-03-22T00:00:00Z");
- // TODO(b/142846107): Enable overrides of retarget core members.
- // MyDateOverride myDate = new MyDateOverride(123456789);
- // System.out.println(myDate.toInstant());
- // System.out.println("1970-01-02T10:17:45.789Z");
+ MyDateDoubleOverride myDateCast2 = new MyDateDoubleOverride(123456789);
+ System.out.println(myDateCast2.toInstant());
+ System.out.println("1970-01-02T10:17:48.789Z");
+
+ MyDateOverride myDate = new MyDateOverride(123456789);
+ System.out.println(myDate.toInstant());
+ System.out.println("1970-01-02T10:17:45.789Z");
MyDateNoOverride myDateN = new MyDateNoOverride(123456789);
System.out.println(myDateN.toInstant());
@@ -112,21 +123,58 @@
System.out.println(myAtomicInteger.updateAndGet(x -> x + 100));
System.out.println("145");
}
+
+ public static void polyTypes() {
+ Date myDateCast = new MyDateOverride(123456789);
+ System.out.println(myDateCast.toInstant());
+ System.out.println("1970-01-02T10:17:45.789Z");
+
+ Date myDateCast2 = new MyDateDoubleOverride(123456789);
+ System.out.println(myDateCast2.toInstant());
+ System.out.println("1970-01-02T10:17:48.789Z");
+
+ Date myDateN = new MyDateNoOverride(123456789);
+ System.out.println(myDateN.toInstant());
+ System.out.println("1970-01-02T10:17:36.789Z");
+
+ GregorianCalendar myCalCast = new MyCalendarOverride(1990, 2, 22);
+ System.out.println(myCalCast.toZonedDateTime());
+ System.out.println("1990-11-22T00:00Z[GMT]");
+ System.out.println(myCalCast.toInstant());
+ System.out.println("1990-03-22T00:00:00Z");
+
+ GregorianCalendar myCalN = new MyCalendarNoOverride(1990, 2, 22);
+ System.out.println(myCalN.toZonedDateTime());
+ System.out.println("1990-03-22T00:00Z[GMT]");
+ System.out.println(myCalN.toInstant());
+ System.out.println("1990-03-22T00:00:00Z");
+ }
+
+ public static void baseTypes() {
+ java.sql.Date date = new java.sql.Date(123456789);
+ // The following one is not working on JVMs, but works on Android...
+ System.out.println(date.toInstant());
+ System.out.println("1970-01-02T10:17:36.789Z");
+
+ GregorianCalendar gregCal = new GregorianCalendar(1990, 2, 22);
+ System.out.println(gregCal.toInstant());
+ System.out.println("1990-03-22T00:00:00Z");
+ }
}
- // static class MyCalendarOverride extends GregorianCalendar {
- //
- // public MyCalendarOverride(int year, int month, int dayOfMonth) {
- // super(year, month, dayOfMonth);
- // }
- //
- // // Cannot override toInstant (final).
- //
- // @Override
- // public ZonedDateTime toZonedDateTime() {
- // return super.toZonedDateTime().withMonth(11);
- // }
- // }
+ static class MyCalendarOverride extends GregorianCalendar {
+
+ public MyCalendarOverride(int year, int month, int dayOfMonth) {
+ super(year, month, dayOfMonth);
+ }
+
+ // Cannot override toInstant (final).
+
+ @Override
+ public ZonedDateTime toZonedDateTime() {
+ return super.toZonedDateTime().withMonth(11);
+ }
+ }
static class MyCalendarNoOverride extends GregorianCalendar {
public MyCalendarNoOverride(int year, int month, int dayOfMonth) {
@@ -142,17 +190,29 @@
}
}
- // static class MyDateOverride extends Date {
- //
- // public MyDateOverride(long date) {
- // super(date);
- // }
- //
- // @Override
- // public Instant toInstant() {
- // return super.toInstant().plusSeconds(9);
- // }
- // }
+ static class MyDateOverride extends Date {
+
+ public MyDateOverride(long date) {
+ super(date);
+ }
+
+ @Override
+ public Instant toInstant() {
+ return super.toInstant().plusSeconds(9);
+ }
+ }
+
+ static class MyDateDoubleOverride extends MyDateOverride {
+
+ public MyDateDoubleOverride(long date) {
+ super(date);
+ }
+
+ @Override
+ public Instant toInstant() {
+ return super.toInstant().plusSeconds(3);
+ }
+ }
static class MyDateNoOverride extends Date {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SynchronizedCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SynchronizedCollectionTest.java
new file mode 100644
index 0000000..a9df25b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SynchronizedCollectionTest.java
@@ -0,0 +1,52 @@
+// 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.desugar.desugaredlibrary;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SynchronizedCollectionTest extends DesugaredLibraryTestBase {
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+ }
+
+ public SynchronizedCollectionTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testExecution() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8()
+ .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR + "desugaredlib.jar"))
+ .addInnerClasses(SynchronizedCollectionTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), "desugaredlib.SynchronizedCollectionMain")
+ .assertSuccessWithOutput(StringUtils.lines("[1]", "2", "[2, 3]", "true", "2", "2", "2"));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionLargeWarningTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionLargeWarningTest.java
deleted file mode 100644
index fe0e1a5..0000000
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionLargeWarningTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-// 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.desugar.desugaredlibrary.conversiontests;
-
-import static org.hamcrest.CoreMatchers.startsWith;
-
-import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import java.nio.file.Path;
-import java.time.Clock;
-import java.util.function.Function;
-import java.util.stream.Stream;
-import org.junit.Test;
-
-public class APIConversionLargeWarningTest extends DesugaredLibraryTestBase {
-
- @Test
- public void testFinalMethod() throws Exception {
- Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
- testForD8()
- .setMinApi(AndroidApiLevel.B)
- .addProgramClasses(Executor.class)
- .addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
- .compile()
- .assertInfoMessageThatMatches(
- startsWith(
- "Desugared library API conversion: Generating a large wrapper for"
- + " java.util.stream.Stream"))
- .assertNoInfoMessageThatMatches(
- startsWith(
- "Desugared library API conversion: Generating a large wrapper for java.time.Clock"))
- .assertNoInfoMessageThatMatches(
- startsWith(
- "Desugared library API conversion: Generating a large wrapper for"
- + " java.util.function.Function"));
- }
-
- static class Executor {
-
- public static void main(String[] args) {
- CustomLibClass.callClock(Clock.systemUTC());
- CustomLibClass.callStream(Stream.empty());
- CustomLibClass.callFunction(x -> x);
- }
- }
-
- // This class will be put at compilation time as library and on the runtime class path.
- // This class is convenient for easy testing. Each method plays the role of methods in the
- // platform APIs for which argument/return values need conversion.
- static class CustomLibClass {
-
- public static void callStream(Stream stream) {}
-
- public static void callClock(Clock clock) {}
-
- public static void callFunction(Function<String, String> func) {}
- }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
index 4c2007f..1cb8f74 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
@@ -60,7 +60,7 @@
.setMinApi(parameters.getApiLevel())
.enableCoreLibraryDesugaring(parameters.getApiLevel())
.compile()
- .assertOnlyInfos() // No warnings.
+ .assertNoMessages()
.addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
.run(parameters.getRuntime(), Executor.class)
.assertSuccessWithOutput(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
index 433f56a..3f4a1ee 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
@@ -16,7 +16,6 @@
import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
@@ -78,11 +77,6 @@
"org/openjdk/tests/java/util/stream/WhileOpStatefulTest.java",
"org/openjdk/tests/java/util/stream/IterateTest.java",
"org/openjdk/tests/java/util/stream/WhileOpTest.java",
- // forEach failure
- "org/openjdk/tests/java/util/stream/FindFirstOpTest.java",
- "org/openjdk/tests/java/util/stream/MapOpTest.java",
- // Disabled because explicit cast done on a wrapped value.
- // "org/openjdk/tests/java/util/SplittableRandomTest.java",
// Assertion error
"org/openjdk/tests/java/util/stream/StreamCloseTest.java",
"org/openjdk/tests/java/util/stream/CollectAndSummaryStatisticsTest.java",
@@ -90,8 +84,9 @@
// J9 Random problem
"org/openjdk/tests/java/util/stream/LongPrimitiveOpsTests.java",
"org/openjdk/tests/java/util/stream/IntPrimitiveOpsTests.java",
- "org/openjdk/tests/java/util/stream/DistinctOpTest.java",
"org/openjdk/tests/java/util/stream/DoublePrimitiveOpsTests.java"
+ // Disabled because explicit cast done on a wrapped value.
+ // "org/openjdk/tests/java/util/SplittableRandomTest.java",
};
// Disabled because time to run > 1 min for each test.
@@ -112,6 +107,9 @@
private static String[] SUCCESSFUL_RUNNABLE_TESTS =
new String[] {
+ "org/openjdk/tests/java/util/stream/FindFirstOpTest.java",
+ "org/openjdk/tests/java/util/stream/MapOpTest.java",
+ "org/openjdk/tests/java/util/stream/DistinctOpTest.java",
"org/openjdk/tests/java/util/MapTest.java",
"org/openjdk/tests/java/util/FillableStringTest.java",
"org/openjdk/tests/java/util/stream/ForEachOpTest.java",
@@ -154,7 +152,6 @@
"takeWhile(",
"dropWhile(",
"iterate(",
- "ofNullable(",
"range(",
"doubles(",
// Collectors
@@ -233,6 +230,7 @@
compileResult.run(
parameters.getRuntime(), "TestNGMainRunner", verbosity, runnableTests.get(path));
assertTrue(
+ "Failure in " + path + "\n" + result,
result
.getStdOut()
.endsWith(
@@ -250,36 +248,15 @@
D8TestRunResult result =
compileResult.run(
parameters.getRuntime(), "TestNGMainRunner", verbosity, runnableTests.get(path));
- if (result
- .getStdOut()
- .endsWith(
- StringUtils.lines("Tests result in " + runnableTests.get(path) + ": SUCCESS"))) {
- // The test succeeds, this can happen on tests succeeding only on high API levels.
- assertTrue(
- parameters.getRuntime().asDex().getMinApiLevel().getLevel()
- >= AndroidApiLevel.N.getLevel());
- } else if (result.getStdOut().contains("java.lang.NoSuchMethodError")
+ if (result.getStdOut().contains("java.lang.NoSuchMethodError")
&& Arrays.stream(missingDesugaredMethods())
.anyMatch(method -> result.getStdOut().contains(method))) {
// TODO(b/134732760): support Java 9 APIs.
- } else if (result
- .getStdOut()
- .contains("java.lang.NoSuchMethodError: No interface method forEach")) {
- // TODO(b/134732760): fix tests no to use Iterable#forEach
} else if (result.getStdOut().contains("in class Ljava/util/Random")
&& result.getStdOut().contains("java.lang.NoSuchMethodError")) {
// TODO(b/134732760): Random Java 9 Apis, support or do not use them.
} else if (result.getStdOut().contains("java.lang.AssertionError")) {
// TODO(b/134732760): Investigate and fix these issues.
- } else if (result.getStdOut().contains("java.lang.NoClassDefFoundError")) {
- // Use of high library API on low API device, cannot do anything about this.
- if (!shrinkDesugaredLibrary) {
- assertTrue(
- result.getStdErr().contains(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX));
- }
- assertTrue(
- parameters.getRuntime().asDex().getMinApiLevel().getLevel()
- < AndroidApiLevel.N.getLevel());
} else {
String errorMessage = "STDOUT:\n" + result.getStdOut() + "STDERR:\n" + result.getStdErr();
fail(errorMessage);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
index 736acc5..f6a4285 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
@@ -71,14 +71,11 @@
"test.java.time.format.TestDateTimeFormatter",
"test.java.time.TestLocalDate",
};
- // TODO(b/134732760): Investigate why these tests fail.
- private static String[] forEachProblem =
+ private static String[] formattingProblem =
new String[] {
- // ForEach problem
"test.java.time.format.TestNarrowMonthNamesAndDayNames",
};
- private static String[] successes =
- new String[] {
+ private static String[] successes = new String[] {
"test.java.time.TestYearMonth",
"test.java.time.TestZonedDateTime",
"test.java.time.TestClock_Tick",
@@ -159,20 +156,19 @@
} else if (result.getStdErr().contains("no microsecond precision")) {
// Emulator precision, won't fix.
} else {
- assertTrue(result.getStdOut().contains(StringUtils.lines(success + ": SUCCESS")));
+ assertTrue(
+ "Failure in " + success + "\n" + result,
+ result.getStdOut().contains(StringUtils.lines(success + ": SUCCESS")));
}
}
- for (String success : forEachProblem) {
+ for (String issue : formattingProblem) {
D8TestRunResult result =
- compileResult.run(parameters.getRuntime(), "TestNGMainRunner", verbosity, success);
+ compileResult.run(parameters.getRuntime(), "TestNGMainRunner", verbosity, issue);
if (requiresAnyCoreLibDesugaring(parameters)) {
- assertTrue(
- result.getStdOut().contains("AssertionError")
- // TODO(b/143275651) raise the right error.
- || result.getStdOut().contains("NoClassDefFoundError: $r8$wrapper$java")
- || result.getStdOut().contains("forEach("));
+ // Fails due to formatting differences in desugared library.
+ assertTrue(result.getStdOut().contains("for style NARROW"));
} else {
- assertTrue(result.getStdOut().contains(StringUtils.lines(success + ": SUCCESS")));
+ assertTrue(result.getStdOut().contains(StringUtils.lines(issue + ": SUCCESS")));
}
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
index 91d0e98..cd8ec64 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.D8;
import com.android.tools.r8.D8TestBuilder;
import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.R8;
import com.android.tools.r8.TestDiagnosticMessages;
import com.android.tools.r8.TestParameters;
@@ -99,17 +100,18 @@
@Test
public void testHelloCompiledWithD8Dex() throws Exception {
Path helloOutput = temp.newFolder("helloOutput").toPath().resolve("out.zip").toAbsolutePath();
- compileR8ToDexWithD8()
- .run(
- parameters.getRuntime(),
- D8.class,
- "--release",
- "--output",
- helloOutput.toString(),
- "--lib",
- commandLinePathFor(ToolHelper.JAVA_8_RUNTIME),
- HELLO_PATH)
- .assertSuccess();
+ D8TestRunResult run =
+ compileR8ToDexWithD8()
+ .run(
+ parameters.getRuntime(),
+ D8.class,
+ "--release",
+ "--output",
+ helloOutput.toString(),
+ "--lib",
+ commandLinePathFor(ToolHelper.JAVA_8_RUNTIME),
+ HELLO_PATH);
+ run.assertSuccess();
verifyResult(helloOutput);
}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
index d448e1e..4b5e452 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
@@ -40,6 +40,15 @@
}
@Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramFiles(classesMatching("NestPvtMethodCallInlined"))
+ .run(parameters.getRuntime(), getMainClass("pvtCallInlined"))
+ .disassemble()
+ .assertSuccessWithOutput(getExpectedResult("pvtCallInlined"));
+ }
+
+ @Test
public void testPvtMethodCallInlined() throws Exception {
List<Path> toCompile = classesMatching("NestPvtMethodCallInlined");
testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/ReturnTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/ReturnTest.java
new file mode 100644
index 0000000..3317df1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/ReturnTest.java
@@ -0,0 +1,74 @@
+// 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.desugaring.interfacemethods;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ReturnTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ReturnTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReturn() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(ReturnTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(
+ StringUtils.lines(
+ "com.android.tools.r8.desugaring.interfacemethods.ReturnTest$SuperA",
+ "com.android.tools.r8.desugaring.interfacemethods.ReturnTest$A"));
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new SuperA().print();
+ new A().print();
+ }
+ }
+
+ static class SuperA implements SuperI {
+ public void print() {
+ SuperA a = get();
+ System.out.println(a.getClass().getName());
+ }
+ }
+
+ static class A extends SuperA implements I {
+ public void print() {
+ A a = get();
+ System.out.println(a.getClass().getName());
+ }
+ }
+
+ interface I extends SuperI {
+ default A get() {
+ return new A();
+ }
+ }
+
+ interface SuperI {
+ default SuperA get() {
+ return new SuperA();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/TrapeziumTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/TrapeziumTest.java
new file mode 100644
index 0000000..ee0308f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/TrapeziumTest.java
@@ -0,0 +1,61 @@
+// 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.desugaring.interfacemethods;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TrapeziumTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public TrapeziumTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testTrapezium() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(TrapeziumTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(StringUtils.lines("foo from superI", "foo from I"));
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new SuperA().foo();
+ new A().foo();
+ }
+ }
+
+ static class SuperA implements SuperI {}
+
+ static class A extends SuperA implements I {}
+
+ interface I extends SuperI {
+ default void foo() {
+ System.out.println("foo from I");
+ }
+ }
+
+ interface SuperI {
+ default void foo() {
+ System.out.println("foo from superI");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
index 5bf5a36..f71538c 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
@@ -3,48 +3,371 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
-import com.android.tools.r8.AsmTestBase;
-import com.android.tools.r8.ToolHelper;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.VmTestRunner;
-import com.android.tools.r8.VmTestRunner.IgnoreForRangeOfVmVersions;
-import com.android.tools.r8.graph.invokesuper.Consumer;
-import com.android.tools.r8.graph.invokesuper.InvokerClassDump;
-import com.android.tools.r8.graph.invokesuper.InvokerClassFailingDump;
-import com.android.tools.r8.graph.invokesuper.MainClass;
-import com.android.tools.r8.graph.invokesuper.MainClassFailing;
-import com.android.tools.r8.graph.invokesuper.SubLevel1;
-import com.android.tools.r8.graph.invokesuper.SubLevel2;
-import com.android.tools.r8.graph.invokesuper.SubclassOfInvokerClass;
-import com.android.tools.r8.graph.invokesuper.Super;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
-@RunWith(VmTestRunner.class)
-public class InvokeSuperTest extends AsmTestBase {
+@RunWith(Parameterized.class)
+public class InvokeSuperTest extends TestBase {
- @Test
- @IgnoreForRangeOfVmVersions(from = Version.V5_1_1, to = Version.V6_0_1)
- public void testInvokeSuperTargets() throws Exception {
- ensureSameOutput(MainClass.class.getCanonicalName(),
- ToolHelper.getClassAsBytes(MainClass.class),
- ToolHelper.getClassAsBytes(Consumer.class),
- ToolHelper.getClassAsBytes(Super.class),
- ToolHelper.getClassAsBytes(SubLevel1.class),
- ToolHelper.getClassAsBytes(SubLevel2.class),
- InvokerClassDump.dump(),
- ToolHelper.getClassAsBytes(SubclassOfInvokerClass.class));
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ final TestParameters parameters;
+
+ public InvokeSuperTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ static final String EXPECTED =
+ StringUtils.lines(
+ "superMethod in SubLevel2",
+ "superMethod in SubLevel2",
+ "superMethod in SubLevel2",
+ "java.lang.NoSuchMethodError",
+ "subLevel1Method in SubLevel2",
+ "subLevel1Method in SubLevel2",
+ "subLevel2Method in SubLevel2",
+ "From SubLevel1: otherSuperMethod in Super");
+
+ static final String UNEXPECTED_DEX_5_AND_6_OUTPUT =
+ StringUtils.lines(
+ "superMethod in Super",
+ "superMethod in SubLevel1",
+ "superMethod in SubLevel2",
+ "java.lang.NoSuchMethodError",
+ "subLevel1Method in SubLevel1",
+ "subLevel1Method in SubLevel2",
+ "subLevel2Method in SubLevel2",
+ "From SubLevel1: otherSuperMethod in Super");
+
+ String getExpectedOutput() {
+ if (parameters.isDexRuntime()) {
+ Version version = parameters.getRuntime().asDex().getVm().getVersion();
+ if (version.isAtLeast(Version.V5_1_1) && version.isOlderThanOrEqual(Version.V6_0_1)) {
+ return UNEXPECTED_DEX_5_AND_6_OUTPUT;
+ }
+ }
+ return EXPECTED;
}
@Test
- public void testInvokeSuperTargetsNonVerifying() throws Exception {
- ensureR8FailsWithCompilationError(MainClassFailing.class.getCanonicalName(),
- ToolHelper.getClassAsBytes(MainClassFailing.class),
- ToolHelper.getClassAsBytes(Consumer.class),
- ToolHelper.getClassAsBytes(Super.class),
- ToolHelper.getClassAsBytes(SubLevel1.class),
- ToolHelper.getClassAsBytes(SubLevel2.class),
- InvokerClassFailingDump.dump(),
- ToolHelper.getClassAsBytes(SubclassOfInvokerClass.class));
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(
+ MainClass.class,
+ Consumer.class,
+ Super.class,
+ SubLevel1.class,
+ SubLevel2.class,
+ SubClassOfInvokerClass.class)
+ .addProgramClassFileData(InvokerClassDump.dumpVerifying())
+ .run(parameters.getRuntime(), MainClass.class)
+ .assertSuccessWithOutput(getExpectedOutput());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(
+ MainClass.class,
+ Consumer.class,
+ Super.class,
+ SubLevel1.class,
+ SubLevel2.class,
+ SubClassOfInvokerClass.class)
+ .addProgramClassFileData(InvokerClassDump.dumpVerifying())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MainClass.class)
+ .run(parameters.getRuntime(), MainClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testReferenceNonVerifying() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(
+ MainClassFailing.class,
+ Consumer.class,
+ Super.class,
+ SubLevel1.class,
+ SubLevel2.class,
+ SubClassOfInvokerClass.class)
+ .addProgramClassFileData(InvokerClassDump.dumpNonVerifying())
+ .run(parameters.getRuntime(), MainClassFailing.class)
+ .apply(this::checkNonVerifyingResult);
+ }
+
+ private void checkNonVerifyingResult(TestRunResult<?> result) {
+ // The input is invalid and any JVM will fail at verification time.
+ if (parameters.isCfRuntime()) {
+ result.assertFailureWithErrorThatMatches(containsString(VerifyError.class.getName()));
+ return;
+ }
+ // D8 cannot verify its inputs and the behavior of the compiled output differs.
+ // The failure is due to lambda desugaring on pre-7 and fails at runtime on 7+.
+ Version version = parameters.getRuntime().asDex().getVm().getVersion();
+ if (version.isOlderThanOrEqual(Version.V6_0_1)) {
+ result.assertFailureWithErrorThatMatches(
+ allOf(containsString("java.lang.NoClassDefFoundError"), containsString("-$$Lambda$")));
+ return;
+ }
+ result.assertSuccessWithOutputLines(NoSuchMethodError.class.getName());
+ }
+
+ @Test
+ public void testR8NonVerifying() throws Exception {
+ try {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(
+ MainClassFailing.class,
+ Consumer.class,
+ Super.class,
+ SubLevel1.class,
+ SubLevel2.class,
+ SubClassOfInvokerClass.class)
+ .addProgramClassFileData(InvokerClassDump.dumpNonVerifying())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MainClassFailing.class)
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertErrorMessageThatMatches(containsString("Illegal invoke-super"));
+ });
+ fail("Expected compilation to fail");
+ } catch (CompilationFailedException e) {
+ // Expected compilation failure.
+ }
+ }
+
+ /** Copy of {@ref java.util.function.Consumer} to allow tests to run on early versions of art. */
+ interface Consumer<T> {
+
+ void accept(T item);
+ }
+
+ static class Super {
+
+ public void superMethod() {
+ System.out.println("superMethod in Super");
+ }
+
+ public void otherSuperMethod() {
+ System.out.println("otherSuperMethod in Super");
+ }
+ }
+
+ static class SubLevel1 extends Super {
+
+ @Override
+ public void superMethod() {
+ System.out.println("superMethod in SubLevel1");
+ }
+
+ public void subLevel1Method() {
+ System.out.println("subLevel1Method in SubLevel1");
+ }
+
+ public void otherSuperMethod() {
+ System.out.println("otherSuperMethod in SubLevel1");
+ }
+
+ public void callOtherSuperMethod() {
+ System.out.print("From SubLevel1: ");
+ super.otherSuperMethod();
+ }
+ }
+
+ static class SubClassOfInvokerClass extends InvokerClass {
+
+ public void subLevel2Method() {
+ System.out.println("subLevel2Method in SubClassOfInvokerClass");
+ }
+ }
+
+ static class SubLevel2 extends SubLevel1 {
+
+ @Override
+ public void superMethod() {
+ System.out.println("superMethod in SubLevel2");
+ }
+
+ @Override
+ public void subLevel1Method() {
+ System.out.println("subLevel1Method in SubLevel2");
+ }
+
+ public void subLevel2Method() {
+ System.out.println("subLevel2Method in SubLevel2");
+ }
+
+ public void otherSuperMethod() {
+ System.out.println("otherSuperMethod in SubLevel2");
+ }
+
+ public void callOtherSuperMethodIndirect() {
+ callOtherSuperMethod();
+ }
+ }
+
+ static class MainClass {
+
+ private static void tryInvoke(Consumer<InvokerClass> function) {
+ InvokerClass invoker = new InvokerClass();
+ try {
+ function.accept(invoker);
+ } catch (Throwable e) {
+ System.out.println(e.getClass().getCanonicalName());
+ }
+ }
+
+ public static void main(String... args) {
+ tryInvoke(InvokerClass::invokeSuperMethodOnSuper);
+ tryInvoke(InvokerClass::invokeSuperMethodOnSubLevel1);
+ tryInvoke(InvokerClass::invokeSuperMethodOnSubLevel2);
+ tryInvoke(InvokerClass::invokeSubLevel1MethodOnSuper);
+ tryInvoke(InvokerClass::invokeSubLevel1MethodOnSubLevel1);
+ tryInvoke(InvokerClass::invokeSubLevel1MethodOnSubLevel2);
+ tryInvoke(InvokerClass::invokeSubLevel2MethodOnSubLevel2);
+ tryInvoke(InvokerClass::callOtherSuperMethodIndirect);
+ }
+ }
+
+ static class MainClassFailing {
+
+ private static void tryInvoke(java.util.function.Consumer<InvokerClass> function) {
+ InvokerClass invoker = new InvokerClass();
+ try {
+ function.accept(invoker);
+ } catch (Throwable e) {
+ System.out.println(e.getClass().getCanonicalName());
+ }
+ }
+
+ public static void main(String... args) {
+ tryInvoke(InvokerClass::invokeSubLevel2MethodOnSubClassOfInvokerClass);
+ }
+ }
+
+ /**
+ * This class is a stub class needed to compile the dependent Java classes. The actual
+ * implementation that will be used at runtime is generated by {@link InvokerClassDump}.
+ */
+ static class InvokerClass extends SubLevel2 {
+
+ public void invokeSuperMethodOnSubLevel2() {
+ stubIsUnreachable();
+ }
+
+ public void invokeSuperMethodOnSubLevel1() {
+ stubIsUnreachable();
+ }
+
+ public void invokeSuperMethodOnSuper() {
+ stubIsUnreachable();
+ }
+
+ public void invokeSubLevel1MethodOnSubLevel2() {
+ stubIsUnreachable();
+ }
+
+ public void invokeSubLevel1MethodOnSubLevel1() {
+ stubIsUnreachable();
+ }
+
+ public void invokeSubLevel1MethodOnSuper() {
+ stubIsUnreachable();
+ }
+
+ public void invokeSubLevel2MethodOnSubLevel2() {
+ stubIsUnreachable();
+ }
+
+ public void invokeSubLevel2MethodOnSubClassOfInvokerClass() {
+ stubIsUnreachable();
+ }
+
+ private static void stubIsUnreachable() {
+ throw new RuntimeException("Stub should never be called.");
+ }
+ }
+
+ // This modifies the above {@link InvokerClass} with invoke-special for the corresponding methods.
+ static class InvokerClassDump implements Opcodes {
+
+ public static byte[] dumpVerifying() throws Exception {
+ return dump(true);
+ }
+
+ public static byte[] dumpNonVerifying() throws Exception {
+ return dump(false);
+ }
+
+ static byte[] dump(boolean verifying) throws Exception {
+ return transformer(InvokerClass.class)
+ .addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access,
+ String name,
+ String descriptor,
+ String signature,
+ String[] exceptions) {
+ // Keep the constructor as is.
+ if (name.equals("<init>")) {
+ return super.visitMethod(access, name, descriptor, signature, exceptions);
+ }
+ // Remove all methods not in the form of invokeXonY.
+ if (!name.startsWith("invoke")) {
+ return null;
+ }
+ // If dumping valid methods drop the invoke on a subclass, otherwise drop all
+ // others.
+ if (verifying == name.equals("invokeSubLevel2MethodOnSubClassOfInvokerClass")) {
+ return null;
+ }
+ // Replace the body of invokeXonY methods by invoke of X on class Y.
+ MethodVisitor mv =
+ super.visitMethod(access, name, descriptor, signature, exceptions);
+ int split = name.indexOf("On");
+ assertTrue(split > 0);
+ String targetMethodRaw = name.substring("invoke".length(), split);
+ String targetMethod =
+ targetMethodRaw.substring(0, 1).toLowerCase() + targetMethodRaw.substring(1);
+ String targetHolderRaw = name.substring(split + 2);
+ String targetHolderType =
+ InvokeSuperTest.class.getTypeName() + "$" + targetHolderRaw;
+ String targetHolderName =
+ DescriptorUtils.getBinaryNameFromJavaType(targetHolderType);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, targetHolderName, targetMethod, "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ return null;
+ }
+ })
+ .transform();
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
index 67191c8..ecdaa6f 100644
--- a/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
@@ -58,7 +58,7 @@
}
@Test(expected = CompilationFailedException.class)
- public void testCfInvokeOnStaticInterfaceMethod_failed()
+ public void testCfInvokeOnStaticInterfaceMethod_errorNotAllowed()
throws ExecutionException, CompilationFailedException, IOException {
testForR8(parameters.getBackend())
.addProgramClasses(I.class)
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/Consumer.java b/src/test/java/com/android/tools/r8/graph/invokesuper/Consumer.java
deleted file mode 100644
index b323136..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/Consumer.java
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) 2017, 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.invokesuper;
-
-/**
- * Copy of {@ref java.util.function.Consumer} to allow tests to run on early versions of art.
- */
-public interface Consumer<T> {
-
- void accept(T item);
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/InvokerClass.java b/src/test/java/com/android/tools/r8/graph/invokesuper/InvokerClass.java
deleted file mode 100644
index e0c9f93..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/InvokerClass.java
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) 2017, 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.invokesuper;
-
-import com.android.tools.r8.errors.Unreachable;
-
-/**
- * This class is a stub class needed to compile the dependent Java classes. The actual
- * implementation that will be used at runtime is generated by {@link InvokerClassDump} and {@link
- * InvokerClassFailingDump}.
- */
-public class InvokerClass extends SubLevel2 {
-
- public void invokeSuperMethodOnSubLevel2() {
- stubIsUnreachable();
- }
-
- public void invokeSuperMethodOnSubLevel1() {
- stubIsUnreachable();
- }
-
- public void invokeSuperMethodOnSuper() {
- stubIsUnreachable();
- }
-
- public void invokeSubLevel1MethodOnSubLevel2() {
- stubIsUnreachable();
- }
-
- public void invokeSubLevel1MethodOnSubLevel1() {
- stubIsUnreachable();
- }
-
- public void invokeSubLevel1MethodOnSuper() {
- stubIsUnreachable();
- }
-
- public void invokeSubLevel2MethodOnSubLevel2() {
- stubIsUnreachable();
- }
-
- public void invokeSubLevel2MethodOnSubClassOfInvokerClass() {
- stubIsUnreachable();
- }
-
- private static void stubIsUnreachable() {
- throw new Unreachable("Stub should never be called.");
- }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/InvokerClassDump.java b/src/test/java/com/android/tools/r8/graph/invokesuper/InvokerClassDump.java
deleted file mode 100644
index c01700d..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/InvokerClassDump.java
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright (c) 2017, 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.invokesuper;
-
-import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-
-/**
- * This is a modified version of {@link InvokerClass} with invoke special instructions corresponding
- * to the methods' names.
- */
-public class InvokerClassDump implements Opcodes {
-
- public static byte[] dump() throws Exception {
-
- ClassWriter cw = new ClassWriter(0);
- FieldVisitor fv;
- MethodVisitor mv;
- AnnotationVisitor av0;
-
- cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "com/android/tools/r8/graph/invokesuper/InvokerClass",
- null, "com/android/tools/r8/graph/invokesuper/SubLevel2", null);
-
- {
- mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
- mv.visitCode();
- mv.visitVarInsn(ALOAD, 0);
- mv.visitMethodInsn(INVOKESPECIAL, "com/android/tools/r8/graph/invokesuper/SubLevel2",
- "<init>", "()V", false);
- mv.visitInsn(RETURN);
- mv.visitMaxs(1, 1);
- mv.visitEnd();
- }
- {
- mv = cw.visitMethod(ACC_PUBLIC, "invokeSuperMethodOnSubLevel2", "()V", null, null);
- mv.visitCode();
- mv.visitVarInsn(ALOAD, 0);
- mv.visitMethodInsn(INVOKESPECIAL, "com/android/tools/r8/graph/invokesuper/SubLevel2",
- "superMethod", "()V", false);
- mv.visitInsn(RETURN);
- mv.visitMaxs(1, 1);
- mv.visitEnd();
- }
- {
- mv = cw.visitMethod(ACC_PUBLIC, "invokeSuperMethodOnSubLevel1", "()V", null, null);
- mv.visitCode();
- mv.visitVarInsn(ALOAD, 0);
- mv.visitMethodInsn(INVOKESPECIAL, "com/android/tools/r8/graph/invokesuper/SubLevel1",
- "superMethod", "()V", false);
- mv.visitInsn(RETURN);
- mv.visitMaxs(1, 1);
- mv.visitEnd();
- }
- {
- mv = cw.visitMethod(ACC_PUBLIC, "invokeSuperMethodOnSuper", "()V", null, null);
- mv.visitCode();
- mv.visitVarInsn(ALOAD, 0);
- mv.visitMethodInsn(INVOKESPECIAL, "com/android/tools/r8/graph/invokesuper/Super",
- "superMethod", "()V", false);
- mv.visitInsn(RETURN);
- mv.visitMaxs(1, 1);
- mv.visitEnd();
- }
- {
- mv = cw.visitMethod(ACC_PUBLIC, "invokeSubLevel1MethodOnSubLevel2", "()V", null, null);
- mv.visitCode();
- mv.visitVarInsn(ALOAD, 0);
- mv.visitMethodInsn(INVOKESPECIAL, "com/android/tools/r8/graph/invokesuper/SubLevel2",
- "subLevel1Method", "()V", false);
- mv.visitInsn(RETURN);
- mv.visitMaxs(1, 1);
- mv.visitEnd();
- }
- {
- mv = cw.visitMethod(ACC_PUBLIC, "invokeSubLevel1MethodOnSubLevel1", "()V", null, null);
- mv.visitCode();
- mv.visitVarInsn(ALOAD, 0);
- mv.visitMethodInsn(INVOKESPECIAL, "com/android/tools/r8/graph/invokesuper/SubLevel1",
- "subLevel1Method", "()V", false);
- mv.visitInsn(RETURN);
- mv.visitMaxs(1, 1);
- mv.visitEnd();
- }
- {
- mv = cw.visitMethod(ACC_PUBLIC, "invokeSubLevel1MethodOnSuper", "()V", null, null);
- mv.visitCode();
- mv.visitVarInsn(ALOAD, 0);
- mv.visitMethodInsn(INVOKESPECIAL, "com/android/tools/r8/graph/invokesuper/Super",
- "subLevel1Method", "()V", false);
- mv.visitInsn(RETURN);
- mv.visitMaxs(1, 1);
- mv.visitEnd();
- }
- {
- mv = cw.visitMethod(ACC_PUBLIC, "invokeSubLevel2MethodOnSubLevel2", "()V", null, null);
- mv.visitCode();
- mv.visitVarInsn(ALOAD, 0);
- mv.visitMethodInsn(INVOKESPECIAL, "com/android/tools/r8/graph/invokesuper/SubLevel2",
- "subLevel2Method", "()V", false);
- mv.visitInsn(RETURN);
- mv.visitMaxs(1, 1);
- mv.visitEnd();
- }
- // The below fails to verify on the JavaVM, so we cannot test it there.
- // {
- // mv = cw.visitMethod(ACC_PUBLIC, "invokeSubLevel2MethodOnSubClassOfInvokerClass", "()V",
- // null,
- // null);
- // mv.visitCode();
- // mv.visitVarInsn(ALOAD, 0);
- // mv.visitMethodInsn(INVOKESPECIAL,
- // "com/android/tools/r8/graph/invokesuper/SubclassOfInvokerClass",
- // "subLevel2Method", "()V", false);
- // mv.visitInsn(RETURN);
- // mv.visitMaxs(1, 1);
- // mv.visitEnd();
- // }
- cw.visitEnd();
-
- return cw.toByteArray();
- }
-}
-
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/InvokerClassFailingDump.java b/src/test/java/com/android/tools/r8/graph/invokesuper/InvokerClassFailingDump.java
deleted file mode 100644
index 18eebe4..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/InvokerClassFailingDump.java
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) 2017, 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.invokesuper;
-
-import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-
-/**
- /**
- * This is a modified version of {@link InvokerClass} with invoke special instructions corresponding
- * to the methods' names.
- * <p>
- * This class contains methods that do not verify on Java VMs.
- */
-public class InvokerClassFailingDump implements Opcodes {
-
- public static byte[] dump() throws Exception {
-
- ClassWriter cw = new ClassWriter(0);
- FieldVisitor fv;
- MethodVisitor mv;
- AnnotationVisitor av0;
-
- cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "com/android/tools/r8/graph/invokesuper/InvokerClass",
- null, "com/android/tools/r8/graph/invokesuper/SubLevel2", null);
-
- {
- mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
- mv.visitCode();
- mv.visitVarInsn(ALOAD, 0);
- mv.visitMethodInsn(INVOKESPECIAL, "com/android/tools/r8/graph/invokesuper/SubLevel2",
- "<init>", "()V", false);
- mv.visitInsn(RETURN);
- mv.visitMaxs(1, 1);
- mv.visitEnd();
- }
- {
- mv = cw.visitMethod(ACC_PUBLIC, "invokeSubLevel2MethodOnSubClassOfInvokerClass", "()V", null,
- null);
- mv.visitCode();
- mv.visitVarInsn(ALOAD, 0);
- mv.visitMethodInsn(INVOKESPECIAL,
- "com/android/tools/r8/graph/invokesuper/SubclassOfInvokerClass",
- "subLevel2Method", "()V", false);
- mv.visitInsn(RETURN);
- mv.visitMaxs(1, 1);
- mv.visitEnd();
- }
- cw.visitEnd();
-
- return cw.toByteArray();
- }
-}
-
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/MainClass.java b/src/test/java/com/android/tools/r8/graph/invokesuper/MainClass.java
deleted file mode 100644
index 80e4d09..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/MainClass.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2017, 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.invokesuper;
-
-public class MainClass {
-
- private static void tryInvoke(Consumer<InvokerClass> function) {
- InvokerClass invoker = new InvokerClass();
- try {
- function.accept(invoker);
- } catch (Throwable e) {
- System.out.println(e.getClass().getCanonicalName());
- }
- }
-
- public static void main(String... args) {
- tryInvoke(InvokerClass::invokeSuperMethodOnSuper);
- tryInvoke(InvokerClass::invokeSuperMethodOnSubLevel1);
- tryInvoke(InvokerClass::invokeSuperMethodOnSubLevel2);
- tryInvoke(InvokerClass::invokeSubLevel1MethodOnSuper);
- tryInvoke(InvokerClass::invokeSubLevel1MethodOnSubLevel1);
- tryInvoke(InvokerClass::invokeSubLevel1MethodOnSubLevel2);
- tryInvoke(InvokerClass::invokeSubLevel2MethodOnSubLevel2);
- tryInvoke(InvokerClass::callOtherSuperMethodIndirect);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/MainClassFailing.java b/src/test/java/com/android/tools/r8/graph/invokesuper/MainClassFailing.java
deleted file mode 100644
index c662ff3..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/MainClassFailing.java
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2017, 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.invokesuper;
-
-import java.util.function.Consumer;
-
-public class MainClassFailing {
-
- private static void tryInvoke(Consumer<InvokerClass> function) {
- InvokerClass invoker = new InvokerClass();
- try {
- function.accept(invoker);
- } catch (Throwable e) {
- System.out.println(e.getClass().getCanonicalName());
- }
- }
-
- public static void main(String... args) {
- tryInvoke(InvokerClass::invokeSubLevel2MethodOnSubClassOfInvokerClass);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/SubLevel1.java b/src/test/java/com/android/tools/r8/graph/invokesuper/SubLevel1.java
deleted file mode 100644
index 50fa633..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/SubLevel1.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2017, 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.invokesuper;
-
-public class SubLevel1 extends Super {
-
- @Override
- public void superMethod() {
- System.out.println("superMethod in SubLevel1");
- }
-
- public void subLevel1Method() {
- System.out.println("subLevel1Method in SubLevel1");
- }
-
- public void otherSuperMethod() {
- System.out.println("otherSuperMethod in SubLevel1");
- }
-
- public void callOtherSuperMethod() {
- System.out.print("From SubLevel1: ");
- super.otherSuperMethod();
- }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/SubLevel2.java b/src/test/java/com/android/tools/r8/graph/invokesuper/SubLevel2.java
deleted file mode 100644
index 9cb04ec..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/SubLevel2.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2017, 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.invokesuper;
-
-public class SubLevel2 extends SubLevel1 {
-
- @Override
- public void superMethod() {
- System.out.println("superMethod in SubLevel2");
- }
-
- @Override
- public void subLevel1Method() {
- System.out.println("subLevel1Method in SubLevel2");
- }
-
- public void subLevel2Method() {
- System.out.println("subLevel2Method in SubLevel2");
- }
-
- public void otherSuperMethod() {
- System.out.println("otherSuperMethod in SubLevel2");
- }
-
- public void callOtherSuperMethodIndirect() {
- callOtherSuperMethod();
- }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/SubclassOfInvokerClass.java b/src/test/java/com/android/tools/r8/graph/invokesuper/SubclassOfInvokerClass.java
deleted file mode 100644
index 77d6aab..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/SubclassOfInvokerClass.java
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright (c) 2017, 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.invokesuper;
-
-public class SubclassOfInvokerClass extends InvokerClass {
-
- public void subLevel2Method() {
- System.out.println("subLevel2Method in SubclassOfInvokerClass");
- }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/Super.java b/src/test/java/com/android/tools/r8/graph/invokesuper/Super.java
deleted file mode 100644
index 51596fe..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/Super.java
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2017, 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.invokesuper;
-
-public class Super {
-
- public void superMethod() {
- System.out.println("superMethod in Super");
- }
-
- public void otherSuperMethod() {
- System.out.println("otherSuperMethod in Super");
- }
-}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
index 3127150..82362aa 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -7,7 +7,6 @@
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.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
@@ -80,10 +79,6 @@
.compile()
.inspect(this::inspect);
- // TODO(b/112437944): Should never allow dynamicMethod() to be inlined unless MethodToInvoke is
- // guaranteed to be different from MethodToInvoke.BUILD_MESSAGE_INFO.
- assumeTrue(mains.size() > 1);
-
for (String main : mains) {
result.run(parameters.getRuntime(), main).assertSuccessWithOutput(getExpectedOutput(main));
}
@@ -160,39 +155,19 @@
}
private void inspect(CodeInspector outputInspector) {
- // TODO(b/112437944): Should only be present if proto2.BuilderWithReusedSettersTestClass.main()
- // is kept.
- assertThat(outputInspector.clazz(LITE_BUILDER), isPresent());
-
- // TODO(b/112437944): Should be absent.
+ boolean primitivesBuilderShouldBeLive =
+ mains.contains("proto2.BuilderWithReusedSettersTestClass");
assertThat(
- outputInspector.clazz("com.android.tools.r8.proto2.TestProto$NestedMessage$Builder"),
- isNestedMessageBuilderUsed(mains) ? isPresent() : not(isPresent()));
-
- // TODO(b/112437944): Should be absent.
- assertThat(
- outputInspector.clazz("com.android.tools.r8.proto2.TestProto$OuterMessage$Builder"),
- isOuterMessageBuilderUsed(mains) ? isPresent() : not(isPresent()));
-
- // TODO(b/112437944): Should only be present if proto2.BuilderWithReusedSettersTestClass.main()
- // is kept.
+ outputInspector.clazz(LITE_BUILDER),
+ primitivesBuilderShouldBeLive ? isPresent() : not(isPresent()));
assertThat(
outputInspector.clazz("com.android.tools.r8.proto2.TestProto$Primitives$Builder"),
- isPrimitivesBuilderUsed(mains) ? isPresent() : not(isPresent()));
- }
-
- private static boolean isNestedMessageBuilderUsed(List<String> mains) {
- return mains.contains("proto2.BuilderWithProtoBuilderSetterTestClass")
- || mains.contains("proto2.BuilderWithProtoSetterTestClass");
- }
-
- private static boolean isOuterMessageBuilderUsed(List<String> mains) {
- return isNestedMessageBuilderUsed(mains);
- }
-
- private static boolean isPrimitivesBuilderUsed(List<String> mains) {
- return mains.contains("proto2.BuilderWithOneofSetterTestClass")
- || mains.contains("proto2.BuilderWithPrimitiveSettersTestClass")
- || mains.contains("proto2.BuilderWithReusedSettersTestClass");
+ primitivesBuilderShouldBeLive ? isPresent() : not(isPresent()));
+ assertThat(
+ outputInspector.clazz("com.android.tools.r8.proto2.TestProto$OuterMessage$Builder"),
+ not(isPresent()));
+ assertThat(
+ outputInspector.clazz("com.android.tools.r8.proto2.TestProto$NestedMessage$Builder"),
+ not(isPresent()));
}
}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index aed2c26..eb62734 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -79,7 +79,6 @@
.addKeepMainRule("proto2.TestClass")
.addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
.addKeepRules(allGeneratedMessageLiteSubtypesAreInstantiatedRule())
- .addKeepRules(alwaysInlineNewSingularGeneratedExtensionRule())
.addOptionsModification(
options -> {
options.enableFieldBitAccessAnalysis = true;
diff --git a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
index 19b3857..a1ecab4 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
@@ -77,14 +77,6 @@
"}");
}
- static String alwaysInlineNewSingularGeneratedExtensionRule() {
- return StringUtils.lines(
- "-alwaysinline class com.google.protobuf.GeneratedMessageLite {",
- " com.google.protobuf.GeneratedMessageLite$GeneratedExtension"
- + " newSingularGeneratedExtension(...);",
- "}");
- }
-
static String keepAllProtosRule() {
return StringUtils.lines(
"-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }");
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java b/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
index b35254e..2d1f0e5 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
@@ -8,6 +8,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.ir.conversion.CallGraph.Node;
import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator;
import com.android.tools.r8.utils.InternalOptions;
@@ -148,24 +149,25 @@
nodes.add(n5);
nodes.add(n6);
- Set<Node> wave = Sets.newIdentityHashSet();
+ CallGraph callGraph = new CallGraph(nodes, null);
+ Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
- PostMethodProcessor.extractRoots(nodes, wave::add);
+ wave.addAll(callGraph.extractRoots());
assertEquals(2, wave.size());
- assertThat(wave, hasItem(n1));
- assertThat(wave, hasItem(n5));
+ assertThat(wave, hasItem(n1.method));
+ assertThat(wave, hasItem(n5.method));
wave.clear();
- PostMethodProcessor.extractRoots(nodes, wave::add);
+ wave.addAll(callGraph.extractRoots());
assertEquals(2, wave.size());
- assertThat(wave, hasItem(n2));
- assertThat(wave, hasItem(n6));
+ assertThat(wave, hasItem(n2.method));
+ assertThat(wave, hasItem(n6.method));
wave.clear();
- PostMethodProcessor.extractRoots(nodes, wave::add);
+ wave.addAll(callGraph.extractRoots());
assertEquals(2, wave.size());
- assertThat(wave, hasItem(n3));
- assertThat(wave, hasItem(n4));
+ assertThat(wave, hasItem(n3.method));
+ assertThat(wave, hasItem(n4.method));
assertTrue(nodes.isEmpty());
}
@@ -200,24 +202,25 @@
CycleEliminator cycleEliminator = new CycleEliminator(nodes, options);
assertEquals(1, cycleEliminator.breakCycles().numberOfRemovedEdges());
- Set<Node> wave = Sets.newIdentityHashSet();
+ CallGraph callGraph = new CallGraph(nodes, null);
+ Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
- PostMethodProcessor.extractRoots(nodes, wave::add);
+ wave.addAll(callGraph.extractRoots());
assertEquals(2, wave.size());
- assertThat(wave, hasItem(n1));
- assertThat(wave, hasItem(n5));
+ assertThat(wave, hasItem(n1.method));
+ assertThat(wave, hasItem(n5.method));
wave.clear();
- PostMethodProcessor.extractRoots(nodes, wave::add);
+ wave.addAll(callGraph.extractRoots());
assertEquals(2, wave.size());
- assertThat(wave, hasItem(n2));
- assertThat(wave, hasItem(n6));
+ assertThat(wave, hasItem(n2.method));
+ assertThat(wave, hasItem(n6.method));
wave.clear();
- PostMethodProcessor.extractRoots(nodes, wave::add);
+ wave.addAll(callGraph.extractRoots());
assertEquals(2, wave.size());
- assertThat(wave, hasItem(n3));
- assertThat(wave, hasItem(n4));
+ assertThat(wave, hasItem(n3.method));
+ assertThat(wave, hasItem(n4.method));
assertTrue(nodes.isEmpty());
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
index 880139f..8b027e4 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
@@ -126,22 +126,22 @@
assertNotNull(m4);
assertNotNull(m5);
- Set<Node> wave = Sets.newIdentityHashSet();
+ Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
- PostMethodProcessor.extractRoots(pg.nodes, wave::add);
+ wave.addAll(pg.extractRoots());
assertEquals(2, wave.size());
- assertThat(wave, hasItem(m1));
- assertThat(wave, hasItem(m5));
+ assertThat(wave, hasItem(m1.method));
+ assertThat(wave, hasItem(m5.method));
wave.clear();
- PostMethodProcessor.extractRoots(pg.nodes, wave::add);
+ wave.addAll(pg.extractRoots());
assertEquals(1, wave.size());
- assertThat(wave, hasItem(m2));
+ assertThat(wave, hasItem(m2.method));
wave.clear();
- PostMethodProcessor.extractRoots(pg.nodes, wave::add);
+ wave.addAll(pg.extractRoots());
assertEquals(1, wave.size());
- assertThat(wave, hasItem(m4));
+ assertThat(wave, hasItem(m4.method));
assertTrue(pg.nodes.isEmpty());
}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index 25c4a53..e5c7948 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -83,6 +83,7 @@
ObjectsMethods.class,
OptionalMethods.class,
ShortMethods.class,
+ StreamMethods.class,
StringMethods.class);
final TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/StreamMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/StreamMethods.java
new file mode 100644
index 0000000..65e6780
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/StreamMethods.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.backports;
+
+import java.util.stream.Stream;
+
+public class StreamMethods {
+ public static <T> Stream<T> ofNullable(T t) {
+ return t == null ? Stream.empty() : Stream.of(t);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
index 4fe7fde..9b424d3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
@@ -68,6 +68,7 @@
static class Host {
private static final Companion companion = new Companion();
+ @NeverClassInline
static class Companion {
@NeverInline
public void foo(Object arg) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithAccessibleStaticGetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithAccessibleStaticGetTest.java
new file mode 100644
index 0000000..596c9e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithAccessibleStaticGetTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlineInstanceInitializerWithAccessibleStaticGetTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInlineInstanceInitializerWithAccessibleStaticGetTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassInlineInstanceInitializerWithAccessibleStaticGetTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ assertThat(inspector.clazz(Candidate.class), not(isPresent()));
+ assertThat(inspector.clazz(CandidateBase.class), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Environment.VALUE = true;
+ System.out.print(new Candidate().get());
+ Environment.VALUE = false;
+ System.out.println(new Candidate().get());
+ }
+ }
+
+ @NeverMerge
+ static class CandidateBase {
+
+ final String f;
+
+ CandidateBase() {
+ if (Environment.VALUE) {
+ f = "Hello";
+ } else {
+ f = " world!";
+ }
+ }
+ }
+
+ static class Candidate extends CandidateBase {
+
+ @NeverInline
+ String get() {
+ return f;
+ }
+ }
+
+ static class Environment {
+
+ static boolean VALUE;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithCheckCastTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithCheckCastTest.java
new file mode 100644
index 0000000..c0f45d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithCheckCastTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlineInstanceInitializerWithCheckCastTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInlineInstanceInitializerWithCheckCastTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassInlineInstanceInitializerWithCheckCastTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ assertThat(inspector.clazz(Candidate.class), not(isPresent()));
+ assertThat(inspector.clazz(CandidateBase.class), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.print(new Candidate("Hello").get());
+ System.out.println(new Candidate(" world!").get());
+ }
+ }
+
+ @NeverMerge
+ static class CandidateBase {
+
+ final String f;
+
+ CandidateBase(Object o) {
+ f = (String) o;
+ }
+ }
+
+ static class Candidate extends CandidateBase {
+
+ Candidate(Object o) {
+ super(o);
+ }
+
+ @NeverInline
+ String get() {
+ return f;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIfTest.java
new file mode 100644
index 0000000..912374d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIfTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlineInstanceInitializerWithIfTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInlineInstanceInitializerWithIfTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassInlineInstanceInitializerWithIfTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ assertThat(inspector.clazz(Candidate.class), not(isPresent()));
+ assertThat(inspector.clazz(CandidateBase.class), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.print(new Candidate(true).get());
+ System.out.println(new Candidate(false).get());
+ }
+ }
+
+ @NeverMerge
+ static class CandidateBase {
+
+ final String f;
+
+ CandidateBase(boolean b) {
+ if (b) {
+ f = "Hello";
+ } else {
+ f = " world!";
+ }
+ }
+ }
+
+ static class Candidate extends CandidateBase {
+
+ Candidate(boolean b) {
+ super(b);
+ }
+
+ @NeverInline
+ String get() {
+ return f;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.java
new file mode 100644
index 0000000..6d70ba5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.optimize.classinliner.testclasses.ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses;
+import com.android.tools.r8.ir.optimize.classinliner.testclasses.ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.CandidateBase;
+import com.android.tools.r8.ir.optimize.classinliner.testclasses.ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.Environment;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlineInstanceInitializerWithInaccessibleStaticGetTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInlineInstanceInitializerWithInaccessibleStaticGetTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(
+ ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.class,
+ ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.class)
+ .addKeepMainRule(TestClass.class)
+ .allowClassInlinerGracefulExit()
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ assertThat(inspector.clazz(Candidate.class), isPresent());
+ assertThat(inspector.clazz(CandidateBase.class), isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Environment.setValue(true);
+ System.out.print(new Candidate().get());
+ Environment.setValue(false);
+ System.out.println(new Candidate().get());
+ }
+ }
+
+ static class Candidate extends CandidateBase {
+
+ @NeverInline
+ String get() {
+ return f;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest.java
new file mode 100644
index 0000000..c86e0fc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableMemberValuePropagationAnnotations()
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ assertThat(inspector.clazz(Candidate.class), isPresent());
+ assertThat(inspector.clazz(CandidateBase.class), isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(new Candidate().get());
+ }
+ }
+
+ @NeverMerge
+ static class CandidateBase {
+
+ CandidateBase() {
+ Escape.escape(getReceiver());
+ }
+
+ @NeverInline
+ Object getReceiver() {
+ return System.currentTimeMillis() >= 0 ? this : null;
+ }
+ }
+
+ static class Candidate extends CandidateBase {
+
+ @NeverInline
+ @NeverPropagateValue
+ String get() {
+ return "Hello world!";
+ }
+ }
+
+ static class Escape {
+
+ @NeverInline
+ static void escape(Object o) {
+ if (System.currentTimeMillis() < 0) {
+ System.out.println(o);
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInstanceOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInstanceOfTest.java
new file mode 100644
index 0000000..43e2998
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInstanceOfTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlineInstanceInitializerWithInstanceOfTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInlineInstanceInitializerWithInstanceOfTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassInlineInstanceInitializerWithInstanceOfTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ assertThat(inspector.clazz(Candidate.class), not(isPresent()));
+ assertThat(inspector.clazz(CandidateBase.class), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.print(new Candidate("42").get() ? "Hello" : " world!");
+ System.out.println(new Candidate(42).get() ? "Hello" : " world!");
+ }
+ }
+
+ @NeverMerge
+ static class CandidateBase {
+
+ final boolean f;
+
+ CandidateBase(Object o) {
+ f = o instanceof String;
+ }
+ }
+
+ static class Candidate extends CandidateBase {
+
+ Candidate(Object o) {
+ super(o);
+ }
+
+ @NeverInline
+ boolean get() {
+ return f;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineSingletonFieldOfOtherTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineSingletonFieldOfOtherTypeTest.java
new file mode 100644
index 0000000..18739a4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineSingletonFieldOfOtherTypeTest.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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 ClassInlineSingletonFieldOfOtherTypeTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInlineSingletonFieldOfOtherTypeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassInlineSingletonFieldOfOtherTypeTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ assertThat(inspector.clazz(Candidate.class), not(isPresent()));
+ assertThat(inspector.clazz(Container.class), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Container.getInstance().greet();
+ }
+ }
+
+ static class Candidate {
+
+ @NeverInline
+ void greet() {
+ System.out.println("Hello world!");
+ }
+ }
+
+ static class Container {
+
+ static Candidate INSTANCE = new Candidate();
+
+ static Candidate getInstance() {
+ return INSTANCE;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 325bdd5..1058615 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -11,6 +11,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -34,7 +35,6 @@
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
import com.android.tools.r8.utils.AndroidApp;
-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;
@@ -84,9 +84,9 @@
testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableInliningAnnotations()
+ .enableSideEffectAnnotations()
.addKeepMainRule(main)
.addKeepAttributes("LineNumberTable")
- .addOptionsModification(this::configure)
.allowAccessModification()
.noMinification()
.run(main)
@@ -168,10 +168,7 @@
AndroidApp compiled =
compileWithR8(
- builder.build(),
- getProguardConfig(mainClass.name),
- this::configure,
- parameters.getBackend());
+ builder.build(), getProguardConfig(mainClass.name), null, parameters.getBackend());
// Check that the code fails with an IncompatibleClassChangeError with Java.
ProcessResult javaResult =
@@ -200,9 +197,9 @@
testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableInliningAnnotations()
+ .enableSideEffectAnnotations()
.addKeepMainRule(main)
.addKeepAttributes("LineNumberTable")
- .addOptionsModification(this::configure)
.allowAccessModification()
.noMinification()
.run(main)
@@ -244,7 +241,6 @@
// TODO(b/143129517, 141719453): The limit seems to only be needed for DEX...
o.classInliningInstructionLimit = 100;
o.classInliningInstructionAllowance = 1000;
- configure(o);
})
.allowAccessModification()
.noMinification()
@@ -299,7 +295,6 @@
o -> {
// TODO(b/141719453): Identify single instances instead of increasing the limit.
o.classInliningInstructionLimit = 20;
- configure(o);
})
.allowAccessModification()
.enableInliningAnnotations()
@@ -323,12 +318,8 @@
.filter(name -> name.contains(LAMBDA_CLASS_NAME_PREFIX))
.collect(Collectors.toList()));
assertEquals(expectedTypes, collectTypes(clazz.uniqueMethodWithName("testStatefulLambda")));
-
- // TODO(b/120814598): Should be 0. Lambdas are not class inlined because parameter usage is not
- // available for each lambda constructor.
- assertEquals(
- 3,
- inspector.allClasses().stream().filter(ClassSubject::isSynthesizedJavaLambdaClass).count());
+ assertTrue(
+ inspector.allClasses().stream().noneMatch(ClassSubject::isSynthesizedJavaLambdaClass));
}
private String getProguardConfig(String main) {
@@ -338,8 +329,4 @@
"-allowaccessmodification",
"-keepattributes LineNumberTable");
}
-
- private void configure(InternalOptions options) {
- options.enableSideEffectAnalysis = false;
- }
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerWithSimpleSuperTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerWithSimpleSuperTypeTest.java
index 4cdbc84..b8b159c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerWithSimpleSuperTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerWithSimpleSuperTypeTest.java
@@ -31,7 +31,8 @@
@Parameters(name = "{1}, enable class inlining: {0}")
public static List<Object[]> data() {
- return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
}
public ClassInlinerWithSimpleSuperTypeTest(
@@ -48,7 +49,7 @@
.addOptionsModification(options -> options.enableClassInlining = enableClassInlining)
.enableInliningAnnotations()
.enableMergeAnnotations()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::verifyCandidateIsClassInlined)
.run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/C.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/C.java
index dcfb5eb..ae5ef86 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/C.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/C.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize.classinliner.code;
+import com.android.tools.r8.AssumeMayHaveSideEffects;
import com.android.tools.r8.NeverInline;
public class C {
@@ -27,16 +28,19 @@
}
}
+ @AssumeMayHaveSideEffects
@NeverInline
public static int method1() {
return new L(1).x;
}
+ @AssumeMayHaveSideEffects
@NeverInline
public static int method2() {
return new L(1).getX();
}
+ @AssumeMayHaveSideEffects
@NeverInline
public static int method3() {
return F.I.getX();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/testclasses/ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/testclasses/ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.java
new file mode 100644
index 0000000..50eeac4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/testclasses/ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.testclasses;
+
+import com.android.tools.r8.NeverMerge;
+
+public class ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses {
+
+ @NeverMerge
+ public static class CandidateBase {
+
+ public final String f;
+
+ public CandidateBase() {
+ if (Environment.VALUE) {
+ f = "Hello";
+ } else {
+ f = " world!";
+ }
+ }
+ }
+
+ public static class Environment {
+
+ /*package-private*/ static boolean VALUE;
+
+ public static void setValue(boolean value) {
+ VALUE = value;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
index 9b7835d..86cf25e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize.classinliner.trivial;
+import com.android.tools.r8.AssumeMayHaveSideEffects;
import com.android.tools.r8.NeverInline;
public class TrivialTestClass {
@@ -51,6 +52,7 @@
System.out.println(o.getA() + o.getB() + o.getConcat());
}
+ @AssumeMayHaveSideEffects
@NeverInline
private void testEmptyClass() {
new EmptyClass();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerShouldNotInlineDefinitelyNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerShouldNotInlineDefinitelyNullTest.java
index 654e0f2..be2ffe2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerShouldNotInlineDefinitelyNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerShouldNotInlineDefinitelyNullTest.java
@@ -13,7 +13,6 @@
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 com.android.tools.r8.utils.codeinspector.InstructionSubject;
import java.io.IOException;
import java.util.Arrays;
@@ -27,9 +26,6 @@
@RunWith(Parameterized.class)
public class InlinerShouldNotInlineDefinitelyNullTest extends TestBase {
- public static String EXPECTED_STACK_TRACE = StringUtils.joinLines(
- "java.lang.NullPointerException", " at " + Main.class.getTypeName() + ".main(");
-
public static class A {
void foo() {
@@ -78,13 +74,13 @@
.anyMatch(InstructionSubject::isThrow));
})
.run(parameters.getRuntime(), Main.class)
- .assertFailure();
+ .assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"))
+ .inspectStackTrace(
+ stackTrace -> {
+ assertThat(
+ stackTrace.toString(), containsString(Main.class.getTypeName() + ".main("));
+ });
String[] split = result.proguardMap().split("\n");
assertTrue(Arrays.stream(split).noneMatch(l -> l.contains(A.class.getTypeName() + ".foo")));
- assertThat(
- StringUtils.joinLines(result.retrace())
- .replace("\tat", " at")
- .replace(": throw with null exception", ""),
- containsString(EXPECTED_STACK_TRACE));
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
index 58ad103..b0bf180 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
@@ -70,12 +70,10 @@
testDefinitelyNotNullMethodSubject
.streamInstructions()
.noneMatch(InstructionSubject::isInstanceGet));
- // TODO(b/125282093): Should be able to remove the new-instance instruction since the instance
- // ends up being unused.
assertTrue(
testDefinitelyNotNullMethodSubject
.streamInstructions()
- .anyMatch(InstructionSubject::isNewInstance));
+ .noneMatch(InstructionSubject::isNewInstance));
// Verify that all instance-get instructions in testMaybeNull() has been removed by member value
// propagation.
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index a3ec1a2..177a8a5 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -25,7 +25,6 @@
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
@@ -37,15 +36,11 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.junit.Assume;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-@RunWith(Parameterized.class)
public abstract class AbstractR8KotlinTestBase extends KotlinTestBase {
// This is the name of the Jasmin-generated class which contains the "main" method which will
@@ -57,9 +52,9 @@
private final List<Path> classpath = new ArrayList<>();
private final List<Path> extraClasspath = new ArrayList<>();
- @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
- public static Collection<Object[]> data() {
- return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+ // Some tests defined in subclasses, e.g., Metadata tests, don't care about access relaxation.
+ protected AbstractR8KotlinTestBase(KotlinTargetVersion kotlinTargetVersion) {
+ this(kotlinTargetVersion, false);
}
protected AbstractR8KotlinTestBase(
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 38ff405..5d55ba2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -27,15 +28,24 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
+import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class KotlinClassInlinerTest extends AbstractR8KotlinTestBase {
+ @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+ }
+
public KotlinClassInlinerTest(
KotlinTargetVersion targetVersion, boolean allowAccessModification) {
super(targetVersion, allowAccessModification);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
index b32bc04..6087795 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
@@ -11,15 +11,25 @@
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
import com.google.common.base.Predicates;
+import java.util.Collection;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class KotlinClassStaticizerTest extends AbstractR8KotlinTestBase {
+ @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+ }
+
public KotlinClassStaticizerTest(
KotlinTargetVersion targetVersion, boolean allowAccessModification) {
super(targetVersion, allowAccessModification);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
index 8eb6a2e..35e3109 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
@@ -17,8 +17,10 @@
import java.util.Collection;
import java.util.function.Consumer;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class KotlinDuplicateAnnotationTest extends AbstractR8KotlinTestBase {
private static final String FOLDER = "duplicate_annotation";
private static final String MAIN = FOLDER + ".MainKt";
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
index 717462c..dc917ab 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
@@ -16,8 +16,10 @@
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.Collection;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class KotlinIntrinsicsInlineTest extends AbstractR8KotlinTestBase {
private static final String FOLDER = "intrinsics";
private static final String MAIN = FOLDER + ".InlineKt";
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java
index b85f583..6ad7046 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Result;
import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Source;
import com.android.tools.r8.utils.StringUtils;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -38,6 +39,7 @@
}
@Test
+ @Ignore("b/145985445")
public void testParsingNoInlineSources() {
String annotationData =
StringUtils.join(
@@ -66,6 +68,7 @@
}
@Test
+ @Ignore("b/145985445")
public void testParsingSimpleStrata() {
// Taken from src/test/examplesKotlin/retrace/mainKt
String annotationData =
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
index 95ec356..77de408 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
@@ -9,14 +9,25 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
+import java.util.Collection;
import java.util.function.Consumer;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class KotlinUnusedArgumentsInLambdasTest extends AbstractR8KotlinTestBase {
+
+ @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+ }
+
private Consumer<InternalOptions> optionsModifier =
o -> {
o.enableInlining = true;
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java
index e58c00c..c82a4a4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java
@@ -10,6 +10,7 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FieldSubject;
@@ -17,10 +18,19 @@
import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
+import java.util.Collection;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class KotlinUnusedSingletonTest extends AbstractR8KotlinTestBase {
+ @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+ }
+
private static final String printlnSignature =
"void java.io.PrintStream.println(java.lang.Object)";
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index cfeb611..e5ce02c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -17,17 +17,22 @@
import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FieldSubject;
import java.nio.file.Path;
+import java.util.Collection;
import java.util.Collections;
import java.util.function.Consumer;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class R8KotlinAccessorTest extends AbstractR8KotlinTestBase {
private static final String JAVA_LANG_STRING = "java.lang.String";
@@ -65,6 +70,11 @@
private Consumer<InternalOptions> disableClassStaticizer =
opts -> opts.enableClassStaticizer = false;
+ @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+ }
+
public R8KotlinAccessorTest(
KotlinTargetVersion targetVersion, boolean allowAccessModification) {
super(targetVersion, allowAccessModification);
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index 7e16b2f..59c92ff 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -8,14 +8,19 @@
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Collection;
import java.util.Collections;
import java.util.function.Consumer;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class R8KotlinDataClassTest extends AbstractR8KotlinTestBase {
private static final TestKotlinDataClass TEST_DATA_CLASS =
@@ -38,6 +43,11 @@
private Consumer<InternalOptions> disableClassInliner = o -> o.enableClassInlining = false;
+ @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+ }
+
public R8KotlinDataClassTest(
KotlinTargetVersion targetVersion, boolean allowAccessModification) {
super(targetVersion, allowAccessModification);
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
index c74b790..7a9635e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
@@ -6,10 +6,12 @@
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
+import java.util.Collection;
import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -21,6 +23,11 @@
private static final TestKotlinDataClass KOTLIN_INTRINSICS_CLASS =
new TestKotlinDataClass("kotlin.jvm.internal.Intrinsics");
+ @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+ }
+
public R8KotlinIntrinsicsTest(
KotlinTargetVersion targetVersion, boolean allowAccessModification) {
super(targetVersion, allowAccessModification);
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index fcb2872..f572789 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -10,13 +10,18 @@
import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import java.util.Collection;
import java.util.function.Consumer;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class R8KotlinPropertiesTest extends AbstractR8KotlinTestBase {
private static final String PACKAGE_NAME = "properties";
@@ -89,6 +94,11 @@
o.enableClassStaticizer = false;
};
+ @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+ }
+
public R8KotlinPropertiesTest(
KotlinTargetVersion targetVersion, boolean allowAccessModification) {
super(targetVersion, allowAccessModification);
diff --git a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
index b9ee37a..aebb0cf 100644
--- a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -7,16 +7,26 @@
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
+import java.util.Collection;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class SimplifyIfNotNullKotlinTest extends AbstractR8KotlinTestBase {
private static final String FOLDER = "non_null";
private static final String STRING = "java.lang.String";
+ @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+ }
+
public SimplifyIfNotNullKotlinTest(
KotlinTargetVersion targetVersion, boolean allowAccessModification) {
super(targetVersion, allowAccessModification);
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
new file mode 100644
index 0000000..1c46c19
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
@@ -0,0 +1,91 @@
+// 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.kotlin.lambda;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.nio.file.Path;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KotlinLambdaMergerValidationTest extends AbstractR8KotlinTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), KotlinTargetVersion.values());
+ }
+
+ public KotlinLambdaMergerValidationTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion, false);
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8_excludeKotlinStdlib() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+
+ String pkg = getClass().getPackage().getName();
+ String folder = DescriptorUtils.getBinaryNameFromJavaType(pkg);
+ CfRuntime cfRuntime =
+ parameters.isCfRuntime() ? parameters.getRuntime().asCf() : TestRuntime.getCheckedInJdk9();
+ Path ktClasses =
+ kotlinc(cfRuntime, KOTLINC, targetVersion)
+ .addSourceFiles(getKotlinFileInTest(folder, "b143165163"))
+ .compile();
+ testForR8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+ .addLibraryFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(ktClasses)
+ .addKeepMainRule("**.B143165163Kt")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ // TODO(b/143165163): better not output info like this.
+ .assertInfoMessageThatMatches(containsString("Unrecognized Kotlin lambda"))
+ .assertInfoMessageThatMatches(containsString("unexpected static method"))
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar())
+ .run(parameters.getRuntime(), pkg + ".B143165163Kt")
+ .assertSuccessWithOutputLines("outer foo bar", "outer foo default");
+ }
+
+ @Test
+ public void testR8_includeKotlinStdlib() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+
+ String pkg = getClass().getPackage().getName();
+ String folder = DescriptorUtils.getBinaryNameFromJavaType(pkg);
+ CfRuntime cfRuntime =
+ parameters.isCfRuntime() ? parameters.getRuntime().asCf() : TestRuntime.getCheckedInJdk9();
+ Path ktClasses =
+ kotlinc(cfRuntime, KOTLINC, targetVersion)
+ .addSourceFiles(getKotlinFileInTest(folder, "b143165163"))
+ .compile();
+ testForR8(parameters.getBackend())
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(ktClasses)
+ .addKeepMainRule("**.B143165163Kt")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ // TODO(b/143165163): better not output info like this.
+ .assertInfoMessageThatMatches(containsString("Unrecognized Kotlin lambda"))
+ .assertInfoMessageThatMatches(containsString("does not implement any interfaces"))
+ .run(parameters.getRuntime(), pkg + ".B143165163Kt")
+ .assertSuccessWithOutputLines("outer foo bar", "outer foo default");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
similarity index 98%
rename from src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
rename to src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
index d8c059d..bab791c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
@@ -2,7 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.kotlin;
+package com.android.tools.r8.kotlin.lambda;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
+import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -26,8 +27,10 @@
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class KotlinLambdaMergingTest extends AbstractR8KotlinTestBase {
private static final String KOTLIN_FUNCTION_IFACE = "Lkotlin/jvm/functions/Function";
private static final String KOTLIN_FUNCTION_IFACE_STR = "kotlin.jvm.functions.Function";
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithReprocessingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithReprocessingTest.java
similarity index 68%
rename from src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithReprocessingTest.java
rename to src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithReprocessingTest.java
index da30438..5ba846c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithReprocessingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithReprocessingTest.java
@@ -1,13 +1,19 @@
// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.kotlin;
+package com.android.tools.r8.kotlin.lambda;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions;
+import java.util.Collection;
import java.util.function.Consumer;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class KotlinLambdaMergingWithReprocessingTest extends AbstractR8KotlinTestBase {
private Consumer<InternalOptions> optionsModifier =
o -> {
@@ -16,6 +22,11 @@
o.enableLambdaMerging = true;
};
+ @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+ }
+
public KotlinLambdaMergingWithReprocessingTest(
KotlinTargetVersion targetVersion, boolean allowAccessModification) {
super(targetVersion, allowAccessModification);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithSmallInliningBudgetTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
similarity index 69%
rename from src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithSmallInliningBudgetTest.java
rename to src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
index 24b0524..446d982 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithSmallInliningBudgetTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
@@ -1,13 +1,19 @@
// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.kotlin;
+package com.android.tools.r8.kotlin.lambda;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions;
+import java.util.Collection;
import java.util.function.Consumer;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class KotlinLambdaMergingWithSmallInliningBudgetTest extends AbstractR8KotlinTestBase {
private Consumer<InternalOptions> optionsModifier =
o -> {
@@ -17,6 +23,11 @@
o.inliningInstructionAllowance = 3;
};
+ @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+ }
+
public KotlinLambdaMergingWithSmallInliningBudgetTest(
KotlinTargetVersion targetVersion, boolean allowAccessModification) {
super(targetVersion, allowAccessModification);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinxMetadataExtensionsServiceTest.java
similarity index 91%
rename from src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java
rename to src/test/java/com/android/tools/r8/kotlin/lambda/KotlinxMetadataExtensionsServiceTest.java
index ed135b0..26cc829 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinxMetadataExtensionsServiceTest.java
@@ -1,10 +1,10 @@
// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.kotlin;
+package com.android.tools.r8.kotlin.lambda;
import static com.android.tools.r8.ToolHelper.EXAMPLES_KOTLIN_RESOURCE_DIR;
-import static com.android.tools.r8.kotlin.KotlinLambdaMergingTest.kstyle;
+import static com.android.tools.r8.kotlin.lambda.KotlinLambdaMergingTest.kstyle;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -15,9 +15,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.kotlin.KotlinLambdaMergingTest.Group;
-import com.android.tools.r8.kotlin.KotlinLambdaMergingTest.Lambda;
-import com.android.tools.r8.kotlin.KotlinLambdaMergingTest.Verifier;
+import com.android.tools.r8.kotlin.lambda.KotlinLambdaMergingTest.Group;
+import com.android.tools.r8.kotlin.lambda.KotlinLambdaMergingTest.Lambda;
+import com.android.tools.r8.kotlin.lambda.KotlinLambdaMergingTest.Verifier;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b143165163.kt b/src/test/java/com/android/tools/r8/kotlin/lambda/b143165163.kt
new file mode 100644
index 0000000..d8d7a49
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b143165163.kt
@@ -0,0 +1,13 @@
+// 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.kotlin.lambda
+
+fun main() {
+ fun String.inner(foo: String, bar: String = "default") {
+ println("$this $foo $bar")
+ }
+ "outer".inner("foo", "bar")
+ "outer".inner("foo")
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
index cdb5538..d866cca 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
@@ -3,11 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.kotlin.metadata;
-import com.android.tools.r8.KotlinTestBase;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
import com.android.tools.r8.utils.DescriptorUtils;
-abstract class KotlinMetadataTestBase extends KotlinTestBase {
+abstract class KotlinMetadataTestBase extends AbstractR8KotlinTestBase {
KotlinMetadataTestBase(KotlinTargetVersion targetVersion) {
super(targetVersion);
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java
new file mode 100644
index 0000000..d5297d1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java
@@ -0,0 +1,168 @@
+// 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRenameInClasspathTypeTest extends KotlinMetadataTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRenameInClasspathTypeTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static Path baseLibJar;
+ private static Path extLibJar;
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ String baseLibFolder = PKG_PREFIX + "/classpath_lib_base";
+ baseLibJar =
+ kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addSourceFiles(getKotlinFileInTest(baseLibFolder, "itf"))
+ .compile();
+ String extLibFolder = PKG_PREFIX + "/classpath_lib_ext";
+ extLibJar =
+ kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addClasspathFiles(baseLibJar)
+ .addSourceFiles(getKotlinFileInTest(extLibFolder, "impl"))
+ .compile();
+ }
+
+ @Test
+ public void testMetadataInClasspathType_merged() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(baseLibJar)
+ .addProgramFiles(extLibJar)
+ // Keep the Extra class and its interface (which has the method).
+ .addKeepRules("-keep class **.Extra")
+ // Keep the ImplKt extension method which requires metadata
+ // to be called with Kotlin syntax from other kotlin code.
+ .addKeepRules("-keep class **.ImplKt { <methods>; }")
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile();
+ String pkg = getClass().getPackage().getName();
+ final String implClassName = pkg + ".classpath_lib_ext.Impl";
+ final String extraClassName = pkg + ".classpath_lib_ext.Extra";
+ compileResult.inspect(inspector -> {
+ ClassSubject impl = inspector.clazz(implClassName);
+ assertThat(impl, not(isPresent()));
+
+ ClassSubject extra = inspector.clazz(extraClassName);
+ assertThat(extra, isPresent());
+ assertThat(extra, not(isRenamed()));
+ // API entry is kept, hence the presence of Metadata.
+ KmClassSubject kmClass = extra.getKmClass();
+ assertThat(kmClass, isPresent());
+ List<ClassSubject> superTypes = kmClass.getSuperTypes();
+ assertTrue(superTypes.stream().noneMatch(
+ supertype -> supertype.getFinalDescriptor().contains("Impl")));
+ // Can't build ClassSubject with Itf in classpath. Instead, check if the reference to Itf is
+ // not altered via descriptors.
+ List<String> superTypeDescriptors = kmClass.getSuperTypeDescriptors();
+ assertTrue(superTypeDescriptors.stream().noneMatch(supertype -> supertype.contains("Impl")));
+ assertTrue(superTypeDescriptors.stream().anyMatch(supertype -> supertype.contains("Itf")));
+ });
+
+ Path libJar = compileResult.writeToZip();
+
+ String appFolder = PKG_PREFIX + "/classpath_app";
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addClasspathFiles(baseLibJar, libJar)
+ .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), baseLibJar, libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), pkg + ".classpath_app.MainKt")
+ .assertSuccessWithOutputLines("Impl::foo");
+ }
+
+ @Test
+ public void testMetadataInClasspathType_renamed() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(baseLibJar)
+ .addProgramFiles(extLibJar)
+ // Keep the Extra class and its interface (which has the method).
+ .addKeepRules("-keep class **.Extra")
+ // Keep Super, but allow minification.
+ .addKeepRules("-keep,allowobfuscation class **.Impl")
+ // Keep the ImplKt extension method which requires metadata
+ // to be called with Kotlin syntax from other kotlin code.
+ .addKeepRules("-keep class **.ImplKt { <methods>; }")
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile();
+ String pkg = getClass().getPackage().getName();
+ final String implClassName = pkg + ".classpath_lib_ext.Impl";
+ final String extraClassName = pkg + ".classpath_lib_ext.Extra";
+ compileResult.inspect(inspector -> {
+ ClassSubject impl = inspector.clazz(implClassName);
+ assertThat(impl, isPresent());
+ assertThat(impl, isRenamed());
+
+ ClassSubject extra = inspector.clazz(extraClassName);
+ assertThat(extra, isPresent());
+ assertThat(extra, not(isRenamed()));
+ // API entry is kept, hence the presence of Metadata.
+ KmClassSubject kmClass = extra.getKmClass();
+ assertThat(kmClass, isPresent());
+ List<ClassSubject> superTypes = kmClass.getSuperTypes();
+ assertTrue(superTypes.stream().noneMatch(
+ supertype -> supertype.getFinalDescriptor().contains("Impl")));
+ assertTrue(superTypes.stream().anyMatch(
+ supertype -> supertype.getFinalDescriptor().equals(impl.getFinalDescriptor())));
+ });
+
+ Path libJar = compileResult.writeToZip();
+
+ String appFolder = PKG_PREFIX + "/classpath_app";
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addClasspathFiles(baseLibJar, libJar)
+ .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), baseLibJar, libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), pkg + ".classpath_app.MainKt")
+ .assertSuccessWithOutputLines("Impl::foo");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
index e256e37..e7692bc 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
@@ -6,23 +6,20 @@
import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
import java.nio.file.Path;
import java.util.Collection;
+import java.util.List;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,15 +47,10 @@
@BeforeClass
public static void createLibJar() throws Exception {
String extLibFolder = PKG_PREFIX + "/extension_lib";
- extLibJar = getStaticTemp().newFile("ext_lib.jar").toPath();
- ProcessResult processResult =
- ToolHelper.runKotlinc(
- null,
- extLibJar,
- null,
- getKotlinFileInTest(extLibFolder, "B")
- );
- assertEquals(0, processResult.exitCode);
+ extLibJar =
+ kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addSourceFiles(getKotlinFileInTest(extLibFolder, "B"))
+ .compile();
}
@Test
@@ -85,25 +77,28 @@
assertThat(impl, isPresent());
assertThat(impl, not(isRenamed()));
// API entry is kept, hence the presence of Metadata.
- DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
- assertNotNull(metadata);
- assertThat(metadata.toString(), not(containsString("Super")));
+ KmClassSubject kmClass = impl.getKmClass();
+ assertThat(kmClass, isPresent());
+ List<ClassSubject> superTypes = kmClass.getSuperTypes();
+ assertTrue(superTypes.stream().noneMatch(
+ supertype -> supertype.getFinalDescriptor().contains("Super")));
});
- Path r8ProcessedLibZip = temp.newFile("r8-lib.zip").toPath();
- compileResult.writeToZip(r8ProcessedLibZip);
+ Path libJar = compileResult.writeToZip();
String appFolder = PKG_PREFIX + "/extension_app";
- ProcessResult kotlinTestCompileResult =
+ Path output =
kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
- .addClasspathFiles(r8ProcessedLibZip)
+ .addClasspathFiles(libJar)
.addSourceFiles(getKotlinFileInTest(appFolder, "main"))
.setOutputPath(temp.newFolder().toPath())
- // TODO(b/143687784): update to just .compile() once fixed.
- .compileRaw();
- // TODO(b/143687784): should be able to compile!
- assertNotEquals(0, kotlinTestCompileResult.exitCode);
- assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: doStuff"));
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), pkg + ".extension_app.MainKt")
+ .assertSuccessWithOutputLines("do stuff", "do stuff");
}
@Test
@@ -133,13 +128,16 @@
assertThat(impl, isPresent());
assertThat(impl, not(isRenamed()));
// API entry is kept, hence the presence of Metadata.
- DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
- assertNotNull(metadata);
- assertThat(metadata.toString(), not(containsString("Super")));
+ KmClassSubject kmClass = impl.getKmClass();
+ assertThat(kmClass, isPresent());
+ List<ClassSubject> superTypes = kmClass.getSuperTypes();
+ assertTrue(superTypes.stream().noneMatch(
+ supertype -> supertype.getFinalDescriptor().contains("Super")));
+ assertTrue(superTypes.stream().anyMatch(
+ supertype -> supertype.getFinalDescriptor().equals(sup.getFinalDescriptor())));
});
- Path libJar = temp.newFile("lib.jar").toPath();
- compileResult.writeToZip(libJar);
+ Path libJar = compileResult.writeToZip();
String appFolder = PKG_PREFIX + "/extension_app";
Path output =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParametertypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParameterTypeTest.java
similarity index 71%
rename from src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParametertypeTest.java
rename to src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParameterTypeTest.java
index 1030339..3fedef1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParametertypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParameterTypeTest.java
@@ -9,27 +9,26 @@
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
import java.nio.file.Path;
import java.util.Collection;
+import java.util.List;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
-public class MetadataRenameInParametertypeTest extends KotlinMetadataTestBase {
+public class MetadataRenameInParameterTypeTest extends KotlinMetadataTestBase {
private final TestParameters parameters;
@@ -39,33 +38,28 @@
getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
}
- public MetadataRenameInParametertypeTest(
+ public MetadataRenameInParameterTypeTest(
TestParameters parameters, KotlinTargetVersion targetVersion) {
super(targetVersion);
this.parameters = parameters;
}
- private static Path parameterLibJar;
+ private static Path parameterTypeLibJar;
@BeforeClass
public static void createLibJar() throws Exception {
- String paramLibFolder = PKG_PREFIX + "/parametertype_lib";
- parameterLibJar = getStaticTemp().newFile("param_lib.jar").toPath();
- ProcessResult processResult =
- ToolHelper.runKotlinc(
- null,
- parameterLibJar,
- null,
- getKotlinFileInTest(paramLibFolder, "lib")
- );
- assertEquals(0, processResult.exitCode);
+ String parameterTypeLibFolder = PKG_PREFIX + "/parametertype_lib";
+ parameterTypeLibJar =
+ kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addSourceFiles(getKotlinFileInTest(parameterTypeLibFolder, "lib"))
+ .compile();
}
@Test
- public void testMetadataInParameter_renamed() throws Exception {
+ public void testMetadataInParameterType_renamed() throws Exception {
R8TestCompileResult compileResult =
testForR8(parameters.getBackend())
- .addProgramFiles(parameterLibJar)
+ .addProgramFiles(parameterTypeLibJar)
// Keep non-private members of Impl
.addKeepRules("-keep public class **.Impl { !private *; }")
// Keep Itf, but allow minification.
@@ -84,14 +78,20 @@
assertThat(impl, isPresent());
assertThat(impl, not(isRenamed()));
// API entry is kept, hence the presence of Metadata.
- DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
- assertNotNull(metadata);
+ KmClassSubject kmClass = impl.getKmClass();
+ assertThat(kmClass, isPresent());
+ List<ClassSubject> superTypes = kmClass.getSuperTypes();
+ assertTrue(superTypes.stream().noneMatch(
+ supertype -> supertype.getFinalDescriptor().contains("Itf")));
+ assertTrue(superTypes.stream().anyMatch(
+ supertype -> supertype.getFinalDescriptor().equals(itf.getFinalDescriptor())));
// TODO(b/70169921): should not refer to Itf
- assertThat(metadata.toString(), containsString("Itf"));
+ List<ClassSubject> parameterTypes = kmClass.getParameterTypesInFunctions();
+ assertTrue(parameterTypes.stream().anyMatch(
+ parameterType -> parameterType.getOriginalDescriptor().contains("Itf")));
});
- Path libJar = temp.newFile("lib.jar").toPath();
- compileResult.writeToZip(libJar);
+ Path libJar = compileResult.writeToZip();
String appFolder = PKG_PREFIX + "/parametertype_app";
ProcessResult processResult =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTypeTest.java
similarity index 71%
rename from src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java
rename to src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTypeTest.java
index fe815d0..2e92333 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTypeTest.java
@@ -9,27 +9,26 @@
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
import java.nio.file.Path;
import java.util.Collection;
+import java.util.List;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
-public class MetadataRenameInPropertyTest extends KotlinMetadataTestBase {
+public class MetadataRenameInPropertyTypeTest extends KotlinMetadataTestBase {
private final TestParameters parameters;
@@ -39,33 +38,28 @@
getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
}
- public MetadataRenameInPropertyTest(
+ public MetadataRenameInPropertyTypeTest(
TestParameters parameters, KotlinTargetVersion targetVersion) {
super(targetVersion);
this.parameters = parameters;
}
- private static Path propertyLibJar;
+ private static Path propertyTypeLibJar;
@BeforeClass
public static void createLibJar() throws Exception {
- String propertyLibFolder = PKG_PREFIX + "/propertytype_lib";
- propertyLibJar = getStaticTemp().newFile("property_lib.jar").toPath();
- ProcessResult processResult =
- ToolHelper.runKotlinc(
- null,
- propertyLibJar,
- null,
- getKotlinFileInTest(propertyLibFolder, "lib")
- );
- assertEquals(0, processResult.exitCode);
+ String propertyTypeLibFolder = PKG_PREFIX + "/propertytype_lib";
+ propertyTypeLibJar =
+ kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addSourceFiles(getKotlinFileInTest(propertyTypeLibFolder, "lib"))
+ .compile();
}
@Test
public void testMetadataInProperty_renamed() throws Exception {
R8TestCompileResult compileResult =
testForR8(parameters.getBackend())
- .addProgramFiles(propertyLibJar)
+ .addProgramFiles(propertyTypeLibJar)
// Keep non-private members of Impl
.addKeepRules("-keep public class **.Impl { !private *; }")
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
@@ -82,14 +76,20 @@
assertThat(impl, isPresent());
assertThat(impl, not(isRenamed()));
// API entry is kept, hence the presence of Metadata.
- DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
- assertNotNull(metadata);
+ KmClassSubject kmClass = impl.getKmClass();
+ assertThat(kmClass, isPresent());
+ List<ClassSubject> superTypes = kmClass.getSuperTypes();
+ assertTrue(superTypes.stream().noneMatch(
+ supertype -> supertype.getFinalDescriptor().contains("Itf")));
+ assertTrue(superTypes.stream().anyMatch(
+ supertype -> supertype.getFinalDescriptor().equals(itf.getFinalDescriptor())));
// TODO(b/70169921): should not refer to Itf
- assertThat(metadata.toString(), containsString("Itf"));
+ List<ClassSubject> propertyReturnTypes = kmClass.getReturnTypesInProperties();
+ assertTrue(propertyReturnTypes.stream().anyMatch(
+ propertyType -> propertyType.getOriginalDescriptor().contains("Itf")));
});
- Path libJar = temp.newFile("lib.jar").toPath();
- compileResult.writeToZip(libJar);
+ Path libJar = compileResult.writeToZip();
String appFolder = PKG_PREFIX + "/propertytype_app";
ProcessResult processResult =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturntypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturnTypeTest.java
similarity index 72%
rename from src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturntypeTest.java
rename to src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturnTypeTest.java
index da6bf76..e7efcae 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturntypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturnTypeTest.java
@@ -9,27 +9,26 @@
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
import java.nio.file.Path;
import java.util.Collection;
+import java.util.List;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
-public class MetadataRenameInReturntypeTest extends KotlinMetadataTestBase {
+public class MetadataRenameInReturnTypeTest extends KotlinMetadataTestBase {
private final TestParameters parameters;
@Parameterized.Parameters(name = "{0} target: {1}")
@@ -38,33 +37,28 @@
getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
}
- public MetadataRenameInReturntypeTest(
+ public MetadataRenameInReturnTypeTest(
TestParameters parameters, KotlinTargetVersion targetVersion) {
super(targetVersion);
this.parameters = parameters;
}
- private static Path returntypeLibJar;
+ private static Path returnTypeLibJar;
@BeforeClass
public static void createLibJar() throws Exception {
- String returntypeLibFolder = PKG_PREFIX + "/returntype_lib";
- returntypeLibJar = getStaticTemp().newFile("returntype_lib.jar").toPath();
- ProcessResult processResult =
- ToolHelper.runKotlinc(
- null,
- returntypeLibJar,
- null,
- getKotlinFileInTest(returntypeLibFolder, "lib")
- );
- assertEquals(0, processResult.exitCode);
+ String returnTypeLibFolder = PKG_PREFIX + "/returntype_lib";
+ returnTypeLibJar =
+ kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addSourceFiles(getKotlinFileInTest(returnTypeLibFolder, "lib"))
+ .compile();
}
@Test
- public void testmetadataInReturnType_renamed() throws Exception {
+ public void testMetadataInReturnType_renamed() throws Exception {
R8TestCompileResult compileResult =
testForR8(parameters.getBackend())
- .addProgramFiles(returntypeLibJar)
+ .addProgramFiles(returnTypeLibJar)
// Keep non-private members of Impl
.addKeepRules("-keep public class **.Impl { !private *; }")
// Keep Itf, but allow minification.
@@ -83,14 +77,20 @@
assertThat(impl, isPresent());
assertThat(impl, not(isRenamed()));
// API entry is kept, hence the presence of Metadata.
- DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
- assertNotNull(metadata);
+ KmClassSubject kmClass = impl.getKmClass();
+ assertThat(kmClass, isPresent());
+ List<ClassSubject> superTypes = kmClass.getSuperTypes();
+ assertTrue(superTypes.stream().noneMatch(
+ supertype -> supertype.getFinalDescriptor().contains("Itf")));
+ assertTrue(superTypes.stream().anyMatch(
+ supertype -> supertype.getFinalDescriptor().equals(itf.getFinalDescriptor())));
// TODO(b/70169921): should not refer to Itf
- assertThat(metadata.toString(), containsString("Itf"));
+ List<ClassSubject> functionReturnTypes = kmClass.getReturnTypesInFunctions();
+ assertTrue(functionReturnTypes.stream().anyMatch(
+ returnType -> returnType.getOriginalDescriptor().contains("Itf")));
});
- Path libJar = temp.newFile("lib.jar").toPath();
- compileResult.writeToZip(libJar);
+ Path libJar = compileResult.writeToZip();
String appFolder = PKG_PREFIX + "/returntype_app";
ProcessResult processResult =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSupertypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSuperTypeTest.java
similarity index 72%
rename from src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSupertypeTest.java
rename to src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSuperTypeTest.java
index dc29f2c..4ad8e97 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSupertypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSuperTypeTest.java
@@ -6,29 +6,27 @@
import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
import java.nio.file.Path;
import java.util.Collection;
+import java.util.List;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
-public class MetadataRenameInSupertypeTest extends KotlinMetadataTestBase {
+public class MetadataRenameInSuperTypeTest extends KotlinMetadataTestBase {
private final TestParameters parameters;
@@ -38,34 +36,30 @@
getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
}
- public MetadataRenameInSupertypeTest(
+ public MetadataRenameInSuperTypeTest(
TestParameters parameters, KotlinTargetVersion targetVersion) {
super(targetVersion);
this.parameters = parameters;
}
- private static Path supertypeLibJar;
+ private static Path superTypeLibJar;
@BeforeClass
public static void createLibJar() throws Exception {
- String supertypeLibFolder = PKG_PREFIX + "/supertype_lib";
- supertypeLibJar = getStaticTemp().newFile("supertype_lib.jar").toPath();
- ProcessResult processResult =
- ToolHelper.runKotlinc(
- null,
- supertypeLibJar,
- null,
- getKotlinFileInTest(supertypeLibFolder, "impl"),
- getKotlinFileInTest(supertypeLibFolder + "/internal", "itf")
- );
- assertEquals(0, processResult.exitCode);
+ String superTypeLibFolder = PKG_PREFIX + "/supertype_lib";
+ superTypeLibJar =
+ kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addSourceFiles(
+ getKotlinFileInTest(superTypeLibFolder, "impl"),
+ getKotlinFileInTest(superTypeLibFolder + "/internal", "itf"))
+ .compile();
}
@Test
public void b143687784_merged() throws Exception {
R8TestCompileResult compileResult =
testForR8(parameters.getBackend())
- .addProgramFiles(supertypeLibJar)
+ .addProgramFiles(superTypeLibJar)
// Keep non-private members except for ones in `internal` definitions.
.addKeepRules("-keep public class !**.internal.**, * { !private *; }")
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
@@ -81,25 +75,27 @@
assertThat(impl, isPresent());
assertThat(impl, not(isRenamed()));
// API entry is kept, hence the presence of Metadata.
- DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
- assertNotNull(metadata);
- assertThat(metadata.toString(), not(containsString("internal")));
- assertThat(metadata.toString(), not(containsString("Itf")));
+ KmClassSubject kmClass = impl.getKmClass();
+ assertThat(kmClass, isPresent());
+ List<ClassSubject> superTypes = kmClass.getSuperTypes();
+ assertTrue(superTypes.stream().noneMatch(
+ supertype -> supertype.getFinalDescriptor().contains("internal")));
+ assertTrue(superTypes.stream().noneMatch(
+ supertype -> supertype.getFinalDescriptor().contains("Itf")));
});
- Path r8ProcessedLibZip = temp.newFile("r8-lib.zip").toPath();
- compileResult.writeToZip(r8ProcessedLibZip);
+ Path libJar = compileResult.writeToZip();
String appFolder = PKG_PREFIX + "/supertype_app";
Path output =
kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
- .addClasspathFiles(r8ProcessedLibZip)
+ .addClasspathFiles(libJar)
.addSourceFiles(getKotlinFileInTest(appFolder, "main"))
.setOutputPath(temp.newFolder().toPath())
.compile();
testForJvm()
- .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), r8ProcessedLibZip)
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
.addClasspath(output)
.run(parameters.getRuntime(), pkg + ".supertype_app.MainKt")
.assertSuccessWithOutputLines("Impl::foo", "Program::foo");
@@ -109,7 +105,7 @@
public void b143687784_renamed() throws Exception {
R8TestCompileResult compileResult =
testForR8(parameters.getBackend())
- .addProgramFiles(supertypeLibJar)
+ .addProgramFiles(superTypeLibJar)
// Keep non-private members except for ones in `internal` definitions.
.addKeepRules("-keep public class !**.internal.**, * { !private *; }")
// Keep `internal` definitions, but allow minification.
@@ -128,15 +124,18 @@
assertThat(impl, isPresent());
assertThat(impl, not(isRenamed()));
// API entry is kept, hence the presence of Metadata.
- DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
- assertNotNull(metadata);
- assertThat(metadata.toString(), not(containsString("internal")));
- assertThat(metadata.toString(), not(containsString("Itf")));
- assertThat(metadata.toString(), containsString("a/a"));
+ KmClassSubject kmClass = impl.getKmClass();
+ assertThat(kmClass, isPresent());
+ List<ClassSubject> superTypes = kmClass.getSuperTypes();
+ assertTrue(superTypes.stream().noneMatch(
+ supertype -> supertype.getFinalDescriptor().contains("internal")));
+ assertTrue(superTypes.stream().noneMatch(
+ supertype -> supertype.getFinalDescriptor().contains("Itf")));
+ assertTrue(superTypes.stream().anyMatch(
+ supertype -> supertype.getFinalDescriptor().equals(itf.getFinalDescriptor())));
});
- Path libJar = temp.newFile("lib.jar").toPath();
- compileResult.writeToZip(libJar);
+ Path libJar = compileResult.writeToZip();
String appFolder = PKG_PREFIX + "/supertype_app";
Path output =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index 00b5606..35dd597 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -7,8 +7,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import com.android.tools.r8.KotlinTestBase;
import com.android.tools.r8.R8TestRunResult;
@@ -16,6 +14,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.util.Collection;
@@ -61,11 +60,12 @@
assertThat(clazz, isPresent());
assertThat(clazz, not(isRenamed()));
// Main class is kept, hence the presence of Metadata.
- assertNotNull(retrieveMetadata(clazz.getDexClass()));
+ AnnotationSubject annotationSubject = clazz.annotation(METADATA_TYPE);
+ assertThat(annotationSubject, isPresent());
ClassSubject impl1 = inspector.clazz(implementer1ClassName);
assertThat(impl1, isPresent());
assertThat(impl1, isRenamed());
// All other classes can be renamed, hence the absence of Metadata;
- assertNull(retrieveMetadata(impl1.getDexClass()));
+ assertThat(impl1.annotation(METADATA_TYPE), not(isPresent()));
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt
new file mode 100644
index 0000000..3c1585a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt
@@ -0,0 +1,11 @@
+// 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.kotlin.metadata.classpath_app
+
+import com.android.tools.r8.kotlin.metadata.classpath_lib_ext.Extra
+import com.android.tools.r8.kotlin.metadata.classpath_lib_ext.extension
+
+fun main() {
+ Extra().extension()
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_base/itf.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_base/itf.kt
new file mode 100644
index 0000000..9df88be
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_base/itf.kt
@@ -0,0 +1,8 @@
+// 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.kotlin.metadata.classpath_lib_base
+
+interface Itf {
+ fun foo()
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt
new file mode 100644
index 0000000..975865b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt
@@ -0,0 +1,18 @@
+// 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.kotlin.metadata.classpath_lib_ext
+
+import com.android.tools.r8.kotlin.metadata.classpath_lib_base.Itf
+
+open class Impl : Itf {
+ override fun foo() {
+ println("Impl::foo")
+ }
+}
+
+class Extra : Impl()
+
+fun Extra.extension() {
+ foo()
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_lib/B.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_lib/B.kt
index a6fe9ab..df51d42 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_lib/B.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_lib/B.kt
@@ -13,7 +13,7 @@
}
}
-class B : Super() { }
+class B : Super()
fun B.extension() {
doStuff()
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/supertype_lib/internal/itf.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/supertype_lib/internal/itf.kt
index 50e18d4..7fff074 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/supertype_lib/internal/itf.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/supertype_lib/internal/itf.kt
@@ -4,5 +4,5 @@
package com.android.tools.r8.kotlin.metadata.supertype_lib.internal
interface Itf {
- fun foo();
+ fun foo()
}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/naming/AbstractR8KotlinNamingTestBase.java b/src/test/java/com/android/tools/r8/naming/AbstractR8KotlinNamingTestBase.java
index fdc195a..2815aed 100644
--- a/src/test/java/com/android/tools/r8/naming/AbstractR8KotlinNamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/AbstractR8KotlinNamingTestBase.java
@@ -10,27 +10,15 @@
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FieldSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.Collection;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-@RunWith(Parameterized.class)
public abstract class AbstractR8KotlinNamingTestBase extends AbstractR8KotlinTestBase {
protected final boolean minification;
- @Parameters(name = "target: {0}, allowAccessModification: {1}, minification: {2}")
- public static Collection<Object[]> data() {
- return buildParameters(
- KotlinTargetVersion.values(), BooleanUtils.values(), BooleanUtils.values());
- }
-
AbstractR8KotlinNamingTestBase(
KotlinTargetVersion kotlinTargetVersion,
boolean allowAccessModification,
diff --git a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
index 43e8719..7828143 100644
--- a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.kotlin.TestKotlinClass;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -21,13 +22,24 @@
import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Iterator;
import java.util.stream.Collectors;
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 KotlinIntrinsicsIdentifierTest extends AbstractR8KotlinNamingTestBase {
private static final String FOLDER = "intrinsics_identifiers";
+ @Parameters(name = "target: {0}, allowAccessModification: {1}, minification: {2}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ KotlinTargetVersion.values(), BooleanUtils.values(), BooleanUtils.values());
+ }
+
public KotlinIntrinsicsIdentifierTest(
KotlinTargetVersion targetVersion, boolean allowAccessModification, boolean minification) {
super(targetVersion, allowAccessModification, minification);
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
index a97372e..bcb02da 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
@@ -25,12 +25,12 @@
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.invokesuper.Consumer;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import java.util.function.Consumer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
index 5c01601..5737496 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
@@ -26,7 +26,6 @@
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.invokesuper.Consumer;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -34,6 +33,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
+import java.util.function.Consumer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
index dda4a3a..b19ed16 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
@@ -29,7 +29,6 @@
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.invokesuper.Consumer;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -37,6 +36,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
+import java.util.function.Consumer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingExtendEmptyInterfaceTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingExtendEmptyInterfaceTest.java
new file mode 100644
index 0000000..e16307b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingExtendEmptyInterfaceTest.java
@@ -0,0 +1,118 @@
+// 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.naming.applymapping;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/144151805.
+@RunWith(Parameterized.class)
+public class ApplyMappingExtendEmptyInterfaceTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApplyMappingExtendEmptyInterfaceTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+ testForRuntime(parameters)
+ .addProgramClasses(
+ TestI.class,
+ TestA.class,
+ Main.class,
+ LibI.class,
+ LibI2.class,
+ LibI3.class,
+ Runner.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("injectTestA", "injectObject");
+ }
+
+ @Test
+ public void testInheritLibraryInterface()
+ throws CompilationFailedException, IOException, ExecutionException {
+ final R8TestCompileResult libCompileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(LibI.class, LibI2.class, LibI3.class, Runner.class)
+ .addKeepClassAndMembersRules(Runner.class)
+ .addKeepClassAndMembersRulesWithAllowObfuscation(LibI.class, LibI2.class, LibI3.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestI.class, TestA.class, Main.class)
+ .addClasspathClasses(LibI.class, LibI2.class, LibI3.class, Runner.class)
+ .addKeepAllClassesRule()
+ .addApplyMapping(libCompileResult.getProguardMap())
+ .addRunClasspathFiles(libCompileResult.writeToZip())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("injectTestA", "injectObject");
+ }
+
+ public interface LibI {
+ void inject(Object object);
+ }
+
+ public interface LibI2 extends LibI {}
+
+ // Add an additional interface on top of LibI2 to ensure a class naming is generated here.
+ public interface LibI3 extends LibI2 {
+ void foo();
+ }
+
+ public static class Runner {
+
+ public static void foo(LibI libI) {
+ libI.inject(libI);
+ }
+ }
+
+ public interface TestI extends LibI3 {
+ void inject(TestA testA);
+ }
+
+ public static class TestA implements TestI {
+
+ @Override
+ public void inject(Object object) {
+ System.out.println("injectObject");
+ }
+
+ @Override
+ public void inject(TestA testA) {
+ System.out.println("injectTestA");
+ }
+
+ @Override
+ public void foo() {
+ throw new RuntimeException("Should never be called");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ final TestA testA = new TestA();
+ testA.inject(testA);
+ Runner.foo(testA);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingExtendInterfaceTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingExtendInterfaceTest.java
new file mode 100644
index 0000000..c70d49c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingExtendInterfaceTest.java
@@ -0,0 +1,99 @@
+// 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.naming.applymapping;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/144151805.
+@RunWith(Parameterized.class)
+public class ApplyMappingExtendInterfaceTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApplyMappingExtendInterfaceTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+ testForRuntime(parameters)
+ .addProgramClasses(TestI.class, TestA.class, Main.class, LibI.class, Runner.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("injectTestA", "injectObject");
+ }
+
+ @Test
+ public void testInheritLibraryInterface()
+ throws CompilationFailedException, IOException, ExecutionException {
+ final R8TestCompileResult libCompileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(LibI.class, Runner.class)
+ .addKeepClassAndMembersRules(Runner.class)
+ .addKeepClassAndMembersRulesWithAllowObfuscation(LibI.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestI.class, TestA.class, Main.class)
+ .addClasspathClasses(LibI.class, Runner.class)
+ .addKeepAllClassesRule()
+ .addApplyMapping(libCompileResult.getProguardMap())
+ .addRunClasspathFiles(libCompileResult.writeToZip())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("injectTestA", "injectObject");
+ }
+
+ public interface LibI {
+ void inject(Object object);
+ }
+
+ public static class Runner {
+
+ public static void foo(LibI libI) {
+ libI.inject(libI);
+ }
+ }
+
+ public interface TestI extends LibI {
+ void inject(TestA testA);
+ }
+
+ public static class TestA implements TestI {
+
+ @Override
+ public void inject(Object object) {
+ System.out.println("injectObject");
+ }
+
+ @Override
+ public void inject(TestA testA) {
+ System.out.println("injectTestA");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ final TestA testA = new TestA();
+ testA.inject(testA);
+ Runner.foo(testA);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
index 63c6161..5c1d7a8 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.ir.optimize.NestUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.transformers.ClassFileTransformer;
import com.android.tools.r8.utils.BooleanUtils;
@@ -71,10 +70,7 @@
appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
- // TODO(b/145187573): Update to check the full access control once possible.
- assertEquals(
- inSameNest,
- NestUtils.sameNest(bClass.type, resolutionResult.getSingleTarget().method.holder, appView));
+ assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
index a487f40..bd6dbed 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.ir.optimize.NestUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.transformers.ClassFileTransformer;
import com.android.tools.r8.utils.BooleanUtils;
@@ -73,10 +72,7 @@
appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
- // TODO(b/145187573): Update to check the full access control once possible.
- assertEquals(
- inSameNest,
- NestUtils.sameNest(bClass.type, resolutionResult.getSingleTarget().method.holder, appView));
+ assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
index 59267b2..69cdad5 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.ir.optimize.NestUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.transformers.ClassFileTransformer;
import com.android.tools.r8.utils.BooleanUtils;
@@ -73,10 +72,7 @@
appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
- // TODO(b/145187573): Update to check the full access control once possible.
- assertEquals(
- inSameNest,
- NestUtils.sameNest(bClass.type, resolutionResult.getSingleTarget().method.holder, appView));
+ assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
index 68cc59e..d6343fc 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.ir.optimize.NestUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.transformers.ClassFileTransformer;
import com.android.tools.r8.utils.BooleanUtils;
@@ -76,10 +75,7 @@
appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
- // TODO(b/145187573): Update to check the full access control once possible.
- assertEquals(
- inSameNest,
- NestUtils.sameNest(bClass.type, resolutionResult.getSingleTarget().method.holder, appView));
+ assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java
new file mode 100644
index 0000000..a997622
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java
@@ -0,0 +1,108 @@
+// 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.resolution.access;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SelfVirtualMethodAccessTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("A::bar");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SelfVirtualMethodAccessTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public Collection<Class<?>> getClasses() {
+ return ImmutableList.of(Main.class);
+ }
+
+ public Collection<byte[]> getTransformedClasses() throws Exception {
+ return ImmutableList.of(
+ transformer(A.class).setPrivate(A.class.getDeclaredMethod("bar")).transform());
+ }
+
+ @Test
+ public void testResolutionAccess() throws Exception {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(
+ buildClasses(getClasses()).addClassProgramData(getTransformedClasses()).build(),
+ Main.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexProgramClass aClass =
+ appInfo.definitionFor(buildType(A.class, appInfo.dexItemFactory())).asProgramClass();
+ DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
+ assertTrue(resolutionResult.isAccessibleFrom(aClass, appInfo));
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getClasses())
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(getClasses())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(
+ result -> {
+ if (parameters.isCfRuntime()) {
+ result.assertSuccessWithOutput(EXPECTED);
+ } else {
+ // TODO(b/145187969): R8 compiles an incorrect program.
+ result.assertFailureWithErrorThatMatches(
+ containsString(NullPointerException.class.getName()));
+ }
+ });
+ }
+
+ static class A {
+ /* will be private */ void bar() {
+ System.out.println("A::bar");
+ }
+
+ public void foo() {
+ // Virtual invoke to private method.
+ bar();
+ }
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ new A().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java
new file mode 100644
index 0000000..e517531
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java
@@ -0,0 +1,105 @@
+// 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.resolution.access.indirectfield;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.graph.AccessControl;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.resolution.access.indirectfield.pkg.C;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IndirectFieldAccessTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("42");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public IndirectFieldAccessTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public List<Class<?>> getClasses() {
+ return ImmutableList.of(Main.class, A.class, B.class, C.class);
+ }
+
+ @Test
+ public void testResolutionAccess() throws Exception {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(readClasses(getClasses()), Main.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexProgramClass cClass =
+ appInfo.definitionFor(buildType(C.class, appInfo.dexItemFactory())).asProgramClass();
+ DexField f =
+ buildField(
+ // Reflecting on B.class.getField("f") will give A.f, so manually create the reference.
+ Reference.field(Reference.classFromClass(B.class), "f", Reference.INT),
+ appInfo.dexItemFactory());
+ DexClass initialResolutionHolder = appInfo.definitionFor(f.holder);
+ DexEncodedField resolutionTarget = appInfo.resolveField(f);
+ // TODO(b/145723539): Test access via the resolution result once possible.
+ assertTrue(
+ AccessControl.isFieldAccessible(
+ resolutionTarget, initialResolutionHolder, cClass, appInfo));
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getClasses())
+ .run(parameters.getRuntime(), Main.class)
+ .disassemble()
+ .apply(this::checkExpectedResult);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(getClasses())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkExpectedResult);
+ }
+
+ private void checkExpectedResult(TestRunResult<?> result) {
+ result.assertSuccessWithOutput(EXPECTED);
+ }
+
+ /* non-public */ static class A {
+ public int f = 42;
+ }
+
+ public static class B extends A {
+ // Intentionally emtpy.
+ // Provides access to A.f from outside this package, eg, from pkg.C::bar().
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ new C().bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectfield/pkg/C.java b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/pkg/C.java
new file mode 100644
index 0000000..9094062
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/pkg/C.java
@@ -0,0 +1,12 @@
+// 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.resolution.access.indirectfield.pkg;
+
+import com.android.tools.r8.resolution.access.indirectfield.IndirectFieldAccessTest.B;
+
+public class C {
+ public void bar() {
+ System.out.println(new B().f);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java
new file mode 100644
index 0000000..1d75a8b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java
@@ -0,0 +1,108 @@
+// 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.resolution.access.indirectmethod;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.resolution.access.indirectmethod.pkg.C;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IndirectMethodAccessTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("A::foo");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public IndirectMethodAccessTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public List<Class<?>> getClasses() {
+ return ImmutableList.of(Main.class, A.class, C.class);
+ }
+
+ private List<byte[]> getTransforms() throws IOException {
+ return ImmutableList.of(
+ // Compilation with javac will generate a synthetic bridge for foo. Remove it.
+ transformer(B.class)
+ .removeMethods((access, name, descriptor, signature, exceptions) -> name.equals("foo"))
+ .transform());
+ }
+
+ @Test
+ public void testResolutionAccess() throws Exception {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(
+ buildClasses(getClasses()).addClassProgramData(getTransforms()).build(), Main.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexProgramClass cClass =
+ appInfo.definitionFor(buildType(C.class, appInfo.dexItemFactory())).asProgramClass();
+ DexMethod bar = buildMethod(B.class.getMethod("foo"), appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
+ assertTrue(resolutionResult.isAccessibleForVirtualDispatchFrom(cClass, appInfo));
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getClasses())
+ .addProgramClassFileData(getTransforms())
+ .run(parameters.getRuntime(), Main.class)
+ .disassemble()
+ .apply(this::checkExpectedResult);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(getClasses())
+ .addProgramClassFileData(getTransforms())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkExpectedResult);
+ }
+
+ private void checkExpectedResult(TestRunResult<?> result) {
+ result.assertSuccessWithOutput(EXPECTED);
+ }
+
+ /* non-public */ static class A {
+ public void foo() {
+ System.out.println("A::foo");
+ }
+ }
+
+ public static class B extends A {
+ // Intentionally emtpy.
+ // Provides access to A.foo from outside this package, eg, from pkg.C::bar().
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ new C().bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/pkg/C.java b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/pkg/C.java
new file mode 100644
index 0000000..2ff8db8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/pkg/C.java
@@ -0,0 +1,12 @@
+// 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.resolution.access.indirectmethod.pkg;
+
+import com.android.tools.r8.resolution.access.indirectmethod.IndirectMethodAccessTest.B;
+
+public class C {
+ public void bar() {
+ new B().foo();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index cba0f8b..d81c771 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -4,22 +4,34 @@
package com.android.tools.r8.retrace;
+import static com.android.tools.r8.Collectors.toSingle;
import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
+import static com.android.tools.r8.utils.codeinspector.Matchers.containsInlinePosition;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.StringContains.containsString;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfRuntime;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.io.IOException;
-import java.util.List;
+import java.nio.file.Path;
import java.util.concurrent.ExecutionException;
+import java.util.function.Function;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -40,15 +52,21 @@
this.parameters = parameters;
}
+ private static Function<TestRuntime, Path> compilationResults =
+ memoizeFunction(KotlinInlineFunctionRetraceTest::compileKotlinCode);
+
+ private static Path compileKotlinCode(TestRuntime runtime) throws IOException {
+ CfRuntime cfRuntime = runtime.isCf() ? runtime.asCf() : TestRuntime.getCheckedInJdk9();
+ return kotlinc(cfRuntime, getStaticTemp(), KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addSourceFiles(
+ getFilesInTestFolderRelativeToClass(KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
+ .compile();
+ }
+
@Test
public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
testForRuntime(parameters)
- .addProgramFiles(
- kotlinc(TestRuntime.getCheckedInJdk8(), KOTLINC, KotlinTargetVersion.JAVA_8)
- .addSourceFiles(
- getFilesInTestFolderRelativeToClass(
- KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
- .compile())
+ .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
.addRunClasspathFiles(buildOnDexRuntime(parameters, ToolHelper.getKotlinStdlibJar()))
.run(parameters.getRuntime(), "retrace.MainKt")
.assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
@@ -59,79 +77,128 @@
public void testRetraceKotlinInlineStaticFunction()
throws ExecutionException, CompilationFailedException, IOException {
String main = "retrace.MainKt";
- R8TestRunResult result =
- testForR8(parameters.getBackend())
- .addProgramFiles(
- kotlinc(TestRuntime.getCheckedInJdk8(), KOTLINC, KotlinTargetVersion.JAVA_8)
- .addSourceFiles(
- getFilesInTestFolderRelativeToClass(
- KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
- .compile())
- .addProgramFiles(ToolHelper.getKotlinStdlibJar())
- .addKeepAttributes("SourceFile", "LineNumberTable")
- .setMode(CompilationMode.RELEASE)
- .addKeepMainRule(main)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), main)
- .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
- .assertFailureWithErrorThatMatches(containsString("at retrace.MainKt.main(Main.kt:2)"));
- List<String> retrace = result.retrace();
- // TODO(b/141817471): Change the tracing information when solved.
- // assertThat(retrace.get(1), containsString("at retrace.MainKt.main(Main.kt:15)"));
+ testForR8(parameters.getBackend())
+ .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addKeepAttributes("SourceFile", "LineNumberTable")
+ .setMode(CompilationMode.RELEASE)
+ .addKeepMainRule(main)
+ .addOptionsModification(
+ internalOptions -> internalOptions.enableSourceDebugExtensionRewriter = true)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), main)
+ .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
+ .inspectStackTrace(
+ (stackTrace, codeInspector) -> {
+ MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
+ InlinePosition inlineStack =
+ InlinePosition.stack(
+ InlinePosition.create(
+ "retrace.InlineFunctionKt", "inlineExceptionStatic", 2, 8),
+ InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 15));
+ checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
+ });
}
@Test
public void testRetraceKotlinInlineInstanceFunction()
throws ExecutionException, CompilationFailedException, IOException {
String main = "retrace.MainInstanceKt";
- R8TestRunResult result =
- testForR8(parameters.getBackend())
- .addProgramFiles(
- kotlinc(TestRuntime.getCheckedInJdk8(), KOTLINC, KotlinTargetVersion.JAVA_8)
- .addSourceFiles(
- getFilesInTestFolderRelativeToClass(
- KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
- .compile())
- .addProgramFiles(ToolHelper.getKotlinStdlibJar())
- .addKeepAttributes("SourceFile", "LineNumberTable")
- .setMode(CompilationMode.RELEASE)
- .addKeepMainRule(main)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), main)
- .assertFailureWithErrorThatMatches(containsString("inlineExceptionInstance"))
- .assertFailureWithErrorThatMatches(
- containsString("at retrace.MainInstanceKt.main(MainInstance.kt:2)"));
- List<String> retrace = result.retrace();
- // TODO(b/141817471): Change the tracing information when solved.
- // assertThat(retrace.get(1), containsString("at
- // retrace.MainInstanceKt.main(MainInstance.kt:13)"));
+ testForR8(parameters.getBackend())
+ .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addKeepAttributes("SourceFile", "LineNumberTable")
+ .setMode(CompilationMode.RELEASE)
+ .addKeepMainRule(main)
+ .addOptionsModification(
+ internalOptions -> internalOptions.enableSourceDebugExtensionRewriter = true)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), main)
+ .assertFailureWithErrorThatMatches(containsString("inlineExceptionInstance"))
+ .inspectStackTrace(
+ (stackTrace, codeInspector) -> {
+ MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
+ InlinePosition inlineStack =
+ InlinePosition.stack(
+ InlinePosition.create(
+ "retrace.InlineFunction", "inlineExceptionInstance", 2, 15),
+ InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 13));
+ checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
+ });
}
@Test
public void testRetraceKotlinNestedInlineFunction()
throws ExecutionException, CompilationFailedException, IOException {
- // TODO(b/141817471): Change the tracing information when solved.
- int lineNumber = parameters.isCfRuntime() ? 4 : 3;
String main = "retrace.MainNestedKt";
- R8TestRunResult result =
- testForR8(parameters.getBackend())
- .addProgramFiles(
- kotlinc(TestRuntime.getCheckedInJdk8(), KOTLINC, KotlinTargetVersion.JAVA_8)
- .addSourceFiles(
- getFilesInTestFolderRelativeToClass(
- KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
- .compile())
- .addProgramFiles(ToolHelper.getKotlinStdlibJar())
- .addKeepAttributes("SourceFile", "LineNumberTable")
- .setMode(CompilationMode.RELEASE)
- .addKeepMainRule(main)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), main)
- .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
- .assertFailureWithErrorThatMatches(
- containsString("at retrace.MainNestedKt.main(MainNested.kt:" + lineNumber + ")"));
- List<String> retrace = result.retrace();
- // TODO(b/141817471): Change the tracing information when solved.
- // assertThat(retrace.get(1), containsString("at retrace.MainNestedKt.main(MainNested.kt:19)"));
+ testForR8(parameters.getBackend())
+ .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addKeepAttributes("SourceFile", "LineNumberTable")
+ .setMode(CompilationMode.RELEASE)
+ .addKeepMainRule(main)
+ .addOptionsModification(
+ internalOptions -> internalOptions.enableSourceDebugExtensionRewriter = true)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), main)
+ .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
+ .inspectStackTrace(
+ (stackTrace, codeInspector) -> {
+ MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
+ InlinePosition inlineStack =
+ InlinePosition.stack(
+ InlinePosition.create(
+ "retrace.InlineFunctionKt", "inlineExceptionStatic", 3, 8),
+ InlinePosition.create(
+ "retrace.NestedInlineFunctionKt", "nestedInline", 3, 10),
+ InlinePosition.create(mainSubject.asFoundMethodSubject(), 3, 19));
+ checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
+ });
+ }
+
+ @Test
+ public void testRetraceKotlinNestedInlineFunctionOnFirstLine()
+ throws ExecutionException, CompilationFailedException, IOException {
+ String main = "retrace.MainNestedFirstLineKt";
+ testForR8(parameters.getBackend())
+ .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addKeepAttributes("SourceFile", "LineNumberTable")
+ .setMode(CompilationMode.RELEASE)
+ .addKeepMainRule(main)
+ .addOptionsModification(
+ internalOptions -> internalOptions.enableSourceDebugExtensionRewriter = true)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), main)
+ .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
+ .inspectStackTrace(
+ (stackTrace, codeInspector) -> {
+ MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
+ InlinePosition inlineStack =
+ InlinePosition.stack(
+ InlinePosition.create(
+ "retrace.InlineFunctionKt", "inlineExceptionStatic", 2, 8),
+ InlinePosition.create(
+ "retrace.NestedInlineFunctionKt", "nestedInlineOnFirstLine", 2, 15),
+ InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 20));
+ checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
+ });
+ }
+
+ private void checkInlineInformation(
+ StackTrace stackTrace,
+ CodeInspector codeInspector,
+ MethodSubject mainSubject,
+ InlinePosition inlineStack) {
+ assertThat(mainSubject, isPresent());
+ RetraceMethodResult retraceResult =
+ mainSubject
+ .streamInstructions()
+ .filter(InstructionSubject::isThrow)
+ .collect(toSingle())
+ .retracePosition(codeInspector.retrace());
+ assertThat(retraceResult, isInlineFrame());
+ assertThat(retraceResult, isInlineStack(inlineStack));
+ assertThat(stackTrace, containsInlinePosition(inlineStack));
}
}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
new file mode 100644
index 0000000..bb69fc3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
@@ -0,0 +1,750 @@
+// 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.retrace;
+
+import static junit.framework.TestCase.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.retrace.stacktraces.InlineFileNameStackTrace;
+import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetraceRegularExpressionTests extends TestBase {
+
+ private static final String DEFAULT_REGULAR_EXPRESSION =
+ "(?:.*?\\bat\\s+%c\\.%m\\s*\\(%s(?::%l)?\\)\\s*(?:~\\[.*\\])?)|(?:(?:.*?[:\"]\\s+)?%c(?::.*)?)";
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public RetraceRegularExpressionTests(TestParameters parameters) {}
+
+ @Test
+ public void ensureNotMatchingOnLiteral() {
+ runRetraceTest(
+ "foo\\%c",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("foocom.android.tools.r8.a");
+ }
+
+ @Override
+ public String mapping() {
+ return "com.android.tools.r8.R8 -> com.android.tools.r8.a:";
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("foocom.android.tools.r8.a");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void matchMultipleTypeNamesOnLine() {
+ runRetraceTest(
+ "%c\\s%c\\s%c",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("a.a.a b.b.b c.c.c");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "AA.AA.AA -> a.a.a:", "BB.BB.BB -> b.b.b:", "CC.CC.CC -> c.c.c:");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("AA.AA.AA BB.BB.BB CC.CC.CC");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void matchMultipleSlashNamesOnLine() {
+ runRetraceTest(
+ "%C\\s%C\\s%C",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("a/a/a b/b/b c/c/c");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines("AA.AA -> a.a.a:", "BB.BB -> b.b.b:", "CC.CC -> c.c.c:");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("AA/AA BB/BB CC/CC");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void matchMethodNameInNoContext() {
+ runRetraceTest(
+ "a.b.c.%m",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("a.b.c.a");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines("com.android.tools.r8.R8 -> a.b.c:", " void foo() -> a");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("a.b.c.a");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void matchMethodNameInContext() {
+ runRetraceTest(
+ "%c.%m",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("a.b.c.a");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines("com.android.tools.r8.R8 -> a.b.c:", " void foo() -> a");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.foo");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void matchUnknownMethodNameInContext() {
+ runRetraceTest(
+ "%c.%m",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("a.b.c.a");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines("com.android.tools.r8.R8 -> a.b.c:", " void foo() -> b");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.a");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void matchQualifiedMethodInTrace() {
+ runRetraceTest(DEFAULT_REGULAR_EXPRESSION, new InlineFileNameStackTrace());
+ }
+
+ @Test
+ public void matchFieldNameInNoContext() {
+ runRetraceTest(
+ "a.b.c.%f",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("a.b.c.a");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines("com.android.tools.r8.R8 -> a.b.c:", " int foo -> a");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("a.b.c.a");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void matchFieldNameInContext() {
+ runRetraceTest(
+ "%c.%f",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("a.b.c.a");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines("com.android.tools.r8.R8 -> a.b.c:", " int foo -> a");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.foo");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void matchUnknownFieldNameInContext() {
+ runRetraceTest(
+ "%c.%f",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("a.b.c.a");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines("com.android.tools.r8.R8 -> a.b.c:", " boolean foo -> b");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.a");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void matchQualifiedFieldInTrace() {
+ runRetraceTest(
+ "%c\\.%f\\(%s\\)",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return Collections.singletonList("a.b.c.d(Main.dummy)");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return Collections.singletonList("foo.Bar$Baz.baz(Bar.dummy)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "com.android.tools.r8.naming.retrace.Main -> a.b.c:",
+ " int foo.Bar$Baz.baz -> d");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void matchSourceFileInContext() {
+ runRetraceTest(
+ "%c\\(%s\\)",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("a.b.c(SourceFile)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines("com.android.tools.r8.R8 -> a.b.c:", " boolean foo -> b");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8(R8.java)");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void matchSourceFileInNoContext() {
+ runRetraceTest(
+ "%c\\(%s\\)",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("a.b.d(SourceFile)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines("com.android.tools.r8.R8 -> a.b.c:", " boolean foo -> b");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("a.b.d(d.java)");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void testLineNumberInMethodContext() {
+ runRetraceTest(
+ "%c\\.%m\\(%l\\)",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("a.b.c.a(3)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "com.android.tools.r8.R8 -> a.b.c:", " 3:3:boolean foo():7 -> a");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.foo(7)");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void testNotFoundLineNumberInMethodContext() {
+ runRetraceTest(
+ "%c\\.%m\\(%l\\)",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("a.b.c.a()");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "com.android.tools.r8.R8 -> a.b.c:", " 3:3:boolean foo():7 -> a");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("a.b.c.a()");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void testNotFoundLineNumberInNoLineNumberMappingMethodContext() {
+ runRetraceTest(
+ "%c\\.%m\\(%l\\)",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("a.b.c.a(4)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines("com.android.tools.r8.R8 -> a.b.c:", " boolean foo() -> a");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.foo(4)");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void testPruningByLineNumber() {
+ runRetraceTest(
+ "%c\\.%m\\(%l\\)",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("a.b.c.a(3)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "com.android.tools.r8.R8 -> a.b.c:",
+ " 3:3:boolean foo():7 -> a",
+ " 4:4:boolean bar(int):8 -> a");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.foo(7)");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void matchOnType() {
+ runRetraceTest(
+ "%t",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("void", "a.a.a[]", "a.a.a[][][]");
+ }
+
+ @Override
+ public String mapping() {
+ return "";
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("void", "a.a.a[]", "a.a.a[][][]");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void matchOnFieldOrReturnType() {
+ runRetraceTest(
+ "%t %c.%m",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("void a.b.c.a", "a.a.a[] a.b.c.b");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "com.android.tools.r8.D8 -> a.a.a:",
+ "com.android.tools.r8.R8 -> a.b.c:",
+ " void foo() -> a",
+ " com.android.tools.r8.D8[] bar() -> b");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of(
+ "void com.android.tools.r8.R8.foo",
+ "com.android.tools.r8.D8[] com.android.tools.r8.R8.bar");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void useReturnTypeToNarrowMethodMatches() {
+ runRetraceTest(
+ "%t %c.%m",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("void a.b.c.a", "a.a.a[] a.b.c.b");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "com.android.tools.r8.D8 -> a.a.a:",
+ "com.android.tools.r8.R8 -> a.b.c:",
+ " void foo() -> a",
+ " int foo(int) -> a",
+ " com.android.tools.r8.D8[] bar() -> b",
+ " com.android.tools.r8.D8 bar(int) -> b");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of(
+ "void com.android.tools.r8.R8.foo",
+ "com.android.tools.r8.D8[] com.android.tools.r8.R8.bar");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void returnTypeCanMatchVoid() {
+ runRetraceTest(
+ "%t",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("void");
+ }
+
+ @Override
+ public String mapping() {
+ return "";
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("void");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void testArguments() {
+ runRetraceTest(
+ "%c.%m\\(%a\\)",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("a.b.c.a(int,a.a.a[],boolean)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "com.android.tools.r8.D8 -> a.a.a:",
+ "com.android.tools.r8.R8 -> a.b.c:",
+ " void foo(int,com.android.tools.r8.D8[],boolean) -> a");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of(
+ "com.android.tools.r8.R8.foo(int,com.android.tools.r8.D8[],boolean)");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void testNoArguments() {
+ runRetraceTest(
+ "%c.%m\\(%a\\)",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("a.b.c.a()");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "com.android.tools.r8.D8 -> a.a.a:",
+ "com.android.tools.r8.R8 -> a.b.c:",
+ " void foo() -> a");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.foo()");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void testPruningOfMethodsByFormals() {
+ runRetraceTest(
+ "%c.%m\\(%a\\)",
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of("a.b.c.a(a.a.a)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "com.android.tools.r8.D8 -> a.a.a:",
+ "com.android.tools.r8.R8 -> a.b.c:",
+ " void foo() -> a",
+ " void bar(com.android.tools.r8.D8) -> a");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.bar(com.android.tools.r8.D8)");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ @Test
+ public void matchOnSimpleStackTrace() {
+ runRetraceTest(
+ DEFAULT_REGULAR_EXPRESSION,
+ new StackTraceForTest() {
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return ImmutableList.of(
+ "com.android.tools.r8.a: foo bar baz",
+ " at com.android.tools.r8.b.a(SourceFile)",
+ " at a.c.a.b(SourceFile)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.joinLines(
+ "com.android.tools.r8.R8 -> com.android.tools.r8.a:",
+ "com.android.tools.r8.Bar -> com.android.tools.r8.b:",
+ " void foo() -> a",
+ "com.android.tools.r8.Baz -> a.c.a:",
+ " void bar() -> b");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return ImmutableList.of(
+ "com.android.tools.r8.R8: foo bar baz",
+ " at com.android.tools.r8.Bar.foo(Bar.java)",
+ " at com.android.tools.r8.Baz.bar(Baz.java)");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+ });
+ }
+
+ private TestDiagnosticMessagesImpl runRetraceTest(
+ String regularExpression, StackTraceForTest stackTraceForTest) {
+ TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
+ RetraceCommand retraceCommand =
+ RetraceCommand.builder(diagnosticsHandler)
+ .setProguardMapProducer(stackTraceForTest::mapping)
+ .setStackTrace(stackTraceForTest.obfuscatedStackTrace())
+ .setRetracedStackTraceConsumer(
+ retraced -> {
+ assertEquals(stackTraceForTest.retracedStackTrace(), retraced);
+ })
+ .setRegularExpression(regularExpression)
+ .build();
+ Retrace.run(retraceCommand);
+ return diagnosticsHandler;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/kt/MainNestedFirstLine.kt b/src/test/java/com/android/tools/r8/retrace/kt/MainNestedFirstLine.kt
new file mode 100644
index 0000000..6c34142
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/kt/MainNestedFirstLine.kt
@@ -0,0 +1,16 @@
+// 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 retrace
+
+// Some spaces to better see if retrace is working as expected.
+
+
+fun main(args: Array<String>) {
+ nestedInlineOnFirstLine {
+ throw Exception("Never get's here")
+ }
+}
+
+
+
diff --git a/src/test/java/com/android/tools/r8/retrace/kt/NestedInlineFunction.kt b/src/test/java/com/android/tools/r8/retrace/kt/NestedInlineFunction.kt
index d8e4736..3fdedb3 100644
--- a/src/test/java/com/android/tools/r8/retrace/kt/NestedInlineFunction.kt
+++ b/src/test/java/com/android/tools/r8/retrace/kt/NestedInlineFunction.kt
@@ -3,9 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package retrace
+// Some space to distinguish line number with Inlinefunction numbers.
+
inline fun nestedInline(f: () -> Unit) {
println("in nestedInline")
inlineExceptionStatic(f)
println("will never be printed")
}
+inline fun nestedInlineOnFirstLine(f: () -> Unit) {
+ inlineExceptionStatic(f)
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index 94c6be6..8443f06 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -169,11 +169,7 @@
assertTrue(clazz.isPresent());
assertToStringWasNotReplaced(clazz.uniqueMethodWithName("typeToString"));
- if (parameters.isCfRuntime()) {
- assertToStringWasNotReplaced(clazz.uniqueMethodWithName("valueWithToString"));
- } else {
- assertToStringReplacedWithConst(clazz.uniqueMethodWithName("valueWithToString"), "one");
- }
+ assertToStringReplacedWithConst(clazz.uniqueMethodWithName("valueWithToString"), "one");
assertToStringWasNotReplaced(clazz.uniqueMethodWithName("valueWithoutToString"));
if (enableOptimization) {
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
index 3cd46e12..eebb7d5 100644
--- a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.invokesuper.Consumer;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
@@ -21,6 +20,7 @@
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.List;
+import java.util.function.Consumer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 48c6dea..ee8629f 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -18,7 +18,6 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.invokesuper.Consumer;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.shaking.forceproguardcompatibility.TestMain.MentionedClass;
@@ -40,6 +39,7 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
index 8f19ef8..f2f6731 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
@@ -11,13 +11,13 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import com.android.tools.r8.graph.invokesuper.Consumer;
import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.List;
+import java.util.function.Consumer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
diff --git a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java
index f62e21b..f618e93 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java
@@ -97,7 +97,7 @@
localVariableTable.get(0),
0,
"this",
- classSubject.asFoundClassSubject().asTypeSybject(),
+ classSubject.asFoundClassSubject().asTypeSubject(),
null);
checkLocalVariable(
localVariableTable.get(1), 1, "parameter1", inspector.getTypeSubject("int"), null);
@@ -121,7 +121,7 @@
localVariableTable.get(0),
0,
"this",
- classSubject.asFoundClassSubject().asTypeSybject(),
+ classSubject.asFoundClassSubject().asTypeSubject(),
null);
checkLocalVariable(
localVariableTable.get(1), 1, "parameter1", inspector.getTypeSubject("long"), null);
@@ -141,7 +141,7 @@
localVariableTable.get(0),
0,
"this",
- classSubject.asFoundClassSubject().asTypeSybject(),
+ classSubject.asFoundClassSubject().asTypeSubject(),
null);
checkLocalVariable(
localVariableTable.get(1),
diff --git a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java
index d858322..c92d7e7 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java
@@ -73,7 +73,7 @@
localVariableTable.get(0),
0,
"this",
- classSubject.asFoundClassSubject().asTypeSybject(),
+ classSubject.asFoundClassSubject().asTypeSubject(),
null);
checkLocalVariable(
localVariableTable.get(1), 1, "parameter1", inspector.getTypeSubject("int"), null);
diff --git a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java
index a5fbc59..18188ff 100644
--- a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java
@@ -66,16 +66,7 @@
for (Class<?> nonEscapingClass : nonEscapingClasses) {
ClassSubject classSubject = inspector.clazz(nonEscapingClass);
assertThat(classSubject, isPresent());
-
- // TODO(b/142772856): None of the non-escaping classes should have a toString() method. It is
- // a requirement that the instance initializers are considered trivial for this to work,
- // though, even when they have a side effect (as long as the receiver does not escape via the
- // side effecting instruction).
- if (nonEscapingClass == DoesNotEscapeWithSubThatDoesNotOverrideSub.class) {
- assertThat(classSubject.uniqueMethodWithName("toString"), not(isPresent()));
- } else {
- assertThat(classSubject.uniqueMethodWithName("toString"), isPresent());
- }
+ assertThat(classSubject.uniqueMethodWithName("toString"), not(isPresent()));
}
}
@@ -135,8 +126,6 @@
@NeverClassInline
static class DoesNotEscape {
- // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
- // side effect.
DoesNotEscape() {
// Side effect to ensure that the constructor is not removed from main().
System.out.print("");
@@ -151,8 +140,6 @@
@NeverClassInline
static class DoesNotEscapeWithSubThatDoesNotOverride {
- // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
- // side effect.
DoesNotEscapeWithSubThatDoesNotOverride() {
// Side effect to ensure that the constructor is not removed from main().
System.out.print("");
@@ -168,8 +155,6 @@
static class DoesNotEscapeWithSubThatDoesNotOverrideSub
extends DoesNotEscapeWithSubThatDoesNotOverride {
- // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
- // side effect.
DoesNotEscapeWithSubThatDoesNotOverrideSub() {
// Side effect to ensure that the constructor is not removed from main().
System.out.print("");
@@ -179,8 +164,6 @@
@NeverClassInline
static class DoesNotEscapeWithSubThatOverrides {
- // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
- // side effect.
DoesNotEscapeWithSubThatOverrides() {
// Side effect to ensure that the constructor is not removed from main().
System.out.print("");
@@ -195,8 +178,6 @@
@NeverClassInline
static class DoesNotEscapeWithSubThatOverridesSub extends DoesNotEscapeWithSubThatOverrides {
- // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
- // side effect.
DoesNotEscapeWithSubThatOverridesSub() {
// Side effect to ensure that the constructor is not removed from main().
System.out.print("");
@@ -215,8 +196,6 @@
@NeverClassInline
static class DoesNotEscapeWithSubThatOverridesAndEscapes {
- // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
- // side effect.
DoesNotEscapeWithSubThatOverridesAndEscapes() {
// Side effect to ensure that the constructor is not removed from main().
System.out.print("");
@@ -232,8 +211,6 @@
static class DoesNotEscapeWithSubThatOverridesAndEscapesSub
extends DoesNotEscapeWithSubThatOverridesAndEscapes {
- // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
- // side effect.
DoesNotEscapeWithSubThatOverridesAndEscapesSub() {
// Side effect to ensure that the constructor is not removed from main().
System.out.print("");
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/C.java b/src/test/java/com/android/tools/r8/shaking/testrules/C.java
index e547e0b..f402864 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/C.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/C.java
@@ -6,9 +6,7 @@
import com.android.tools.r8.AssumeMayHaveSideEffects;
import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
-@NeverMerge
public class C {
private static int i;
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
index 2da0f7d..382ee13 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
@@ -14,7 +14,8 @@
import static org.junit.Assert.fail;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
@@ -25,22 +26,23 @@
@RunWith(Parameterized.class)
public class ForceInlineTest extends TestBase {
- private Backend backend;
- @Parameterized.Parameters(name = "Backend: {0}")
- public static Backend[] data() {
- return ToolHelper.getBackends();
+ private TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public ForceInlineTest(Backend backend) {
- this.backend = backend;
+ public ForceInlineTest(TestParameters parameters) {
+ this.parameters = parameters;
}
private CodeInspector runTest(List<String> proguardConfiguration) throws Exception {
- return testForR8(backend)
+ return testForR8(parameters.getBackend())
.addProgramClasses(Main.class, A.class, B.class, C.class)
.addKeepRules(proguardConfiguration)
- .assumeAllMethodsMayHaveSideEffects()
+ .enableProguardTestOptions()
.compile()
.inspector();
}
@@ -61,7 +63,7 @@
ClassSubject classMain = inspector.clazz(Main.class);
assertThat(classA, isPresent());
assertThat(classB, isPresent());
- assertThat(classC, isPresent());
+ assertThat(classC, not(isPresent()));
assertThat(classMain, isPresent());
// By default A.m *will not* be inlined (called several times and not small).
@@ -92,7 +94,7 @@
ClassSubject classMain = inspector.clazz(Main.class);
assertThat(classA, isPresent());
assertThat(classB, isPresent());
- assertThat(classC, isPresent());
+ assertThat(classC, not(isPresent()));
assertThat(classMain, isPresent());
// Compared to the default method is no longer inlined.
@@ -114,16 +116,11 @@
"-neverinline class *{ @com.android.tools.r8.NeverInline <methods>;}",
"-dontobfuscate"));
- ClassSubject classA = inspector.clazz(A.class);
- ClassSubject classB = inspector.clazz(B.class);
- ClassSubject classC = inspector.clazz(C.class);
- ClassSubject classMain = inspector.clazz(Main.class);
-
// Compared to the default m is now inlined and method still is, so classes A and B are gone.
- assertThat(classA, not(isPresent()));
- assertThat(classB, not(isPresent()));
- assertThat(classC, isPresent());
- assertThat(classMain, 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(Main.class), isPresent());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 09faf46..461323d 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -134,7 +134,7 @@
return this;
}
- /** Base addtion of a transformer on methods. */
+ /** Base addition of a transformer on methods. */
public ClassFileTransformer addMethodTransformer(MethodTransformer transformer) {
methodTransformers.add(transformer);
return this;
@@ -263,6 +263,24 @@
});
}
+ @FunctionalInterface
+ public interface MethodPredicate {
+ boolean test(int access, String name, String descriptor, String signature, String[] exceptions);
+ }
+
+ public ClassFileTransformer removeMethods(MethodPredicate predicate) {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String descriptor, String signature, String[] exceptions) {
+ return predicate.test(access, name, descriptor, signature, exceptions)
+ ? null
+ : super.visitMethod(access, name, descriptor, signature, exceptions);
+ }
+ });
+ }
+
/** Abstraction of the MethodVisitor.visitMethodInsn method with its continuation. */
@FunctionalInterface
public interface MethodInsnTransform {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 8e2f554..e665dcf 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -137,4 +137,9 @@
public String getFinalSignatureAttribute() {
return null;
}
+
+ @Override
+ public KmClassSubject getKmClass() {
+ return null;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
new file mode 100644
index 0000000..201f644
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
@@ -0,0 +1,36 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.kotlin.Kotlin;
+import kotlinx.metadata.KmClass;
+
+public class AbsentKmClassSubject extends KmClassSubject {
+
+ @Override
+ public DexClass getDexClass() {
+ return null;
+ }
+
+ @Override
+ public KmClass getKmClass(Kotlin kotlin) {
+ return null;
+ }
+
+ @Override
+ public boolean isPresent() {
+ return false;
+ }
+
+ @Override
+ public boolean isRenamed() {
+ return false;
+ }
+
+ @Override
+ public boolean isSynthetic() {
+ return false;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionIterator.java
index 0a3263a..6273334 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionIterator.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionIterator.java
@@ -11,10 +11,12 @@
class CfInstructionIterator implements InstructionIterator {
private final CodeInspector codeInspector;
+ private final MethodSubject method;
private final Iterator<CfInstruction> iterator;
CfInstructionIterator(CodeInspector codeInspector, MethodSubject method) {
this.codeInspector = codeInspector;
+ this.method = method;
assert method.isPresent();
Code code = method.getMethod().getCode();
assert code != null && code.isCfCode();
@@ -28,6 +30,6 @@
@Override
public InstructionSubject next() {
- return codeInspector.createInstructionSubject(iterator.next());
+ return codeInspector.createInstructionSubject(iterator.next(), method);
}
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index d7ea3ae..a9ea1b7 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -40,10 +40,13 @@
import org.objectweb.asm.Opcodes;
public class CfInstructionSubject implements InstructionSubject {
- protected final CfInstruction instruction;
- public CfInstructionSubject(CfInstruction instruction) {
+ protected final CfInstruction instruction;
+ private final MethodSubject method;
+
+ public CfInstructionSubject(CfInstruction instruction, MethodSubject method) {
this.instruction = instruction;
+ this.method = method;
}
@Override
@@ -330,6 +333,7 @@
return instruction instanceof CfArrayStore;
}
+
@Override
public int size() {
// TODO(b/122302789): CfInstruction#getSize()
@@ -343,6 +347,11 @@
}
@Override
+ public MethodSubject getMethodSubject() {
+ return method;
+ }
+
+ @Override
public boolean equals(Object other) {
return other instanceof CfInstructionSubject
&& instruction.equals(((CfInstructionSubject) other).instruction);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 8b8f519..705337c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -166,4 +166,6 @@
public abstract String getOriginalSignatureAttribute();
public abstract String getFinalSignatureAttribute();
+
+ public abstract KmClassSubject getKmClass();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 2bc1dd4..4aec544 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -37,6 +37,8 @@
import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.RetraceBase;
+import com.android.tools.r8.retrace.RetraceBaseImpl;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.BiMapContainer;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -70,6 +72,10 @@
public static MethodSignature MAIN =
new MethodSignature("main", "void", new String[] {"java.lang.String[]"});
+ public CodeInspector(String path) throws IOException, ExecutionException {
+ this(Paths.get(path));
+ }
+
public CodeInspector(Path file, String mappingFile) throws IOException, ExecutionException {
this(Collections.singletonList(file), mappingFile, null);
}
@@ -331,31 +337,31 @@
return originalTypeName != null ? originalTypeName : minifiedTypeName;
}
- InstructionSubject createInstructionSubject(Instruction instruction) {
- DexInstructionSubject dexInst = new DexInstructionSubject(instruction);
+ InstructionSubject createInstructionSubject(Instruction instruction, MethodSubject method) {
+ DexInstructionSubject dexInst = new DexInstructionSubject(instruction, method);
if (dexInst.isInvoke()) {
- return new InvokeDexInstructionSubject(this, instruction);
+ return new InvokeDexInstructionSubject(this, instruction, method);
} else if (dexInst.isFieldAccess()) {
- return new FieldAccessDexInstructionSubject(this, instruction);
+ return new FieldAccessDexInstructionSubject(this, instruction, method);
} else if (dexInst.isNewInstance()) {
- return new NewInstanceDexInstructionSubject(instruction);
+ return new NewInstanceDexInstructionSubject(instruction, method);
} else if (dexInst.isConstString(JumboStringMode.ALLOW)) {
- return new ConstStringDexInstructionSubject(instruction);
+ return new ConstStringDexInstructionSubject(instruction, method);
} else {
return dexInst;
}
}
- InstructionSubject createInstructionSubject(CfInstruction instruction) {
- CfInstructionSubject cfInst = new CfInstructionSubject(instruction);
+ InstructionSubject createInstructionSubject(CfInstruction instruction, MethodSubject method) {
+ CfInstructionSubject cfInst = new CfInstructionSubject(instruction, method);
if (cfInst.isInvoke()) {
- return new InvokeCfInstructionSubject(this, instruction);
+ return new InvokeCfInstructionSubject(this, instruction, method);
} else if (cfInst.isFieldAccess()) {
- return new FieldAccessCfInstructionSubject(this, instruction);
+ return new FieldAccessCfInstructionSubject(this, instruction, method);
} else if (cfInst.isNewInstance()) {
- return new NewInstanceCfInstructionSubject(instruction);
+ return new NewInstanceCfInstructionSubject(instruction, method);
} else if (cfInst.isConstString(JumboStringMode.ALLOW)) {
- return new ConstStringCfInstructionSubject(instruction);
+ return new ConstStringCfInstructionSubject(instruction, method);
} else {
return cfInst;
}
@@ -458,4 +464,8 @@
// nothing to do
}
}
+
+ public RetraceBase retrace() {
+ return RetraceBaseImpl.create(mapping);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringCfInstructionSubject.java
index da545e4..c351e10 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringCfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringCfInstructionSubject.java
@@ -10,8 +10,8 @@
public class ConstStringCfInstructionSubject extends CfInstructionSubject
implements ConstStringInstructionSubject {
- public ConstStringCfInstructionSubject(CfInstruction instruction) {
- super(instruction);
+ public ConstStringCfInstructionSubject(CfInstruction instruction, MethodSubject method) {
+ super(instruction, method);
assert isConstString(JumboStringMode.ALLOW);
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringDexInstructionSubject.java
index cb69144..ddff370 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringDexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringDexInstructionSubject.java
@@ -11,8 +11,8 @@
public class ConstStringDexInstructionSubject extends DexInstructionSubject
implements ConstStringInstructionSubject {
- public ConstStringDexInstructionSubject(Instruction instruction) {
- super(instruction);
+ public ConstStringDexInstructionSubject(Instruction instruction, MethodSubject method) {
+ super(instruction, method);
assert isConstString(JumboStringMode.ALLOW);
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java
index 8ae5609..0d98b29 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java
@@ -12,10 +12,12 @@
private final CodeInspector codeInspector;
private final DexCode code;
+ private final MethodSubject methodSubject;
private int index;
DexInstructionIterator(CodeInspector codeInspector, MethodSubject method) {
this.codeInspector = codeInspector;
+ this.methodSubject = method;
assert method.isPresent();
Code code = method.getMethod().getCode();
assert code != null && code.isDexCode();
@@ -33,6 +35,6 @@
if (index == code.instructions.length) {
throw new NoSuchElementException();
}
- return codeInspector.createInstructionSubject(code.instructions[index++]);
+ return codeInspector.createInstructionSubject(code.instructions[index++], methodSubject);
}
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 53fb02c..c46c123 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -104,10 +104,13 @@
import com.android.tools.r8.ir.code.WideConstant;
public class DexInstructionSubject implements InstructionSubject {
- protected final Instruction instruction;
- public DexInstructionSubject(Instruction instruction) {
+ protected final Instruction instruction;
+ protected final MethodSubject method;
+
+ public DexInstructionSubject(Instruction instruction, MethodSubject method) {
this.instruction = instruction;
+ this.method = method;
}
@Override
@@ -438,6 +441,11 @@
}
@Override
+ public MethodSubject getMethodSubject() {
+ return method;
+ }
+
+ @Override
public boolean equals(Object other) {
return other instanceof DexInstructionSubject
&& instruction.equals(((DexInstructionSubject) other).instruction);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessCfInstructionSubject.java
index ae7d420..2e1d073 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessCfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessCfInstructionSubject.java
@@ -11,8 +11,9 @@
implements FieldAccessInstructionSubject {
private final CodeInspector codeInspector;
- public FieldAccessCfInstructionSubject(CodeInspector codeInspector, CfInstruction instruction) {
- super(instruction);
+ public FieldAccessCfInstructionSubject(
+ CodeInspector codeInspector, CfInstruction instruction, MethodSubject method) {
+ super(instruction, method);
this.codeInspector = codeInspector;
assert isFieldAccess();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessDexInstructionSubject.java
index 361213a..fcfdbbe 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessDexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessDexInstructionSubject.java
@@ -11,8 +11,9 @@
private final CodeInspector codeInspector;
- public FieldAccessDexInstructionSubject(CodeInspector codeInspector, Instruction instruction) {
- super(instruction);
+ public FieldAccessDexInstructionSubject(
+ CodeInspector codeInspector, Instruction instruction, MethodSubject method) {
+ super(instruction, method);
this.codeInspector = codeInspector;
assert isFieldAccess();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
index 0a53c91..a97f040 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
@@ -12,7 +12,7 @@
private final DexAnnotation annotation;
- public FoundAnnotationSubject(DexAnnotation annotation) {
+ FoundAnnotationSubject(DexAnnotation annotation) {
this.annotation = annotation;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 45276f4..adc0dbd 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -4,6 +4,9 @@
package com.android.tools.r8.utils.codeinspector;
+import static com.android.tools.r8.KotlinTestBase.METADATA_TYPE;
+import static org.junit.Assert.assertTrue;
+
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
@@ -23,6 +26,7 @@
import com.android.tools.r8.utils.StringUtils;
import java.util.List;
import java.util.function.Consumer;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
public class FoundClassSubject extends ClassSubject {
@@ -324,7 +328,21 @@
return dexClass.toSourceString();
}
- public TypeSubject asTypeSybject() {
+ public TypeSubject asTypeSubject() {
return new TypeSubject(codeInspector, getDexClass().type);
}
+
+ @Override
+ public KmClassSubject getKmClass() {
+ AnnotationSubject annotationSubject = annotation(METADATA_TYPE);
+ if (!annotationSubject.isPresent()) {
+ return new AbsentKmClassSubject();
+ }
+ KotlinClassMetadata metadata =
+ KotlinClassMetadataReader.toKotlinClassMetadata(
+ codeInspector.getFactory().kotlin, annotationSubject.getAnnotation());
+ assertTrue(metadata instanceof KotlinClassMetadata.Class);
+ KotlinClassMetadata.Class kClass = (KotlinClassMetadata.Class) metadata;
+ return new FoundKmClassSubject(codeInspector, getDexClass(), kClass.toKmClass());
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
new file mode 100644
index 0000000..358a016
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
@@ -0,0 +1,145 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmClass;
+import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeVisitor;
+
+public class FoundKmClassSubject extends KmClassSubject {
+ private final CodeInspector codeInspector;
+ private final DexClass clazz;
+ private final KmClass kmClass;
+
+ FoundKmClassSubject(CodeInspector codeInspector, DexClass clazz, KmClass kmClass) {
+ this.codeInspector = codeInspector;
+ this.clazz = clazz;
+ this.kmClass = kmClass;
+ }
+
+ @Override
+ public DexClass getDexClass() {
+ return clazz;
+ }
+
+ @Override
+ public KmClass getKmClass(Kotlin kotlin) {
+ return kmClass;
+ }
+
+ @Override
+ public boolean isPresent() {
+ return true;
+ }
+
+ @Override
+ public boolean isRenamed() {
+ return !clazz.type.getInternalName().equals(kmClass.name);
+ }
+
+ @Override
+ public boolean isSynthetic() {
+ // TODO(b/70169921): This should return `true` conditionally if we start synthesizing @Metadata
+ // from scratch.
+ return false;
+ }
+
+ private String getDescriptorFromKmType(KmType kmType) {
+ if (kmType == null) {
+ return null;
+ }
+ Box<String> descriptor = new Box<>(null);
+ kmType.accept(new KmTypeVisitor() {
+ @Override
+ public void visitClass(String name) {
+ descriptor.set(DescriptorUtils.getDescriptorFromKotlinClassifier(name));
+ }
+ });
+ return descriptor.get();
+ }
+
+ @Override
+ public List<String> getSuperTypeDescriptors() {
+ return kmClass.getSupertypes().stream()
+ .map(this::getDescriptorFromKmType)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<String> getParameterTypeDescriptorsInFunctions() {
+ return kmClass.getFunctions().stream()
+ .flatMap(kmFunction ->
+ kmFunction.getValueParameters().stream()
+ .map(kmValueParameter -> getDescriptorFromKmType(kmValueParameter.getType()))
+ .filter(Objects::nonNull))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<String> getReturnTypeDescriptorsInFunctions() {
+ return kmClass.getFunctions().stream()
+ .map(kmFunction -> getDescriptorFromKmType(kmFunction.getReturnType()))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<String> getReturnTypeDescriptorsInProperties() {
+ return kmClass.getProperties().stream()
+ .map(kmProperty -> getDescriptorFromKmType(kmProperty.getReturnType()))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ private ClassSubject getClassSubjectFromKmType(KmType kmType) {
+ String descriptor = getDescriptorFromKmType(kmType);
+ if (descriptor == null) {
+ return new AbsentClassSubject();
+ }
+ return codeInspector.clazz(Reference.classFromDescriptor(descriptor));
+ }
+
+ @Override
+ public List<ClassSubject> getSuperTypes() {
+ return kmClass.getSupertypes().stream()
+ .map(this::getClassSubjectFromKmType)
+ .filter(ClassSubject::isPresent)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<ClassSubject> getParameterTypesInFunctions() {
+ return kmClass.getFunctions().stream()
+ .flatMap(kmFunction ->
+ kmFunction.getValueParameters().stream()
+ .map(kmValueParameter -> getClassSubjectFromKmType(kmValueParameter.getType()))
+ .filter(ClassSubject::isPresent))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<ClassSubject> getReturnTypesInFunctions() {
+ return kmClass.getFunctions().stream()
+ .map(kmFunction -> getClassSubjectFromKmType(kmFunction.getReturnType()))
+ .filter(ClassSubject::isPresent)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<ClassSubject> getReturnTypesInProperties() {
+ return kmClass.getProperties().stream()
+ .map(kmProperty -> getClassSubjectFromKmType(kmProperty.getReturnType()))
+ .filter(ClassSubject::isPresent)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 0466c4e..071fb4f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -29,15 +29,18 @@
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.signature.GenericSignatureParser;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.LocalVariableTable.LocalVariableTableEntry;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
-import it.unimi.dsi.fastutil.objects.Reference2IntMap;
-import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.Arrays;
import java.util.Iterator;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
public class FoundMethodSubject extends MethodSubject {
@@ -260,14 +263,14 @@
private LineNumberTable getCfLineNumberTable(CfCode code) {
int currentLine = -1;
- Reference2IntMap<InstructionSubject> lineNumberTable =
- new Reference2IntOpenHashMap<>(code.getInstructions().size());
+ Object2IntMap<InstructionSubject> lineNumberTable =
+ new Object2IntOpenHashMap<>(code.getInstructions().size());
for (CfInstruction insn : code.getInstructions()) {
if (insn instanceof CfPosition) {
currentLine = ((CfPosition) insn).getPosition().line;
}
if (currentLine != -1) {
- lineNumberTable.put(new CfInstructionSubject(insn), currentLine);
+ lineNumberTable.put(new CfInstructionSubject(insn, this), currentLine);
}
}
return currentLine == -1 ? null : new LineNumberTable(lineNumberTable);
@@ -278,8 +281,7 @@
if (debugInfo == null) {
return null;
}
- Reference2IntMap<InstructionSubject> lineNumberTable =
- new Reference2IntOpenHashMap<>(code.instructions.length);
+ Object2IntMap<InstructionSubject> lineNumberTable = new Object2IntOpenHashMap<>();
DexDebugPositionState state =
new DexDebugPositionState(debugInfo.startLine, getMethod().method);
Iterator<DexDebugEvent> iterator = Arrays.asList(debugInfo.events).iterator();
@@ -288,7 +290,7 @@
while (state.getCurrentPc() < offset && iterator.hasNext()) {
iterator.next().accept(state);
}
- lineNumberTable.put(new DexInstructionSubject(insn), state.getCurrentLine());
+ lineNumberTable.put(new DexInstructionSubject(insn, this), state.getCurrentLine());
}
return new LineNumberTable(lineNumberTable);
}
@@ -316,8 +318,8 @@
localVariable.getLocal().signature == null
? null
: localVariable.getLocal().signature.toString(),
- new CfInstructionSubject(localVariable.getStart()),
- new CfInstructionSubject(localVariable.getEnd())));
+ new CfInstructionSubject(localVariable.getStart(), this),
+ new CfInstructionSubject(localVariable.getEnd(), this)));
}
return new LocalVariableTable(builder.build());
}
@@ -338,4 +340,20 @@
? new AbsentAnnotationSubject()
: new FoundAnnotationSubject(annotation);
}
+
+ @Override
+ public FoundMethodSubject asFoundMethodSubject() {
+ return this;
+ }
+
+ public MethodReference asMethodReference() {
+ DexMethod method = dexMethod.method;
+ return Reference.method(
+ Reference.classFromDescriptor(method.holder.toDescriptorString()),
+ method.name.toString(),
+ Arrays.stream(method.proto.parameters.values)
+ .map(type -> Reference.typeFromDescriptor(type.toDescriptorString()))
+ .collect(Collectors.toList()),
+ Reference.returnTypeFromDescriptor(method.proto.returnType.toDescriptorString()));
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 25e10f4..9c67ece 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -6,6 +6,8 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.retrace.RetraceBase;
+import com.android.tools.r8.retrace.RetraceMethodResult;
public interface InstructionSubject {
@@ -107,4 +109,19 @@
int size();
InstructionOffsetSubject getOffset(MethodSubject methodSubject);
+
+ MethodSubject getMethodSubject();
+
+ default int getLineNumber() {
+ return getMethodSubject().getLineNumberTable().getLineForInstruction(this);
+ }
+
+ default RetraceMethodResult retracePosition(RetraceBase retraceBase) {
+ MethodSubject methodSubject = getMethodSubject();
+ assert methodSubject.isPresent();
+ int lineNumber = getLineNumber();
+ return retraceBase
+ .retrace(methodSubject.asFoundMethodSubject().asMethodReference())
+ .narrowByLine(lineNumber);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeCfInstructionSubject.java
index c5ceb74..e43ece1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeCfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeCfInstructionSubject.java
@@ -13,8 +13,9 @@
implements InvokeInstructionSubject {
private final CodeInspector codeInspector;
- public InvokeCfInstructionSubject(CodeInspector codeInspector, CfInstruction instruction) {
- super(instruction);
+ public InvokeCfInstructionSubject(
+ CodeInspector codeInspector, CfInstruction instruction, MethodSubject method) {
+ super(instruction, method);
assert isInvoke();
this.codeInspector = codeInspector;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeDexInstructionSubject.java
index 57a0e71..7f5c6ff 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeDexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeDexInstructionSubject.java
@@ -12,8 +12,9 @@
private final CodeInspector codeInspector;
- public InvokeDexInstructionSubject(CodeInspector codeInspector, Instruction instruction) {
- super(instruction);
+ public InvokeDexInstructionSubject(
+ CodeInspector codeInspector, Instruction instruction, MethodSubject method) {
+ super(instruction, method);
this.codeInspector = codeInspector;
assert isInvoke();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
new file mode 100644
index 0000000..aac5196
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
@@ -0,0 +1,46 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.kotlin.Kotlin;
+import java.util.List;
+import kotlinx.metadata.KmClass;
+
+public abstract class KmClassSubject extends Subject {
+ public abstract DexClass getDexClass();
+ public abstract KmClass getKmClass(Kotlin kotlin);
+
+ public List<String> getSuperTypeDescriptors() {
+ return null;
+ }
+
+ public List<String> getParameterTypeDescriptorsInFunctions() {
+ return null;
+ }
+
+ public List<String> getReturnTypeDescriptorsInFunctions() {
+ return null;
+ }
+
+ public List<String> getReturnTypeDescriptorsInProperties() {
+ return null;
+ }
+
+ public List<ClassSubject> getSuperTypes() {
+ return null;
+ }
+
+ public List<ClassSubject> getParameterTypesInFunctions() {
+ return null;
+ }
+
+ public List<ClassSubject> getReturnTypesInFunctions() {
+ return null;
+ }
+
+ public List<ClassSubject> getReturnTypesInProperties() {
+ return null;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KotlinClassMetadataReader.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KotlinClassMetadataReader.java
new file mode 100644
index 0000000..b3a0143
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KotlinClassMetadataReader.java
@@ -0,0 +1,91 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.kotlin.Kotlin;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+
+// TODO(b/145824437): This is a dup of the same class in source to avoid the error while building
+// keep rules for r8lib. Should be able to avoid this redundancy at build configuration level or
+// change -printusage to apply mappings regarding relocated deps.
+class KotlinClassMetadataReader {
+ static KotlinClassMetadata toKotlinClassMetadata(
+ Kotlin kotlin, DexEncodedAnnotation metadataAnnotation) {
+ Map<DexString, DexAnnotationElement> elementMap = new IdentityHashMap<>();
+ for (DexAnnotationElement element : metadataAnnotation.elements) {
+ elementMap.put(element.name, element);
+ }
+
+ DexAnnotationElement kind = elementMap.get(kotlin.metadata.kind);
+ if (kind == null) {
+ throw new MetadataError("element 'k' is missing.");
+ }
+ Integer k = (Integer) kind.value.getBoxedValue();
+ DexAnnotationElement metadataVersion = elementMap.get(kotlin.metadata.metadataVersion);
+ int[] mv = metadataVersion == null ? null : getUnboxedIntArray(metadataVersion.value, "mv");
+ DexAnnotationElement bytecodeVersion = elementMap.get(kotlin.metadata.bytecodeVersion);
+ int[] bv = bytecodeVersion == null ? null : getUnboxedIntArray(bytecodeVersion.value, "bv");
+ DexAnnotationElement data1 = elementMap.get(kotlin.metadata.data1);
+ String[] d1 = data1 == null ? null : getUnboxedStringArray(data1.value, "d1");
+ DexAnnotationElement data2 = elementMap.get(kotlin.metadata.data2);
+ String[] d2 = data2 == null ? null : getUnboxedStringArray(data2.value, "d2");
+ DexAnnotationElement extraString = elementMap.get(kotlin.metadata.extraString);
+ String xs = extraString == null ? null : getUnboxedString(extraString.value, "xs");
+ DexAnnotationElement packageName = elementMap.get(kotlin.metadata.packageName);
+ String pn = packageName == null ? null : getUnboxedString(packageName.value, "pn");
+ DexAnnotationElement extraInt = elementMap.get(kotlin.metadata.extraInt);
+ Integer xi = extraInt == null ? null : (Integer) extraInt.value.getBoxedValue();
+
+ KotlinClassHeader header = new KotlinClassHeader(k, mv, bv, d1, d2, xs, pn, xi);
+ return KotlinClassMetadata.read(header);
+ }
+
+ private static int[] getUnboxedIntArray(DexValue v, String elementName) {
+ if (!(v instanceof DexValueArray)) {
+ throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString());
+ }
+ DexValueArray intArrayValue = (DexValueArray) v;
+ DexValue[] values = intArrayValue.getValues();
+ int[] result = new int [values.length];
+ for (int i = 0; i < values.length; i++) {
+ result[i] = (Integer) values[i].getBoxedValue();
+ }
+ return result;
+ }
+
+ private static String[] getUnboxedStringArray(DexValue v, String elementName) {
+ if (!(v instanceof DexValueArray)) {
+ throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString());
+ }
+ DexValueArray stringArrayValue = (DexValueArray) v;
+ DexValue[] values = stringArrayValue.getValues();
+ String[] result = new String [values.length];
+ for (int i = 0; i < values.length; i++) {
+ result[i] = getUnboxedString(values[i], elementName + "[" + i + "]");
+ }
+ return result;
+ }
+
+ private static String getUnboxedString(DexValue v, String elementName) {
+ if (!(v instanceof DexValueString)) {
+ throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString());
+ }
+ return ((DexValueString) v).getValue().toString();
+ }
+
+ private static class MetadataError extends RuntimeException {
+ MetadataError(String cause) {
+ super(cause);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java b/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java
index 7c9c30f..a90707f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java
@@ -4,16 +4,20 @@
package com.android.tools.r8.utils.codeinspector;
import it.unimi.dsi.fastutil.ints.IntCollection;
-import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
public class LineNumberTable {
- private final Reference2IntMap<InstructionSubject> lineNumberTable;
+ private final Object2IntMap<InstructionSubject> lineNumberTable;
- public LineNumberTable(Reference2IntMap<InstructionSubject> lineNumberTable) {
+ public LineNumberTable(Object2IntMap<InstructionSubject> lineNumberTable) {
this.lineNumberTable = lineNumberTable;
}
public IntCollection getLines() {
return lineNumberTable.values();
}
+
+ public int getLineForInstruction(InstructionSubject subject) {
+ return lineNumberTable.getInt(subject);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 417e40e..d9f11d6 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -6,7 +6,16 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.retrace.RetraceMethodResult.Element;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.stream.Collectors;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
@@ -48,6 +57,10 @@
type = "method";
} else if (subject instanceof FieldSubject) {
type = "field";
+ } else if (subject instanceof AnnotationSubject) {
+ type = "annotation";
+ } else if (subject instanceof KmClassSubject) {
+ type = "@Metadata.KmClass";
}
return type;
}
@@ -60,6 +73,10 @@
name = ((MethodSubject) subject).getOriginalName();
} else if (subject instanceof FieldSubject) {
name = ((FieldSubject) subject).getOriginalName();
+ } else if (subject instanceof AnnotationSubject) {
+ name = ((AnnotationSubject) subject).getAnnotation().type.toSourceString();
+ } else if (subject instanceof KmClassSubject) {
+ name = ((KmClassSubject) subject).getDexClass().toSourceString();
}
return name;
}
@@ -339,4 +356,189 @@
}
};
}
+
+ public static Matcher<RetraceMethodResult> isInlineFrame() {
+ return new TypeSafeMatcher<RetraceMethodResult>() {
+ @Override
+ protected boolean matchesSafely(RetraceMethodResult item) {
+ return !item.isAmbiguous() && item.stream().count() > 1;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("is not an inline frame");
+ }
+ };
+ }
+
+ public static Matcher<RetraceMethodResult> isInlineStack(InlinePosition startPosition) {
+ return new TypeSafeMatcher<RetraceMethodResult>() {
+ @Override
+ protected boolean matchesSafely(RetraceMethodResult item) {
+ Box<InlinePosition> currentPosition = new Box<>(startPosition);
+ Box<Boolean> returnValue = new Box<>();
+ item.forEach(
+ element -> {
+ boolean sameMethod;
+ InlinePosition currentInline = currentPosition.get();
+ if (currentInline == null) {
+ returnValue.set(false);
+ return;
+ }
+ if (currentInline.hasMethodSubject()) {
+ sameMethod =
+ element
+ .getMethodReference()
+ .equals(
+ currentInline.methodSubject.asFoundMethodSubject().asMethodReference());
+ } else {
+ MethodReference methodReference = element.getMethodReference();
+ sameMethod =
+ methodReference.getMethodName().equals(currentInline.methodName)
+ || methodReference
+ .getHolderClass()
+ .getTypeName()
+ .equals(currentInline.holder);
+ }
+ boolean samePosition =
+ element.getOriginalLineNumber(currentInline.minifiedPosition)
+ == currentInline.originalPosition;
+ if (!returnValue.isSet() || returnValue.get()) {
+ returnValue.set(sameMethod & samePosition);
+ }
+ currentPosition.set(currentInline.caller);
+ });
+ return returnValue.isSet() && returnValue.get();
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("is not matching the inlining stack");
+ }
+ };
+ }
+
+ public static Matcher<RetraceMethodResult> isInlinedInto(InlinePosition inlinePosition) {
+ return new TypeSafeMatcher<RetraceMethodResult>() {
+ @Override
+ protected boolean matchesSafely(RetraceMethodResult item) {
+ if (item.isAmbiguous() || !inlinePosition.methodSubject.isPresent()) {
+ return false;
+ }
+ List<Element> references = item.stream().collect(Collectors.toList());
+ if (references.size() < 2) {
+ return false;
+ }
+ Element lastElement = ListUtils.last(references);
+ if (!lastElement
+ .getMethodReference()
+ .equals(inlinePosition.methodSubject.asFoundMethodSubject().asMethodReference())) {
+ return false;
+ }
+ return lastElement.getFirstLineNumberOfOriginalRange() == inlinePosition.originalPosition;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("is not inlined into " + inlinePosition.getMethodName());
+ }
+ };
+ }
+
+ public static Matcher<StackTrace> containsInlinePosition(InlinePosition inlinePosition) {
+ return new TypeSafeMatcher<StackTrace>() {
+ @Override
+ protected boolean matchesSafely(StackTrace item) {
+ return containsInlineStack(item, 0, inlinePosition);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("cannot be found in stack trace");
+ }
+
+ private boolean containsInlineStack(
+ StackTrace stackTrace, int index, InlinePosition currentPosition) {
+ if (currentPosition == null) {
+ return true;
+ }
+ if (index >= stackTrace.size()) {
+ return false;
+ }
+ StackTraceLine stackTraceLine = stackTrace.get(index);
+ boolean resultHere =
+ stackTraceLine.className.equals(currentPosition.getClassName())
+ && stackTraceLine.methodName.equals(currentPosition.getMethodName())
+ && stackTraceLine.lineNumber == currentPosition.originalPosition;
+ if (resultHere && containsInlineStack(stackTrace, index + 1, currentPosition.caller)) {
+ return true;
+ }
+ // Maybe the inline position starts from the top on the next position.
+ return containsInlineStack(stackTrace, index + 1, inlinePosition);
+ }
+ };
+ }
+
+ public static class InlinePosition {
+ private final FoundMethodSubject methodSubject;
+ private final String holder;
+ private final String methodName;
+ private final int minifiedPosition;
+ private final int originalPosition;
+
+ private InlinePosition caller;
+
+ private InlinePosition(
+ FoundMethodSubject methodSubject,
+ String holder,
+ String methodName,
+ int minifiedPosition,
+ int originalPosition) {
+ this.methodSubject = methodSubject;
+ this.holder = holder;
+ this.methodName = methodName;
+ this.minifiedPosition = minifiedPosition;
+ this.originalPosition = originalPosition;
+ assert methodSubject != null || holder != null;
+ assert methodSubject != null || methodName != null;
+ }
+
+ public static InlinePosition create(
+ FoundMethodSubject methodSubject, int minifiedPosition, int originalPosition) {
+ return new InlinePosition(methodSubject, null, null, minifiedPosition, originalPosition);
+ }
+
+ public static InlinePosition create(
+ String holder, String methodName, int minifiedPosition, int originalPosition) {
+ return new InlinePosition(null, holder, methodName, minifiedPosition, originalPosition);
+ }
+
+ public static InlinePosition stack(InlinePosition... stack) {
+ setCaller(1, stack);
+ return stack[0];
+ }
+
+ private static void setCaller(int index, InlinePosition... stack) {
+ assert index > 0;
+ if (index >= stack.length) {
+ return;
+ }
+ stack[index - 1].caller = stack[index];
+ setCaller(index + 1, stack);
+ }
+
+ boolean hasMethodSubject() {
+ return methodSubject != null;
+ }
+
+ String getMethodName() {
+ return hasMethodSubject() ? methodSubject.getOriginalName(false) : methodName;
+ }
+
+ String getClassName() {
+ return hasMethodSubject()
+ ? methodSubject.asMethodReference().getHolderClass().getTypeName()
+ : holder;
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 4fe5fb0..af6e1a4 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -41,6 +41,10 @@
public abstract boolean isVirtual();
+ public FoundMethodSubject asFoundMethodSubject() {
+ return null;
+ }
+
@Override
public abstract MethodSignature getOriginalSignature();
@@ -82,6 +86,11 @@
return Streams.stream(iterateInstructions());
}
+ public void getLineNumberForInstruction(InstructionSubject subject) {
+ assert hasLineNumberTable();
+ getLineNumberTable().getLineForInstruction(subject);
+ }
+
@Override
public MethodSubject asMethodSubject() {
return this;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceCfInstructionSubject.java
index d3b0a93..f4e0a95 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceCfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceCfInstructionSubject.java
@@ -10,8 +10,8 @@
public class NewInstanceCfInstructionSubject extends CfInstructionSubject
implements NewInstanceInstructionSubject {
- public NewInstanceCfInstructionSubject(CfInstruction instruction) {
- super(instruction);
+ public NewInstanceCfInstructionSubject(CfInstruction instruction, MethodSubject method) {
+ super(instruction, method);
}
@Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceDexInstructionSubject.java
index a396468..ef883fa 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceDexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceDexInstructionSubject.java
@@ -10,8 +10,8 @@
public class NewInstanceDexInstructionSubject extends DexInstructionSubject
implements NewInstanceInstructionSubject {
- public NewInstanceDexInstructionSubject(Instruction instruction) {
- super(instruction);
+ public NewInstanceDexInstructionSubject(Instruction instruction, MethodSubject method) {
+ super(instruction, method);
}
@Override
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index 59376e6..fe98a17 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -18,7 +18,6 @@
import com.android.tools.r8.experimental.graphinfo.GraphNode;
import com.android.tools.r8.experimental.graphinfo.KeepRuleGraphNode;
import com.android.tools.r8.experimental.graphinfo.MethodGraphNode;
-import com.android.tools.r8.graph.invokesuper.Consumer;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
import com.android.tools.r8.position.TextPosition;
@@ -38,6 +37,7 @@
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiPredicate;
+import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.junit.Assert;
diff --git a/src/test/r8OnArtBackport/MockedPath.java b/src/test/r8OnArtBackport/MockedPath.java
index c5147df..3caa9cd 100644
--- a/src/test/r8OnArtBackport/MockedPath.java
+++ b/src/test/r8OnArtBackport/MockedPath.java
@@ -99,6 +99,11 @@
}
@Override
+ public Path resolve(String other) {
+ return new MockedPath(new File(wrappedFile.getPath(), other));
+ }
+
+ @Override
public Path relativize(Path other) {
throw new RuntimeException("Mocked Path does not implement relativize");
}
diff --git a/third_party/openjdk/desugar_jdk_libs.tar.gz.sha1 b/third_party/openjdk/desugar_jdk_libs.tar.gz.sha1
index fad80cc..7b8d748 100644
--- a/third_party/openjdk/desugar_jdk_libs.tar.gz.sha1
+++ b/third_party/openjdk/desugar_jdk_libs.tar.gz.sha1
@@ -1 +1 @@
-f06937c3611cbc82d08ff043aa5c176ce6989295
\ No newline at end of file
+2fdc16c4253420d7e240d5cf109bda5856984a8d
\ No newline at end of file
diff --git a/tools/archive_desugar_jdk_libs.py b/tools/archive_desugar_jdk_libs.py
index 903f3b8..f866564 100755
--- a/tools/archive_desugar_jdk_libs.py
+++ b/tools/archive_desugar_jdk_libs.py
@@ -19,7 +19,6 @@
# repository to fetch the artifact com.android.tools:desugar_jdk_libs:1.0.0
import archive
-import defines
import git_utils
import optparse
import os
@@ -28,19 +27,6 @@
import subprocess
import sys
import utils
-import zipfile
-
-if defines.IsLinux():
- JDK8_JAVAC = os.path.join(
- defines.THIRD_PARTY, 'openjdk', 'jdk8', 'linux-x86', 'bin', 'javac')
-elif defines.IsOsX():
- JDK8_JAVAC = os.path.join(
- defines.THIRD_PARTY, 'openjdk', 'jdk8', 'darwin-x86', 'bin', 'javac')
-elif defines.IsWindows():
- raise Exception('Cannot compile using JDK8 on Windows hence cannot archive.')
-
-CONVERSION_FOLDER = os.path.join(
- defines.REPO_ROOT, 'src', 'test', 'desugaredLibraryConversions')
VERSION_FILE = 'VERSION.txt'
LIBRARY_NAME = 'desugar_jdk_libs'
@@ -64,15 +50,12 @@
def GetVersion():
with open(VERSION_FILE, 'r') as version_file:
- lines = version_file.readlines()
+ lines = [line.strip() for line in version_file.readlines()]
+ lines = [line for line in lines if not line.startswith('#')]
if len(lines) != 1:
raise Exception('Version file '
+ VERSION_FILE + ' is expected to have exactly one line')
version = lines[0].strip()
- if (version == '1.0.1'):
- raise Exception('Version file ' + VERSION_FILE + 'cannot have version 1.0.1')
- if (version == '1.0.0'):
- version = '1.0.1'
utils.check_basic_semver_version(
version, 'in version file ' + VERSION_FILE)
return version
@@ -94,13 +77,6 @@
print('File available at: %s' %
destination.replace('gs://', 'http://storage.googleapis.com/', 1))
-def GetFilesInFolder(folder):
- resulting_files = []
- for root, dirs, files in os.walk(folder):
- for name in files:
- resulting_files.append(os.path.join(root, name))
- assert len(resulting_files) > 0
- return resulting_files
def Main(argv):
(options, args) = ParseOptions(argv)
@@ -146,46 +122,12 @@
utils.PrintCmd(cmd)
subprocess.check_call(cmd)
- # Compile the stubs for conversion files compilation.
- stub_compiled_folder = os.path.join(checkout_dir, 'stubs')
- os.mkdir(stub_compiled_folder)
- all_stubs = GetFilesInFolder(os.path.join(CONVERSION_FOLDER, 'stubs'))
- cmd = [JDK8_JAVAC, '-d', stub_compiled_folder] + all_stubs
- utils.PrintCmd(cmd)
- subprocess.check_call(cmd)
-
- # Compile the conversion files.
- conversions_compiled_folder = os.path.join(checkout_dir, 'conversions')
- os.mkdir(conversions_compiled_folder)
- all_conversions = GetFilesInFolder(
- os.path.join(CONVERSION_FOLDER, 'conversions'))
- cmd = [JDK8_JAVAC, '-cp', stub_compiled_folder, '-d',
- conversions_compiled_folder] + all_conversions
- utils.PrintCmd(cmd)
- subprocess.check_call(cmd)
-
# Locate the library jar and the maven zip with the jar from the
# bazel build.
library_jar = os.path.join(
'bazel-bin', 'src', 'share', 'classes', 'java', 'libjava.jar')
maven_zip = os.path.join('bazel-bin', LIBRARY_NAME +'.zip')
- # Make a writable copy of the jar.
- jar_folder = os.path.join(checkout_dir, 'jar')
- os.mkdir(jar_folder)
- shutil.copy(library_jar, jar_folder)
- library_jar = os.path.join(jar_folder,'libjava.jar')
- os.chmod(library_jar, 0o777)
-
- # Add conversion classes into the jar.
- all_compiled_conversions = GetFilesInFolder(conversions_compiled_folder)
- with zipfile.ZipFile(library_jar, mode='a', allowZip64=True) as jar:
- for clazz in all_compiled_conversions:
- jar.write(clazz,arcname=os.path.relpath(
- clazz,
- conversions_compiled_folder),
- compress_type = zipfile.ZIP_DEFLATED)
-
storage_path = LIBRARY_NAME + '/' + version
# Upload the jar file with the library.
destination = archive.GetUploadDestination(
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 39f60c0..366b908 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -11,6 +11,9 @@
import subprocess
import sys
import urllib
+import xml
+import xml.etree.ElementTree as et
+import zipfile
import update_prebuilds_in_android
import utils
@@ -19,6 +22,11 @@
R8_VERSION_FILE = os.path.join(
'src', 'main', 'java', 'com', 'android', 'tools', 'r8', 'Version.java')
THIS_FILE_RELATIVE = os.path.join('tools', 'r8_release.py')
+ADMRT = '/google/data/ro/teams/android-devtools-infra/tools/admrt'
+
+DESUGAR_JDK_LIBS = 'desugar_jdk_libs'
+DESUGAR_JDK_LIBS_CONFIGURATION = DESUGAR_JDK_LIBS + '_configuration'
+ANDROID_TOOLS_PACKAGE = 'com.android.tools'
def prepare_release(args):
@@ -262,8 +270,7 @@
def g4_change(version, r8version):
return subprocess.check_output(
'g4 change --desc "Update R8 to version %s %s\n\n'
- 'IGNORE_COMPLIANCELINT=D8 and R8 are built externally to produce a fully '
- 'tested R8lib"' % (version, r8version),
+ 'IGNORE_COMPLIANCELINT=b/145307639"' % (version, r8version),
shell=True)
@@ -289,7 +296,7 @@
def prepare_google3(args):
assert args.version
# Check if an existing client exists.
- if not options.use_existing_work_branch:
+ if not args.use_existing_work_branch:
if ':update-r8:' in subprocess.check_output('g4 myclients', shell=True):
print "Remove the existing 'update-r8' client before continuing."
sys.exit(1)
@@ -372,6 +379,139 @@
return release_google3
+def prepare_desugar_library(args):
+
+ def make_release(args):
+ library_version = args.desugar_library[0]
+ configuration_hash = args.desugar_library[1]
+
+ library_archive = DESUGAR_JDK_LIBS + '.zip'
+ library_artifact_id = \
+ '%s:%s:%s' % (ANDROID_TOOLS_PACKAGE, DESUGAR_JDK_LIBS, library_version)
+
+ with utils.TempDir() as temp:
+ with utils.ChangedWorkingDirectory(temp):
+ download_file(
+ '%s/%s' % (DESUGAR_JDK_LIBS, library_version),
+ library_archive,
+ library_archive)
+ configuration_archive = DESUGAR_JDK_LIBS_CONFIGURATION + '.zip'
+ configuration_artifact_id = \
+ download_configuration(configuration_hash, configuration_archive)
+
+ print 'Preparing maven release of:'
+ print ' %s' % library_artifact_id
+ print ' %s' % configuration_artifact_id
+ print
+
+ admrt_stage(
+ [library_archive, configuration_archive],
+ [library_artifact_id, configuration_artifact_id],
+ args)
+
+ admrt_lorry(
+ [library_archive, configuration_archive],
+ [library_artifact_id, configuration_artifact_id],
+ args)
+
+ return make_release
+
+
+def download_configuration(hash, archive):
+ print
+ print 'Downloading %s from GCS' % archive
+ print
+ download_file('master/' + hash, archive, archive)
+ zip = zipfile.ZipFile(archive)
+ zip.extractall()
+ dirs = os.listdir(
+ os.path.join('com', 'android', 'tools', DESUGAR_JDK_LIBS_CONFIGURATION))
+ if len(dirs) != 1:
+ print 'Unexpected archive content, %s' + dirs
+ sys.exit(1)
+
+ version = dirs[0]
+ pom_file = os.path.join(
+ 'com',
+ 'android',
+ 'tools',
+ DESUGAR_JDK_LIBS_CONFIGURATION,
+ version,
+ '%s-%s.pom' % (DESUGAR_JDK_LIBS_CONFIGURATION, version))
+ version_from_pom = extract_version_from_pom(pom_file)
+ if version != version_from_pom:
+ print 'Version mismatch, %s != %s' % (version, version_from_pom)
+ sys.exit(1)
+ return '%s:%s:%s' % \
+ (ANDROID_TOOLS_PACKAGE, DESUGAR_JDK_LIBS_CONFIGURATION, version)
+
+def extract_version_from_pom(pom_file):
+ ns = "http://maven.apache.org/POM/4.0.0"
+ xml.etree.ElementTree.register_namespace('', ns)
+ tree = xml.etree.ElementTree.ElementTree()
+ tree.parse(pom_file)
+ return tree.getroot().find("{%s}version" % ns).text
+
+
+def admrt_stage(archives, artifact_ids, args):
+ if args.dry_run:
+ print 'Dry-run, just copying archives to %s' % args.dry_run_output
+ for archive in archives:
+ print 'Copying: %s' % archive
+ shutil.copyfile(archive, os.path.join(args.dry_run_output, archive))
+ return
+
+ admrt(archives, 'stage')
+
+ jdk9_home = os.path.join(
+ utils.REPO_ROOT, 'third_party', 'openjdk', 'openjdk-9.0.4', 'linux')
+ print
+ print "Use the following commands to test with 'redir':"
+ print
+ print 'export BUCKET_PATH=/studio_staging/maven2/<user>/<id>'
+ print '/google/data/ro/teams/android-devtools-infra/tools/redir \\'
+ print ' --alsologtostderr \\'
+ print ' --gcs_bucket_path=$BUCKET_PATH \\'
+ print ' --port=1480'
+ print
+ print "When the 'redir' server is running use the following commands"
+ print 'to retreive the artifact:'
+ print
+ print 'rm -rf /tmp/maven_repo_local'
+ print ('JAVA_HOME=%s ' % jdk9_home
+ + 'mvn org.apache.maven.plugins:maven-dependency-plugin:2.4:get \\')
+ print ' -Dmaven.repo.local=/tmp/maven_repo_local \\'
+ print ' -DremoteRepositories=http://localhost:1480 \\'
+ print ' -Dartifact=%s \\' % artifact_ids[0]
+ print ' -Ddest=%s' % archives[0]
+ print
+
+
+def admrt_lorry(archives, artifact_ids, args):
+ if args.dry_run:
+ print 'Dry run - no lorry action'
+ return
+
+ print
+ print 'Continue with running in lorry mode for release of:'
+ for artifact_id in artifact_ids:
+ print ' %s' % artifact_id
+ input = raw_input('[y/N]:')
+
+ if input != 'y':
+ print 'Aborting release to Google maven'
+ sys.exit(1)
+
+ admrt(archives, 'lorry')
+
+
+def admrt(archives, action):
+ cmd = [ADMRT, '--archives']
+ cmd.extend(archives)
+ cmd.extend(['--action', action])
+ subprocess.check_call()
+
+
def branch_change_diff(diff, old_version, new_version):
invalid_line = None
for line in diff.splitlines():
@@ -496,19 +636,23 @@
group.add_argument('--dev-release',
metavar=('<master hash>'),
help='The hash to use for the new dev version of R8')
+ group.add_argument('--version',
+ metavar=('<version>'),
+ help='The new version of R8 (e.g., 1.4.51) to release to selected channels')
+ group.add_argument('--desugar-library',
+ nargs=2,
+ metavar=('<version>', '<configuration hash>'),
+ help='The new version of com.android.tools:desugar_jdk_libs')
+ group.add_argument('--new-dev-branch',
+ nargs=2,
+ metavar=('<version>', '<master hash>'),
+ help='Create a new branch starting a version line (e.g. 2.0)')
result.add_argument('--dev-pre-cherry-pick',
metavar=('<master hash(s)>'),
default=[],
action='append',
help='List of commits to cherry pick before doing full '
'merge, mostly used for reverting cherry picks')
- group.add_argument('--version',
- metavar=('<version>'),
- help='The new version of R8 (e.g., 1.4.51) to release to selected channels')
- group.add_argument('--new-dev-branch',
- nargs=2,
- metavar=('<version>', '<master hash>'),
- help='Create a new branch starting a version line (e.g. 2.0)')
result.add_argument('--no-sync', '--no_sync',
default=False,
action='store_true',
@@ -542,6 +686,10 @@
default=False,
action='store_true',
help='Only perform non-commiting tasks and print others.')
+ result.add_argument('--dry-run-output', '--dry_run_output',
+ default=os.getcwd(),
+ metavar=('<path>'),
+ help='Location for dry run output.')
args = result.parse_args()
if args.version and not 'dev' in args.version and args.bug == []:
print "When releasing a release version add the list of bugs by using '--bug'"
@@ -570,7 +718,9 @@
sys.exit(1)
targets_to_run.append(prepare_release(args))
- if args.google3 or (args.studio and not args.no_sync):
+ if (args.google3
+ or (args.studio and not args.no_sync)
+ or (args.desugar_library and not args.dry_run)):
utils.check_prodacces()
if args.google3:
@@ -580,6 +730,9 @@
if args.aosp:
targets_to_run.append(prepare_aosp(args))
+ if args.desugar_library:
+ targets_to_run.append(prepare_desugar_library(args))
+
final_results = []
for target_closure in targets_to_run:
final_results.append(target_closure(args))