Merge commit '62e041b54dace122b1a70dfeb40d985951ca3d44' into dev-release
diff --git a/src/main/java/com/android/tools/r8/AssertionsConfiguration.java b/src/main/java/com/android/tools/r8/AssertionsConfiguration.java
index f77bbce..15acd6f 100644
--- a/src/main/java/com/android/tools/r8/AssertionsConfiguration.java
+++ b/src/main/java/com/android/tools/r8/AssertionsConfiguration.java
@@ -4,12 +4,14 @@
package com.android.tools.r8;
-import com.android.tools.r8.utils.StringDiagnostic;
+import java.util.ArrayList;
+import java.util.List;
@Keep
public class AssertionsConfiguration {
/** The possible transformations of the javac generated assertion code during compilation. */
+ @Keep
public enum AssertionTransformation {
/** Unconditionally enable the javac generated assertion code. */
ENABLE,
@@ -22,14 +24,55 @@
PASSTHROUGH
}
- private AssertionTransformation transformation;
-
- private AssertionsConfiguration(AssertionTransformation transformation) {
- this.transformation = transformation;
+ public enum ConfigurationType {
+ ALL,
+ PACKAGE,
+ CLASS
}
- static Builder builder(DiagnosticsHandler handler) {
- return new Builder(handler);
+ public static class ConfigurationEntry {
+ private final AssertionTransformation transformation;
+ private final ConfigurationType type;
+ private final String value;
+
+ private ConfigurationEntry(
+ AssertionTransformation transformation, ConfigurationType type, String value) {
+ assert value != null || type == ConfigurationType.ALL;
+ this.transformation = transformation;
+ this.type = type;
+ this.value = value;
+ }
+
+ public AssertionTransformation getTransformation() {
+ return transformation;
+ }
+
+ public ConfigurationType getType() {
+ return type;
+ }
+
+ public String getValue() {
+ return value;
+ }
+ }
+
+ // Methods which need to be public.
+ public static class InternalAssertionConfiguration {
+
+ public static List<ConfigurationEntry> getConfiguration(AssertionsConfiguration configuration) {
+ return configuration.entries;
+ }
+ }
+
+ private final List<ConfigurationEntry> entries;
+
+ private AssertionsConfiguration(List<ConfigurationEntry> entries) {
+ this.entries = entries;
+ }
+
+ static AssertionsConfiguration.Builder builder(AssertionsConfiguration previous) {
+ return new AssertionsConfiguration.Builder(
+ previous != null ? previous.entries : new ArrayList<>());
}
/**
@@ -38,24 +81,35 @@
* <p>A builder is obtained by calling {@link
* BaseCompilerCommand.Builder#addAssertionsConfiguration}.
*/
- public AssertionTransformation getTransformation() {
- return transformation;
- }
-
@Keep
public static class Builder {
- private AssertionTransformation transformation = null;
+ private final List<ConfigurationEntry> entries;
- private final DiagnosticsHandler handler;
+ private Builder(List<ConfigurationEntry> previousEntries) {
+ assert previousEntries != null;
+ this.entries = previousEntries;
+ }
- private Builder(DiagnosticsHandler handler) {
- this.handler = handler;
+ private void addEntry(
+ AssertionTransformation transformation, ConfigurationType type, String value) {
+ entries.add(new ConfigurationEntry(transformation, type, value));
}
/** Set how to handle javac generated assertion code. */
public AssertionsConfiguration.Builder setTransformation(
AssertionTransformation transformation) {
- this.transformation = transformation;
+ addEntry(transformation, ConfigurationType.ALL, null);
+ return this;
+ }
+
+ AssertionsConfiguration.Builder setDefault(AssertionTransformation transformation) {
+ // Add the default by inserting a transform all entry at the beginning of the list, if there
+ // isn't already one.
+ ConfigurationEntry defaultEntry =
+ new ConfigurationEntry(transformation, ConfigurationType.ALL, null);
+ if (entries.size() == 0 || entries.get(0).type != ConfigurationType.ALL) {
+ entries.listIterator().add(defaultEntry);
+ }
return this;
}
@@ -86,7 +140,7 @@
/** Set how to handle javac generated assertion code in package and all subpackages. */
public AssertionsConfiguration.Builder setTransformationForPackage(
String packageName, AssertionTransformation transformation) {
- handler.error(new StringDiagnostic("Unsupported"));
+ addEntry(transformation, ConfigurationType.PACKAGE, packageName);
return this;
}
@@ -123,7 +177,7 @@
/** Set how to handle javac generated assertion code in class. */
public AssertionsConfiguration.Builder setTransformationForClass(
String className, AssertionTransformation transformation) {
- handler.error(new StringDiagnostic("Unsupported"));
+ addEntry(transformation, ConfigurationType.CLASS, className);
return this;
}
@@ -151,7 +205,7 @@
/** Build and return the {@link AssertionsConfiguration}. */
public AssertionsConfiguration build() {
- return new AssertionsConfiguration(transformation);
+ return new AssertionsConfiguration(entries);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index ce60661..2b94a54 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexItemFactory;
@@ -134,8 +135,11 @@
return optimizeMultidexForLinearAlloc;
}
- public AssertionsConfiguration getAssertionsConfiguration() {
- return assertionsConfiguration;
+ AssertionsConfiguration getAssertionsConfiguration(
+ AssertionTransformation defaultTransformation) {
+ return AssertionsConfiguration.builder(assertionsConfiguration)
+ .setDefault(defaultTransformation)
+ .build();
}
Reporter getReporter() {
@@ -491,7 +495,8 @@
Function<AssertionsConfiguration.Builder, AssertionsConfiguration>
assertionsConfigurationGenerator) {
assertionsConfiguration =
- assertionsConfigurationGenerator.apply(AssertionsConfiguration.builder(getReporter()));
+ assertionsConfigurationGenerator.apply(
+ AssertionsConfiguration.builder(assertionsConfiguration));
return self();
}
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 2214e2d..d3ec378 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -6,7 +6,6 @@
import static com.android.tools.r8.D8Command.USAGE_MESSAGE;
import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.dex.Marker;
@@ -19,6 +18,7 @@
import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
+import com.android.tools.r8.ir.optimize.AssertionsRewriter;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.naming.PrefixRewritingNamingLens;
import com.android.tools.r8.origin.CommandLineOrigin;
@@ -159,7 +159,7 @@
final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
- if (options.assertionTransformation != AssertionTransformation.PASSTHROUGH) {
+ if (!AssertionsRewriter.isPassthroughAll(options.assertionsConfiguration)) {
// Run analysis to mark all <clinit> methods having the javac generated assertion
// enabling code.
ClassInitializerAssertionEnablingAnalysis analysis =
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 6bdc35c..2401aa5 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -361,15 +361,10 @@
internal.desugaredLibraryConfiguration = libraryConfiguration;
internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
- assert internal.assertionTransformation == null;
- if (getAssertionsConfiguration() != null) {
- internal.assertionTransformation = getAssertionsConfiguration().getTransformation();
- }
- if (internal.assertionTransformation == null) {
- // Default, when no configuration is provided, is to disable all javac generated assertion
- // code when generating dex.
- internal.assertionTransformation = AssertionTransformation.DISABLE;
- }
+ assert internal.assertionsConfiguration == null;
+ // Default, when no configuration is provided, is to remove all javac generated assertion
+ // code when generating dex.
+ internal.assertionsConfiguration = getAssertionsConfiguration(AssertionTransformation.DISABLE);
return internal;
}
diff --git a/src/main/java/com/android/tools/r8/DesugarGraphConsumer.java b/src/main/java/com/android/tools/r8/DesugarGraphConsumer.java
index f771dd5..d75b2b0 100644
--- a/src/main/java/com/android/tools/r8/DesugarGraphConsumer.java
+++ b/src/main/java/com/android/tools/r8/DesugarGraphConsumer.java
@@ -10,17 +10,15 @@
public interface DesugarGraphConsumer {
/**
- * Callback indicating that code originating from {@code src} was used to correctly desugar some
- * code originating from {@code dst}.
- *
- * <p>In other words, {@code src} is a dependency for the desugaring of {@code dst}.
+ * Callback indicating that code originating from {@code dependency} is needed to correctly
+ * desugar code originating from {@code dependent}.
*
* <p>Note: this callback may be called on multiple threads.
*
* <p>Note: this callback places no guarantees on order of calls or on duplicate calls.
*
- * @param src Origin of some code input that is needed to desugar {@code dst}.
- * @param dst Origin of some code that was dependent on code in {@code src}.
+ * @param dependent Origin of code that is dependent on code in {@code dependency}.
+ * @param dependency Origin of code that is a dependency to compile {@code dependent}.
*/
- void accept(Origin src, Origin dst);
+ void accept(Origin dependent, Origin dependency);
}
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index ff64d62..1346623 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
import com.android.tools.r8.graph.DexItemFactory;
@@ -141,6 +142,11 @@
// TODO(134732760): This is still work in progress.
internal.desugaredLibraryConfiguration = libraryConfiguration;
+ assert internal.assertionsConfiguration == null;
+ // Default, when no configuration is provided, is to remove all javac generated assertion
+ // code when generating dex.
+ internal.assertionsConfiguration = getAssertionsConfiguration(AssertionTransformation.DISABLE);
+
return internal;
}
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index b906cb2..4a41c67 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -79,10 +79,16 @@
class UseCollector extends UseRegistry {
+ private DexProgramClass context;
+
UseCollector(DexItemFactory factory) {
super(factory);
}
+ public void setContext(DexProgramClass context) {
+ this.context = context;
+ }
+
@Override
public boolean registerInvokeVirtual(DexMethod method) {
DexEncodedMethod target = appInfo.lookupVirtualTarget(method.holder, method);
@@ -231,7 +237,10 @@
}
private void registerMethod(DexEncodedMethod method) {
- DexEncodedMethod superTarget = appInfo.lookupSuperTarget(method.method, method.method.holder);
+ DexEncodedMethod superTarget =
+ appInfo
+ .resolveMethod(method.method.holder, method.method)
+ .lookupInvokeSpecialTarget(context, appInfo);
if (superTarget != null) {
addMethod(superTarget.method);
}
@@ -344,6 +353,7 @@
private void analyze() {
UseCollector useCollector = new UseCollector(appInfo.dexItemFactory());
for (DexProgramClass dexProgramClass : application.classes()) {
+ useCollector.setContext(dexProgramClass);
useCollector.registerSuperType(dexProgramClass, dexProgramClass.superType);
for (DexType implementsType : dexProgramClass.interfaces.values) {
useCollector.registerSuperType(dexProgramClass, implementsType);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 03235a4..8c26650 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -6,7 +6,6 @@
import static com.android.tools.r8.R8Command.USAGE_MESSAGE;
import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.dex.Marker;
@@ -31,8 +30,8 @@
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.AssertionsRewriter;
import com.android.tools.r8.ir.optimize.EnumInfoMapCollector;
import com.android.tools.r8.ir.optimize.MethodPoolCollection;
import com.android.tools.r8.ir.optimize.NestReducer;
@@ -321,12 +320,6 @@
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);
@@ -733,7 +726,8 @@
// When line number optimization is turned off the identity mapping for line numbers is
// used. We still run the line number optimizer to collect line numbers and inline frame
// information for the mapping file.
- ClassNameMapper classNameMapper = LineNumberOptimizer.run(appView, application, namingLens);
+ ClassNameMapper classNameMapper =
+ LineNumberOptimizer.run(appView, application, inputApp, namingLens);
timing.end();
proguardMapSupplier = ProguardMapSupplier.fromClassNameMapper(classNameMapper, options);
@@ -813,7 +807,7 @@
if (appView.options().enableInitializedClassesInInstanceMethodsAnalysis) {
enqueuer.registerAnalysis(new InitializedClassesInInstanceMethodsAnalysis(appView));
}
- if (appView.options().assertionTransformation != AssertionTransformation.PASSTHROUGH) {
+ if (!AssertionsRewriter.isPassthroughAll(appView.options().assertionsConfiguration)) {
enqueuer.registerAnalysis(
new ClassInitializerAssertionEnablingAnalysis(
appView.dexItemFactory(), OptimizationFeedbackSimple.getInstance()));
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 45f9e84..265fa38 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -870,19 +870,14 @@
internal.syntheticProguardRulesConsumer = syntheticProguardRulesConsumer;
- // Default is to remove Java assertion code as Dalvik and Art does not reliable support
- // Java assertions. When generation class file output always keep the Java assertions code.
- assert internal.assertionTransformation == null;
- if (getAssertionsConfiguration() == null) {
- // Default, when no configuration is provided, is to disable all javac generated assertion
- // code when generating dex and leave it when generating class files.
- internal.assertionTransformation =
- internal.isGeneratingClassFiles()
- ? AssertionTransformation.PASSTHROUGH
- : AssertionTransformation.DISABLE;
- } else {
- internal.assertionTransformation = getAssertionsConfiguration().getTransformation();
- }
+ assert internal.assertionsConfiguration == null;
+ // Default, when no configuration is provided, is to remove all javac generated assertion
+ // code when generating dex and leave it when generating class files.
+ internal.assertionsConfiguration =
+ getAssertionsConfiguration(
+ internal.isGeneratingClassFiles()
+ ? AssertionTransformation.PASSTHROUGH
+ : AssertionTransformation.DISABLE);
// When generating class files the build is "intermediate" and we cannot pollute the namespace
// with the a hard-coded outline class. Doing so would prohibit subsequent merging of two
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 cc8af52..86b2cb8 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -5,9 +5,11 @@
import com.android.tools.r8.graph.ResolutionResult.ArrayCloneMethodResult;
import com.android.tools.r8.graph.ResolutionResult.ClassNotFoundResult;
+import com.android.tools.r8.graph.ResolutionResult.IllegalAccessOrNoSuchMethodResult;
import com.android.tools.r8.graph.ResolutionResult.IncompatibleClassResult;
import com.android.tools.r8.graph.ResolutionResult.NoSuchMethodResult;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
@@ -143,6 +145,18 @@
return app.definitionFor(type);
}
+ public DexClass definitionForDesugarDependency(DexClass dependent, DexType type) {
+ if (dependent.type == type) {
+ return dependent;
+ }
+ DexClass definition = definitionFor(type);
+ if (definition != null && !definition.isLibraryClass() && dependent.isProgramClass()) {
+ InterfaceMethodRewriter.reportDependencyEdge(
+ dependent.asProgramClass(), definition, options());
+ }
+ return definition;
+ }
+
@Override
public DexProgramClass definitionForProgramType(DexType type) {
return app.programDefinitionFor(type);
@@ -348,7 +362,7 @@
assert checkIfObsolete();
assert !clazz.isInterface();
// Step 2:
- SingleResolutionResult result = resolveMethodOnClassStep2(clazz, method, clazz);
+ ResolutionResult result = resolveMethodOnClassStep2(clazz, method, clazz);
if (result != null) {
return result;
}
@@ -361,7 +375,7 @@
* 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 SingleResolutionResult resolveMethodOnClassStep2(
+ private ResolutionResult 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">
@@ -373,6 +387,14 @@
// Pt 2: Find a method that matches the descriptor.
result = clazz.lookupMethod(method);
if (result != null) {
+ // If the resolved method is private, then it can only be accessed if the symbolic reference
+ // that initiated the resolution was the type at which the method resolved on. If that is not
+ // the case, then the error is either an IllegalAccessError, or in the case where access is
+ // allowed because of nests, a NoSuchMethodError. Which error cannot be determined without
+ // knowing the calling context.
+ if (result.isPrivateMethod() && clazz != initialResolutionHolder) {
+ return new IllegalAccessOrNoSuchMethodResult(result);
+ }
return new SingleResolutionResult(initialResolutionHolder, clazz, result);
}
// Pt 3: Apply step two to direct superclass of holder.
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 ac4e4eb..650bf61 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.graph;
+import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
@@ -13,7 +14,6 @@
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;
@@ -22,6 +22,8 @@
import com.android.tools.r8.utils.OptionalBool;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -58,7 +60,7 @@
private HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses;
private VerticallyMergedClasses verticallyMergedClasses;
- private SourceDebugExtensionRewriter sourceDebugExtensionRewriter;
+ private Map<DexClass, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
private AppView(
T appInfo, WholeProgramOptimizations wholeProgramOptimizations, InternalOptions options) {
@@ -144,14 +146,6 @@
allCodeProcessed = true;
}
- public void setSourceDebugExtensionRewriter(SourceDebugExtensionRewriter rewriter) {
- this.sourceDebugExtensionRewriter = rewriter;
- }
-
- public SourceDebugExtensionRewriter getSourceDebugExtensionRewriter() {
- return this.sourceDebugExtensionRewriter;
- }
-
public AppServices appServices() {
return appServices;
}
@@ -169,6 +163,14 @@
this.classesEscapingIntoLibrary = classesEscapingIntoLibrary;
}
+ public void setSourceDebugExtensionForType(DexClass clazz, DexValueString sourceDebugExtension) {
+ this.sourceDebugExtensions.put(clazz, sourceDebugExtension);
+ }
+
+ public DexValueString getSourceDebugExtensionForType(DexClass clazz) {
+ return this.sourceDebugExtensions.get(clazz);
+ }
+
@Override
public final DexDefinition definitionFor(DexReference reference) {
return appInfo().definitionFor(reference);
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 2a87607..77545740 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -13,20 +13,24 @@
import com.android.tools.r8.utils.PredicateUtils;
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
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.Iterator;
import java.util.List;
import java.util.ListIterator;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
+import kotlinx.metadata.KmConstructor;
+import kotlinx.metadata.KmFunction;
import kotlinx.metadata.KmProperty;
public abstract class DexClass extends DexDefinition {
@@ -210,14 +214,48 @@
return Arrays.asList(virtualMethods);
}
- public List<DexEncodedMethod> kotlinFunctions(List<KmProperty> kmProperties) {
- List<DexEncodedMethod> functions = new ArrayList<>();
- for (DexEncodedMethod method : virtualMethods) {
- if (method.isKotlinFunction(kmProperties)) {
- functions.add(method);
+ public Map<DexEncodedMethod, KmConstructor> kotlinConstructors(
+ List<KmConstructor> constructors, AppView<?> appView) {
+ ImmutableMap.Builder<DexEncodedMethod, KmConstructor> builder = ImmutableMap.builder();
+ for (DexEncodedMethod method : directMethods) {
+ if (method.isInstanceInitializer()) {
+ KmConstructor constructor = method.findCompatibleKotlinConstructor(constructors, appView);
+ if (constructor != null) {
+ // Found a compatible constructor that is likely asked to keep.
+ builder.put(method, constructor);
+ }
}
}
- return functions;
+ return builder.build();
+ }
+
+ public Map<DexEncodedMethod, KmFunction> kotlinExtensions(
+ List<KmFunction> extensions, AppView<?> appView) {
+ ImmutableMap.Builder<DexEncodedMethod, KmFunction> builder = ImmutableMap.builder();
+ for (DexEncodedMethod method : directMethods) {
+ KmFunction extension = method.findCompatibleKotlinExtension(extensions, appView);
+ if (extension != null) {
+ // Found a compatible extension that is likely asked to keep.
+ builder.put(method, extension);
+ }
+ }
+ return builder.build();
+ }
+
+ public List<DexEncodedMethod> kotlinFunctions(
+ List<KmFunction> functions, List<KmProperty> properties, AppView<?> appView) {
+ ImmutableList.Builder<DexEncodedMethod> builder = ImmutableList.builder();
+ for (DexEncodedMethod method : virtualMethods) {
+ KmFunction function = method.findCompatibleKotlinFunction(functions, appView);
+ if (function != null) {
+ // Found a compatible function that is likely asked to keep.
+ builder.add(method);
+ } else if (!method.isKotlinProperty(properties)) {
+ // This could be a newly merged method that is not part of properties.
+ builder.add(method);
+ }
+ }
+ return builder.build();
}
public void appendVirtualMethod(DexEncodedMethod method) {
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 e4fff90..dbd455b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -54,6 +54,7 @@
import com.android.tools.r8.ir.synthetic.FieldAccessorSourceCode;
import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
+import com.android.tools.r8.kotlin.KotlinMetadataSynthesizer;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -73,6 +74,8 @@
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.IntPredicate;
+import kotlinx.metadata.KmConstructor;
+import kotlinx.metadata.KmFunction;
import kotlinx.metadata.KmProperty;
import org.objectweb.asm.Opcodes;
@@ -349,8 +352,43 @@
return accessFlags.isSynthetic();
}
- boolean isKotlinFunction(List<KmProperty> properties) {
- return !isStaticMember() && !isKotlinProperty(properties);
+ // TODO(b/70169921): Handling JVM extensions as well.
+ KmConstructor findCompatibleKotlinConstructor(
+ List<KmConstructor> constructors, AppView<?> appView) {
+ if (!isInstanceInitializer()) {
+ return null;
+ }
+ for (KmConstructor constructor : constructors) {
+ if (KotlinMetadataSynthesizer.isCompatibleConstructor(constructor, this, appView)) {
+ return constructor;
+ }
+ }
+ return null;
+ }
+
+ // TODO(b/70169921): Handling JVM extensions as well.
+ KmFunction findCompatibleKotlinExtension(List<KmFunction> extensions, AppView<?> appView) {
+ if (!isStaticMember()) {
+ return null;
+ }
+ for (KmFunction extension : extensions) {
+ if (KotlinMetadataSynthesizer.isCompatibleExtension(extension, this, appView)) {
+ return extension;
+ }
+ }
+ return null;
+ }
+
+ KmFunction findCompatibleKotlinFunction(List<KmFunction> functions, AppView<?> appView) {
+ if (isStaticMember()) {
+ return null;
+ }
+ for (KmFunction function : functions) {
+ if (KotlinMetadataSynthesizer.isCompatibleFunction(function, this, appView)) {
+ return function;
+ }
+ }
+ return null;
}
// E.g., property `prop: T` is mapped to `getProp()T`, `setProp(T)V`, `prop$annotations()V`.
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 5d85611..ab96c0d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -119,6 +119,7 @@
public final DexString longDescriptor = createString("J");
public final DexString shortDescriptor = createString("S");
public final DexString voidDescriptor = createString("V");
+ public final DexString descriptorSeparator = createString("/");
public final DexString boxedBooleanDescriptor = createString("Ljava/lang/Boolean;");
public final DexString boxedByteDescriptor = createString("Ljava/lang/Byte;");
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index 131cee6..f3a829c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -442,6 +442,22 @@
return true;
}
+ public boolean contains(DexString s) {
+ // TODO(b/146621590): This does not handle character boundaries correctly.
+ int index = 0;
+ while (content.length - index >= s.content.length) {
+ int i = 0;
+ while (i < s.content.length - 1 && content[index + i] == s.content[i]) {
+ i++;
+ }
+ if (i == s.content.length - 1) {
+ return true;
+ }
+ index++;
+ }
+ return false;
+ }
+
public boolean endsWith(DexString suffix) {
if (content.length < suffix.content.length) {
return false;
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 cc39610..bbed9bc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -24,13 +24,19 @@
import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
import com.android.tools.r8.utils.Pair;
import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
import java.util.Arrays;
+import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
public class DexType extends DexReference implements PresortedComparable<DexType> {
public static final DexType[] EMPTY_ARRAY = {};
+ // Bundletool is merging classes that may originate from a build with an old version of R8.
+ // Allow merging of classes that use names from older versions of R8.
+ private static List<String> OLD_SYNTHESIZED_NAMES = ImmutableList.of("$r8$java8methods$utility");
+
public final DexString descriptor;
private String toStringCache = null;
@@ -264,7 +270,17 @@
|| name.contains(TwrCloseResourceRewriter.UTILITY_CLASS_NAME)
|| name.contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME)
|| name.contains(BackportedMethodRewriter.UTILITY_CLASS_NAME_PREFIX)
- || name.contains(ServiceLoaderRewriter.SERVICE_LOADER_CLASS_NAME);
+ || name.contains(ServiceLoaderRewriter.SERVICE_LOADER_CLASS_NAME)
+ || oldSynthesizedName(name);
+ }
+
+ private boolean oldSynthesizedName(String name) {
+ for (String synthesizedPrefix : OLD_SYNTHESIZED_NAMES) {
+ if (name.contains(synthesizedPrefix)) {
+ return true;
+ }
+ }
+ return false;
}
public boolean isProgramType(DexDefinitionSupplier definitions) {
diff --git a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
index 147c165..27f4c9c 100644
--- a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
@@ -3,8 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@@ -29,11 +29,11 @@
}
public Type getAsmObjectType(String name) {
- return asmObjectTypeCache.computeIfAbsent(name, (key) -> Type.getObjectType(key));
+ return asmObjectTypeCache.computeIfAbsent(name, Type::getObjectType);
}
public Type getAsmType(String name) {
- return asmTypeCache.computeIfAbsent(name, (key) -> Type.getType(key));
+ return asmTypeCache.computeIfAbsent(name, Type::getType);
}
public DexItemFactory getFactory() {
@@ -111,8 +111,8 @@
public DexProto getProto(String desc) {
assert isValidDescriptor(desc);
- String returnTypeDescriptor = getReturnTypeDescriptor(desc);
- String[] argumentDescriptors = getArgumentTypeDescriptors(desc);
+ String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(desc);
+ String[] argumentDescriptors = DescriptorUtils.getArgumentTypeDescriptors(desc);
StringBuilder shortyDescriptor = new StringBuilder();
shortyDescriptor.append(getShortyDescriptor(returnTypeDescriptor));
for (int i = 0; i < argumentDescriptors.length; i++) {
@@ -143,78 +143,6 @@
}
public Type getReturnType(final String methodDescriptor) {
- return getAsmType(getReturnTypeDescriptor(methodDescriptor));
- }
-
- private static String getReturnTypeDescriptor(final String methodDescriptor) {
- assert methodDescriptor.indexOf(')') != -1;
- return methodDescriptor.substring(methodDescriptor.indexOf(')') + 1);
- }
-
- public static int getArgumentCount(final String methodDescriptor) {
- int charIdx = 1;
- char c;
- int argCount = 0;
- while ((c = methodDescriptor.charAt(charIdx++)) != ')') {
- if (c == 'L') {
- while (methodDescriptor.charAt(charIdx++) != ';');
- argCount++;
- } else if (c != '[') {
- argCount++;
- }
- }
- return argCount;
- }
-
- public Type[] getArgumentTypes(final String methodDescriptor) {
- String[] argDescriptors = getArgumentTypeDescriptors(methodDescriptor);
- Type[] args = new Type[argDescriptors.length];
- int argIdx = 0;
- for (String argDescriptor : argDescriptors) {
- args[argIdx++] = getAsmType(argDescriptor);
- }
- return args;
- }
-
- private static String[] getArgumentTypeDescriptors(final String methodDescriptor) {
- String[] argDescriptors = new String[getArgumentCount(methodDescriptor)];
- int charIdx = 1;
- char c;
- int argIdx = 0;
- int startType;
- while ((c = methodDescriptor.charAt(charIdx)) != ')') {
- switch (c) {
- case 'V':
- throw new Unreachable();
- case 'Z':
- case 'C':
- case 'B':
- case 'S':
- case 'I':
- case 'F':
- case 'J':
- case 'D':
- argDescriptors[argIdx++] = Character.toString(c);
- break;
- case '[':
- startType = charIdx;
- while (methodDescriptor.charAt(++charIdx) == '[') {
- }
- if (methodDescriptor.charAt(charIdx) == 'L') {
- while (methodDescriptor.charAt(++charIdx) != ';');
- }
- argDescriptors[argIdx++] = methodDescriptor.substring(startType, charIdx + 1);
- break;
- case 'L':
- startType = charIdx;
- while (methodDescriptor.charAt(++charIdx) != ';');
- argDescriptors[argIdx++] = methodDescriptor.substring(startType, charIdx + 1);
- break;
- default:
- throw new Unreachable();
- }
- charIdx++;
- }
- return argDescriptors;
+ return getAsmType(DescriptorUtils.getReturnTypeDescriptor(methodDescriptor));
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index da8d8d7..cf4a98e 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -683,7 +683,7 @@
this.parent = parent;
this.method = parent.application.getMethod(parent.type, name, desc);
this.flags = createMethodAccessFlags(name, access);
- parameterCount = JarApplicationReader.getArgumentCount(desc);
+ parameterCount = DescriptorUtils.getArgumentCount(desc);
if (exceptions != null && exceptions.length > 0) {
DexValue[] values = new DexValue[exceptions.length];
for (int i = 0; i < exceptions.length; i++) {
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 65a2c7c..3080960 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -12,6 +12,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
+import java.util.function.BiPredicate;
import java.util.function.Consumer;
public abstract class ResolutionResult {
@@ -162,8 +163,70 @@
if (!isAccessibleFrom(context, appInfo)) {
return null;
}
+ DexEncodedMethod target =
+ internalInvokeSpecialOrSuper(
+ context, appInfo, (sup, sub) -> isSuperclass(sup, sub, appInfo));
+ if (target == null) {
+ return null;
+ }
+ // Should we check access control again?
+ DexClass holder = appInfo.definitionFor(target.method.holder);
+ if (!AccessControl.isMethodAccessible(target, holder, context, appInfo)) {
+ return null;
+ }
+ return target;
+ }
- // Statics cannot be targeted by invoke-special.
+ /**
+ * Lookup the target of an invoke-super.
+ *
+ * <p>This will return the target iff the resolution succeeded and the target is valid (i.e.,
+ * non-static and non-initializer) and accessible from {@code context}.
+ *
+ * <p>Additionally, this will also verify that the invoke-super is valid, i.e., it is on the a
+ * super type of the current context. Any invoke-special targeting the same type should have
+ * been mapped to an invoke-direct, but could change due to merging so we need to still allow
+ * the context to be equal to the targeted (symbolically referenced) type.
+ *
+ * @param context 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 no valid target is found.
+ */
+ @Override
+ public DexEncodedMethod lookupInvokeSuperTarget(
+ DexProgramClass context, AppInfoWithSubtyping appInfo) {
+ if (!isAccessibleFrom(context, appInfo)) {
+ return null;
+ }
+ DexEncodedMethod target = lookupInvokeSuperTarget(context.asDexClass(), appInfo);
+ if (target == null) {
+ return null;
+ }
+ // Should we check access control again?
+ DexClass holder = appInfo.definitionFor(target.method.holder);
+ if (!AccessControl.isMethodAccessible(target, holder, context, appInfo)) {
+ return null;
+ }
+ return target;
+ }
+
+ @Override
+ public DexEncodedMethod lookupInvokeSuperTarget(DexClass context, AppInfo appInfo) {
+ assert context != null;
+ if (resolvedMethod.isInstanceInitializer()
+ || (appInfo.hasSubtyping()
+ && initialResolutionHolder != context
+ && !isSuperclass(initialResolutionHolder, context, appInfo.withSubtyping()))) {
+ throw new CompilationError(
+ "Illegal invoke-super to " + resolvedMethod.toSourceString(), context.getOrigin());
+ }
+ return internalInvokeSpecialOrSuper(context, appInfo, (sup, sub) -> true);
+ }
+
+ private DexEncodedMethod internalInvokeSpecialOrSuper(
+ DexClass context, AppInfo appInfo, BiPredicate<DexClass, DexClass> isSuperclass) {
+
+ // Statics cannot be targeted by invoke-special/super.
if (getResolvedMethod().isStatic()) {
return null;
}
@@ -182,9 +245,9 @@
final DexClass initialType;
if (!resolvedMethod.isInstanceInitializer()
&& !symbolicReference.isInterface()
- && isSuperclass(symbolicReference, context, appInfo)) {
+ && isSuperclass.test(symbolicReference, context)) {
// If reference is a super type of the context then search starts at the immediate super.
- initialType = appInfo.definitionFor(context.superType);
+ initialType = context.superType == null ? null : appInfo.definitionFor(context.superType);
} else {
// Otherwise it starts at the reference itself.
initialType = symbolicReference;
@@ -228,10 +291,6 @@
if (target.isAbstract()) {
return null;
}
- // Should we check access control again?
- if (!AccessControl.isMethodAccessible(target, initialType, context, appInfo)) {
- return null;
- }
return target;
}
@@ -239,72 +298,6 @@
return sup != sub && appInfo.isSubtype(sub.type, sup.type);
}
- /**
- * 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(
- DexProgramClass context, AppInfoWithSubtyping appInfo) {
- if (!isAccessibleFrom(context, appInfo)) {
- return null;
- }
- return lookupInvokeSuperTarget(context.asDexClass(), appInfo);
- }
-
- @Override
- public DexEncodedMethod lookupInvokeSuperTarget(DexClass context, AppInfo appInfo) {
- assert context != null;
- 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.type, initialResolutionHolder.type)) {
- throw new CompilationError(
- "Illegal invoke-super to " + method.toSourceString() + " from class " + context,
- context.getOrigin());
- }
-
- // 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.
- if (context.superType == null) {
- return null;
- }
- SingleResolutionResult resolution =
- appInfo.resolveMethodOnClass(context.superType, method).asSingleResolution();
- return resolution != null && !resolution.resolvedMethod.isStatic()
- ? resolution.resolvedMethod
- : null;
- }
-
@Override
// TODO(b/140204899): Leverage refined receiver type if available.
public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
@@ -515,33 +508,45 @@
}
}
- public static class IncompatibleClassResult extends FailedResolutionResult {
- static final IncompatibleClassResult INSTANCE =
- new IncompatibleClassResult(Collections.emptyList());
+ abstract static class FailedResolutionWithCausingMethods extends FailedResolutionResult {
private final Collection<DexEncodedMethod> methodsCausingError;
- private IncompatibleClassResult(Collection<DexEncodedMethod> methodsCausingError) {
+ private FailedResolutionWithCausingMethods(Collection<DexEncodedMethod> methodsCausingError) {
this.methodsCausingError = methodsCausingError;
}
- static IncompatibleClassResult create(Collection<DexEncodedMethod> methodsCausingError) {
- return methodsCausingError.isEmpty()
- ? INSTANCE
- : new IncompatibleClassResult(methodsCausingError);
- }
-
@Override
public void forEachFailureDependency(Consumer<DexEncodedMethod> methodCausingFailureConsumer) {
this.methodsCausingError.forEach(methodCausingFailureConsumer);
}
}
- public static class NoSuchMethodResult extends FailedResolutionResult {
- static final NoSuchMethodResult INSTANCE = new NoSuchMethodResult();
+ public static class IncompatibleClassResult extends FailedResolutionWithCausingMethods {
+ static final IncompatibleClassResult INSTANCE =
+ new IncompatibleClassResult(Collections.emptyList());
- private NoSuchMethodResult() {
- // Intentionally left empty.
+ private IncompatibleClassResult(Collection<DexEncodedMethod> methodsCausingError) {
+ super(methodsCausingError);
+ }
+
+ static IncompatibleClassResult create(Collection<DexEncodedMethod> methodsCausingError) {
+ return methodsCausingError.isEmpty()
+ ? INSTANCE
+ : new IncompatibleClassResult(methodsCausingError);
+ }
+ }
+
+ public static class NoSuchMethodResult extends FailedResolutionResult {
+
+ static final NoSuchMethodResult INSTANCE = new NoSuchMethodResult();
+ }
+
+ public static class IllegalAccessOrNoSuchMethodResult extends FailedResolutionWithCausingMethods {
+
+ public IllegalAccessOrNoSuchMethodResult(DexEncodedMethod methodCausingError) {
+ super(Collections.singletonList(methodCausingError));
+ assert methodCausingError != null;
}
}
}
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 32b7cbe..00d2f79 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
@@ -6,7 +6,6 @@
import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.IncludeAllResources;
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
@@ -49,6 +48,7 @@
import com.android.tools.r8.ir.desugar.StringConcatRewriter;
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import com.android.tools.r8.ir.optimize.AliasIntroducer;
+import com.android.tools.r8.ir.optimize.AssertionsRewriter;
import com.android.tools.r8.ir.optimize.Assumer;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization;
import com.android.tools.r8.ir.optimize.CodeRewriter;
@@ -160,6 +160,7 @@
public final Collection<Assumer> assumers = new ArrayList<>();
private final DynamicTypeOptimization dynamicTypeOptimization;
+ final AssertionsRewriter assertionsRewriter;
final DeadCodeRemover deadCodeRemover;
private final MethodOptimizationInfoCollector methodOptimizationInfoCollector;
@@ -198,6 +199,7 @@
this.stringOptimizer = new StringOptimizer(appView);
this.stringBuilderOptimizer = new StringBuilderOptimizer(appView);
this.deadCodeRemover = new DeadCodeRemover(appView, codeRewriter);
+ this.assertionsRewriter = new AssertionsRewriter(appView);
this.idempotentFunctionCallCanonicalizer = new IdempotentFunctionCallCanonicalizer(appView);
this.neverMergePrefixes =
options.neverMergePrefixes.stream()
@@ -766,7 +768,7 @@
clearDexMethodCompilationState();
if (identifierNameStringMarker != null) {
- identifierNameStringMarker.decoupleIdentifierNameStringsInFields();
+ identifierNameStringMarker.decoupleIdentifierNameStringsInFields(executorService);
}
if (Log.ENABLED) {
@@ -1172,13 +1174,13 @@
if (memberValuePropagation != null) {
memberValuePropagation.rewriteWithConstantValues(code, method.method.holder);
}
+
if (options.enableEnumValueOptimization) {
assert appView.enableWholeProgramOptimizations();
codeRewriter.removeSwitchMaps(code);
}
- if (options.assertionTransformation != AssertionTransformation.PASSTHROUGH) {
- codeRewriter.processAssertions(appView, method, code, feedback);
- }
+
+ assertionsRewriter.run(method, code);
previous = printMethod(code, "IR after disable assertions (SSA)", previous);
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
deleted file mode 100644
index 20fc11c..0000000
--- a/src/main/java/com/android/tools/r8/ir/conversion/SourceDebugExtensionRewriter.java
+++ /dev/null
@@ -1,205 +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.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 fa68de4..8f33a5e 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
@@ -62,6 +62,8 @@
public final class BackportedMethodRewriter {
+ // Don't change this name, at least not without adding special-casing in DexType to support
+ // merging old dex code in Bundletool.
public static final String UTILITY_CLASS_NAME_PREFIX = "$r8$backportedMethods$utility";
private final AppView<?> appView;
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 f4fbdde..a4fc2e9 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
@@ -140,12 +140,8 @@
: 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 DexClass definitionFor(DexType type, AppView<?> appView) {
+ return appView.appInfo().definitionForDesugarDependency(directSubClass, type);
}
public void reportMissingType(DexType missingType, InterfaceMethodRewriter rewriter) {
@@ -168,8 +164,8 @@
}
@Override
- public void reportDependency(DexClass clazz, AppView<?> appView) {
- // Don't report dependencies in the library.
+ public DexClass definitionFor(DexType type, AppView<?> appView) {
+ return appView.definitionFor(type);
}
@Override
@@ -290,8 +286,9 @@
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);
+ if (resolution instanceof IncompatibleClassResult) {
+ addICCEThrowingMethod(method, clazz);
+ }
return;
}
DexEncodedMethod target = resolution.getSingleTarget();
@@ -400,7 +397,7 @@
if (type == null || type == dexItemFactory.objectType) {
return null;
}
- DexClass clazz = appView.definitionFor(type);
+ DexClass clazz = context.definitionFor(type, appView);
if (clazz == null) {
context.reportMissingType(type, rewriter);
return null;
@@ -418,7 +415,6 @@
if (clazz.isLibraryClass()) {
return ClassInfo.EMPTY;
}
- context.reportDependency(clazz, appView);
return classInfo.computeIfAbsent(clazz, key -> visitClassInfoRaw(key, context));
}
@@ -469,7 +465,6 @@
if (iface.isLibraryClass() && ignoreLibraryInfo()) {
return MethodSignatures.EMPTY;
}
- context.reportDependency(iface, appView);
return interfaceInfo.computeIfAbsent(iface, key -> visitInterfaceInfoRaw(key, context));
}
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 a43d321..7f4c043 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
@@ -961,7 +961,7 @@
InterfaceProcessor processor = new InterfaceProcessor(appView, this);
for (DexProgramClass clazz : builder.getProgramClasses()) {
if (shouldProcess(clazz, flavour, true)) {
- processor.process(clazz.asProgramClass(), graphLensBuilder);
+ processor.process(clazz, graphLensBuilder);
}
}
for (Entry<DexLibraryClass, Set<DexProgramClass>> entry : requiredDispatchClasses.entrySet()) {
@@ -1114,8 +1114,8 @@
// At this point we likely have a non-library type that may depend on default method information
// from its interfaces and the dependency should be reported.
- if (!definedInterface.isLibraryClass()) {
- reportDependencyEdge(definedInterface, implementing, appView);
+ if (implementing.isProgramClass() && !definedInterface.isLibraryClass()) {
+ reportDependencyEdge(implementing.asProgramClass(), definedInterface, appView.options());
}
// Merge information from all superinterfaces.
@@ -1139,13 +1139,14 @@
}
public static void reportDependencyEdge(
- DexClass dependency, DexClass dependent, AppView<?> appView) {
- DesugarGraphConsumer consumer = appView.options().desugarGraphConsumer;
+ DexProgramClass dependent, DexClass dependency, InternalOptions options) {
+ assert !dependency.isLibraryClass();
+ DesugarGraphConsumer consumer = options.desugarGraphConsumer;
if (consumer != null) {
- Origin dependencyOrigin = dependency.getOrigin();
Origin dependentOrigin = dependent.getOrigin();
- if (dependencyOrigin != dependentOrigin) {
- consumer.accept(dependencyOrigin, dependentOrigin);
+ Origin dependencyOrigin = dependency.getOrigin();
+ if (dependentOrigin != dependencyOrigin) {
+ consumer.accept(dependentOrigin, dependencyOrigin);
}
}
}
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 787fbe1..9599834 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
@@ -34,6 +34,7 @@
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.Pair;
import com.google.common.collect.BiMap;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -302,6 +303,12 @@
return true;
}
+ private DexClass definitionForDependency(DexType dependency, DexClass dependent) {
+ return dependent.isProgramClass()
+ ? appView.appInfo().definitionForDesugarDependency(dependent.asProgramClass(), dependency)
+ : appView.definitionFor(dependency);
+ }
+
// Returns true if the given interface method must be kept on [iface] after moving its
// implementation to the companion class of [iface]. This is always the case for non-bridge
// methods. Bridge methods that does not override an implementation in a super-interface must
@@ -313,32 +320,33 @@
}
}
if (method.accessFlags.isBridge()) {
- Deque<DexType> worklist = new ArrayDeque<>();
+ Deque<Pair<DexClass, DexType>> worklist = new ArrayDeque<>();
Set<DexType> seenBefore = new HashSet<>();
- if (iface.superType != null) {
- worklist.add(iface.superType);
- }
- Collections.addAll(worklist, iface.interfaces.values);
+ addSuperTypes(iface, worklist);
while (!worklist.isEmpty()) {
- DexType superType = worklist.pop();
- if (!seenBefore.add(superType)) {
+ Pair<DexClass, DexType> item = worklist.pop();
+ DexClass clazz = definitionForDependency(item.getSecond(), item.getFirst());
+ if (clazz == null || !seenBefore.add(clazz.type)) {
continue;
}
- DexClass clazz = appView.definitionFor(superType);
- if (clazz != null) {
- if (clazz.lookupVirtualMethod(method.method) != null) {
- return false;
- }
- if (clazz.superType != null) {
- worklist.add(clazz.superType);
- }
- Collections.addAll(worklist, clazz.interfaces.values);
+ if (clazz.lookupVirtualMethod(method.method) != null) {
+ return false;
}
+ addSuperTypes(clazz, worklist);
}
}
return true;
}
+ private static void addSuperTypes(DexClass clazz, Deque<Pair<DexClass, DexType>> worklist) {
+ if (clazz.superType != null) {
+ worklist.add(new Pair<>(clazz, clazz.superType));
+ }
+ for (DexType iface : clazz.interfaces.values) {
+ worklist.add(new Pair<>(clazz, iface));
+ }
+ }
+
private boolean isStaticMethod(DexEncodedMethod method) {
if (method.accessFlags.isNative()) {
throw new Unimplemented("Native interface methods are not yet supported.");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java
index f09f73f..5892466 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java
@@ -15,7 +15,7 @@
public static boolean isPinned(DexEncodedMethod method, AppView<AppInfoWithLiveness> appView) {
return appView.appInfo().isPinned(method.method)
|| appView.appInfo().bootstrapMethods.contains(method.method)
- || appView.appInfo().brokenSuperInvokes.contains(method.method)
+ || appView.appInfo().failedResolutionTargets.contains(method.method)
|| appView.appInfo().methodsTargetedByInvokeDynamic.contains(method.method)
|| method.accessFlags.isNative();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
new file mode 100644
index 0000000..2292b0a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -0,0 +1,268 @@
+// 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;
+
+import com.android.tools.r8.AssertionsConfiguration;
+import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
+import com.android.tools.r8.AssertionsConfiguration.ConfigurationEntry;
+import com.android.tools.r8.AssertionsConfiguration.ConfigurationType;
+import com.android.tools.r8.AssertionsConfiguration.InternalAssertionConfiguration;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+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.InvokeMethod;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ThrowingCharIterator;
+import java.io.UTFDataFormatException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class AssertionsRewriter {
+
+ private static class ConfigurationEntryWithDexString {
+
+ private ConfigurationEntry entry;
+ private final DexString value;
+
+ private ConfigurationEntryWithDexString(
+ ConfigurationEntry entry, DexItemFactory dexItemFactory) {
+ this.entry = entry;
+ switch (entry.getType()) {
+ case PACKAGE:
+ if (entry.getValue().length() == 0) {
+ value = dexItemFactory.createString("");
+ } else {
+ value =
+ dexItemFactory.createString(
+ "L"
+ + entry
+ .getValue()
+ .replace(
+ DescriptorUtils.JAVA_PACKAGE_SEPARATOR,
+ DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR)
+ + "/");
+ }
+ break;
+ case CLASS:
+ value =
+ dexItemFactory.createString(
+ "L"
+ + entry
+ .getValue()
+ .replace(
+ DescriptorUtils.JAVA_PACKAGE_SEPARATOR,
+ DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR)
+ + ";");
+ break;
+ case ALL:
+ value = null;
+ break;
+ default:
+ throw new Unreachable();
+ }
+ }
+ }
+
+ private final AppView<?> appView;
+ private final DexItemFactory dexItemFactory;
+ private final List<ConfigurationEntryWithDexString> configuration;
+ private final boolean enabled;
+
+ public AssertionsRewriter(AppView<?> appView) {
+ this.appView = appView;
+ this.dexItemFactory = appView.dexItemFactory();
+ if (appView.options().assertionsConfiguration == null) {
+ this.configuration = null;
+ this.enabled = false;
+ } else {
+ List<ConfigurationEntry> configuration =
+ InternalAssertionConfiguration
+ .getConfiguration(appView.options().assertionsConfiguration);
+ this.configuration =
+ configuration.stream()
+ .map(entry -> new ConfigurationEntryWithDexString(entry, appView.dexItemFactory()))
+ .collect(Collectors.toList());
+ this.enabled = !isPassthroughAll(appView.options().assertionsConfiguration);
+ }
+ }
+
+ public static boolean isPassthroughAll(AssertionsConfiguration assertionsConfiguration) {
+ List<ConfigurationEntry> configuration =
+ InternalAssertionConfiguration.getConfiguration(assertionsConfiguration);
+ return configuration.size() == 1
+ && configuration.get(0).getTransformation() == AssertionTransformation.PASSTHROUGH
+ && configuration.get(0).getType() == ConfigurationType.ALL;
+ }
+
+ private AssertionTransformation getTransformationForMethod(DexEncodedMethod method) {
+ AssertionTransformation transformation = null;
+ for (ConfigurationEntryWithDexString entry : configuration) {
+ switch (entry.entry.getType()) {
+ case ALL:
+ transformation = entry.entry.getTransformation();
+ break;
+ case PACKAGE:
+ if (entry.value.size == 0) {
+ if (!method.method.holder.descriptor.contains(dexItemFactory.descriptorSeparator)) {
+ transformation = entry.entry.getTransformation();
+ }
+ } else if (method.method.holder.descriptor.startsWith(entry.value)) {
+ transformation = entry.entry.getTransformation();
+ }
+ break;
+ case CLASS:
+ if (method.method.holder.descriptor.equals(entry.value)) {
+ transformation = entry.entry.getTransformation();
+ }
+ if (isDescriptorForClassOrInnerClass(entry.value, method.method.holder.descriptor)) {
+ transformation = entry.entry.getTransformation();
+ }
+ break;
+ default:
+ throw new Unreachable();
+ }
+ }
+ assert transformation != null; // Default transformation are always added.
+ return transformation;
+ }
+
+ private boolean isDescriptorForClassOrInnerClass(
+ DexString classDescriptor, DexString classOrInnerClassDescriptor) {
+ // Same string same class.
+ if (classOrInnerClassDescriptor == classDescriptor) {
+ return true;
+ }
+
+ // Check for inner class name by checking if the prefix is the class descriptor,
+ // where ';' is replaced whit '$' and no '/' after that.
+ if (classOrInnerClassDescriptor.size < classDescriptor.size) {
+ return false;
+ }
+ ThrowingCharIterator<UTFDataFormatException> i1 = classDescriptor.iterator();
+ ThrowingCharIterator<UTFDataFormatException> i2 = classOrInnerClassDescriptor.iterator();
+ try {
+ while (i1.hasNext()) {
+ char c1 = i1.nextChar();
+ char c2 = i2.nextChar();
+ // The Java VM behaviour is including all inner classes as well when a class is specified.
+ if (c1 == ';' && c2 == DescriptorUtils.INNER_CLASS_SEPARATOR) {
+ // If there is a '/' after the '$' this is not an inner class after all.
+ while (i2.hasNext()) {
+ if (i2.nextChar() == DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR) {
+ return false;
+ }
+ }
+ return true;
+ }
+ if (c1 != c2) {
+ return false;
+ }
+ }
+ assert i2.hasNext();
+ return false;
+ } catch (UTFDataFormatException e) {
+ return false;
+ }
+ }
+
+ /**
+ * For supporting assert javac adds the static field $assertionsDisabled to all classes which have
+ * methods with assertions. This is used to support the Java VM -ea flag.
+ *
+ * <p>The class:
+ *
+ * <pre>
+ * class A {
+ * void m() {
+ * assert xxx;
+ * }
+ * }
+ * </pre>
+ *
+ * Is compiled into:
+ *
+ * <pre>
+ * class A {
+ * static boolean $assertionsDisabled;
+ * static {
+ * $assertionsDisabled = A.class.desiredAssertionStatus();
+ * }
+ *
+ * // method with "assert xxx";
+ * void m() {
+ * if (!$assertionsDisabled) {
+ * if (xxx) {
+ * throw new AssertionError(...);
+ * }
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * With the rewriting below (and other rewritings) the resulting code is:
+ *
+ * <pre>
+ * class A {
+ * void m() {
+ * }
+ * }
+ * </pre>
+ */
+ public void run(DexEncodedMethod method, IRCode code) {
+ if (!enabled) {
+ return;
+ }
+ AssertionTransformation transformation = getTransformationForMethod(method);
+ if (transformation == AssertionTransformation.PASSTHROUGH) {
+ return;
+ }
+ DexEncodedMethod clinit;
+ // If the <clinit> of this class did not have have code to turn on assertions don't try to
+ // remove assertion code from the method (including <clinit> itself.
+ if (method.isClassInitializer()) {
+ clinit = method;
+ } else {
+ DexClass clazz = appView.definitionFor(method.method.holder);
+ if (clazz == null) {
+ return;
+ }
+ clinit = clazz.getClassInitializer();
+ }
+ if (clinit == null || !clinit.getOptimizationInfo().isInitializerEnablingJavaAssertions()) {
+ return;
+ }
+
+ // This code will process the assertion code in all methods including <clinit>.
+ InstructionListIterator iterator = code.instructionListIterator();
+ while (iterator.hasNext()) {
+ Instruction current = iterator.next();
+ if (current.isInvokeMethod()) {
+ InvokeMethod invoke = current.asInvokeMethod();
+ if (invoke.getInvokedMethod() == dexItemFactory.classMethods.desiredAssertionStatus) {
+ iterator.replaceCurrentInstruction(code.createIntConstant(0));
+ }
+ } else if (current.isStaticPut()) {
+ StaticPut staticPut = current.asStaticPut();
+ if (staticPut.getField().name == dexItemFactory.assertionsDisabled) {
+ iterator.remove();
+ }
+ } else if (current.isStaticGet()) {
+ StaticGet staticGet = current.asStaticGet();
+ if (staticGet.getField().name == dexItemFactory.assertionsDisabled) {
+ iterator.replaceCurrentInstruction(
+ code.createIntConstant(transformation == AssertionTransformation.DISABLE ? 1 : 0));
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 527874a..65e86b7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -9,7 +9,6 @@
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isTypeVisibleFromContext;
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
@@ -72,7 +71,6 @@
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Switch;
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.Value;
@@ -80,7 +78,6 @@
import com.android.tools.r8.ir.code.Xor;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
import com.android.tools.r8.utils.InternalOptions;
@@ -1275,95 +1272,6 @@
assert code.isConsistentSSA();
}
- /**
- * For supporting assert javac adds the static field $assertionsDisabled to all classes which have
- * methods with assertions. This is used to support the Java VM -ea flag.
- *
- * <p>The class:
- *
- * <pre>
- * class A {
- * void m() {
- * assert xxx;
- * }
- * }
- * </pre>
- *
- * Is compiled into:
- *
- * <pre>
- * class A {
- * static boolean $assertionsDisabled;
- * static {
- * $assertionsDisabled = A.class.desiredAssertionStatus();
- * }
- *
- * // method with "assert xxx";
- * void m() {
- * if (!$assertionsDisabled) {
- * if (xxx) {
- * throw new AssertionError(...);
- * }
- * }
- * }
- * }
- * </pre>
- *
- * With the rewriting below (and other rewritings) the resulting code is:
- *
- * <pre>
- * class A {
- * void m() {
- * }
- * }
- * </pre>
- */
- public void processAssertions(
- AppView<?> appView, DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
- assert appView.options().assertionTransformation != AssertionTransformation.PASSTHROUGH;
- DexEncodedMethod clinit;
- // If the <clinit> of this class did not have have code to turn on assertions don't try to
- // remove assertion code from the method (including <clinit> itself.
- if (method.isClassInitializer()) {
- clinit = method;
- } else {
- DexClass clazz = appView.definitionFor(method.method.holder);
- if (clazz == null) {
- return;
- }
- clinit = clazz.getClassInitializer();
- }
- if (clinit == null || !clinit.getOptimizationInfo().isInitializerEnablingJavaAssertions()) {
- return;
- }
-
- // This code will process the assertion code in all methods including <clinit>.
- InstructionListIterator iterator = code.instructionListIterator();
- while (iterator.hasNext()) {
- Instruction current = iterator.next();
- if (current.isInvokeMethod()) {
- InvokeMethod invoke = current.asInvokeMethod();
- if (invoke.getInvokedMethod() == dexItemFactory.classMethods.desiredAssertionStatus) {
- iterator.replaceCurrentInstruction(code.createIntConstant(0));
- }
- } else if (current.isStaticPut()) {
- StaticPut staticPut = current.asStaticPut();
- if (staticPut.getField().name == dexItemFactory.assertionsDisabled) {
- iterator.remove();
- }
- } else if (current.isStaticGet()) {
- StaticGet staticGet = current.asStaticGet();
- if (staticGet.getField().name == dexItemFactory.assertionsDisabled) {
- iterator.replaceCurrentInstruction(
- code.createIntConstant(
- appView.options().assertionTransformation == AssertionTransformation.DISABLE
- ? 1
- : 0));
- }
- }
- }
- }
-
enum RemoveCheckCastInstructionIfTrivialResult {
NO_REMOVALS,
REMOVED_CAST_DO_NARROW
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index a938ff2..194b84a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -227,9 +227,8 @@
for (int i = 0; i < directMethods.size(); i++) {
DexEncodedMethod method = directMethods.get(i);
- // If this is a private or static method that is targeted by an invoke-super instruction, then
- // don't remove any unused arguments.
- if (appView.appInfo().brokenSuperInvokes.contains(method.method)) {
+ // If this is a method with known resolution issues, then don't remove any unused arguments.
+ if (appView.appInfo().failedResolutionTargets.contains(method.method)) {
continue;
}
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 e76bbcf..06db71b 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -5,8 +5,11 @@
package com.android.tools.r8.kotlin;
import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.isExtension;
import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toKmType;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmConstructor;
import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunction;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunctionAsExtension;
import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmType;
import com.android.tools.r8.graph.AppView;
@@ -15,8 +18,12 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
import kotlinx.metadata.KmClass;
+import kotlinx.metadata.KmConstructor;
import kotlinx.metadata.KmFunction;
import kotlinx.metadata.KmProperty;
import kotlinx.metadata.KmType;
@@ -65,13 +72,42 @@
superTypes.add(toKmType(addKotlinPrefix("Any;")));
}
+ List<KmConstructor> constructors = kmClass.getConstructors();
+ List<KmConstructor> originalConstructors = new ArrayList<>(constructors);
+ constructors.clear();
+ for (Map.Entry<DexEncodedMethod, KmConstructor> entry :
+ clazz.kotlinConstructors(originalConstructors, appView).entrySet()) {
+ KmConstructor constructor =
+ toRenamedKmConstructor(entry.getKey(), entry.getValue(), appView, lens);
+ if (constructor != null) {
+ constructors.add(constructor);
+ }
+ }
+
List<KmFunction> functions = kmClass.getFunctions();
+ List<KmFunction> originalFunctions =
+ functions.stream()
+ .filter(kmFunction -> !isExtension(kmFunction))
+ .collect(Collectors.toList());
+ List<KmFunction> originalExtensions =
+ functions.stream()
+ .filter(KotlinMetadataSynthesizer::isExtension)
+ .collect(Collectors.toList());
functions.clear();
+
List<KmProperty> properties = kmClass.getProperties();
- for (DexEncodedMethod method : clazz.kotlinFunctions(properties)) {
- KmFunction kmFunction = toRenamedKmFunction(method.method, appView, lens);
- if (kmFunction != null) {
- functions.add(kmFunction);
+ for (DexEncodedMethod method : clazz.kotlinFunctions(originalFunctions, properties, appView)) {
+ KmFunction function = toRenamedKmFunction(method, null, appView, lens);
+ if (function != null) {
+ functions.add(function);
+ }
+ }
+ for (Map.Entry<DexEncodedMethod, KmFunction> entry :
+ clazz.kotlinExtensions(originalExtensions, appView).entrySet()) {
+ KmFunction extension =
+ toRenamedKmFunctionAsExtension(entry.getKey(), entry.getValue(), appView, lens);
+ if (extension != null) {
+ functions.add(extension);
}
}
}
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 2b1f9bc..68b607df 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -4,10 +4,17 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunctionAsExtension;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmFunction;
import kotlinx.metadata.KmPackage;
import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -37,15 +44,29 @@
@Override
void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
- // TODO(b/70169921): no idea yet!
- assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
- : toString();
+ List<KmFunction> functions = kmPackage.getFunctions();
+ List<KmFunction> originalExtensions =
+ functions.stream()
+ .filter(KotlinMetadataSynthesizer::isExtension)
+ .collect(Collectors.toList());
+ functions.clear();
+
+ for (Map.Entry<DexEncodedMethod, KmFunction> entry :
+ clazz.kotlinExtensions(originalExtensions, appView).entrySet()) {
+ KmFunction extension =
+ toRenamedKmFunctionAsExtension(entry.getKey(), entry.getValue(), appView, lens);
+ if (extension != null) {
+ functions.add(extension);
+ }
+ }
}
@Override
KotlinClassHeader createHeader() {
- // TODO(b/70169921): may need to update if `rewrite` is implemented.
- return metadata.getHeader();
+ KotlinClassMetadata.MultiFileClassPart.Writer writer =
+ new KotlinClassMetadata.MultiFileClassPart.Writer();
+ kmPackage.accept(writer);
+ return writer.write(metadata.getFacadeClassName()).getHeader();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
index e2bb332..e0a3935 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -4,10 +4,17 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunctionAsExtension;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmFunction;
import kotlinx.metadata.KmPackage;
import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -37,15 +44,28 @@
@Override
void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
- // TODO(b/70169921): no idea yet!
- assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
- : toString();
+ List<KmFunction> functions = kmPackage.getFunctions();
+ List<KmFunction> originalExtensions =
+ functions.stream()
+ .filter(KotlinMetadataSynthesizer::isExtension)
+ .collect(Collectors.toList());
+ functions.clear();
+
+ for (Map.Entry<DexEncodedMethod, KmFunction> entry :
+ clazz.kotlinExtensions(originalExtensions, appView).entrySet()) {
+ KmFunction extension =
+ toRenamedKmFunctionAsExtension(entry.getKey(), entry.getValue(), appView, lens);
+ if (extension != null) {
+ functions.add(extension);
+ }
+ }
}
@Override
KotlinClassHeader createHeader() {
- // TODO(b/70169921): may need to update if `rewrite` is implemented.
- return metadata.getHeader();
+ KotlinClassMetadata.FileFacade.Writer writer = new KotlinClassMetadata.FileFacade.Writer();
+ kmPackage.accept(writer);
+ return writer.write().getHeader();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
index 0e195e7..ff9ad15 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -5,6 +5,7 @@
import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
import static com.android.tools.r8.utils.DescriptorUtils.descriptorToInternalName;
+import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKmType;
import static kotlinx.metadata.FlagsKt.flagsOf;
import com.android.tools.r8.graph.AppView;
@@ -15,12 +16,19 @@
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 java.util.List;
+import kotlinx.metadata.KmConstructor;
import kotlinx.metadata.KmFunction;
import kotlinx.metadata.KmType;
import kotlinx.metadata.KmValueParameter;
-class KotlinMetadataSynthesizer {
+public class KotlinMetadataSynthesizer {
+
+ static boolean isExtension(KmFunction kmFunction) {
+ return kmFunction.getReceiverParameterType() != null;
+ }
+
static KmType toKmType(String descriptor) {
KmType kmType = new KmType(flagsOf());
kmType.visitClass(descriptorToInternalName(descriptor));
@@ -61,34 +69,176 @@
return kmType;
}
- static KmFunction toRenamedKmFunction(
- DexMethod method, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
- DexEncodedMethod encodedMethod = appView.definitionFor(method);
- if (encodedMethod == null) {
+ private static boolean isCompatible(KmType kmType, DexType type, AppView<?> appView) {
+ if (kmType == null || type == null) {
+ return false;
+ }
+ String descriptor = null;
+ if (appView.dexItemFactory().kotlin.knownTypeConversion.containsKey(type)) {
+ DexType convertedType = appView.dexItemFactory().kotlin.knownTypeConversion.get(type);
+ descriptor = convertedType.toDescriptorString();
+ }
+ if (descriptor == null) {
+ descriptor = type.toDescriptorString();
+ }
+ assert descriptor != null;
+ return descriptor.equals(getDescriptorFromKmType(kmType));
+ }
+
+ public static boolean isCompatibleConstructor(
+ KmConstructor constructor, DexEncodedMethod method, AppView<?> appView) {
+ List<KmValueParameter> parameters = constructor.getValueParameters();
+ if (method.method.proto.parameters.size() != parameters.size()) {
+ return false;
+ }
+ for (int i = 0; i < method.method.proto.parameters.size(); i++) {
+ KmType kmType = parameters.get(i).getType();
+ if (!isCompatible(kmType, method.method.proto.parameters.values[i], appView)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean isCompatibleFunction(
+ KmFunction function, DexEncodedMethod method, AppView<?> appView) {
+ if (!function.getName().equals(method.method.name.toString())) {
+ return false;
+ }
+ if (!isCompatible(function.getReturnType(), method.method.proto.returnType, appView)) {
+ return false;
+ }
+ List<KmValueParameter> parameters = function.getValueParameters();
+ if (method.method.proto.parameters.size() != parameters.size()) {
+ return false;
+ }
+ for (int i = 0; i < method.method.proto.parameters.size(); i++) {
+ KmType kmType = parameters.get(i).getType();
+ if (!isCompatible(kmType, method.method.proto.parameters.values[i], appView)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // TODO(b/70169921): Handling JVM extensions as well.
+ public static boolean isCompatibleExtension(
+ KmFunction extension, DexEncodedMethod method, AppView<?> appView) {
+ if (!extension.getName().equals(method.method.name.toString())) {
+ return false;
+ }
+ if (!isCompatible(extension.getReturnType(), method.method.proto.returnType, appView)) {
+ return false;
+ }
+ List<KmValueParameter> parameters = extension.getValueParameters();
+ if (method.method.proto.parameters.size() != parameters.size() + 1) {
+ return false;
+ }
+ assert method.method.proto.parameters.size() > 0;
+ assert extension.getReceiverParameterType() != null;
+ if (!isCompatible(
+ extension.getReceiverParameterType(), method.method.proto.parameters.values[0], appView)) {
+ return false;
+ }
+ for (int i = 1; i < method.method.proto.parameters.size(); i++) {
+ KmType kmType = parameters.get(i - 1).getType();
+ if (!isCompatible(kmType, method.method.proto.parameters.values[i], appView)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static KmConstructor toRenamedKmConstructor(
+ DexEncodedMethod method,
+ KmConstructor original,
+ AppView<AppInfoWithLiveness> appView,
+ NamingLens lens) {
+ // Make sure it is an instance initializer and live.
+ if (!method.isInstanceInitializer()
+ || !appView.appInfo().liveMethods.contains(method.method)) {
return null;
}
+ // TODO(b/70169921): {@link KmConstructor.extensions} is private, i.e., no way to alter!
+ // Thus, we rely on original metadata for now.
+ Box<Boolean> hasJvmExtension = new Box<>(false);
+ KmConstructor kmConstructor =
+ hasJvmExtension.get()
+ ? original
+ // TODO(b/70169921): Consult kotlinx.metadata.Flag.Constructor to set IS_PRIMARY.
+ : new KmConstructor(method.accessFlags.getAsKotlinFlags());
+ List<KmValueParameter> parameters = kmConstructor.getValueParameters();
+ parameters.clear();
+ populateKmValueParameters(parameters, method, appView, lens, false);
+ return kmConstructor;
+ }
+
+ static KmFunction toRenamedKmFunction(
+ DexEncodedMethod method,
+ KmFunction original,
+ AppView<AppInfoWithLiveness> appView,
+ NamingLens lens) {
+ return toRenamedKmFunctionHelper(method, original, appView, lens, false);
+ }
+
+ static KmFunction toRenamedKmFunctionAsExtension(
+ DexEncodedMethod method,
+ KmFunction original,
+ AppView<AppInfoWithLiveness> appView,
+ NamingLens lens) {
+ return toRenamedKmFunctionHelper(method, original, appView, lens, true);
+ }
+
+ private static KmFunction toRenamedKmFunctionHelper(
+ DexEncodedMethod method,
+ KmFunction original,
+ AppView<AppInfoWithLiveness> appView,
+ NamingLens lens,
+ boolean isExtension) {
// For library overrides, synthesize @Metadata always.
// For regular methods, make sure it is live.
- if (!encodedMethod.isLibraryMethodOverride().isTrue()
- && !appView.appInfo().liveMethods.contains(method)) {
+ if (!method.isLibraryMethodOverride().isTrue()
+ && !appView.appInfo().liveMethods.contains(method.method)) {
return null;
}
- DexMethod renamedMethod = lens.lookupMethod(method, appView.dexItemFactory());
+ DexMethod renamedMethod = lens.lookupMethod(method.method, appView.dexItemFactory());
// For a library method override, we should not have renamed it.
- assert !encodedMethod.isLibraryMethodOverride().isTrue() || renamedMethod == method
+ assert !method.isLibraryMethodOverride().isTrue() || renamedMethod == method.method
: method.toSourceString() + " -> " + renamedMethod.toSourceString();
- // TODO(b/70169921): Consult kotlinx.metadata.Flag.Function for kind (e.g., suspend).
+ // TODO(b/70169921): {@link KmFunction.extensions} is private, i.e., no way to alter!
+ // Thus, we rely on original metadata for now.
+ assert !isExtension || original != null;
KmFunction kmFunction =
- new KmFunction(encodedMethod.accessFlags.getAsKotlinFlags(), renamedMethod.name.toString());
- KmType kmReturnType = toRenamedKmType(method.proto.returnType, appView, lens);
+ isExtension
+ ? original
+ // TODO(b/70169921): Consult kotlinx.metadata.Flag.Function for kind (e.g., suspend).
+ : new KmFunction(method.accessFlags.getAsKotlinFlags(), renamedMethod.name.toString());
+ KmType kmReturnType = toRenamedKmType(method.method.proto.returnType, appView, lens);
assert kmReturnType != null;
kmFunction.setReturnType(kmReturnType);
+ if (isExtension) {
+ assert method.method.proto.parameters.values.length > 0;
+ KmType kmReceiverType =
+ toRenamedKmType(method.method.proto.parameters.values[0], appView, lens);
+ assert kmReceiverType != null;
+ kmFunction.setReceiverParameterType(kmReceiverType);
+ }
List<KmValueParameter> parameters = kmFunction.getValueParameters();
- for (int i = 0; i < method.proto.parameters.values.length; i++) {
- DexType paramType = method.proto.parameters.values[i];
- DebugLocalInfo debugLocalInfo = encodedMethod.getParameterInfo().get(i);
- String parameterName =
- debugLocalInfo != null ? debugLocalInfo.name.toString() : ("p" + i);
+ parameters.clear();
+ populateKmValueParameters(parameters, method, appView, lens, isExtension);
+ return kmFunction;
+ }
+
+ private static void populateKmValueParameters(
+ List<KmValueParameter> parameters,
+ DexEncodedMethod method,
+ AppView<AppInfoWithLiveness> appView,
+ NamingLens lens,
+ boolean isExtension) {
+ for (int i = isExtension ? 1 : 0; i < method.method.proto.parameters.values.length; i++) {
+ DexType paramType = method.method.proto.parameters.values[i];
+ DebugLocalInfo debugLocalInfo = method.getParameterInfo().get(i);
+ String parameterName = debugLocalInfo != null ? debugLocalInfo.name.toString() : ("p" + i);
// TODO(b/70169921): Consult kotlinx.metadata.Flag.ValueParameter.
KmValueParameter kmValueParameter = new KmValueParameter(flagsOf(), parameterName);
KmType kmParamType = toRenamedKmType(paramType, appView, lens);
@@ -96,6 +246,5 @@
kmValueParameter.setType(kmParamType);
parameters.add(kmValueParameter);
}
- return kmFunction;
}
}
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 a0d4df2..8c6c2b8 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.kotlin;
import com.android.tools.r8.naming.Range;
+import com.android.tools.r8.utils.SegmentTree;
import com.android.tools.r8.utils.ThrowingConsumer;
import java.io.BufferedReader;
import java.io.Closeable;
@@ -211,9 +212,10 @@
private static void addDebugEntryToBuilder(String debugEntry, ResultBuilder builder)
throws KotlinSourceDebugExtensionParserException {
- // <from>#<file>,<to>:<debug-line-position>
+ // <from>#<file>,<size>:<debug-line-position>
// or
// <from>#<file>:<debug-line-position>
+ // All positions should define intervals for mappings.
try {
int targetSplit = debugEntry.indexOf(':');
int target = Integer.parseInt(debugEntry.substring(targetSplit + 1));
@@ -223,7 +225,7 @@
// The range may have a different end than start.
String fileAndEndRange = original.substring(fileIndexSplit + 1);
int endRangeCharPosition = fileAndEndRange.indexOf(',');
- int size = originalStart;
+ int size = 1;
if (endRangeCharPosition > -1) {
// The file should be at least one number wide.
assert endRangeCharPosition > 0;
@@ -233,16 +235,13 @@
}
int fileIndex = Integer.parseInt(fileAndEndRange.substring(0, endRangeCharPosition));
Source thisFileSource = builder.files.get(fileIndex);
- if (thisFileSource != null) {
- Range range = new Range(originalStart, originalStart + size);
- Position position = new Position(thisFileSource, range);
- Position existingPosition = builder.positions.put(target, position);
- assert existingPosition == null
- : "Position index "
- + target
- + " was already mapped to an existing position: "
- + position;
+ if (thisFileSource == null) {
+ throw new KotlinSourceDebugExtensionParserException(
+ "Could not find file with index " + fileIndex);
}
+ Range range = new Range(originalStart, originalStart + (size - 1));
+ Position position = new Position(thisFileSource, range);
+ builder.segmentTree.add(target, target + (size - 1), position);
} catch (NumberFormatException e) {
throw new KotlinSourceDebugExtensionParserException("Could not convert position to number");
}
@@ -250,29 +249,28 @@
public static class Result {
- private final Map<Integer, Source> files;
- private final Map<Integer, Position> positions;
+ private final SegmentTree<Position> segmentTree;
- private Result(Map<Integer, Source> files, Map<Integer, Position> positions) {
- this.files = files;
- this.positions = positions;
+ public Result(SegmentTree<Position> segmentTree) {
+ this.segmentTree = segmentTree;
}
- public Map<Integer, Source> getFiles() {
- return files;
+ public Map.Entry<Integer, Position> lookup(int point) {
+ return segmentTree.findEntry(point);
}
- public Map<Integer, Position> getPositions() {
- return positions;
+ public int size() {
+ return segmentTree.size();
}
}
public static class ResultBuilder {
- final Map<Integer, Source> files = new HashMap<>();
- final Map<Integer, Position> positions = new HashMap<>();
+
+ SegmentTree<Position> segmentTree = new SegmentTree<>(false);
+ Map<Integer, Source> files = new HashMap<>();
public Result build() {
- return new Result(files, positions);
+ return new Result(segmentTree);
}
}
@@ -326,6 +324,7 @@
sb.append(",");
sb.append(range.to);
}
+ sb.append(":");
return sb.toString();
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 75d1037..de60241 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -40,6 +39,7 @@
import com.android.tools.r8.position.TextPosition;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.Streams;
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import java.util.Arrays;
@@ -47,6 +47,8 @@
import java.util.ListIterator;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
public class IdentifierNameStringMarker {
@@ -61,12 +63,17 @@
this.throwingInfo = ThrowingInfo.defaultForConstString(appView.options());
}
- public void decoupleIdentifierNameStringsInFields() {
- for (DexProgramClass clazz : appView.appInfo().classes()) {
- for (DexEncodedField field : clazz.staticFields()) {
- decoupleIdentifierNameStringInStaticField(field);
- }
- }
+ public void decoupleIdentifierNameStringsInFields(
+ ExecutorService executorService) throws ExecutionException {
+ ThreadUtils.processItems(
+ appView.appInfo().classes(),
+ clazz -> {
+ for (DexEncodedField field : clazz.staticFields()) {
+ decoupleIdentifierNameStringInStaticField(field);
+ }
+ },
+ executorService
+ );
}
private void decoupleIdentifierNameStringInStaticField(DexEncodedField encodedField) {
diff --git a/src/main/java/com/android/tools/r8/retrace/AmbiguousComparator.java b/src/main/java/com/android/tools/r8/retrace/AmbiguousComparator.java
new file mode 100644
index 0000000..35266e7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/AmbiguousComparator.java
@@ -0,0 +1,47 @@
+// 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.Comparator;
+import java.util.function.BiFunction;
+
+public abstract class AmbiguousComparator<T> implements Comparator<T> {
+
+ public enum SortKeys {
+ CLASS,
+ METHOD,
+ SOURCE,
+ LINE
+ }
+
+ private final BiFunction<T, SortKeys, String> getter;
+
+ public AmbiguousComparator(BiFunction<T, SortKeys, String> getter) {
+ this.getter = getter;
+ }
+
+ @Override
+ public int compare(T o1, T o2) {
+ int compare = getter.apply(o1, SortKeys.CLASS).compareTo(getter.apply(o2, SortKeys.CLASS));
+ if (compare != 0) {
+ return compare;
+ }
+ compare = getter.apply(o1, SortKeys.METHOD).compareTo(getter.apply(o2, SortKeys.METHOD));
+ if (compare != 0) {
+ return compare;
+ }
+ compare = getter.apply(o1, SortKeys.SOURCE).compareTo(getter.apply(o2, SortKeys.SOURCE));
+ if (compare != 0) {
+ return compare;
+ }
+ try {
+ return Integer.compare(
+ Integer.parseInt(getter.apply(o1, SortKeys.LINE)),
+ Integer.parseInt(getter.apply(o2, SortKeys.SOURCE)));
+ } catch (NumberFormatException ignore) {
+ return 0;
+ }
+ }
+}
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 d537f03..f0719ea 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
@@ -162,8 +162,20 @@
return mappedRange != null ? mappedRange.getOriginalLineNumber(linePosition) : linePosition;
}
+ public boolean containsMinifiedLineNumber(int linePosition) {
+ if (hasNoLineNumberRange()) {
+ return false;
+ }
+ return mappedRange.minifiedRange.from <= linePosition
+ && linePosition <= mappedRange.minifiedRange.to;
+ }
+
+ public boolean hasNoLineNumberRange() {
+ return mappedRange == null || mappedRange.minifiedRange == null;
+ }
+
public int getFirstLineNumberOfOriginalRange() {
- if (mappedRange == null) {
+ if (hasNoLineNumberRange()) {
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
index d6b4cb9..b561305 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
@@ -13,6 +13,7 @@
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.android.tools.r8.utils.StringUtils;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Arrays;
@@ -75,14 +76,56 @@
// We could not find a match. Output the identity.
result.add(string);
} else {
+ boolean isAmbiguous = retracedStrings.size() > 1 && retracedStrings.get(0).isAmbiguous;
+ if (isAmbiguous) {
+ retracedStrings.sort(new RetraceLineComparator());
+ }
+ ClassReference previousContext = null;
for (RetraceString retracedString : retracedStrings) {
- result.add(retracedString.getRetracedString());
+ String finalString = retracedString.getRetracedString();
+ if (!isAmbiguous) {
+ result.add(finalString);
+ continue;
+ }
+ assert retracedString.getClassContext() != null;
+ ClassReference currentContext = retracedString.getClassContext().getClassReference();
+ if (currentContext.equals(previousContext)) {
+ int firstNonWhitespaceCharacter = StringUtils.firstNonWhitespaceCharacter(finalString);
+ finalString =
+ finalString.substring(0, firstNonWhitespaceCharacter)
+ + "<OR> "
+ + finalString.substring(firstNonWhitespaceCharacter);
+ }
+ previousContext = currentContext;
+ result.add(finalString);
}
}
}
return new RetraceCommandLineResult(result);
}
+ static class RetraceLineComparator extends AmbiguousComparator<RetraceString> {
+
+ RetraceLineComparator() {
+ super(
+ (line, t) -> {
+ switch (t) {
+ case CLASS:
+ return line.getClassContext().getClassReference().getTypeName();
+ case METHOD:
+ return line.getMethodContext().getMethodReference().getMethodName();
+ case SOURCE:
+ return line.getSource();
+ case LINE:
+ return line.getLineNumber() + "";
+ default:
+ assert false;
+ }
+ throw new RuntimeException("Comparator key is unknown");
+ });
+ }
+ }
+
private String registerGroups(
String regularExpression, List<RegularExpressionGroupHandler> handlers) {
int currentIndex = 0;
@@ -128,6 +171,9 @@
private final boolean hasTypeOrReturnTypeContext;
private final String retracedString;
private final int adjustedIndex;
+ private final boolean isAmbiguous;
+ private final int lineNumber;
+ private final String source;
private RetraceString(
Element classContext,
@@ -137,7 +183,10 @@
TypeReference typeOrReturnTypeContext,
boolean hasTypeOrReturnTypeContext,
String retracedString,
- int adjustedIndex) {
+ int adjustedIndex,
+ boolean isAmbiguous,
+ int lineNumber,
+ String source) {
this.classContext = classContext;
this.classNameGroup = classNameGroup;
this.qualifiedContext = qualifiedContext;
@@ -146,6 +195,9 @@
this.hasTypeOrReturnTypeContext = hasTypeOrReturnTypeContext;
this.retracedString = retracedString;
this.adjustedIndex = adjustedIndex;
+ this.isAmbiguous = isAmbiguous;
+ this.lineNumber = lineNumber;
+ this.source = source;
}
String getRetracedString() {
@@ -176,6 +228,14 @@
return RetraceStringBuilder.create(this);
}
+ public int getLineNumber() {
+ return lineNumber;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
static class RetraceStringBuilder {
private Element classContext;
@@ -186,6 +246,9 @@
private boolean hasTypeOrReturnTypeContext;
private String retracedString;
private int adjustedIndex;
+ private boolean isAmbiguous;
+ private int lineNumber;
+ private String source;
private int maxReplaceStringIndex = NO_MATCH;
@@ -197,7 +260,10 @@
TypeReference typeOrReturnTypeContext,
boolean hasTypeOrReturnTypeContext,
String retracedString,
- int adjustedIndex) {
+ int adjustedIndex,
+ boolean isAmbiguous,
+ int lineNumber,
+ String source) {
this.classContext = classContext;
this.classNameGroup = classNameGroup;
this.qualifiedContext = qualifiedContext;
@@ -206,10 +272,14 @@
this.hasTypeOrReturnTypeContext = hasTypeOrReturnTypeContext;
this.retracedString = retracedString;
this.adjustedIndex = adjustedIndex;
+ this.isAmbiguous = isAmbiguous;
+ this.lineNumber = lineNumber;
+ this.source = source;
}
static RetraceStringBuilder create(String string) {
- return new RetraceStringBuilder(null, null, null, null, null, false, string, 0);
+ return new RetraceStringBuilder(
+ null, null, null, null, null, false, string, 0, false, 0, "");
}
static RetraceStringBuilder create(RetraceString string) {
@@ -221,7 +291,10 @@
string.typeOrReturnTypeContext,
string.hasTypeOrReturnTypeContext,
string.retracedString,
- string.adjustedIndex);
+ string.adjustedIndex,
+ string.isAmbiguous,
+ string.lineNumber,
+ string.source);
}
RetraceStringBuilder setClassContext(Element classContext, ClassNameGroup classNameGroup) {
@@ -246,6 +319,21 @@
return this;
}
+ RetraceStringBuilder setAmbiguous(boolean isAmbiguous) {
+ this.isAmbiguous = isAmbiguous;
+ return this;
+ }
+
+ RetraceStringBuilder setLineNumber(int lineNumber) {
+ this.lineNumber = lineNumber;
+ return this;
+ }
+
+ RetraceStringBuilder setSource(String source) {
+ this.source = source;
+ return this;
+ }
+
RetraceStringBuilder replaceInString(String oldString, String newString) {
int oldStringStartIndex = retracedString.indexOf(oldString);
assert oldStringStartIndex > NO_MATCH;
@@ -278,7 +366,10 @@
typeOrReturnTypeContext,
hasTypeOrReturnTypeContext,
retracedString,
- adjustedIndex);
+ adjustedIndex,
+ isAmbiguous,
+ lineNumber,
+ source);
}
}
}
@@ -391,7 +482,7 @@
@Override
String subExpression() {
- return javaIdentifierSegment;
+ return "(?:(" + javaIdentifierSegment + "|\\<init\\>|\\<clinit\\>))";
}
@Override
@@ -438,6 +529,7 @@
}
newRetraceString
.setMethodContext(element)
+ .setAmbiguous(element.getRetraceMethodResult().isAmbiguous())
.replaceInString(
methodReference.getMethodName(),
matcher.start(captureGroup),
@@ -513,7 +605,7 @@
@Override
String subExpression() {
- return "(?:\\w+\\.)*\\w+";
+ return "(?:(\\w*[\\. ])?(\\w*)?)";
}
@Override
@@ -540,6 +632,7 @@
retracedStrings.add(
retraceString
.transform()
+ .setSource(fileName)
.replaceInString(
newSourceFile, matcher.start(captureGroup), matcher.end(captureGroup))
.build());
@@ -571,12 +664,17 @@
int lineNumber =
lineNumberAsString.isEmpty() ? NO_MATCH : Integer.parseInt(lineNumberAsString);
List<RetraceString> retracedStrings = new ArrayList<>();
+ boolean seenRange = false;
for (RetraceString retraceString : strings) {
RetraceMethodResult.Element methodContext = retraceString.methodContext;
- if (methodContext == null) {
+ if (methodContext == null || methodContext.getMethodReference().isUnknown()) {
retracedStrings.add(retraceString);
continue;
}
+ if (methodContext.hasNoLineNumberRange()) {
+ continue;
+ }
+ seenRange = true;
Set<MethodReference> narrowedSet =
methodContext.getRetraceMethodResult().narrowByLine(lineNumber).stream()
.map(RetraceMethodResult.Element::getMethodReference)
@@ -588,23 +686,33 @@
new StringDiagnostic(
"Pruning "
+ retraceString.getRetracedString()
- + " from result because line number "
- + lineNumber
- + " does not match."));
+ + " from result because method is not defined on line number "
+ + lineNumber));
continue;
}
- String newLineNumber =
- lineNumber > NO_MATCH
- ? methodContext.getOriginalLineNumber(lineNumber) + ""
- : lineNumberAsString;
+ // The same method can be represented multiple times if it has multiple mappings.
+ if (!methodContext.containsMinifiedLineNumber(lineNumber)) {
+ diagnosticsHandler.info(
+ new StringDiagnostic(
+ "Pruning "
+ + retraceString.getRetracedString()
+ + " from result because method is not in range on line number "
+ + lineNumber));
+ continue;
+ }
+ int originalLineNumber = methodContext.getOriginalLineNumber(lineNumber);
retracedStrings.add(
retraceString
.transform()
+ .setAmbiguous(false)
+ .setLineNumber(originalLineNumber)
.replaceInString(
- newLineNumber, matcher.start(captureGroup), matcher.end(captureGroup))
+ originalLineNumber + "",
+ matcher.start(captureGroup),
+ matcher.end(captureGroup))
.build());
}
- return retracedStrings;
+ return seenRange ? retracedStrings : strings;
};
}
}
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 7c899a7..3a564d8 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
@@ -14,7 +14,6 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
-import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;
@@ -48,7 +47,8 @@
assert line.isAtLine();
AtLine atLine = line.asAtLine();
if (atLine.isAmbiguous) {
- strings.add(atLine.toString(previousClazz.isEmpty() ? atLine.at : "or ", previousClazz));
+ strings.add(
+ atLine.toString(previousClazz.isEmpty() ? atLine.at : "<OR> " + atLine.at, ""));
} else {
strings.add(atLine.toString());
}
@@ -57,25 +57,27 @@
}
}
- static class AtStackTraceLineComparator implements Comparator<StackTraceLine> {
+ static class AtStackTraceLineComparator extends AmbiguousComparator<StackTraceLine> {
- @Override
- public int compare(StackTraceLine o1, StackTraceLine o2) {
- AtLine a1 = (AtLine) o1;
- AtLine a2 = (AtLine) o2;
- int compare = a1.clazz.compareTo(a2.clazz);
- if (compare != 0) {
- return compare;
- }
- compare = a1.method.compareTo(a2.method);
- if (compare != 0) {
- return compare;
- }
- compare = a1.fileName.compareTo(a2.fileName);
- if (compare != 0) {
- return compare;
- }
- return Integer.compare(a1.linePosition, a2.linePosition);
+ AtStackTraceLineComparator() {
+ super(
+ (line, t) -> {
+ assert line.isAtLine();
+ AtLine atLine = line.asAtLine();
+ switch (t) {
+ case CLASS:
+ return atLine.clazz;
+ case METHOD:
+ return atLine.method;
+ case SOURCE:
+ return atLine.fileName;
+ case LINE:
+ return atLine.linePosition + "";
+ default:
+ assert false;
+ }
+ throw new RuntimeException("Comparator key is unknown");
+ });
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 701904c..fd3e7f5 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedAnnotation;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -18,7 +19,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.Collections;
@@ -39,24 +39,23 @@
this.classesToRetainInnerClassAttributeFor = classesToRetainInnerClassAttributeFor;
}
- /**
- * Used to filter annotations on classes, methods and fields.
- */
- private boolean filterAnnotations(DexAnnotation annotation) {
- return shouldKeepAnnotation(
- annotation, isAnnotationTypeLive(annotation), appView.dexItemFactory(), appView.options());
+ /** Used to filter annotations on classes, methods and fields. */
+ private boolean filterAnnotations(DexDefinition holder, DexAnnotation annotation) {
+ return shouldKeepAnnotation(holder, annotation, isAnnotationTypeLive(annotation), appView);
}
static boolean shouldKeepAnnotation(
+ DexDefinition holder,
DexAnnotation annotation,
boolean isAnnotationTypeLive,
- DexItemFactory dexItemFactory,
- InternalOptions options) {
+ AppView<?> appView) {
ProguardKeepAttributes config =
- options.getProguardConfiguration() != null
- ? options.getProguardConfiguration().getKeepAttributes()
+ appView.options().getProguardConfiguration() != null
+ ? appView.options().getProguardConfiguration().getKeepAttributes()
: ProguardKeepAttributes.fromPatterns(ImmutableList.of());
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+
switch (annotation.visibility) {
case DexAnnotation.VISIBILITY_SYSTEM:
// InnerClass and EnclosingMember are represented in class attributes, not annotations.
@@ -70,9 +69,11 @@
if (config.signature && DexAnnotation.isSignatureAnnotation(annotation, dexItemFactory)) {
return true;
}
- if (config.sourceDebugExtension
- && DexAnnotation.isSourceDebugExtension(annotation, dexItemFactory)) {
- return true;
+ if (DexAnnotation.isSourceDebugExtension(annotation, dexItemFactory)) {
+ assert holder.isDexClass();
+ appView.setSourceDebugExtensionForType(
+ holder.asDexClass(), annotation.annotation.elements[0].value.asDexValueString());
+ return config.sourceDebugExtension;
}
if (config.methodParameters
&& DexAnnotation.isParameterNameAnnotation(annotation, dexItemFactory)) {
@@ -229,25 +230,28 @@
public void run() {
for (DexProgramClass clazz : appView.appInfo().classes()) {
stripAttributes(clazz);
- clazz.annotations = clazz.annotations.rewrite(this::rewriteAnnotation);
+ clazz.annotations =
+ clazz.annotations.rewrite(annotation -> rewriteAnnotation(clazz, annotation));
clazz.forEachMethod(this::processMethod);
clazz.forEachField(this::processField);
}
}
private void processMethod(DexEncodedMethod method) {
- method.annotations = method.annotations.rewrite(this::rewriteAnnotation);
+ method.annotations =
+ method.annotations.rewrite(annotation -> rewriteAnnotation(method, annotation));
method.parameterAnnotationsList =
method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
}
private void processField(DexEncodedField field) {
- field.annotations = field.annotations.rewrite(this::rewriteAnnotation);
+ field.annotations =
+ field.annotations.rewrite(annotation -> rewriteAnnotation(field, annotation));
}
- private DexAnnotation rewriteAnnotation(DexAnnotation original) {
+ private DexAnnotation rewriteAnnotation(DexDefinition holder, DexAnnotation original) {
// Check if we should keep this annotation first.
- if (!filterAnnotations(original)) {
+ if (!filterAnnotations(holder, original)) {
return null;
}
// Then, filter out values that refer to dead definitions.
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 0aea415..25a7b15 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -82,7 +82,9 @@
*/
final SortedSet<DexMethod> targetedMethods;
- final Set<DexMethod> targetedMethodsThatMustRemainNonAbstract;
+ /** Set of targets that lead to resolution errors, such as non-existing or invalid targets. */
+ public final Set<DexMethod> failedResolutionTargets;
+
/**
* Set of program methods that are used as the bootstrap method for an invoke-dynamic instruction.
*/
@@ -117,11 +119,6 @@
* will have been removed from the code.
*/
public final Set<DexCallSite> callSites;
- /**
- * Set of method signatures used in invoke-super instructions that either cannot be resolved or
- * resolve to a private method (leading to an IllegalAccessError).
- */
- public final SortedSet<DexMethod> brokenSuperInvokes;
/** Set of all items that have to be kept independent of whether they are used. */
final Set<DexReference> pinnedItems;
/** All items with assumemayhavesideeffects rule. */
@@ -189,7 +186,7 @@
Set<DexType> instantiatedAppServices,
Set<DexType> instantiatedTypes,
SortedSet<DexMethod> targetedMethods,
- Set<DexMethod> targetedMethodsThatMustRemainNonAbstract,
+ Set<DexMethod> failedResolutionTargets,
SortedSet<DexMethod> bootstrapMethods,
SortedSet<DexMethod> methodsTargetedByInvokeDynamic,
SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect,
@@ -201,7 +198,6 @@
SortedMap<DexMethod, Set<DexEncodedMethod>> directInvokes,
SortedMap<DexMethod, Set<DexEncodedMethod>> staticInvokes,
Set<DexCallSite> callSites,
- SortedSet<DexMethod> brokenSuperInvokes,
Set<DexReference> pinnedItems,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
Map<DexReference, ProguardMemberRule> noSideEffects,
@@ -228,7 +224,7 @@
this.instantiatedAppServices = instantiatedAppServices;
this.instantiatedTypes = instantiatedTypes;
this.targetedMethods = targetedMethods;
- this.targetedMethodsThatMustRemainNonAbstract = targetedMethodsThatMustRemainNonAbstract;
+ this.failedResolutionTargets = failedResolutionTargets;
this.bootstrapMethods = bootstrapMethods;
this.methodsTargetedByInvokeDynamic = methodsTargetedByInvokeDynamic;
this.virtualMethodsTargetedByInvokeDirect = virtualMethodsTargetedByInvokeDirect;
@@ -244,7 +240,6 @@
this.directInvokes = directInvokes;
this.staticInvokes = staticInvokes;
this.callSites = callSites;
- this.brokenSuperInvokes = brokenSuperInvokes;
this.alwaysInline = alwaysInline;
this.forceInline = forceInline;
this.neverInline = neverInline;
@@ -270,7 +265,7 @@
Set<DexType> instantiatedAppServices,
Set<DexType> instantiatedTypes,
SortedSet<DexMethod> targetedMethods,
- Set<DexMethod> targetedMethodsThatMustRemainNonAbstract,
+ Set<DexMethod> failedResolutionTargets,
SortedSet<DexMethod> bootstrapMethods,
SortedSet<DexMethod> methodsTargetedByInvokeDynamic,
SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect,
@@ -282,7 +277,6 @@
SortedMap<DexMethod, Set<DexEncodedMethod>> directInvokes,
SortedMap<DexMethod, Set<DexEncodedMethod>> staticInvokes,
Set<DexCallSite> callSites,
- SortedSet<DexMethod> brokenSuperInvokes,
Set<DexReference> pinnedItems,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
Map<DexReference, ProguardMemberRule> noSideEffects,
@@ -309,7 +303,7 @@
this.instantiatedAppServices = instantiatedAppServices;
this.instantiatedTypes = instantiatedTypes;
this.targetedMethods = targetedMethods;
- this.targetedMethodsThatMustRemainNonAbstract = targetedMethodsThatMustRemainNonAbstract;
+ this.failedResolutionTargets = failedResolutionTargets;
this.bootstrapMethods = bootstrapMethods;
this.methodsTargetedByInvokeDynamic = methodsTargetedByInvokeDynamic;
this.virtualMethodsTargetedByInvokeDirect = virtualMethodsTargetedByInvokeDirect;
@@ -325,7 +319,6 @@
this.directInvokes = directInvokes;
this.staticInvokes = staticInvokes;
this.callSites = callSites;
- this.brokenSuperInvokes = brokenSuperInvokes;
this.alwaysInline = alwaysInline;
this.forceInline = forceInline;
this.neverInline = neverInline;
@@ -352,7 +345,7 @@
previous.instantiatedAppServices,
previous.instantiatedTypes,
previous.targetedMethods,
- previous.targetedMethodsThatMustRemainNonAbstract,
+ previous.failedResolutionTargets,
previous.bootstrapMethods,
previous.methodsTargetedByInvokeDynamic,
previous.virtualMethodsTargetedByInvokeDirect,
@@ -364,7 +357,6 @@
previous.directInvokes,
previous.staticInvokes,
previous.callSites,
- previous.brokenSuperInvokes,
previous.pinnedItems,
previous.mayHaveSideEffects,
previous.noSideEffects,
@@ -400,7 +392,7 @@
previous.instantiatedAppServices,
previous.instantiatedTypes,
previous.targetedMethods,
- previous.targetedMethodsThatMustRemainNonAbstract,
+ previous.failedResolutionTargets,
previous.bootstrapMethods,
previous.methodsTargetedByInvokeDynamic,
previous.virtualMethodsTargetedByInvokeDirect,
@@ -412,7 +404,6 @@
previous.directInvokes,
previous.staticInvokes,
previous.callSites,
- previous.brokenSuperInvokes,
additionalPinnedItems == null
? previous.pinnedItems
: CollectionUtils.mergeSets(previous.pinnedItems, additionalPinnedItems),
@@ -452,8 +443,8 @@
this.instantiatedTypes = rewriteItems(previous.instantiatedTypes, lense::lookupType);
this.instantiatedLambdas = rewriteItems(previous.instantiatedLambdas, lense::lookupType);
this.targetedMethods = lense.rewriteMethodsConservatively(previous.targetedMethods);
- this.targetedMethodsThatMustRemainNonAbstract =
- lense.rewriteMethodsConservatively(previous.targetedMethodsThatMustRemainNonAbstract);
+ this.failedResolutionTargets =
+ lense.rewriteMethodsConservatively(previous.failedResolutionTargets);
this.bootstrapMethods = lense.rewriteMethodsConservatively(previous.bootstrapMethods);
this.methodsTargetedByInvokeDynamic =
lense.rewriteMethodsConservatively(previous.methodsTargetedByInvokeDynamic);
@@ -481,7 +472,6 @@
// TODO(sgjesse): Rewrite call sites as well? Right now they are only used by minification
// after second tree shaking.
this.callSites = previous.callSites;
- this.brokenSuperInvokes = lense.rewriteMethodsConservatively(previous.brokenSuperInvokes);
// Don't rewrite pruned types - the removed types are identified by their original name.
this.prunedTypes = previous.prunedTypes;
this.mayHaveSideEffects =
@@ -535,8 +525,7 @@
this.instantiatedTypes = previous.instantiatedTypes;
this.instantiatedLambdas = previous.instantiatedLambdas;
this.targetedMethods = previous.targetedMethods;
- this.targetedMethodsThatMustRemainNonAbstract =
- previous.targetedMethodsThatMustRemainNonAbstract;
+ this.failedResolutionTargets = previous.failedResolutionTargets;
this.bootstrapMethods = previous.bootstrapMethods;
this.methodsTargetedByInvokeDynamic = previous.methodsTargetedByInvokeDynamic;
this.virtualMethodsTargetedByInvokeDirect = previous.virtualMethodsTargetedByInvokeDirect;
@@ -552,7 +541,6 @@
this.directInvokes = previous.directInvokes;
this.staticInvokes = previous.staticInvokes;
this.callSites = previous.callSites;
- this.brokenSuperInvokes = previous.brokenSuperInvokes;
this.alwaysInline = previous.alwaysInline;
this.forceInline = previous.forceInline;
this.neverInline = previous.neverInline;
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 8329fcf..6bd3db2 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -156,11 +156,6 @@
private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
/**
- * Set of method signatures used in invoke-super instructions that either cannot be resolved or
- * resolve to a private method (leading to an IllegalAccessError).
- */
- private final Set<DexMethod> brokenSuperInvokes = Sets.newIdentityHashSet();
- /**
* This map keeps a view of all virtual methods that are reachable from virtual invokes. A method
* is reachable even if no live subtypes exist, so this is not sufficient for inclusion in the
* live set.
@@ -211,8 +206,8 @@
*/
private final SetWithReason<DexEncodedMethod> targetedMethods;
- /** Subset of 'targetedMethods' for which the method must not be marked abstract. */
- private final Set<DexEncodedMethod> targetedMethodsThatMustRemainNonAbstract;
+ /** Set of methods that have invalid resolutions or lookups. */
+ private final Set<DexMethod> failedResolutionTargets;
/**
* Set of program methods that are used as the bootstrap method for an invoke-dynamic instruction.
@@ -329,7 +324,7 @@
// This set is only populated in edge cases due to multiple default interface methods.
// The set is generally expected to be empty and in the unlikely chance it is not, it will
// likely contain two methods. Thus the default capacity of 2.
- targetedMethodsThatMustRemainNonAbstract = SetUtils.newIdentityHashSet(2);
+ failedResolutionTargets = SetUtils.newIdentityHashSet(2);
liveMethods = new LiveMethodsSet(graphReporter::registerMethod);
liveFields = new SetWithReason<>(graphReporter::registerField);
instantiatedInterfaceTypes = new SetWithReason<>(graphReporter::registerInterface);
@@ -1313,7 +1308,7 @@
DexClass clazz = appView.definitionFor(type);
boolean annotationTypeIsLibraryClass = clazz == null || clazz.isNotProgramClass();
boolean isLive = annotationTypeIsLibraryClass || liveTypes.contains(clazz.asProgramClass());
- if (!shouldKeepAnnotation(annotation, isLive, appView.dexItemFactory(), options)) {
+ if (!shouldKeepAnnotation(holder, annotation, isLive, appView)) {
// Remember this annotation for later.
if (!annotationTypeIsLibraryClass) {
deferredAnnotations.computeIfAbsent(type, ignore -> new HashSet<>()).add(annotation);
@@ -1327,21 +1322,22 @@
annotation.annotation.collectIndexedItems(referenceMarker);
}
- private void handleInvokeOfStaticTarget(DexMethod method, KeepReason reason) {
+ private ResolutionResult resolveMethod(DexMethod method, KeepReason reason) {
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- if (resolutionResult == null) {
+ if (resolutionResult.isFailedResolution()) {
reportMissingMethod(method);
+ markFailedResolutionTargets(method, resolutionResult.asFailedResolution(), reason);
+ }
+ return resolutionResult;
+ }
+
+ private void handleInvokeOfStaticTarget(DexMethod method, KeepReason reason) {
+ SingleResolutionResult resolution = resolveMethod(method, reason).asSingleResolution();
+ if (resolution == null || resolution.getResolvedHolder().isNotProgramClass()) {
return;
}
- DexEncodedMethod encodedMethod = resolutionResult.getSingleTarget();
- if (encodedMethod == null) {
- // Note: should this be reported too? Or is this unreachable?
- return;
- }
- DexProgramClass clazz = getProgramClassOrNull(encodedMethod.method.holder);
- if (clazz == null) {
- return;
- }
+ DexProgramClass clazz = resolution.getResolvedHolder().asProgramClass();
+ DexEncodedMethod encodedMethod = resolution.getResolvedMethod();
// We have to mark the resolved method as targeted even if it cannot actually be invoked
// to make sure the invocation will keep failing in the appropriate way.
@@ -1636,7 +1632,7 @@
assert libraryClass.isNotProgramClass();
assert !instantiatedClass.isInterface() || instantiatedClass.accessFlags.isAnnotation();
for (DexEncodedMethod method : libraryClass.virtualMethods()) {
- // Note: it may be worthwhile to add a resolution cache here. If so, it must till ensure
+ // Note: it may be worthwhile to add a resolution cache here. If so, it must still ensure
// that all library override edges are reported to the kept-graph consumer.
ResolutionResult firstResolution =
appView.appInfo().resolveMethod(instantiatedClass, method.method);
@@ -2100,7 +2096,7 @@
appInfo.resolveMethod(method.holder, method, interfaceInvoke);
if (resolutionResult.isFailedResolution()) {
// If the resolution fails, mark each dependency causing a failure.
- markFailedResolutionTargets(resolutionResult.asFailedResolution(), reason);
+ markFailedResolutionTargets(method, resolutionResult.asFailedResolution(), reason);
return MarkedResolutionTarget.unresolved();
}
@@ -2132,12 +2128,13 @@
}
private void markFailedResolutionTargets(
- FailedResolutionResult failedResolution, KeepReason reason) {
+ DexMethod symbolicMethod, FailedResolutionResult failedResolution, KeepReason reason) {
+ failedResolutionTargets.add(symbolicMethod);
failedResolution.forEachFailureDependency(
method -> {
DexProgramClass clazz = getProgramClassOrNull(method.method.holder);
if (clazz != null) {
- targetedMethodsThatMustRemainNonAbstract.add(method);
+ failedResolutionTargets.add(method.method);
markMethodAsTargeted(clazz, method, reason);
}
});
@@ -2168,27 +2165,22 @@
// Package protected due to entry point from worklist.
void markSuperMethodAsReachable(DexMethod method, DexEncodedMethod from) {
- // If the method does not resolve, mark it broken to avoid hiding errors in other optimizations.
- SingleResolutionResult resolution =
- appInfo.resolveMethod(method.holder, method).asSingleResolution();
+ KeepReason reason = KeepReason.targetedBySuperFrom(from);
+ SingleResolutionResult resolution = resolveMethod(method, reason).asSingleResolution();
if (resolution == null) {
- brokenSuperInvokes.add(method);
- reportMissingMethod(method);
return;
}
// If the resolution is in the program, mark it targeted.
if (resolution.getResolvedHolder().isProgramClass()) {
markMethodAsTargeted(
- resolution.getResolvedHolder().asProgramClass(),
- resolution.getResolvedMethod(),
- KeepReason.targetedBySuperFrom(from));
+ resolution.getResolvedHolder().asProgramClass(), resolution.getResolvedMethod(), reason);
}
// If invoke target is invalid (inaccessible or not an instance-method) record it and stop.
// TODO(b/146016987): We should be passing the full program context and not looking it up again.
DexProgramClass fromHolder = appInfo.definitionFor(from.method.holder).asProgramClass();
DexEncodedMethod target = resolution.lookupInvokeSuperTarget(fromHolder, appInfo);
if (target == null) {
- brokenSuperInvokes.add(resolution.getResolvedMethod().method);
+ failedResolutionTargets.add(resolution.getResolvedMethod().method);
return;
}
@@ -2281,8 +2273,7 @@
Collections.unmodifiableSet(instantiatedAppServices),
SetUtils.mapIdentityHashSet(instantiatedTypes.getItems(), DexProgramClass::getType),
Enqueuer.toSortedDescriptorSet(targetedMethods.getItems()),
- SetUtils.mapIdentityHashSet(
- targetedMethodsThatMustRemainNonAbstract, DexEncodedMethod::getKey),
+ Collections.unmodifiableSet(failedResolutionTargets),
ImmutableSortedSet.copyOf(DexMethod::slowCompareTo, bootstrapMethods),
ImmutableSortedSet.copyOf(DexMethod::slowCompareTo, methodsTargetedByInvokeDynamic),
ImmutableSortedSet.copyOf(
@@ -2297,7 +2288,6 @@
toImmutableSortedMap(directInvokes, PresortedComparable::slowCompare),
toImmutableSortedMap(staticInvokes, PresortedComparable::slowCompare),
callSites,
- ImmutableSortedSet.copyOf(DexMethod::slowCompareTo, brokenSuperInvokes),
pinnedItems,
rootSet.mayHaveSideEffects,
rootSet.noSideEffects,
@@ -2642,6 +2632,7 @@
markParameterAndReturnTypesAsLive(method);
}
+
private void markParameterAndReturnTypesAsLive(DexEncodedMethod method) {
for (DexType parameterType : method.method.proto.parameters.values) {
markTypeAsLive(
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index e7a7fe5..116e315 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -297,7 +297,7 @@
&& !method.accessFlags.isSynchronized()
&& !method.accessFlags.isPrivate()
&& !method.accessFlags.isStatic()
- && !appInfo.targetedMethodsThatMustRemainNonAbstract.contains(method.method);
+ && !appInfo.failedResolutionTargets.contains(method.method);
// Private methods and static methods can only be targeted yet non-live as the result of
// an invalid invoke. They will not actually be called at runtime but we have to keep them
// as non-abstract (see above) to produce the same failure mode.
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 9a477cc..c518937 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -263,24 +263,6 @@
}
}
- // Avoid merging two types if this could remove a NoSuchMethodError, as illustrated by the
- // following example. (Alternatively, it would be possible to merge A and B and rewrite the
- // "invoke-super A.m" instruction into "invoke-super Object.m" to preserve the error. This
- // situation should generally not occur in practice, though.)
- //
- // class A {}
- // class B extends A {
- // public void m() {}
- // }
- // class C extends A {
- // public void m() {
- // invoke-super "A.m" <- should yield NoSuchMethodError, cannot merge A and B
- // }
- // }
- for (DexMethod signature : appInfo.brokenSuperInvokes) {
- markTypeAsPinned(signature.holder, AbortReason.UNHANDLED_INVOKE_SUPER);
- }
-
// It is valid to have an invoke-direct instruction in a default interface method that targets
// another default method in the same interface (see InterfaceMethodDesugaringTests.testInvoke-
// SpecialToDefaultMethod). However, in a class, that would lead to a verification error.
@@ -290,7 +272,7 @@
}
// The set of targets that must remain for proper resolution error cases should not be merged.
- for (DexMethod method : appInfo.targetedMethodsThatMustRemainNonAbstract) {
+ for (DexMethod method : appInfo.failedResolutionTargets) {
markTypeAsPinned(method.holder, AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE);
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java b/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
new file mode 100644
index 0000000..94024ee
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
@@ -0,0 +1,107 @@
+// 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;
+
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.ResourceException;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+
+public class CfLineToMethodMapper {
+
+ private final Map<String, Int2ReferenceOpenHashMap<String>> sourceMethodMapping = new HashMap<>();
+ private final AndroidApp inputApp;
+ private static final String NAME_DESCRIPTOR_SEPARATOR = ";;";
+
+ public CfLineToMethodMapper(AndroidApp inputApp) {
+ this.inputApp = inputApp;
+ }
+
+ public String lookupNameAndDescriptor(String binaryName, int lineNumber)
+ throws IOException, ResourceException {
+ if (sourceMethodMapping.isEmpty()) {
+ readLineNumbersFromClassFiles();
+ }
+ Int2ReferenceOpenHashMap<String> lineMappings = sourceMethodMapping.get(binaryName);
+ return lineMappings == null ? null : lineMappings.get(lineNumber);
+ }
+
+ private void readLineNumbersFromClassFiles() throws ResourceException, IOException {
+ ClassVisitor classVisitor = new ClassVisitor();
+ for (ProgramResourceProvider resourceProvider : inputApp.getProgramResourceProviders()) {
+ for (ProgramResource programResource : resourceProvider.getProgramResources()) {
+ if (programResource.getKind() == Kind.CF) {
+ new ClassReader(StreamUtils.StreamToByteArrayClose(programResource.getByteStream()))
+ .accept(classVisitor, ClassReader.SKIP_FRAMES);
+ }
+ }
+ }
+ }
+
+ public static String getName(String nameAndDescriptor) {
+ int index = nameAndDescriptor.indexOf(NAME_DESCRIPTOR_SEPARATOR);
+ assert index > 0;
+ return nameAndDescriptor.substring(0, index);
+ }
+
+ public static String getDescriptor(String nameAndDescriptor) {
+ int index = nameAndDescriptor.indexOf(NAME_DESCRIPTOR_SEPARATOR);
+ assert index > 0;
+ return nameAndDescriptor.substring(index + NAME_DESCRIPTOR_SEPARATOR.length());
+ }
+
+ private class ClassVisitor extends org.objectweb.asm.ClassVisitor {
+
+ private Int2ReferenceOpenHashMap<String> currentLineNumberMapping = null;
+
+ private ClassVisitor() {
+ super(InternalOptions.ASM_VERSION);
+ }
+
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ super.visit(version, access, name, signature, superName, interfaces);
+ currentLineNumberMapping =
+ sourceMethodMapping.computeIfAbsent(name, ignored -> new Int2ReferenceOpenHashMap<>());
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String descriptor, String signature, String[] exceptions) {
+ return new MethodLineVisitor(
+ name + NAME_DESCRIPTOR_SEPARATOR + descriptor, currentLineNumberMapping);
+ }
+ }
+
+ private static class MethodLineVisitor extends MethodVisitor {
+
+ private final String nameAndDescriptor;
+ private final Map<Integer, String> lineMethodMapping;
+
+ private MethodLineVisitor(String nameAndDescriptor, Map<Integer, String> lineMethodMapping) {
+ super(InternalOptions.ASM_VERSION);
+ this.nameAndDescriptor = nameAndDescriptor;
+ this.lineMethodMapping = lineMethodMapping;
+ }
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ lineMethodMapping.put(line, nameAndDescriptor);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index c3916cb..dccd176 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -16,6 +16,8 @@
import java.io.File;
import java.nio.file.Path;
import java.util.Map;
+import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeVisitor;
public class DescriptorUtils {
@@ -355,6 +357,36 @@
}
/**
+ * Get a fully qualified name from a classifier in Kotlin metadata.
+ * @param kmType where classifier contains Kotlin internal name, like "org/foo/bar/Baz.Nested"
+ * @return a class descriptor like "Lorg/foo/bar/Baz$Nested;"
+ */
+ public static 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) {
+ // TODO(b/70169921): Remove this if metadata lib is resilient to namespace relocation.
+ // See b/70169921#comment25 for more details.
+ String backwardRelocatedName = name.replace("com/android/tools/r8/jetbrains/", "");
+ descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName));
+ }
+
+ @Override
+ public void visitTypeAlias(String name) {
+ // TODO(b/70169921): Remove this if metadata lib is resilient to namespace relocation.
+ // See b/70169921#comment25 for more details.
+ String backwardRelocatedName = name.replace("com/android/tools/r8/jetbrains/", "");
+ descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName));
+ }
+ });
+ return descriptor.get();
+ }
+
+ /**
* Get unqualified class name from its binary name.
*
* @param classBinaryName a class binary name i.e. "java/lang/Object" or "a/b/C$Inner"
@@ -550,4 +582,76 @@
assert classDescriptor != null && isClassDescriptor(classDescriptor);
return getClassBinaryNameFromDescriptor(classDescriptor) + CLASS_EXTENSION;
}
+
+ public static String getReturnTypeDescriptor(final String methodDescriptor) {
+ assert methodDescriptor.indexOf(')') != -1;
+ return methodDescriptor.substring(methodDescriptor.indexOf(')') + 1);
+ }
+
+ public static String getShortyDescriptor(String descriptor) {
+ if (descriptor.length() == 1) {
+ return descriptor;
+ }
+ assert descriptor.charAt(0) == 'L' || descriptor.charAt(0) == '[';
+ return "L";
+ }
+
+ public static String[] getArgumentTypeDescriptors(final String methodDescriptor) {
+ String[] argDescriptors = new String[getArgumentCount(methodDescriptor)];
+ int charIdx = 1;
+ char c;
+ int argIdx = 0;
+ int startType;
+ while ((c = methodDescriptor.charAt(charIdx)) != ')') {
+ switch (c) {
+ case 'V':
+ throw new Unreachable();
+ case 'Z':
+ case 'C':
+ case 'B':
+ case 'S':
+ case 'I':
+ case 'F':
+ case 'J':
+ case 'D':
+ argDescriptors[argIdx++] = Character.toString(c);
+ break;
+ case '[':
+ startType = charIdx;
+ while (methodDescriptor.charAt(++charIdx) == '[') {}
+ if (methodDescriptor.charAt(charIdx) == 'L') {
+ while (methodDescriptor.charAt(++charIdx) != ';')
+ ;
+ }
+ argDescriptors[argIdx++] = methodDescriptor.substring(startType, charIdx + 1);
+ break;
+ case 'L':
+ startType = charIdx;
+ while (methodDescriptor.charAt(++charIdx) != ';')
+ ;
+ argDescriptors[argIdx++] = methodDescriptor.substring(startType, charIdx + 1);
+ break;
+ default:
+ throw new Unreachable();
+ }
+ charIdx++;
+ }
+ return argDescriptors;
+ }
+
+ public static int getArgumentCount(final String methodDescriptor) {
+ int charIdx = 1;
+ char c;
+ int argCount = 0;
+ while ((c = methodDescriptor.charAt(charIdx++)) != ')') {
+ if (c == 'L') {
+ while (methodDescriptor.charAt(charIdx++) != ';')
+ ;
+ argCount++;
+ } else if (c != '[') {
+ argCount++;
+ }
+ }
+ return argCount;
+ }
}
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 c4815ae..9b47305 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -5,7 +5,7 @@
import static com.google.common.base.Predicates.not;
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
+import com.android.tools.r8.AssertionsConfiguration;
import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.DataResourceConsumer;
@@ -216,7 +216,6 @@
public boolean enablePropagationOfConstantsAtCallSites = false;
public boolean encodeChecksums = false;
public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
- public boolean enableSourceDebugExtensionRewriter = false;
public boolean enableCfInterfaceMethodDesugaring = false;
public int callGraphLikelySpuriousCallEdgeThreshold = 50;
@@ -435,7 +434,7 @@
public boolean ignoreMissingClasses = false;
// EXPERIMENTAL flag to get behaviour as close to Proguard as possible.
public boolean forceProguardCompatibility = false;
- public AssertionTransformation assertionTransformation = null;
+ public AssertionsConfiguration assertionsConfiguration = null;
public boolean configurationDebugging = false;
// Don't convert Code objects to IRCode.
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index bdfabb0..933f4fb 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import com.android.tools.r8.ResourceException;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfPosition;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -10,6 +11,7 @@
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexDebugEvent;
import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
@@ -31,8 +33,11 @@
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.DexValue.DexValueString;
import com.android.tools.r8.graph.GraphLense;
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.naming.ClassNameMapper;
import com.android.tools.r8.naming.ClassNaming;
import com.android.tools.r8.naming.ClassNaming.Builder;
@@ -43,6 +48,7 @@
import com.android.tools.r8.naming.Range;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.google.common.base.Suppliers;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
@@ -56,15 +62,14 @@
// PositionRemapper is a stateful function which takes a position (represented by a
// DexDebugPositionState) and returns a remapped Position.
private interface PositionRemapper {
- Position createRemappedPosition(
- int line, DexString file, DexMethod method, Position callerPosition);
+ Pair<Position, Position> createRemappedPosition(Position position);
}
private static class IdentityPositionRemapper implements PositionRemapper {
+
@Override
- public Position createRemappedPosition(
- int line, DexString file, DexMethod method, Position callerPosition) {
- return new Position(line, file, method, callerPosition);
+ public Pair<Position, Position> createRemappedPosition(Position position) {
+ return new Pair<>(position, position);
}
}
@@ -82,21 +87,116 @@
}
@Override
- public Position createRemappedPosition(
- int line, DexString file, DexMethod method, Position callerPosition) {
- assert method != null;
- if (previousMethod == method) {
+ public Pair<Position, Position> createRemappedPosition(Position position) {
+ assert position.method != null;
+ if (previousMethod == position.method) {
assert previousSourceLine >= 0;
- if (line > previousSourceLine && line - previousSourceLine <= maxLineDelta) {
- nextOptimizedLineNumber += (line - previousSourceLine) - 1;
+ if (position.line > previousSourceLine
+ && position.line - previousSourceLine <= maxLineDelta) {
+ nextOptimizedLineNumber += (position.line - previousSourceLine) - 1;
}
}
- Position newPosition = new Position(nextOptimizedLineNumber, file, method, null);
+ Position newPosition =
+ new Position(nextOptimizedLineNumber, position.file, position.method, null);
++nextOptimizedLineNumber;
- previousSourceLine = line;
- previousMethod = method;
- return newPosition;
+ previousSourceLine = position.line;
+ previousMethod = position.method;
+ return new Pair<>(position, newPosition);
+ }
+ }
+
+ private static class KotlinInlineFunctionPositionRemapper implements PositionRemapper {
+
+ private final AppView<?> appView;
+ private final DexItemFactory factory;
+ private final Map<DexType, Result> parsedKotlinSourceDebugExtensions = new IdentityHashMap<>();
+ private final CfLineToMethodMapper lineToMethodMapper;
+ private final PositionRemapper baseRemapper;
+
+ // Fields for the current context.
+ private DexEncodedMethod currentMethod;
+ private Result parsedData = null;
+
+ private KotlinInlineFunctionPositionRemapper(
+ AppView<?> appView,
+ AndroidApp inputApp,
+ PositionRemapper baseRemapper,
+ CfLineToMethodMapper lineToMethodMapper) {
+ this.appView = appView;
+ this.factory = appView.dexItemFactory();
+ this.baseRemapper = baseRemapper;
+ this.lineToMethodMapper = lineToMethodMapper;
+ }
+
+ @Override
+ public Pair<Position, Position> createRemappedPosition(Position position) {
+ assert currentMethod != null;
+ int line = position.line;
+ Result parsedData = getAndParseSourceDebugExtension(position.method.holder);
+ if (parsedData == null) {
+ return baseRemapper.createRemappedPosition(position);
+ }
+ Map.Entry<Integer, KotlinSourceDebugExtensionParser.Position> currentPosition =
+ parsedData.lookup(line);
+ if (currentPosition == null) {
+ return baseRemapper.createRemappedPosition(position);
+ }
+ int delta = line - currentPosition.getKey();
+ int originalPosition = currentPosition.getValue().getRange().from + delta;
+ try {
+ String binaryName = currentPosition.getValue().getSource().getPath();
+ String nameAndDescriptor =
+ lineToMethodMapper.lookupNameAndDescriptor(binaryName, originalPosition);
+ if (nameAndDescriptor == null) {
+ return baseRemapper.createRemappedPosition(position);
+ }
+ String clazzDescriptor = DescriptorUtils.getDescriptorFromClassBinaryName(binaryName);
+ String methodName = CfLineToMethodMapper.getName(nameAndDescriptor);
+ String methodDescriptor = CfLineToMethodMapper.getDescriptor(nameAndDescriptor);
+ String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(methodDescriptor);
+ String[] argumentDescriptors = DescriptorUtils.getArgumentTypeDescriptors(methodDescriptor);
+ DexString[] argumentDexStringDescriptors = new DexString[argumentDescriptors.length];
+ for (int i = 0; i < argumentDescriptors.length; i++) {
+ argumentDexStringDescriptors[i] = factory.createString(argumentDescriptors[i]);
+ }
+ DexMethod inlinee =
+ factory.createMethod(
+ factory.createString(clazzDescriptor),
+ factory.createString(methodName),
+ factory.createString(returnTypeDescriptor),
+ argumentDexStringDescriptors);
+ if (!inlinee.equals(position.method)) {
+ return baseRemapper.createRemappedPosition(
+ new Position(originalPosition, null, inlinee, position));
+ }
+ // This is the same position, so we should really not mark this as an inline position. Fall
+ // through to the default case.
+ } catch (IOException | ResourceException ignored) {
+ // Intentionally left empty. Remapping of kotlin functions utility is a best effort mapping.
+ }
+ return baseRemapper.createRemappedPosition(position);
+ }
+
+ private Result getAndParseSourceDebugExtension(DexType holder) {
+ if (parsedData == null) {
+ parsedData = parsedKotlinSourceDebugExtensions.get(holder);
+ }
+ if (parsedData != null || parsedKotlinSourceDebugExtensions.containsKey(holder)) {
+ return parsedData;
+ }
+ DexClass clazz = appView.definitionFor(currentMethod.method.holder);
+ DexValueString dexValueString = appView.getSourceDebugExtensionForType(clazz);
+ if (dexValueString != null) {
+ parsedData = KotlinSourceDebugExtensionParser.parse(dexValueString.value.toString());
+ }
+ parsedKotlinSourceDebugExtensions.put(holder, parsedData);
+ return parsedData;
+ }
+
+ public void setMethod(DexEncodedMethod method) {
+ this.currentMethod = method;
+ this.parsedData = null;
}
}
@@ -163,7 +263,11 @@
public static ClassNameMapper run(
AppView<AppInfoWithSubtyping> appView,
DexApplication application,
+ AndroidApp inputApp,
NamingLens namingLens) {
+ // For finding methods in kotlin files based on SourceDebugExtensions, we use a line method map.
+ // We create it here to ensure it is only reading class files once.
+ CfLineToMethodMapper cfLineToMethodMapper = new CfLineToMethodMapper(inputApp);
ClassNameMapper.Builder classNameMapperBuilder = ClassNameMapper.builder();
// Collect which files contain which classes that need to have their line numbers optimized.
for (DexProgramClass clazz : application.classes()) {
@@ -209,15 +313,23 @@
? new IdentityPositionRemapper()
: new OptimizingPositionRemapper(appView.options());
+ // Kotlin inline functions and arguments have their inlining information stored in the
+ // source debug extension annotation. Instantiate the kotlin remapper on top of the original
+ // remapper to allow for remapping original positions to kotlin inline positions.
+ KotlinInlineFunctionPositionRemapper kotlinRemapper =
+ new KotlinInlineFunctionPositionRemapper(
+ appView, inputApp, positionRemapper, cfLineToMethodMapper);
+
for (DexEncodedMethod method : methods) {
+ kotlinRemapper.currentMethod = method;
List<MappedPosition> mappedPositions = new ArrayList<>();
Code code = method.getCode();
if (code != null) {
if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
optimizeDexCodePositions(
- method, application, positionRemapper, mappedPositions, identityMapping);
+ method, application, kotlinRemapper, mappedPositions, identityMapping);
} else if (code.isCfCode() && doesContainPositions(code.asCfCode())) {
- optimizeCfCodePositions(method, positionRemapper, mappedPositions, appView);
+ optimizeCfCodePositions(method, kotlinRemapper, mappedPositions, appView);
}
}
@@ -434,6 +546,8 @@
PositionEventEmitter positionEventEmitter =
new PositionEventEmitter(application.dexItemFactory, method.method, processedEvents);
+ Box<Boolean> inlinedOriginalPosition = new Box<>(false);
+
// Debug event visitor to map line numbers.
DexDebugEventVisitor visitor =
new DexDebugPositionState(debugInfo.startLine, method.method) {
@@ -455,18 +569,16 @@
super.visit(defaultEvent);
assert getCurrentLine() >= 0;
Position position =
- positionRemapper.createRemappedPosition(
+ new Position(
getCurrentLine(),
getCurrentFile(),
getCurrentMethod(),
getCurrentCallerPosition());
- mappedPositions.add(
- new MappedPosition(
- getCurrentMethod(),
- getCurrentLine(),
- getCurrentCallerPosition(),
- position.line));
- positionEventEmitter.emitPositionEvents(getCurrentPc(), position);
+ Position currentPosition = remapAndAdd(position, positionRemapper, mappedPositions);
+ if (currentPosition != position) {
+ inlinedOriginalPosition.set(true);
+ }
+ positionEventEmitter.emitPositionEvents(getCurrentPc(), currentPosition);
emittedPc = getCurrentPc();
}
@@ -522,7 +634,7 @@
// TODO(b/111253214) Remove this as soon as we have external tests testing not only the
// remapping but whether the non-positional debug events remain intact.
- if (identityMapping) {
+ if (identityMapping && !inlinedOriginalPosition.get()) {
assert optimizedDebugInfo.startLine == debugInfo.startLine;
assert optimizedDebugInfo.events.length == debugInfo.events.length;
for (int i = 0; i < debugInfo.events.length; ++i) {
@@ -546,17 +658,10 @@
CfInstruction newInstruction;
if (oldInstruction instanceof CfPosition) {
CfPosition cfPosition = (CfPosition) oldInstruction;
- Position oldPosition = cfPosition.getPosition();
- Position newPosition =
- positionRemapper.createRemappedPosition(
- oldPosition.line, oldPosition.file, oldPosition.method, oldPosition.callerPosition);
- mappedPositions.add(
- new MappedPosition(
- oldPosition.method,
- oldPosition.line,
- oldPosition.callerPosition,
- newPosition.line));
- newInstruction = new CfPosition(cfPosition.getLabel(), newPosition);
+ newInstruction =
+ new CfPosition(
+ cfPosition.getLabel(),
+ remapAndAdd(cfPosition.getPosition(), positionRemapper, mappedPositions));
} else {
newInstruction = oldInstruction;
}
@@ -572,4 +677,15 @@
oldCode.getLocalVariables()),
appView);
}
+
+ private static Position remapAndAdd(
+ Position position, PositionRemapper remapper, List<MappedPosition> mappedPositions) {
+ Pair<Position, Position> remappedPosition = remapper.createRemappedPosition(position);
+ Position oldPosition = remappedPosition.getFirst();
+ Position newPosition = remappedPosition.getSecond();
+ mappedPositions.add(
+ new MappedPosition(
+ oldPosition.method, oldPosition.line, oldPosition.callerPosition, newPosition.line));
+ return newPosition;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
index 44da91c..d602a51 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
@@ -10,12 +10,7 @@
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.desugar.BackportedMethodRewriter;
import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
-import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
-import com.android.tools.r8.ir.desugar.LambdaRewriter;
-import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
-import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import com.android.tools.r8.references.Reference;
import com.google.common.collect.ImmutableList;
import java.util.List;
@@ -90,17 +85,13 @@
private static DexProgramClass mergeClasses(
Reporter reporter, DexProgramClass a, DexProgramClass b) {
- if (LambdaRewriter.hasLambdaClassPrefix(a.type)
- || BackportedMethodRewriter.hasRewrittenMethodPrefix(a.type)
- || InterfaceMethodRewriter.hasDispatchClassSuffix(a.type)
- || NestBasedAccessDesugaring.isNestConstructor(a.type)
- || TwrCloseResourceRewriter.isUtilityClassDescriptor(a.type)) {
- assert assertEqualClasses(a, b);
- return a;
- }
if (DesugaredLibraryWrapperSynthesizer.isSynthesizedWrapper(a.type)) {
return mergeWrappers(a, b);
}
+ if (a.type.isD8R8SynthesizedClassType()) {
+ assert assertEqualClasses(a, b);
+ return a;
+ }
throw reportDuplicateTypes(reporter, a, b);
}
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index 265d333..fdf7e98 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -318,4 +318,13 @@
assert s.length() > charsFromEnd;
return s.charAt(s.length() - (charsFromEnd + 1));
}
+
+ public static int firstNonWhitespaceCharacter(String string) {
+ for (int i = 0; i < string.length(); i++) {
+ if (!isWhitespace(string.charAt(i))) {
+ return i;
+ }
+ }
+ return string.length();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index bef8a14..0e510a9 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -22,6 +22,7 @@
// Ordered list of classpath entries.
private List<Path> classpath = new ArrayList<>();
+ private List<String> vmArguments = new ArrayList<>();
private AndroidApp.Builder builder = AndroidApp.builder();
@@ -49,7 +50,8 @@
throws IOException {
assert runtime.isCf();
ProcessResult result =
- ToolHelper.runJava(runtime.asCf(), classpath, ObjectArrays.concat(mainClass, args));
+ ToolHelper.runJava(
+ runtime.asCf(), vmArguments, classpath, ObjectArrays.concat(mainClass, args));
return new JvmTestRunResult(builder.build(), runtime, result);
}
@@ -136,4 +138,13 @@
public JvmTestBuilder addTestClasspath() {
return addClasspath(ToolHelper.getClassPathForTests());
}
+
+ public JvmTestBuilder addVmArguments(Collection<String> arguments) {
+ vmArguments.addAll(arguments);
+ return self();
+ }
+
+ public JvmTestBuilder addVmArguments(String... arguments) {
+ return addVmArguments(Arrays.asList(arguments));
+ }
}
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 0f613d5..cc8d46a 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -118,6 +118,7 @@
}
@Override
+ @Deprecated
public RR run(String mainClass)
throws CompilationFailedException, ExecutionException, IOException {
return compile().run(mainClass);
diff --git a/src/test/java/com/android/tools/r8/classFiltering/TestDesugar.java b/src/test/java/com/android/tools/r8/classFiltering/TestDesugar.java
index a346177..d7cdcd0 100644
--- a/src/test/java/com/android/tools/r8/classFiltering/TestDesugar.java
+++ b/src/test/java/com/android/tools/r8/classFiltering/TestDesugar.java
@@ -9,8 +9,7 @@
lambda.consume("TestDesugar.consume");
}
-
- public interface Consumer {
+ interface Consumer {
public void consume(String s);
}
}
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 7fab7a1..cf013d1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -733,11 +733,6 @@
CF_DIR.resolve("SuperClassWithReferencedMethod.class"),
CF_DIR.resolve("SuperCallRewritingTest.class")
};
- Set<String> preservedClassNames =
- ImmutableSet.of(
- "classmerging.SubClassThatReferencesSuperMethod",
- "classmerging.SuperClassWithReferencedMethod",
- "classmerging.SuperCallRewritingTest");
// Build SubClassThatReferencesMethod.
SmaliBuilder smaliBuilder =
@@ -761,19 +756,28 @@
"move-result-object v1",
"return-object v1");
- // Build app.
- AndroidApp.Builder builder = AndroidApp.builder();
- builder.addProgramFiles(programFiles);
- builder.addDexProgramData(smaliBuilder.compile(), Origin.unknown());
+ String expectedOutput =
+ StringUtils.lines(
+ "Calling referencedMethod on SubClassThatReferencesSuperMethod",
+ "In referencedMethod on SubClassThatReferencesSuperMethod",
+ "In referencedMethod on SuperClassWithReferencedMethod",
+ "SuperClassWithReferencedMethod.referencedMethod()");
- // Run test.
- runTestOnInput(
- main,
- builder.build(),
- preservedClassNames::contains,
- // Prevent class merging, such that the generated code would be invalid if we rewrite the
- // invoke-super instruction into an invoke-direct instruction.
- getProguardConfig(EXAMPLE_KEEP, "-keep class *"));
+ testForD8()
+ .addProgramFiles(programFiles)
+ .addProgramDexFileData(smaliBuilder.compile())
+ .run(main)
+ .assertSuccessWithOutput(expectedOutput);
+
+ testForR8(Backend.DEX)
+ .addOptionsModification(this::configure)
+ .addKeepMainRule(main)
+ // Keep the classes to avoid merge, but don't keep methods which allows inlining.
+ .addKeepRules("-keep class *")
+ .addProgramFiles(programFiles)
+ .addProgramDexFileData(smaliBuilder.compile())
+ .run(main)
+ .assertSuccessWithOutput(expectedOutput);
}
// The following test checks that our rewriting of invoke-super instructions in a class F works
diff --git a/src/test/java/com/android/tools/r8/desugar/BackportedMethodMergeTest.java b/src/test/java/com/android/tools/r8/desugar/BackportedMethodMergeTest.java
index 44f32ee..f71139f 100644
--- a/src/test/java/com/android/tools/r8/desugar/BackportedMethodMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/BackportedMethodMergeTest.java
@@ -4,9 +4,14 @@
package com.android.tools.r8.desugar;
+import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.transformers.ClassTransformer;
import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
import java.nio.file.Path;
+import java.util.concurrent.ExecutionException;
import org.junit.Test;
public class BackportedMethodMergeTest extends TestBase {
@@ -40,6 +45,61 @@
.assertSuccessWithOutput(jvmOutput);
}
+ @Test
+ public void testMergeOldPrefix()
+ throws IOException, CompilationFailedException, ExecutionException {
+ byte[] transform = transformer($r8$java8methods$utility_MergeInputWithOldBackportedPrefix.class)
+ .addClassTransformer(new ClassTransformer() {
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ ClassAccessFlags accessFlags = ClassAccessFlags.fromCfAccessFlags(access);
+ accessFlags.setSynthetic();
+ super.visit(version, accessFlags.getAsCfAccessFlags(),
+ name, signature, superName, interfaces);
+ }
+ }).transform();
+
+ Path zip1 = temp.newFile("first.zip").toPath();
+ Path zip2 = temp.newFile("second.zip").toPath();
+ testForD8()
+ .setMinApi(AndroidApiLevel.L)
+ .addProgramClasses(MergeRunWithOldBackportedPrefix.class)
+ .addProgramClassFileData(transform)
+ .compile()
+ .assertNoMessages()
+ .writeToZip(zip1);
+ testForD8()
+ .setMinApi(AndroidApiLevel.L)
+ .addProgramClassFileData(transform)
+ .compile()
+ .assertNoMessages()
+ .writeToZip(zip2);
+ testForD8()
+ .addProgramFiles(zip1, zip2)
+ .setMinApi(AndroidApiLevel.L)
+ .compile()
+ .assertNoMessages()
+ .run(MergeRunWithOldBackportedPrefix.class)
+ .assertSuccessWithOutputLines("foobar");
+ }
+
+
+ static class $r8$java8methods$utility_MergeInputWithOldBackportedPrefix {
+ public void foo() {
+ System.out.println("foobar");
+ }
+
+ }
+
+ static class MergeRunWithOldBackportedPrefix {
+ public static void main(String[] args) {
+ $r8$java8methods$utility_MergeInputWithOldBackportedPrefix a =
+ new $r8$java8methods$utility_MergeInputWithOldBackportedPrefix();
+ a.foo();
+ }
+ }
+
static class MergeInputA {
public void foo() {
System.out.println(Integer.hashCode(42));
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java b/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java
new file mode 100644
index 0000000..2111b7d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java
@@ -0,0 +1,34 @@
+// 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.graph;
+
+import com.android.tools.r8.DesugarGraphConsumer;
+import com.android.tools.r8.origin.Origin;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class DesugarGraphTestConsumer implements DesugarGraphConsumer {
+
+ private Map<Origin, Set<Origin>> edges = new HashMap<>();
+
+ public boolean contains(Origin dependency, Origin dependent) {
+ return edges.getOrDefault(dependency, Collections.emptySet()).contains(dependent);
+ }
+
+ public int totalEdgeCount() {
+ int count = 0;
+ for (Set<Origin> dependents : edges.values()) {
+ count += dependents.size();
+ }
+ return count;
+ }
+
+ @Override
+ public synchronized void accept(Origin dependent, Origin dependency) {
+ edges.computeIfAbsent(dependency, s -> new HashSet<>()).add(dependent);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphUtils.java b/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphUtils.java
new file mode 100644
index 0000000..af3c6a0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphUtils.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 com.android.tools.r8.desugar.graph;
+
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import java.io.IOException;
+
+public class DesugarGraphUtils {
+
+ public static Origin addClassWithOrigin(Class<?> clazz, D8TestBuilder builder)
+ throws IOException {
+ Origin origin = makeOrigin(clazz.getTypeName());
+ builder.getBuilder().addClassProgramData(ToolHelper.getClassAsBytes(clazz), origin);
+ return origin;
+ }
+
+ private static Origin makeOrigin(String name) {
+ return new Origin(Origin.root()) {
+ @Override
+ public String part() {
+ return name;
+ }
+ };
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/InterfaceBridgeDependencyTest.java b/src/test/java/com/android/tools/r8/desugar/graph/InterfaceBridgeDependencyTest.java
new file mode 100644
index 0000000..ec8fcd4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/graph/InterfaceBridgeDependencyTest.java
@@ -0,0 +1,98 @@
+// 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.graph;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+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 InterfaceBridgeDependencyTest extends TestBase {
+
+ public interface I {
+ Object foo();
+ }
+
+ public interface J extends I {
+ // Covariant override of foo will result in a synthetic bridge method and J will depend on I.
+ default String foo() {
+ return "J::foo";
+ }
+ }
+
+ public interface K extends I {
+ // A simple override does not cause a bridge and there is no desugar dependencies for K.
+ default Object foo() {
+ return "K::foo";
+ }
+ }
+
+ public static class TestClass {
+
+ public static void main(String[] args) {
+ // There are no instances of I, J or K, so just make sure that the code requires they exist.
+ System.out.println(I.class.getName());
+ System.out.println(J.class.getName());
+ System.out.println(K.class.getName());
+ }
+ }
+
+ // Test runner follows.
+
+ private static final String EXPECTED =
+ StringUtils.lines(I.class.getName(), J.class.getName(), K.class.getName());
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public InterfaceBridgeDependencyTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClasses(I.class, J.class, K.class, TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ } else {
+ D8TestBuilder builder = testForD8();
+ DesugarGraphTestConsumer consumer = new DesugarGraphTestConsumer();
+ builder.getBuilder().setDesugarGraphConsumer(consumer);
+ Origin originI = DesugarGraphUtils.addClassWithOrigin(I.class, builder);
+ Origin originJ = DesugarGraphUtils.addClassWithOrigin(J.class, builder);
+ Origin originK = DesugarGraphUtils.addClassWithOrigin(K.class, builder);
+ builder
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ // If API level indicates desugaring is needed check the edges are reported.
+ if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
+ assertTrue(consumer.contains(originI, originJ));
+ assertFalse(consumer.contains(originI, originK));
+ assertEquals(1, consumer.totalEdgeCount());
+ } else {
+ assertEquals(0, consumer.totalEdgeCount());
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java b/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java
index 703a237..7f76cad 100644
--- a/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java
@@ -4,18 +4,14 @@
package com.android.tools.r8.desugar.graph;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.DesugarGraphConsumer;
+import com.android.tools.r8.D8TestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -26,7 +22,7 @@
// The simplest program that gives rise to a dependency edge in the graph is an interface with
// an implementing class. In this case, changes to the interface could require re-dexing the
- // implementing class despite no derived changes happing to the classfile for the implementing
+ // implementing class despite no derived changes happening to the classfile for the implementing
// class.
//
// For example, adding default method I.foo will trigger javac compilation of I and A to I.class
@@ -72,47 +68,24 @@
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines("Hello World!");
} else {
- MyDesugarGraphConsumer consumer = new MyDesugarGraphConsumer();
- Origin originI = makeOrigin("I");
- Origin originA = makeOrigin("A");
- testForD8()
- .setMinApi(parameters.getApiLevel())
+ D8TestBuilder builder = testForD8();
+ DesugarGraphTestConsumer consumer = new DesugarGraphTestConsumer();
+ builder.getBuilder().setDesugarGraphConsumer(consumer);
+ Origin originI = DesugarGraphUtils.addClassWithOrigin(I.class, builder);
+ Origin originA = DesugarGraphUtils.addClassWithOrigin(A.class, builder);
+ builder
.addProgramClasses(TestClass.class)
- .apply(
- b ->
- b.getBuilder()
- .setDesugarGraphConsumer(consumer)
- .addClassProgramData(ToolHelper.getClassAsBytes(I.class), originI)
- .addClassProgramData(ToolHelper.getClassAsBytes(A.class), originA))
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines("Hello World!");
// If API level indicates desugaring is needed check the edges are reported.
if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
- assertEquals(1, consumer.edges.size());
- assertEquals(originI, consumer.edges.keySet().iterator().next());
- assertEquals(originA, consumer.edges.values().iterator().next().iterator().next());
+ assertEquals(1, consumer.totalEdgeCount());
+ assertTrue(consumer.contains(originI, originA));
} else {
- assertEquals(0, consumer.edges.size());
+ assertEquals(0, consumer.totalEdgeCount());
}
}
}
- private Origin makeOrigin(String name) {
- return new Origin(Origin.root()) {
- @Override
- public String part() {
- return name;
- }
- };
- }
-
- public static class MyDesugarGraphConsumer implements DesugarGraphConsumer {
-
- Map<Origin, Set<Origin>> edges = new HashMap<>();
-
- @Override
- public synchronized void accept(Origin src, Origin dst) {
- edges.computeIfAbsent(src, s -> new HashSet<>()).add(dst);
- }
- }
}
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index db68c25..289a0ba 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -170,7 +170,8 @@
assertNull(appInfo.lookupDirectTarget(methodXOnTest));
assertNotNull(appInfo.lookupStaticTarget(methodXOnTestSuper));
- assertNotNull(appInfo.lookupStaticTarget(methodXOnTest));
+ // Accessing a private target on a different type will fail resolution outright.
+ assertNull(appInfo.lookupStaticTarget(methodXOnTest));
assertEquals("OK", runArt(application));
}
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 6ad7046..d7ecc12 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java
@@ -4,9 +4,7 @@
package com.android.tools.r8.kotlin;
-import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -15,9 +13,7 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Position;
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;
@@ -39,7 +35,6 @@
}
@Test
- @Ignore("b/145985445")
public void testParsingNoInlineSources() {
String annotationData =
StringUtils.join(
@@ -56,19 +51,16 @@
"*E");
Result result = KotlinSourceDebugExtensionParser.parse(annotationData);
assertNotNull(result);
- assertEquals(1, result.getFiles().size());
- Source source = result.getFiles().get(1);
- assertEquals("EnumSwitch.kt", source.getFileName());
- assertEquals("enumswitch/EnumSwitchKt", source.getPath());
- assertTrue(result.getPositions().containsKey(1));
- Position position = result.getPositions().get(1);
- assertEquals(source, position.getSource());
+ assertEquals(1, result.size());
+ assertEquals(1, (int) result.lookup(1).getKey());
+ Position position = result.lookup(1).getValue();
+ assertEquals("EnumSwitch.kt", position.getSource().getFileName());
+ assertEquals("enumswitch/EnumSwitchKt", position.getSource().getPath());
assertEquals(1, position.getRange().from);
assertEquals(38, position.getRange().to);
}
@Test
- @Ignore("b/145985445")
public void testParsingSimpleStrata() {
// Taken from src/test/examplesKotlin/retrace/mainKt
String annotationData =
@@ -100,32 +92,33 @@
"*E");
Result result = KotlinSourceDebugExtensionParser.parse(annotationData);
assertNotNull(result);
- assertEquals(3, result.getFiles().size());
+ assertEquals(3, result.size());
+ assertEquals(1, (int) result.lookup(1).getKey());
+ assertEquals(23, (int) result.lookup(23).getKey());
+ assertEquals(24, (int) result.lookup(24).getKey());
+
// Check that files are correctly parsed.
- Source source1 = result.getFiles().get(1);
- assertEquals("Main.kt", source1.getFileName());
- assertEquals("retrace/MainKt", source1.getPath());
+ Position pos1 = result.lookup(1).getValue();
+ assertEquals("Main.kt", pos1.getSource().getFileName());
+ assertEquals("retrace/MainKt", pos1.getSource().getPath());
- Source source2 = result.getFiles().get(2);
- assertEquals("InlineFunction.kt", source2.getFileName());
- assertEquals("retrace/InlineFunctionKt", source2.getPath());
+ Position pos2 = result.lookup(23).getValue();
+ assertEquals("InlineFunction.kt", pos2.getSource().getFileName());
+ assertEquals("retrace/InlineFunctionKt", pos2.getSource().getPath());
- Source source3 = result.getFiles().get(3);
- assertEquals("InlineFunction.kt", source3.getFileName());
- assertEquals("retrace/InlineFunction", source3.getPath());
+ Position pos3 = result.lookup(24).getValue();
+ assertEquals("InlineFunction.kt", pos3.getSource().getFileName());
+ assertEquals("retrace/InlineFunction", pos3.getSource().getPath());
// Check that the inline positions can be traced.
- assertTrue(result.getPositions().containsKey(23));
- Position position1 = result.getPositions().get(23);
- assertEquals(source2, position1.getSource());
- assertEquals(7, position1.getRange().from);
- assertEquals(7, position1.getRange().to);
+ assertEquals(1, pos1.getRange().from);
+ assertEquals(22, pos1.getRange().to);
- assertTrue(result.getPositions().containsKey(24));
- Position position2 = result.getPositions().get(24);
- assertEquals(source3, position2.getSource());
- assertEquals(12, position2.getRange().from);
- assertEquals(12, position2.getRange().to);
+ assertEquals(7, pos2.getRange().from);
+ assertEquals(7, pos2.getRange().to);
+
+ assertEquals(12, pos3.getRange().from);
+ assertEquals(12, pos3.getRange().to);
}
@Test
@@ -286,12 +279,43 @@
"7#2:23",
"12#4:24", // <-- non-existing file index
"*E");
+ assertNull(KotlinSourceDebugExtensionParser.parse(annotationData));
+ }
+
+ @Test
+ public void testRanges() {
+ String annotationData =
+ StringUtils.join(
+ "\n",
+ "SMAP",
+ "Main.kt",
+ "Kotlin",
+ "*S Kotlin",
+ "*F",
+ "+ 1 Main.kt",
+ "retrace/MainKt",
+ "+ 2 InlineFunction.kt",
+ "retrace/InlineFunctionKt",
+ "+ 3 InlineFunction.kt",
+ "retrace/InlineFunction",
+ "*L",
+ "1#1,22:1",
+ "7#2,1:23",
+ "12#3,2:24",
+ "*E",
+ "*S KotlinDebug",
+ "*F",
+ "+ 1 Main.kt",
+ "retrace/MainKt",
+ "*L",
+ "12#1:23",
+ "18#1:24",
+ "*E");
Result parsedResult = KotlinSourceDebugExtensionParser.parse(annotationData);
assertNotNull(parsedResult);
-
- assertEquals(2, parsedResult.getPositions().size());
- assertTrue(parsedResult.getPositions().containsKey(1));
- assertTrue(parsedResult.getPositions().containsKey(23));
- assertFalse(parsedResult.getPositions().containsKey(24));
+ assertEquals(24, (int) parsedResult.lookup(25).getKey());
+ Position value = parsedResult.lookup(25).getValue();
+ assertEquals(12, value.getRange().from);
+ assertEquals(13, value.getRange().to);
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java
index 375d4fa..837c7df 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java
@@ -9,43 +9,40 @@
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 com.android.tools.r8.AsmTestBase;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.ToolHelper;
+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 com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.naming.applymapping.shared.NoMappingDumps.HasMappingDump;
import com.android.tools.r8.naming.applymapping.shared.NoMappingDumps.NoMappingDump;
import com.android.tools.r8.naming.applymapping.shared.NoMappingDumps.NoMappingMainDump;
import com.android.tools.r8.naming.applymapping.shared.SwappingDump.ADump;
import com.android.tools.r8.naming.applymapping.shared.SwappingDump.BDump;
import com.android.tools.r8.naming.applymapping.shared.SwappingDump.MainDump;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
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 MemberResolutionAsmTest extends AsmTestBase {
- private final Backend backend;
+public class MemberResolutionAsmTest extends TestBase {
+ private final TestParameters parameters;
- @Parameterized.Parameters(name = "backend: {0}")
- public static Backend[] data() {
- return ToolHelper.getBackends();
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public MemberResolutionAsmTest(Backend backend) {
- this.backend = backend;
+ public MemberResolutionAsmTest(TestParameters parameters) {
+ this.parameters = parameters;
}
// class HasMapping { // : X
@@ -74,47 +71,47 @@
// new NoMapping();
// }
// }
- @Test
- public void test_noMapping() throws Exception {
- String main = "NoMappingMain";
- AndroidApp input =
- buildAndroidApp(HasMappingDump.dump(), NoMappingDump.dump(), NoMappingMainDump.dump());
+ private final String noMappingMain = "NoMappingMain";
+ private final String noMappingExpected = StringUtils.lines("HasMapping#foo", "NoMapping#foo");
- Path mapPath = temp.newFile("test-mapping.txt").toPath();
- List<String> pgMap =
- ImmutableList.of(
+ private List<byte[]> noMappingInputs() throws Exception {
+ return ImmutableList.of(HasMappingDump.dump(), NoMappingDump.dump(), NoMappingMainDump.dump());
+ }
+
+ @Test
+ public void testNoMappingReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClassFileData(noMappingInputs())
+ .run(parameters.getRuntime(), noMappingMain)
+ .assertSuccessWithOutput(noMappingExpected);
+ }
+
+ @Test
+ public void testNoMappingR8() throws Exception {
+ String pgMap =
+ StringUtils.joinLines(
+ "# Long comment to avoid reformatting of the lines below.",
"HasMapping -> X:",
" void foo() -> a",
"NoMapping -> Y:"
// Intentionally missing a mapping for `private` foo().
);
- FileUtils.writeTextFile(mapPath, pgMap);
- R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(input, emptyConsumer(backend));
- builder
- .addProguardConfiguration(
- ImmutableList.of(
- keepMainProguardConfiguration(main),
- // Do not turn on -allowaccessmodification
- "-applymapping " + mapPath),
- Origin.unknown())
- .addLibraryFiles(runtimeJar(backend));
- AndroidApp processedApp =
- ToolHelper.runR8(
- builder.build(),
- options -> {
- options.enableInlining = false;
- options.enableVerticalClassMerging = false;
- });
+ CodeInspector codeInspector =
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(noMappingInputs())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(noMappingMain)
+ .addApplyMapping(pgMap)
+ .addOptionsModification(
+ options -> {
+ options.enableInlining = false;
+ options.enableVerticalClassMerging = false;
+ })
+ .run(parameters.getRuntime(), noMappingMain)
+ .assertSuccessWithOutput(noMappingExpected)
+ .inspector();
- List<byte[]> classBytes =
- ImmutableList.of(HasMappingDump.dump(), NoMappingDump.dump(), NoMappingMainDump.dump());
- ProcessResult outputBefore = runOnJavaRaw(main, classBytes, ImmutableList.of());
- assertEquals(0, outputBefore.exitCode);
- String outputAfter = runOnVM(processedApp, main, backend);
- assertEquals(outputBefore.stdout.trim(), outputAfter.trim());
-
- CodeInspector codeInspector = new CodeInspector(processedApp, mapPath);
ClassSubject base = codeInspector.clazz("HasMapping");
assertThat(base, isPresent());
assertThat(base, isRenamed());
@@ -156,63 +153,64 @@
// new B().x(); // IllegalAccessError
// }
// }
- @Test
- public void test_swapping() throws Exception {
- String main = "Main";
- AndroidApp input = buildAndroidApp(ADump.dump(), BDump.dump(), MainDump.dump());
+ private final String swappingMain = "Main";
- Path mapPath = temp.newFile("test-mapping.txt").toPath();
- List<String> pgMap =
- ImmutableList.of(
+ private List<byte[]> swappingInputs() throws Exception {
+ return ImmutableList.of(ADump.dump(), BDump.dump(), MainDump.dump());
+ }
+
+ private String getMethodSignature(String type, String method) {
+ if (parameters.isCfRuntime()) {
+ return type + "." + method + "()V";
+ }
+ assert parameters.isDexRuntime();
+ Version version = parameters.getRuntime().asDex().getVm().getVersion();
+ if (version.isOlderThanOrEqual(Version.V4_4_4)) {
+ return "L" + type + ";." + method + " ()V";
+ }
+ return "void " + type + "." + method + "()";
+ }
+
+ @Test
+ public void testSwappingReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClassFileData(swappingInputs())
+ .run(parameters.getRuntime(), swappingMain)
+ .assertFailureWithErrorThatThrows(IllegalAccessError.class)
+ .assertFailureWithErrorThatMatches(containsString(getMethodSignature("A", "x")));
+ }
+
+ @Test
+ public void testSwappingR8() throws Exception {
+ String pgMap =
+ StringUtils.joinLines(
+ "# Long comment to avoid reformatting of the lines below.",
"A -> X:",
" void x() -> y",
" void y() -> x",
"B -> Y:"
// Intentionally missing mappings for non-overridden members
);
- FileUtils.writeTextFile(mapPath, pgMap);
- R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(input, emptyConsumer(backend));
- builder
- .addProguardConfiguration(
- ImmutableList.of(
- keepMainProguardConfiguration(main),
- // Do not turn on -allowaccessmodification
- "-applymapping " + mapPath),
- Origin.unknown())
- .addLibraryFiles(runtimeJar(backend));
- AndroidApp processedApp =
- ToolHelper.runR8(
- builder.build(),
- options -> {
- options.enableInlining = false;
- options.enableVerticalClassMerging = false;
- });
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClassFileData(swappingInputs())
+ .addKeepMainRule(swappingMain)
+ .addApplyMapping(pgMap)
+ .addOptionsModification(
+ options -> {
+ options.enableInlining = false;
+ options.enableVerticalClassMerging = false;
+ })
+ .compile();
- List<byte[]> classBytes = ImmutableList.of(ADump.dump(), BDump.dump(), MainDump.dump());
- ProcessResult outputBefore = runOnJavaRaw(main, classBytes, ImmutableList.of());
- assertNotEquals(0, outputBefore.exitCode);
- String expectedErrorMessage = "IllegalAccessError";
- String expectedErrorSignature = "A.x()V";
- assertThat(outputBefore.stderr, containsString(expectedErrorMessage));
- assertThat(outputBefore.stderr, containsString(expectedErrorSignature));
- ProcessResult outputAfter = runOnVMRaw(processedApp, main, backend);
- assertNotEquals(0, outputAfter.exitCode);
- expectedErrorSignature = "X.y()V";
- if (backend == Backend.DEX) {
- expectedErrorSignature = "void X.y()";
- if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V6_0_1)) {
- expectedErrorMessage = "IncompatibleClassChangeError";
- }
- if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)) {
- expectedErrorMessage = "illegal method access";
- expectedErrorSignature = "LX;.y ()V";
- }
- }
- assertThat(outputAfter.stderr, containsString(expectedErrorMessage));
- assertThat(outputAfter.stderr, containsString(expectedErrorSignature));
+ compileResult
+ .run(parameters.getRuntime(), swappingMain)
+ .assertFailureWithErrorThatThrows(IllegalAccessError.class)
+ .assertFailureWithErrorThatMatches(containsString(getMethodSignature("X", "y")));
- CodeInspector codeInspector = new CodeInspector(processedApp, mapPath);
+ CodeInspector codeInspector = compileResult.inspector();
ClassSubject base = codeInspector.clazz("A");
assertThat(base, isPresent());
assertThat(base, isRenamed());
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
index 5fe17d5..eb4cd97 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
@@ -4,23 +4,21 @@
package com.android.tools.r8.resolution;
import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
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.ToolHelper.DexVm;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.IllegalAccessOrNoSuchMethodResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutionException;
-import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -81,22 +79,9 @@
}
@Test
- public void lookupSingleTarget() {
+ public void resolveTarget() {
ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
- assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
- DexEncodedMethod resolved = resolutionResult.getSingleTarget();
- assertEquals(methodOnA, resolved.method);
- DexEncodedMethod singleVirtualTarget =
- appInfo.lookupSingleVirtualTarget(methodOnB, methodOnB.holder);
- Assert.assertNull(singleVirtualTarget);
- }
-
- @Test
- public void lookupVirtualTargets() {
- ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
- DexEncodedMethod resolved = resolutionResult.getSingleTarget();
- assertEquals(methodOnA, resolved.method);
- assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
+ assertTrue(resolutionResult instanceof IllegalAccessOrNoSuchMethodResult);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
index 22445e6..85ace0b 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
@@ -4,34 +4,29 @@
package com.android.tools.r8.resolution;
import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.AsmTestBase;
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.TestRunResult;
import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
import java.util.List;
-import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@RunWith(Parameterized.class)
-public class VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest extends AsmTestBase {
+public class VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest extends TestBase {
public interface I {
default void f() {}
@@ -64,51 +59,26 @@
public static class BaseDump implements Opcodes {
- static String prefix(String suffix) {
- return VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.class
- .getTypeName()
- .replace('.', '/')
- + suffix;
- }
-
- public static byte[] dump() {
- ClassWriter cw = new ClassWriter(0);
- MethodVisitor mv;
- cw.visit(V1_8, ACC_PUBLIC | ACC_SUPER, prefix("$Base"), null, "java/lang/Object", null);
- cw.visitSource("VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java", null);
- {
- mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
- mv.visitCode();
- mv.visitVarInsn(ALOAD, 0);
- mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
- mv.visitInsn(RETURN);
- mv.visitMaxs(1, 1);
- mv.visitEnd();
- }
- {
- // Changed ACC_PRIVATE to ACC_PUBLIC
- mv = cw.visitMethod(ACC_PUBLIC, "f", "()V", null, null);
- mv.visitCode();
- mv.visitInsn(RETURN);
- mv.visitMaxs(0, 1);
- mv.visitEnd();
- }
- cw.visitEnd();
- return cw.toByteArray();
+ public static byte[] dump() throws Exception {
+ return transformer(Base.class).setPublic(Base.class.getDeclaredMethod("f")).transform();
}
}
public static List<Class<?>> CLASSES =
ImmutableList.of(A.class, B.class, C.class, I.class, Main.class);
- public static List<byte[]> DUMPS = ImmutableList.of(BaseDump.dump());
+ public static List<byte[]> getDumps() throws Exception {
+ return ImmutableList.of(BaseDump.dump());
+ }
private static AppInfoWithLiveness appInfo;
@BeforeClass
public static void computeAppInfo() throws Exception {
appInfo =
- computeAppViewWithLiveness(readClassesAndAsmDump(CLASSES, DUMPS), Main.class).appInfo();
+ computeAppViewWithLiveness(
+ buildClasses(CLASSES).addClassProgramData(getDumps()).build(), Main.class)
+ .appInfo();
}
private static DexMethod buildMethod(Class clazz, String name) {
@@ -130,21 +100,9 @@
}
@Test
- public void lookupSingleTarget() {
- DexEncodedMethod resolved =
- appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB).getSingleTarget();
- assertEquals(methodOnA, resolved.method);
- DexEncodedMethod singleVirtualTarget =
- appInfo.lookupSingleVirtualTarget(methodOnB, methodOnB.holder);
- Assert.assertNull(singleVirtualTarget);
- }
-
- @Test
- public void lookupVirtualTargets() {
+ public void testResolution() {
ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
- DexEncodedMethod resolved = resolutionResult.getSingleTarget();
- assertEquals(methodOnA, resolved.method);
- assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
+ assertTrue(resolutionResult.isFailedResolution());
}
@Test
@@ -154,13 +112,13 @@
runResult =
testForJvm()
.addProgramClasses(CLASSES)
- .addProgramClassFileData(DUMPS)
+ .addProgramClassFileData(getDumps())
.run(parameters.getRuntime(), Main.class);
} else {
runResult =
testForD8()
.addProgramClasses(CLASSES)
- .addProgramClassFileData(DUMPS)
+ .addProgramClassFileData(getDumps())
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class);
}
@@ -172,7 +130,7 @@
R8TestRunResult runResult =
testForR8(parameters.getBackend())
.addProgramClasses(CLASSES)
- .addProgramClassFileData(DUMPS)
+ .addProgramClassFileData(getDumps())
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class);
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
index 52dbd4b..8e21be0 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
@@ -18,6 +18,7 @@
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.graph.ResolutionResult.IllegalAccessOrNoSuchMethodResult;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.transformers.ClassFileTransformer;
@@ -121,6 +122,12 @@
assertEquals(method.holder, declaredClassDefinition.type);
ResolutionResult resolutionResult = appInfo.resolveMethod(declaredClassDefinition, method);
+ // Resolution fails when there is a mismatch between the symbolic reference and the definition.
+ if (!symbolicReferenceIsDefiningType) {
+ assertTrue(resolutionResult instanceof IllegalAccessOrNoSuchMethodResult);
+ return;
+ }
+
// Verify that the resolved method is on the defining class.
assertEquals(
definingClassDefinition, resolutionResult.asSingleResolution().getResolvedHolder());
@@ -178,7 +185,7 @@
.addProgramClasses(getClasses())
.addProgramClassFileData(getTransformedClasses())
.run(parameters.getRuntime(), Main.class)
- .apply(result -> checkExpectedResult(result, false));
+ .apply(this::checkExpectedResult);
}
@Test
@@ -189,10 +196,10 @@
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(Main.class)
.run(parameters.getRuntime(), Main.class)
- .apply(result -> checkExpectedResult(result, true));
+ .apply(this::checkExpectedResult);
}
- private void checkExpectedResult(TestRunResult<?> result, boolean isR8) {
+ private void checkExpectedResult(TestRunResult<?> result) {
// If not in the same nest, the error is always illegal access.
if (!inSameNest) {
result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
@@ -201,11 +208,6 @@
// If in the same nest but the reference is not exact, the error is always no such method.
if (!symbolicReferenceIsDefiningType) {
- // TODO(b/145775365): R8 incorrectly compiles the input to a working program.
- if (isR8) {
- result.assertSuccessWithOutput(EXPECTED);
- return;
- }
// TODO(b/145775365): D8/R8 does not preserve the thrown error.
if (parameters.isDexRuntime()) {
result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
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 bd6dbed..e3ca436 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
@@ -92,20 +92,11 @@
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(Main.class)
.run(parameters.getRuntime(), Main.class)
- .apply(
- result -> {
- if (inSameNest) {
- // TODO(b/145187969): R8 incorrectly compiles out the incorrect access.
- result.assertSuccessWithOutput(EXPECTED);
- } else {
- checkExpectedResult(result);
- }
- });
+ .apply(this::checkExpectedResult);
}
private void checkExpectedResult(TestRunResult<?> result) {
if (inSameNest && parameters.isCfRuntime()) {
- // TODO(b/145187969): Investigate if the change to NoSuchMethodError is according to spec?
result.assertFailureWithErrorThatMatches(containsString(NoSuchMethodError.class.getName()));
} else {
result.assertFailureWithErrorThatMatches(containsString(IllegalAccessError.class.getName()));
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 3e85f1a..cdf2b22 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
@@ -91,23 +91,11 @@
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(Main.class)
.run(parameters.getRuntime(), Main.class)
- .apply(
- result -> {
- if (inSameNest && parameters.isCfRuntime()) {
- // TODO(b/145187969): R8/CF compiles to a "working" program.
- result.assertSuccessWithOutput(EXPECTED);
- } else if (inSameNest && parameters.isDexRuntime()) {
- // TODO(b/145187969): R8/DEX compiles to a throw null program.
- result.assertFailureWithErrorThatThrows(NullPointerException.class);
- } else {
- checkExpectedResult(result);
- }
- });
+ .apply(this::checkExpectedResult);
}
private void checkExpectedResult(TestRunResult<?> result) {
if (inSameNest && parameters.isCfRuntime()) {
- // TODO(b/145187969): Investigate if the change to NoSuchMethodError is according to spec?
result.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
} else {
result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
new file mode 100644
index 0000000..5d2a2d8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
@@ -0,0 +1,126 @@
+// 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 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.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.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;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KotlinInlineFunctionInSameFileRetraceTests extends TestBase {
+
+ private static final String MAIN = "retrace.InlineFunctionsInSameFileKt";
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public KotlinInlineFunctionInSameFileRetraceTests(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private static Function<TestRuntime, Path> compilationResults =
+ memoizeFunction(KotlinInlineFunctionInSameFileRetraceTests::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(compilationResults.apply(parameters.getRuntime()))
+ .addRunClasspathFiles(buildOnDexRuntime(parameters, ToolHelper.getKotlinStdlibJar()))
+ .run(parameters.getRuntime(), MAIN)
+ .assertFailureWithErrorThatMatches(containsString("foo"))
+ .assertFailureWithErrorThatMatches(
+ containsString(
+ "at retrace.InlineFunctionsInSameFileKt.main(InlineFunctionsInSameFile.kt:43"));
+ }
+
+ @Test
+ public void testRetraceKotlinInlineStaticFunction()
+ throws ExecutionException, CompilationFailedException, IOException {
+ Path kotlinSources = compilationResults.apply(parameters.getRuntime());
+ CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
+ testForR8(parameters.getBackend())
+ .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addKeepAttributes("SourceFile", "LineNumberTable")
+ .setMode(CompilationMode.RELEASE)
+ .addKeepMainRule(MAIN)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN)
+ .assertFailureWithErrorThatMatches(containsString("main"))
+ .inspectStackTrace(
+ (stackTrace, codeInspector) -> {
+ MethodSubject mainSubject = codeInspector.clazz(MAIN).uniqueMethodWithName("main");
+ InlinePosition inlineStack =
+ InlinePosition.stack(
+ InlinePosition.create(
+ kotlinInspector
+ .clazz("retrace.InlineFunctionsInSameFileKt")
+ .uniqueMethodWithName("foo")
+ .asFoundMethodSubject(),
+ 1,
+ 8),
+ InlinePosition.create(mainSubject.asFoundMethodSubject(), 1, 43));
+ 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/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index d81c771..e2fe820 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -83,8 +83,6 @@
.addKeepAttributes("SourceFile", "LineNumberTable")
.setMode(CompilationMode.RELEASE)
.addKeepMainRule(main)
- .addOptionsModification(
- internalOptions -> internalOptions.enableSourceDebugExtensionRewriter = true)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), main)
.assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
@@ -110,8 +108,6 @@
.addKeepAttributes("SourceFile", "LineNumberTable")
.setMode(CompilationMode.RELEASE)
.addKeepMainRule(main)
- .addOptionsModification(
- internalOptions -> internalOptions.enableSourceDebugExtensionRewriter = true)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), main)
.assertFailureWithErrorThatMatches(containsString("inlineExceptionInstance"))
@@ -137,8 +133,6 @@
.addKeepAttributes("SourceFile", "LineNumberTable")
.setMode(CompilationMode.RELEASE)
.addKeepMainRule(main)
- .addOptionsModification(
- internalOptions -> internalOptions.enableSourceDebugExtensionRewriter = true)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), main)
.assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
@@ -149,8 +143,8 @@
InlinePosition.stack(
InlinePosition.create(
"retrace.InlineFunctionKt", "inlineExceptionStatic", 3, 8),
- InlinePosition.create(
- "retrace.NestedInlineFunctionKt", "nestedInline", 3, 10),
+ // TODO(b/146399675): There should be a nested frame on
+ // retrace.NestedInlineFunctionKt.nestedInline(line 10).
InlinePosition.create(mainSubject.asFoundMethodSubject(), 3, 19));
checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
});
@@ -166,8 +160,6 @@
.addKeepAttributes("SourceFile", "LineNumberTable")
.setMode(CompilationMode.RELEASE)
.addKeepMainRule(main)
- .addOptionsModification(
- internalOptions -> internalOptions.enableSourceDebugExtensionRewriter = true)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), main)
.assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
@@ -178,8 +170,8 @@
InlinePosition.stack(
InlinePosition.create(
"retrace.InlineFunctionKt", "inlineExceptionStatic", 2, 8),
- InlinePosition.create(
- "retrace.NestedInlineFunctionKt", "nestedInlineOnFirstLine", 2, 15),
+ // TODO(b/146399675): There should be a nested frame on
+ // retrace.NestedInlineFunctionKt.nestedInlineOnFirstLine(line 15).
InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 20));
checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
});
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 77d0846..59a67ad 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -12,7 +12,6 @@
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.Retrace.RetraceAbortException;
import com.android.tools.r8.retrace.stacktraces.ActualBotStackTraceBase;
import com.android.tools.r8.retrace.stacktraces.ActualIdentityStackTrace;
@@ -29,7 +28,9 @@
import com.android.tools.r8.retrace.stacktraces.RetraceAssertionErrorStackTrace;
import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
import com.android.tools.r8.retrace.stacktraces.SuppressedStackTrace;
+import com.android.tools.r8.utils.BooleanUtils;
import com.google.common.collect.ImmutableList;
+import java.util.Collection;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,12 +40,21 @@
@RunWith(Parameterized.class)
public class RetraceTests extends TestBase {
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withNoneRuntime().build();
+ // This is a slight modification of the default regular expression shown for proguard retrace
+ // that allow for retracing classes in the form <class>: lorem ipsum...
+ // Seems like Proguard retrace is expecting the form "Caused by: <class>".
+ private static final String DEFAULT_REGULAR_EXPRESSION =
+ "(?:.*?\\bat\\s+%c\\.%m\\s*\\(%s(?::%l)?\\)\\s*(?:~\\[.*\\])?)|(?:(?:(?:%c|.*)?[:\"]\\s+)?%c(?::.*)?)";
+
+ @Parameters(name = "{0}, use regular expression: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(getTestParameters().withNoneRuntime().build(), BooleanUtils.values());
}
- public RetraceTests(TestParameters parameters) {
+ private final boolean useRegExpParsing;
+
+ public RetraceTests(TestParameters parameters, boolean useRegExpParsing) {
+ this.useRegExpParsing = useRegExpParsing;
}
@Test
@@ -93,11 +103,15 @@
public void testInvalidStackTraceLineWarnings() {
InvalidStackTrace invalidStackTraceTest = new InvalidStackTrace();
TestDiagnosticMessagesImpl diagnosticsHandler = runRetraceTest(invalidStackTraceTest);
- diagnosticsHandler.assertOnlyWarnings();
- diagnosticsHandler.assertWarningsCount(invalidStackTraceTest.expectedWarnings());
- assertThat(
- diagnosticsHandler.getWarnings().get(0).getDiagnosticMessage(),
- containsString(". . . 7 more"));
+ if (!useRegExpParsing) {
+ diagnosticsHandler.assertOnlyWarnings();
+ diagnosticsHandler.assertWarningsCount(invalidStackTraceTest.expectedWarnings());
+ assertThat(
+ diagnosticsHandler.getWarnings().get(0).getDiagnosticMessage(),
+ containsString(". . . 7 more"));
+ } else {
+ diagnosticsHandler.assertNoMessages();
+ }
}
@Test
@@ -110,7 +124,8 @@
List<ActualBotStackTraceBase> stackTraces =
ImmutableList.of(new ActualIdentityStackTrace(), new ActualRetraceBotStackTrace());
for (ActualBotStackTraceBase stackTrace : stackTraces) {
- runRetraceTest(stackTrace).assertWarningsCount(stackTrace.expectedWarnings());
+ runRetraceTest(stackTrace)
+ .assertWarningsCount(useRegExpParsing ? 0 : stackTrace.expectedWarnings());
}
}
@@ -140,6 +155,7 @@
RetraceCommand.builder(diagnosticsHandler)
.setProguardMapProducer(stackTraceForTest::mapping)
.setStackTrace(stackTraceForTest.obfuscatedStackTrace())
+ .setRegularExpression(useRegExpParsing ? DEFAULT_REGULAR_EXPRESSION : null)
.setRetracedStackTraceConsumer(
retraced -> assertEquals(stackTraceForTest.retracedStackTrace(), retraced))
.build();
diff --git a/src/test/java/com/android/tools/r8/retrace/kt/InlineFunctionsInSameFile.kt b/src/test/java/com/android/tools/r8/retrace/kt/InlineFunctionsInSameFile.kt
new file mode 100644
index 0000000..2047a48
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/kt/InlineFunctionsInSameFile.kt
@@ -0,0 +1,22 @@
+// 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
+
+inline fun foo() {
+ bar {
+ throw Exception("foo")
+ }
+}
+
+inline fun bar(f: () -> Unit) {
+ baz { f() }
+}
+
+inline fun baz(f: () -> Unit) {
+ f()
+}
+
+fun main(args: Array<String>) {
+ foo()
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java
index 62aa5ac..b44b364 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java
@@ -27,13 +27,13 @@
return Arrays.asList(
"com.android.tools.r8.CompilationException: foo[parens](Source:3)",
" at com.android.tools.r8.R8.bar(R8.java:7)",
- " or foo(R8.java:7)",
+ " <OR> at com.android.tools.r8.R8.foo(R8.java:7)",
" at com.android.tools.r8.R8.bar(R8.java:8)",
- " or foo(R8.java:8)",
+ " <OR> at com.android.tools.r8.R8.foo(R8.java:8)",
" at com.android.tools.r8.R8.main(R8.java)",
"Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
" at com.android.tools.r8.R8.bar(R8.java:9)",
- " or foo(R8.java:9)",
+ " <OR> at com.android.tools.r8.R8.foo(R8.java:9)",
" ... 42 more");
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java
index 76b15e0..a3a8e58 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java
@@ -54,13 +54,13 @@
return Arrays.asList(
"com.android.tools.r8.CompilationException: foo[parens](Source:3)",
" at com.android.tools.r8.R8.bar(R8.java)",
- " or foo(R8.java)",
+ " <OR> at com.android.tools.r8.R8.foo(R8.java)",
" at com.android.tools.r8.R8.bar(R8.java)",
- " or foo(R8.java)",
+ " <OR> at com.android.tools.r8.R8.foo(R8.java)",
" at com.android.tools.r8.R8.main(R8.java)",
"Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
" at com.android.tools.r8.R8.bar(R8.java)",
- " or foo(R8.java)",
+ " <OR> at com.android.tools.r8.R8.foo(R8.java)",
" ... 42 more");
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
index af84f69..ce9f1c5 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.rewrite.assertions.testclasses.TestClassForInnerClass;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -25,12 +26,35 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
@RunWith(Parameterized.class)
-public class AssertionsConfigurationTest extends TestBase {
+public class AssertionsConfigurationTest extends TestBase implements Opcodes {
private final TestParameters parameters;
+ private Class<?> class1 = com.android.tools.r8.rewrite.assertions.testclasses.Class1.class;
+ private Class<?> class2 = com.android.tools.r8.rewrite.assertions.testclasses.Class2.class;
+ private Class<?> subpackageClass1 =
+ com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class;
+ private Class<?> subpackageClass2 =
+ com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class;
+
+ private List<Class<?>> testClasses =
+ ImmutableList.of(TestClass.class, class1, class2, subpackageClass1, subpackageClass2);
+
+ private String packageName =
+ com.android.tools.r8.rewrite.assertions.testclasses.Class1.class.getPackage().getName();
+ private String subPackageName =
+ com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class
+ .getPackage()
+ .getName();
+
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimes().withAllApiLevels().build();
@@ -47,12 +71,7 @@
List<String> outputLines)
throws Exception {
testForD8()
- .addProgramClasses(
- TestClass.class,
- com.android.tools.r8.rewrite.assertions.testclasses.Class1.class,
- com.android.tools.r8.rewrite.assertions.testclasses.Class2.class,
- com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class,
- com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class)
+ .addProgramClasses(testClasses)
.setMinApi(parameters.getApiLevel())
.addAssertionsConfiguration(assertionsConfigurationBuilder)
.compile()
@@ -79,18 +98,9 @@
throws Exception {
testForR8(parameters.getBackend())
- .addProgramClasses(
- TestClass.class,
- com.android.tools.r8.rewrite.assertions.testclasses.Class1.class,
- com.android.tools.r8.rewrite.assertions.testclasses.Class2.class,
- com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class,
- com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class)
+ .addProgramClasses(testClasses)
.addKeepMainRule(TestClass.class)
- .addKeepClassAndMembersRules(
- com.android.tools.r8.rewrite.assertions.testclasses.Class1.class,
- com.android.tools.r8.rewrite.assertions.testclasses.Class2.class,
- com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class,
- com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class)
+ .addKeepClassAndMembersRules(class1, class2, subpackageClass1, subpackageClass2)
.setMinApi(parameters.getApiLevel())
.addAssertionsConfiguration(assertionsConfigurationBuilder)
.compile()
@@ -125,8 +135,7 @@
return ImmutableList.of("DONE");
}
- private void checkAssertionCodeRemoved(CodeInspector inspector, Class<?> clazz) {
- ClassSubject subject = inspector.clazz(clazz);
+ private void checkAssertionCodeRemoved(ClassSubject subject) {
assertThat(subject, isPresent());
// <clinit> is removed by R8 as it becomes empty.
if (subject.uniqueMethodWithName("<clinit>").isPresent()) {
@@ -143,8 +152,11 @@
.anyMatch(InstructionSubject::isThrow));
}
- private void checkAssertionCodeEnabled(CodeInspector inspector, Class<?> clazz) {
- ClassSubject subject = inspector.clazz(clazz);
+ private void checkAssertionCodeRemoved(CodeInspector inspector, Class<?> clazz) {
+ checkAssertionCodeRemoved(inspector.clazz(clazz));
+ }
+
+ private void checkAssertionCodeEnabled(ClassSubject subject) {
assertThat(subject, isPresent());
// <clinit> is removed by R8.
if (subject.uniqueMethodWithName("<clinit>").isPresent()) {
@@ -161,6 +173,10 @@
.anyMatch(InstructionSubject::isThrow));
}
+ private void checkAssertionCodeEnabled(CodeInspector inspector, Class<?> clazz) {
+ checkAssertionCodeEnabled(inspector.clazz(clazz));
+ }
+
private void checkAssertionCodeLeft(CodeInspector inspector, Class<?> clazz) {
ClassSubject subject = inspector.clazz(clazz);
assertThat(subject, isPresent());
@@ -177,40 +193,28 @@
}
private void checkAssertionCodeRemoved(CodeInspector inspector) {
- checkAssertionCodeRemoved(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class1.class);
- checkAssertionCodeRemoved(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class2.class);
- checkAssertionCodeRemoved(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class);
- checkAssertionCodeRemoved(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class);
+ checkAssertionCodeRemoved(inspector, class1);
+ checkAssertionCodeRemoved(inspector, class2);
+ checkAssertionCodeRemoved(inspector, subpackageClass1);
+ checkAssertionCodeRemoved(inspector, subpackageClass2);
}
private void checkAssertionCodeEnabled(CodeInspector inspector) {
- checkAssertionCodeEnabled(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class1.class);
- checkAssertionCodeEnabled(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class2.class);
- checkAssertionCodeEnabled(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class);
- checkAssertionCodeEnabled(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class);
+ checkAssertionCodeEnabled(inspector, class1);
+ checkAssertionCodeEnabled(inspector, class2);
+ checkAssertionCodeEnabled(inspector, subpackageClass1);
+ checkAssertionCodeEnabled(inspector, subpackageClass2);
}
private void checkAssertionCodeLeft(CodeInspector inspector) {
- checkAssertionCodeLeft(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class1.class);
- checkAssertionCodeLeft(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class2.class);
- checkAssertionCodeLeft(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class);
- checkAssertionCodeLeft(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class);
+ checkAssertionCodeLeft(inspector, class1);
+ checkAssertionCodeLeft(inspector, class2);
+ checkAssertionCodeLeft(inspector, subpackageClass1);
+ checkAssertionCodeLeft(inspector, subpackageClass2);
}
@Test
- public void testEnableAllAssertionsForDex() throws Exception {
+ public void testAssertionsForDex() throws Exception {
Assume.assumeTrue(parameters.isDexRuntime());
// Leaving assertions in or disabling them on Dalvik/Art means no assertions.
runD8Test(
@@ -230,6 +234,15 @@
this::enableAllAssertions, this::checkAssertionCodeEnabled, allAssertionsExpectedLines());
runR8Test(
this::enableAllAssertions, this::checkAssertionCodeEnabled, allAssertionsExpectedLines());
+ // Enabling for the package should enable all.
+ runD8Test(
+ builder -> builder.enableForPackage(packageName).build(),
+ this::checkAssertionCodeEnabled,
+ allAssertionsExpectedLines());
+ runR8Test(
+ builder -> builder.enableForPackage(packageName).build(),
+ this::checkAssertionCodeEnabled,
+ allAssertionsExpectedLines());
}
@Test
@@ -259,6 +272,415 @@
true);
}
+ @Test
+ public void testEnableForPackageForDex() throws Exception {
+ Assume.assumeTrue(parameters.isDexRuntime());
+ runD8Test(
+ builder -> builder.enableForPackage(subPackageName).build(),
+ inspector -> {
+ checkAssertionCodeEnabled(inspector, subpackageClass1);
+ checkAssertionCodeEnabled(inspector, subpackageClass2);
+ },
+ ImmutableList.of(
+ "AssertionError in testclasses.subpackage.Class1",
+ "AssertionError in testclasses.subpackage.Class2",
+ "DONE"));
+ runR8Test(
+ builder -> builder.enableForPackage(subPackageName).build(),
+ inspector -> {
+ checkAssertionCodeEnabled(inspector, subpackageClass1);
+ checkAssertionCodeEnabled(inspector, subpackageClass2);
+ },
+ ImmutableList.of(
+ "AssertionError in testclasses.subpackage.Class1",
+ "AssertionError in testclasses.subpackage.Class2",
+ "DONE"));
+ }
+
+ @Test
+ public void testEnableForClassForDex() throws Exception {
+ Assume.assumeTrue(parameters.isDexRuntime());
+ runD8Test(
+ builder ->
+ builder
+ .enableForClass(class1.getCanonicalName())
+ .enableForClass(subpackageClass2.getCanonicalName())
+ .build(),
+ inspector -> {
+ // checkAssertionCodeEnabled(inspector, class1);
+ // checkAssertionCodeEnabled(inspector, subpackageClass2);
+ },
+ ImmutableList.of(
+ "AssertionError in testclasses.Class1",
+ "AssertionError in testclasses.subpackage.Class2",
+ "DONE"));
+ runR8Test(
+ builder ->
+ builder
+ .enableForClass(class1.getCanonicalName())
+ .enableForClass(subpackageClass2.getCanonicalName())
+ .build(),
+ inspector -> {
+ checkAssertionCodeEnabled(inspector, class1);
+ checkAssertionCodeEnabled(inspector, subpackageClass2);
+ },
+ ImmutableList.of(
+ "AssertionError in testclasses.Class1",
+ "AssertionError in testclasses.subpackage.Class2",
+ "DONE"));
+ }
+
+ @Test
+ public void testMixedForDex() throws Exception {
+ Assume.assumeTrue(parameters.isDexRuntime());
+ runD8Test(
+ builder ->
+ builder
+ .enableForPackage(packageName)
+ .disableForClass(class2.getCanonicalName())
+ .disableForClass(subpackageClass1.getCanonicalName())
+ .build(),
+ inspector -> {
+ checkAssertionCodeEnabled(inspector, class1);
+ checkAssertionCodeRemoved(inspector, class2);
+ checkAssertionCodeRemoved(inspector, subpackageClass1);
+ checkAssertionCodeEnabled(inspector, subpackageClass2);
+ },
+ ImmutableList.of(
+ "AssertionError in testclasses.Class1",
+ "AssertionError in testclasses.subpackage.Class2",
+ "DONE"));
+ runR8Test(
+ builder ->
+ builder
+ .enableForPackage(packageName)
+ .disableForClass(class2.getCanonicalName())
+ .disableForClass(subpackageClass1.getCanonicalName())
+ .build(),
+ inspector -> {
+ checkAssertionCodeEnabled(inspector, class1);
+ checkAssertionCodeRemoved(inspector, class2);
+ checkAssertionCodeRemoved(inspector, subpackageClass1);
+ checkAssertionCodeEnabled(inspector, subpackageClass2);
+ },
+ ImmutableList.of(
+ "AssertionError in testclasses.Class1",
+ "AssertionError in testclasses.subpackage.Class2",
+ "DONE"));
+ }
+
+ @Test
+ public void testUnnamedPackageForDex() throws Exception {
+ Assume.assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addProgramClasses(class1, class2)
+ .addProgramClassFileData(
+ testClassForUnknownPackage(),
+ classInUnnamedPackage("Class1"),
+ classInUnnamedPackage("Class2"))
+ .setMinApi(parameters.getApiLevel())
+ .addAssertionsConfiguration(builder -> builder.enableForPackage("").build())
+ .compile()
+ .inspect(
+ inspector -> {
+ checkAssertionCodeEnabled(inspector.clazz("Class1"));
+ checkAssertionCodeEnabled(inspector.clazz("Class2"));
+ checkAssertionCodeRemoved(inspector.clazz(class1));
+ checkAssertionCodeRemoved(inspector.clazz(class2));
+ })
+ .run(parameters.getRuntime(), "Main")
+ .assertSuccessWithOutputLines(
+ "AssertionError in Class1", "AssertionError in Class2", "DONE");
+ }
+
+ @Test
+ public void testInnerClassForJvm() throws Exception {
+ Assume.assumeTrue(parameters.isCfRuntime());
+ // Pointing to the outer class enables assertions for the inner as well.
+ testForJvm()
+ .addProgramClasses(TestClassForInnerClass.class, TestClassForInnerClass.InnerClass.class)
+ .addVmArguments("-ea:" + TestClassForInnerClass.class.getCanonicalName())
+ .run(parameters.getRuntime(), TestClassForInnerClass.class)
+ .assertSuccessWithOutputLines(
+ "AssertionError in TestClassForInnerClass",
+ "AssertionError in TestClassForInnerClass.InnerClass",
+ "DONE");
+
+ // Pointing to the inner class enables no assertions.
+ testForJvm()
+ .addProgramClasses(TestClassForInnerClass.class, TestClassForInnerClass.InnerClass.class)
+ .addVmArguments("-ea:" + TestClassForInnerClass.InnerClass.class.getCanonicalName())
+ .run(parameters.getRuntime(), TestClassForInnerClass.class)
+ .assertSuccessWithOutputLines("DONE");
+ testForJvm()
+ .addProgramClasses(TestClassForInnerClass.class, TestClassForInnerClass.InnerClass.class)
+ .addVmArguments("-ea:" + TestClassForInnerClass.InnerClass.class.getTypeName())
+ .run(parameters.getRuntime(), TestClassForInnerClass.class)
+ .assertSuccessWithOutputLines("DONE");
+ }
+
+ @Test
+ public void testInnerClassForDex() throws Exception {
+ Assume.assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addProgramClasses(TestClassForInnerClass.class, TestClassForInnerClass.InnerClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .addAssertionsConfiguration(
+ builder ->
+ builder.enableForClass(TestClassForInnerClass.class.getCanonicalName()).build())
+ .compile()
+ .inspect(
+ inspector -> {
+ checkAssertionCodeEnabled(inspector.clazz(TestClassForInnerClass.class));
+ checkAssertionCodeEnabled(inspector.clazz(TestClassForInnerClass.InnerClass.class));
+ })
+ .run(parameters.getRuntime(), TestClassForInnerClass.class)
+ .assertSuccessWithOutputLines(
+ "AssertionError in TestClassForInnerClass",
+ "AssertionError in TestClassForInnerClass.InnerClass",
+ "DONE");
+ }
+ /**
+ * Code for the following class in the unnamed package:
+ *
+ * <p>public class Main { public static void main(String[] args) { try { Class1.m(); } catch
+ * (AssertionError e) { System.out.println("AssertionError in Class1"); } try { Class2.m(); }
+ * catch (AssertionError e) { System.out.println("AssertionError in Class2"); } try {
+ * com.android.tools.r8.rewrite.assertions.Class1.m(); } catch (AssertionError e) {
+ * System.out.println("AssertionError in Class1"); } try {
+ * com.android.tools.r8.rewrite.assertions.Class2.m(); } catch (AssertionError e) {
+ * System.out.println("AssertionError in Class2"); } System.out.println("DONE"); } }
+ */
+ public static byte[] testClassForUnknownPackage() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_FINAL | ACC_SUPER, "Main", null, "java/lang/Object", null);
+
+ classWriter.visitSource("Main.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(1, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ Label label1 = new Label();
+ Label label2 = new Label();
+ methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/AssertionError");
+ Label label3 = new Label();
+ Label label4 = new Label();
+ Label label5 = new Label();
+ methodVisitor.visitTryCatchBlock(label3, label4, label5, "java/lang/AssertionError");
+ Label label6 = new Label();
+ Label label7 = new Label();
+ Label label8 = new Label();
+ methodVisitor.visitTryCatchBlock(label6, label7, label8, "java/lang/AssertionError");
+ Label label9 = new Label();
+ Label label10 = new Label();
+ Label label11 = new Label();
+ methodVisitor.visitTryCatchBlock(label9, label10, label11, "java/lang/AssertionError");
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(4, label0);
+ methodVisitor.visitMethodInsn(INVOKESTATIC, "Class1", "m", "()V", false);
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(7, label1);
+ methodVisitor.visitJumpInsn(GOTO, label3);
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(5, label2);
+ methodVisitor.visitFrame(
+ Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/AssertionError"});
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ Label label12 = new Label();
+ methodVisitor.visitLabel(label12);
+ methodVisitor.visitLineNumber(6, label12);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("AssertionError in Class1");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(9, label3);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitMethodInsn(INVOKESTATIC, "Class2", "m", "()V", false);
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(12, label4);
+ methodVisitor.visitJumpInsn(GOTO, label6);
+ methodVisitor.visitLabel(label5);
+ methodVisitor.visitLineNumber(10, label5);
+ methodVisitor.visitFrame(
+ Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/AssertionError"});
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ Label label13 = new Label();
+ methodVisitor.visitLabel(label13);
+ methodVisitor.visitLineNumber(11, label13);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("AssertionError in Class2");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitLabel(label6);
+ methodVisitor.visitLineNumber(14, label6);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/rewrite/assertions/testclasses/Class1",
+ "m",
+ "()V",
+ false);
+ methodVisitor.visitLabel(label7);
+ methodVisitor.visitLineNumber(17, label7);
+ methodVisitor.visitJumpInsn(GOTO, label9);
+ methodVisitor.visitLabel(label8);
+ methodVisitor.visitLineNumber(15, label8);
+ methodVisitor.visitFrame(
+ Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/AssertionError"});
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ Label label14 = new Label();
+ methodVisitor.visitLabel(label14);
+ methodVisitor.visitLineNumber(16, label14);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("AssertionError in testclasses.Class1");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitLabel(label9);
+ methodVisitor.visitLineNumber(19, label9);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/rewrite/assertions/testclasses/Class2",
+ "m",
+ "()V",
+ false);
+ methodVisitor.visitLabel(label10);
+ methodVisitor.visitLineNumber(22, label10);
+ Label label15 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label15);
+ methodVisitor.visitLabel(label11);
+ methodVisitor.visitLineNumber(20, label11);
+ methodVisitor.visitFrame(
+ Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/AssertionError"});
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ Label label16 = new Label();
+ methodVisitor.visitLabel(label16);
+ methodVisitor.visitLineNumber(21, label16);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("AssertionError in testclasses.Class2");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitLabel(label15);
+ methodVisitor.visitLineNumber(24, label15);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("DONE");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label17 = new Label();
+ methodVisitor.visitLabel(label17);
+ methodVisitor.visitLineNumber(25, label17);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+
+ /**
+ * Code for the following class in the unnamed package:
+ *
+ * <p>public class <name> { public static void m() { assert false; } }
+ */
+ public static byte[] classInUnnamedPackage(String name) {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, name, null, "java/lang/Object", null);
+
+ classWriter.visitSource(name + ".java", null);
+
+ {
+ fieldVisitor =
+ classWriter.visitField(
+ ACC_FINAL | ACC_STATIC | ACC_SYNTHETIC, "$assertionsDisabled", "Z", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(1, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "m", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(3, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, name, "$assertionsDisabled", "Z");
+ Label label1 = new Label();
+ methodVisitor.visitJumpInsn(IFNE, label1);
+ methodVisitor.visitTypeInsn(NEW, "java/lang/AssertionError");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL, "java/lang/AssertionError", "<init>", "()V", false);
+ methodVisitor.visitInsn(ATHROW);
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(4, label1);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 0);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(1, label0);
+ methodVisitor.visitLdcInsn(Type.getType("L" + name + ";"));
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Class", "desiredAssertionStatus", "()Z", false);
+ Label label1 = new Label();
+ methodVisitor.visitJumpInsn(IFNE, label1);
+ methodVisitor.visitInsn(ICONST_1);
+ Label label2 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label2);
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitInsn(ICONST_0);
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {Opcodes.INTEGER});
+ methodVisitor.visitFieldInsn(PUTSTATIC, name, "$assertionsDisabled", "Z");
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 0);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+
static class TestClass {
public static void main(String[] args) {
try {
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
index 446821a..27e53ba 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
@@ -199,7 +199,7 @@
.debug()
.noTreeShaking()
.noMinification()
- .addOptionsModification(o -> o.assertionTransformation = transformation)
+ .addAssertionsConfiguration(builder -> builder.setTransformation(transformation).build())
.compile();
}
@@ -337,7 +337,7 @@
.addProgramClasses(ClassWithAssertions.class)
.debug()
.setMinApi(AndroidApiLevel.B)
- .addOptionsModification(o -> o.assertionTransformation = transformation)
+ .addAssertionsConfiguration(builder -> builder.setTransformation(transformation).build())
.compile();
}
@@ -357,7 +357,7 @@
.addProgramFiles(program)
.debug()
.setMinApi(AndroidApiLevel.B)
- .addOptionsModification(o -> o.assertionTransformation = transformation)
+ .addAssertionsConfiguration(builder -> builder.setTransformation(transformation).build())
.compile();
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/TestClassForInnerClass.java b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/TestClassForInnerClass.java
new file mode 100644
index 0000000..6a719f9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/TestClassForInnerClass.java
@@ -0,0 +1,31 @@
+// 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.rewrite.assertions.testclasses;
+
+public class TestClassForInnerClass {
+ public static class InnerClass {
+ public static void m() {
+ assert false;
+ }
+ }
+
+ public static void m() {
+ assert false;
+ }
+
+ public static void main(String[] args) {
+ try {
+ m();
+ } catch (AssertionError e) {
+ System.out.println("AssertionError in TestClassForInnerClass");
+ }
+ try {
+ InnerClass.m();
+ } catch (AssertionError e) {
+ System.out.println("AssertionError in TestClassForInnerClass.InnerClass");
+ }
+ System.out.println("DONE");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/testing/JvmVmArgumentsTest.java b/src/test/java/com/android/tools/r8/testing/JvmVmArgumentsTest.java
new file mode 100644
index 0000000..8fdc339
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/testing/JvmVmArgumentsTest.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.testing;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class JvmVmArgumentsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withCfRuntimes().build();
+ }
+
+ public JvmVmArgumentsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testArguments() throws Exception {
+ testForJvm()
+ .addTestClasspath()
+ .addVmArguments("-ea")
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("AssertionError!", "DONE");
+ }
+
+ @Test
+ public void testMultipleArguments() throws Exception {
+ testForJvm()
+ .addTestClasspath()
+ .addVmArguments("-ea", "-da")
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("DONE");
+
+ testForJvm()
+ .addTestClasspath()
+ .addVmArguments("-ea")
+ .addVmArguments("-da")
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("DONE");
+ }
+
+ @Test
+ public void testNoArguments() throws Exception {
+ testForJvm()
+ .addTestClasspath()
+ .addVmArguments()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("DONE");
+ }
+
+ static class TestClass {
+ public static void m() {
+ assert false;
+ }
+
+ public static void main(String[] args) {
+ try {
+ m();
+ } catch (AssertionError e) {
+ System.out.println("AssertionError!");
+ }
+ System.out.println("DONE");
+ }
+ }
+}
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 461323d..3d653d0 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -20,6 +20,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
@@ -238,7 +239,27 @@
});
}
+ public ClassFileTransformer setPublic(Method method) {
+ return setAccessFlags(
+ method,
+ accessFlags -> {
+ accessFlags.unsetPrivate();
+ accessFlags.unsetProtected();
+ accessFlags.setPublic();
+ });
+ }
+
public ClassFileTransformer setPrivate(Method method) {
+ return setAccessFlags(
+ method,
+ accessFlags -> {
+ accessFlags.unsetPublic();
+ accessFlags.unsetProtected();
+ accessFlags.setPrivate();
+ });
+ }
+
+ public ClassFileTransformer setAccessFlags(Method method, Consumer<MethodAccessFlags> setter) {
return addClassTransformer(
new ClassTransformer() {
final MethodReference methodReference = Reference.methodFromMethod(method);
@@ -253,9 +274,7 @@
MethodAccessFlags.fromCfAccessFlags(access, isConstructor);
if (name.equals(methodReference.getMethodName())
&& descriptor.equals(methodReference.getMethodDescriptor())) {
- accessFlags.unsetPublic();
- accessFlags.unsetProtected();
- accessFlags.setPrivate();
+ setter.accept(accessFlags);
}
return super.visitMethod(
accessFlags.getAsCfAccessFlags(), name, descriptor, signature, exceptions);
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
index 358a016..e4ce45d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
@@ -3,11 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils.codeinspector;
+import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKotlinClassifier;
+
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;
@@ -53,6 +54,7 @@
return false;
}
+ // TODO(b/145824437): This is a dup of DescriptorUtils#getDescriptorFromKmType
private String getDescriptorFromKmType(KmType kmType) {
if (kmType == null) {
return null;
@@ -61,7 +63,12 @@
kmType.accept(new KmTypeVisitor() {
@Override
public void visitClass(String name) {
- descriptor.set(DescriptorUtils.getDescriptorFromKotlinClassifier(name));
+ descriptor.set(getDescriptorFromKotlinClassifier(name));
+ }
+
+ @Override
+ public void visitTypeAlias(String name) {
+ descriptor.set(getDescriptorFromKotlinClassifier(name));
}
});
return descriptor.get();