Merge commit 'be9a163a9bdd72ec036d1a048cc8cc2b2fecbadd' 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 new file mode 100644 index 0000000..f77bbce --- /dev/null +++ b/src/main/java/com/android/tools/r8/AssertionsConfiguration.java
@@ -0,0 +1,157 @@ +// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8; + +import com.android.tools.r8.utils.StringDiagnostic; + +@Keep +public class AssertionsConfiguration { + + /** The possible transformations of the javac generated assertion code during compilation. */ + public enum AssertionTransformation { + /** Unconditionally enable the javac generated assertion code. */ + ENABLE, + /** + * Unconditionally disable the javac generated assertion code. This will most likely remove the + * javac generated assertion code completely. + */ + DISABLE, + /** Passthrough of the javac generated assertion code. */ + PASSTHROUGH + } + + private AssertionTransformation transformation; + + private AssertionsConfiguration(AssertionTransformation transformation) { + this.transformation = transformation; + } + + static Builder builder(DiagnosticsHandler handler) { + return new Builder(handler); + } + + /** + * Builder for constructing a <code>{@link AssertionsConfiguration}</code>. + * + * <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 DiagnosticsHandler handler; + + private Builder(DiagnosticsHandler handler) { + this.handler = handler; + } + + /** Set how to handle javac generated assertion code. */ + public AssertionsConfiguration.Builder setTransformation( + AssertionTransformation transformation) { + this.transformation = transformation; + return this; + } + + /** + * Unconditionally enable javac generated assertion code in all packages and classes. This + * corresponds to passing <code>-enableassertions</code> or <code>-ea</code> to the java CLI. + */ + public AssertionsConfiguration.Builder enable() { + setTransformation(AssertionTransformation.ENABLE); + return this; + } + + /** + * Disable the javac generated assertion code in all packages and classes. This corresponds to + * passing <code>-disableassertions</code> or <code>-da</code> to the java CLI. + */ + public AssertionsConfiguration.Builder disable() { + setTransformation(AssertionTransformation.DISABLE); + return this; + } + + /** Passthrough of the javac generated assertion code in all packages and classes. */ + public AssertionsConfiguration.Builder passthrough() { + setTransformation(AssertionTransformation.PASSTHROUGH); + return this; + } + + /** 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")); + return this; + } + + /** + * Unconditionally enable javac generated assertion code in package <code>packageName</code> and + * all subpackages. This corresponds to passing <code>-enableassertions:packageName...</code> or + * <code>-ea:packageName...</code> to the java CLI. + * + * <p>If <code>packageName</code> is the empty string, assertions are enabled in the unnamed + * package, which corresponds to passing <code>-enableassertions:...</code> or <code>-ea:... + * </code> to the java CLI. + */ + public AssertionsConfiguration.Builder enableForPackage(String packageName) { + return setTransformationForPackage(packageName, AssertionTransformation.ENABLE); + } + + /** + * Disable the javac generated assertion code in package <code>packageName</code> and all + * subpackages. This corresponds to passing <code>-disableassertions:packageName...</code> or + * <code>-da:packageName...</code> to the java CLI. + * + * <p>If <code>packageName</code> is the empty string assertions are disabled in the unnamed + * package, which corresponds to passing <code>-disableassertions:...</code> or <code>-da:... + * </code> to the java CLI. + */ + public AssertionsConfiguration.Builder disableForPackage(String packageName) { + return setTransformationForPackage(packageName, AssertionTransformation.DISABLE); + } + + public AssertionsConfiguration.Builder passthroughForPackage(String packageName) { + return setTransformationForPackage(packageName, AssertionTransformation.PASSTHROUGH); + } + + /** Set how to handle javac generated assertion code in class. */ + public AssertionsConfiguration.Builder setTransformationForClass( + String className, AssertionTransformation transformation) { + handler.error(new StringDiagnostic("Unsupported")); + return this; + } + + /** + * Unconditionally enable javac generated assertion in class <code>className</code>. This + * corresponds to passing <code> -enableassertions:className</code> or <code>-ea:className + * </code> to the java CLI. + */ + public AssertionsConfiguration.Builder enableForClass(String className) { + return setTransformationForClass(className, AssertionTransformation.ENABLE); + } + + /** + * Disable the javac generated assertion code in class <code>className</code>. This corresponds + * to passing <code> -disableassertions:className</code> or <code>-da:className</code> to the + * java CLI. + */ + public AssertionsConfiguration.Builder disableForClass(String className) { + return setTransformationForClass(className, AssertionTransformation.DISABLE); + } + + public AssertionsConfiguration.Builder passthroughForClass(String className) { + return setTransformationForClass(className, AssertionTransformation.PASSTHROUGH); + } + + /** Build and return the {@link AssertionsConfiguration}. */ + public AssertionsConfiguration build() { + return new AssertionsConfiguration(transformation); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java index b0d5055..ce60661 100644 --- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java +++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.List; import java.util.function.BiPredicate; +import java.util.function.Function; /** * Base class for commands and command builders for compiler applications/tools which besides an @@ -38,6 +39,7 @@ private final boolean includeClassesChecksum; private final boolean optimizeMultidexForLinearAlloc; private final BiPredicate<String, Long> dexClassChecksumFilter; + private final AssertionsConfiguration assertionsConfiguration; BaseCompilerCommand(boolean printHelp, boolean printVersion) { super(printHelp, printVersion); @@ -50,6 +52,7 @@ includeClassesChecksum = false; optimizeMultidexForLinearAlloc = false; dexClassChecksumFilter = (name, checksum) -> true; + assertionsConfiguration = null; } BaseCompilerCommand( @@ -62,7 +65,8 @@ boolean enableDesugaring, boolean optimizeMultidexForLinearAlloc, boolean includeClassesChecksum, - BiPredicate<String, Long> dexClassChecksumFilter) { + BiPredicate<String, Long> dexClassChecksumFilter, + AssertionsConfiguration assertionsConfiguration) { super(app); assert minApiLevel > 0; assert mode != null; @@ -75,6 +79,7 @@ this.optimizeMultidexForLinearAlloc = optimizeMultidexForLinearAlloc; this.includeClassesChecksum = includeClassesChecksum; this.dexClassChecksumFilter = dexClassChecksumFilter; + this.assertionsConfiguration = assertionsConfiguration; } /** @@ -129,6 +134,10 @@ return optimizeMultidexForLinearAlloc; } + public AssertionsConfiguration getAssertionsConfiguration() { + return assertionsConfiguration; + } + Reporter getReporter() { return reporter; } @@ -158,6 +167,7 @@ private boolean lookupLibraryBeforeProgram = true; private boolean optimizeMultidexForLinearAlloc = false; private BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true; + private AssertionsConfiguration assertionsConfiguration; abstract CompilationMode defaultCompilationMode(); @@ -476,6 +486,19 @@ return includeClassesChecksum; } + /** Configure compile time assertion enabling through a {@link AssertionsConfiguration}. */ + public B addAssertionsConfiguration( + Function<AssertionsConfiguration.Builder, AssertionsConfiguration> + assertionsConfigurationGenerator) { + assertionsConfiguration = + assertionsConfigurationGenerator.apply(AssertionsConfiguration.builder(getReporter())); + return self(); + } + + public AssertionsConfiguration getAssertionsConfiguration() { + return assertionsConfiguration; + } + @Override void validate() { Reporter reporter = getReporter();
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java index ebebbf1..2214e2d 100644 --- a/src/main/java/com/android/tools/r8/D8.java +++ b/src/main/java/com/android/tools/r8/D8.java
@@ -6,6 +6,7 @@ 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; @@ -25,7 +26,6 @@ import com.android.tools.r8.utils.CfgPrinter; import com.android.tools.r8.utils.ExceptionUtils; import com.android.tools.r8.utils.InternalOptions; -import com.android.tools.r8.utils.InternalOptions.AssertionProcessing; import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.Timing; import com.google.common.collect.ImmutableList; @@ -159,7 +159,7 @@ final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null; - if (options.assertionProcessing != AssertionProcessing.LEAVE) { + if (options.assertionTransformation != AssertionTransformation.PASSTHROUGH) { // 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 b72548b..6bdc35c 100644 --- a/src/main/java/com/android/tools/r8/D8Command.java +++ b/src/main/java/com/android/tools/r8/D8Command.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.DexFileOverflowDiagnostic; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration; @@ -218,6 +219,7 @@ getDesugarGraphConsumer(), desugaredLibraryKeepRuleConsumer, libraryConfiguration, + getAssertionsConfiguration(), factory); } } @@ -285,6 +287,7 @@ DesugarGraphConsumer desugarGraphConsumer, StringConsumer desugaredLibraryKeepRuleConsumer, DesugaredLibraryConfiguration libraryConfiguration, + AssertionsConfiguration assertionsConfiguration, DexItemFactory factory) { super( inputApp, @@ -296,7 +299,8 @@ enableDesugaring, optimizeMultidexForLinearAlloc, encodeChecksum, - dexClassChecksumFilter); + dexClassChecksumFilter, + assertionsConfiguration); this.intermediate = intermediate; this.desugarGraphConsumer = desugarGraphConsumer; this.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer; @@ -357,6 +361,16 @@ 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; + } + return internal; } }
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java index d96a5a1..ff64d62 100644 --- a/src/main/java/com/android/tools/r8/L8Command.java +++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -61,7 +61,8 @@ true, false, false, - (name, checksum) -> true); + (name, checksum) -> true, + AssertionsConfiguration.builder(null).build()); this.d8Command = d8Command; this.r8Command = r8Command; this.libraryConfiguration = libraryConfiguration;
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index b4ae1c8..03235a4 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -6,6 +6,7 @@ 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; @@ -81,7 +82,6 @@ import com.android.tools.r8.utils.CollectionUtils; import com.android.tools.r8.utils.ExceptionUtils; import com.android.tools.r8.utils.InternalOptions; -import com.android.tools.r8.utils.InternalOptions.AssertionProcessing; import com.android.tools.r8.utils.LineNumberOptimizer; import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.SelfRetraceTest; @@ -813,7 +813,7 @@ if (appView.options().enableInitializedClassesInInstanceMethodsAnalysis) { enqueuer.registerAnalysis(new InitializedClassesInInstanceMethodsAnalysis(appView)); } - if (appView.options().assertionProcessing != AssertionProcessing.LEAVE) { + if (appView.options().assertionTransformation != AssertionTransformation.PASSTHROUGH) { 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 8c63d06..45f9e84 100644 --- a/src/main/java/com/android/tools/r8/R8Command.java +++ b/src/main/java/com/android/tools/r8/R8Command.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.experimental.graphinfo.GraphConsumer; @@ -23,7 +24,6 @@ import com.android.tools.r8.utils.ExceptionDiagnostic; import com.android.tools.r8.utils.FileUtils; import com.android.tools.r8.utils.InternalOptions; -import com.android.tools.r8.utils.InternalOptions.AssertionProcessing; import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization; import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.StringDiagnostic; @@ -562,7 +562,8 @@ getDexClassChecksumFilter(), desugaredLibraryKeepRuleConsumer, libraryConfiguration, - featureSplitConfiguration); + featureSplitConfiguration, + getAssertionsConfiguration()); return command; } @@ -718,7 +719,8 @@ BiPredicate<String, Long> dexClassChecksumFilter, StringConsumer desugaredLibraryKeepRuleConsumer, DesugaredLibraryConfiguration libraryConfiguration, - FeatureSplitConfiguration featureSplitConfiguration) { + FeatureSplitConfiguration featureSplitConfiguration, + AssertionsConfiguration assertionsConfiguration) { super( inputApp, mode, @@ -729,7 +731,8 @@ enableDesugaring, optimizeMultidexForLinearAlloc, encodeChecksum, - dexClassChecksumFilter); + dexClassChecksumFilter, + assertionsConfiguration); assert proguardConfiguration != null; assert mainDexKeepRules != null; this.mainDexKeepRules = mainDexKeepRules; @@ -869,9 +872,16 @@ // 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.assertionProcessing == AssertionProcessing.REMOVE; - if (internal.isGeneratingClassFiles()) { - internal.assertionProcessing = AssertionProcessing.LEAVE; + 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(); } // When generating class files the build is "intermediate" and we cannot pollute the namespace
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java index 5daaccc..7ba04b5 100644 --- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java +++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -71,6 +71,7 @@ public final GraphLense graphLense; public final NamingLens namingLens; public final InternalOptions options; + private final CodeToKeep desugaredLibraryCodeToKeep; public List<Marker> markers; public List<DexString> markerStrings; @@ -167,6 +168,7 @@ this.appView = appView; assert options != null; this.options = options; + this.desugaredLibraryCodeToKeep = CodeToKeep.createCodeToKeep(options, namingLens); this.markers = markers; this.graphLense = graphLense; this.namingLens = namingLens; @@ -310,6 +312,11 @@ } // Wait for all files to be processed before moving on. ThreadUtils.awaitFutures(dexDataFutures); + // A consumer can manage the generated keep rules. + if (options.desugaredLibraryKeepRuleConsumer != null && !desugaredLibraryCodeToKeep.isNop()) { + assert !options.isDesugaredLibraryCompilation(); + desugaredLibraryCodeToKeep.generateKeepRules(options); + } // Fail if there are pending errors, e.g., the program consumers may have reported errors. options.reporter.failIfPendingErrors(); // Supply info to all additional resource consumers. @@ -576,7 +583,14 @@ MethodToCodeObjectMapping codeMapping, ByteBufferProvider provider) { FileWriter fileWriter = - new FileWriter(provider, objectMapping, codeMapping, application, options, namingLens); + new FileWriter( + provider, + objectMapping, + codeMapping, + application, + options, + namingLens, + desugaredLibraryCodeToKeep); // Collect the non-fixed sections. fileWriter.collect(); // Generate and write the bytes.
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java index fa50d92..5cbd975 100644 --- a/src/main/java/com/android/tools/r8/dex/FileWriter.java +++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -101,7 +101,8 @@ MethodToCodeObjectMapping codeMapping, DexApplication application, InternalOptions options, - NamingLens namingLens) { + NamingLens namingLens, + CodeToKeep desugaredLibraryCodeToKeep) { this.mapping = mapping; this.codeMapping = codeMapping; this.application = application; @@ -109,7 +110,7 @@ this.namingLens = namingLens; this.dest = new DexOutputBuffer(provider); this.mixedSectionOffsets = new MixedSectionOffsets(options, codeMapping); - this.desugaredLibraryCodeToKeep = CodeToKeep.createCodeToKeep(options, namingLens); + this.desugaredLibraryCodeToKeep = desugaredLibraryCodeToKeep; } public static void writeEncodedAnnotation( @@ -223,12 +224,6 @@ writeSignature(layout); writeChecksum(layout); - // A consumer can manage the generated keep rules. - if (options.desugaredLibraryKeepRuleConsumer != null && !desugaredLibraryCodeToKeep.isNop()) { - assert !options.isDesugaredLibraryCompilation(); - desugaredLibraryCodeToKeep.generateKeepRules(options); - } - // Wrap backing buffer with actual length. return new ByteBufferResult(dest.stealByteBuffer(), layout.getEndOfFile()); }
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java index 98760b6..61def68 100644 --- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java +++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -3,14 +3,20 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.graph; +import static kotlinx.metadata.FlagsKt.flagsOf; + import com.android.tools.r8.dex.Constants; import com.google.common.collect.ImmutableList; +import java.util.ArrayList; import java.util.List; import java.util.function.BooleanSupplier; +import kotlinx.metadata.Flag; /** Access flags common to classes, methods and fields. */ public abstract class AccessFlags<T extends AccessFlags<T>> { + protected Flag[] EMPTY_FLAG = {}; + protected static final int BASE_FLAGS = Constants.ACC_PUBLIC | Constants.ACC_PRIVATE @@ -65,6 +71,26 @@ public abstract int getAsDexAccessFlags(); + public int getAsKotlinFlags() { + List<Flag> flags = new ArrayList<>(); + if (isPrivate()) { + flags.add(Flag.IS_PRIVATE); + } + if (isProtected()) { + flags.add(Flag.IS_PROTECTED); + } + if (isPublic()) { + flags.add(Flag.IS_PUBLIC); + } + if (isFinal()) { + flags.add(Flag.IS_FINAL); + } + if (isOpen()) { + flags.add(Flag.IS_OPEN); + } + return flagsOf(flags.toArray(EMPTY_FLAG)); + } + public final int getOriginalAccessFlags() { return originalFlags; } @@ -171,6 +197,10 @@ set(Constants.ACC_STATIC); } + public boolean isOpen() { + return !isFinal(); + } + public boolean isFinal() { return isSet(Constants.ACC_FINAL); }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java index b9e8757..2753232 100644 --- a/src/main/java/com/android/tools/r8/graph/CfCode.java +++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -3,6 +3,9 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.graph; +import static com.android.tools.r8.graph.DexCode.FAKE_THIS_PREFIX; +import static com.android.tools.r8.graph.DexCode.FAKE_THIS_SUFFIX; + import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.cf.code.CfFrame; import com.android.tools.r8.cf.code.CfIinc; @@ -26,6 +29,7 @@ import com.android.tools.r8.origin.Origin; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.InternalOptions; +import com.google.common.base.Strings; import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; @@ -518,4 +522,32 @@ } return constraint; } + + void addFakeThisParameter(DexItemFactory factory) { + if (localVariables == null || localVariables.isEmpty()) { + // We have no debugging info in the code. + return; + } + int largestPrefix = 1; + int existingThisIndex = -1; + for (int i = 0; i < localVariables.size(); i++) { + LocalVariableInfo localVariable = localVariables.get(i); + largestPrefix = + Math.max(largestPrefix, DexCode.getLargestPrefix(factory, localVariable.local.name)); + if (localVariable.local.name.toString().equals("this")) { + existingThisIndex = i; + } + } + if (existingThisIndex < 0) { + return; + } + String fakeThisName = Strings.repeat(FAKE_THIS_PREFIX, largestPrefix + 1) + FAKE_THIS_SUFFIX; + DebugLocalInfo debugLocalInfo = + new DebugLocalInfo(factory.createString(fakeThisName), this.originalHolder, null); + LocalVariableInfo thisLocalInfo = localVariables.get(existingThisIndex); + this.localVariables.set( + existingThisIndex, + new LocalVariableInfo( + thisLocalInfo.index, debugLocalInfo, thisLocalInfo.start, thisLocalInfo.end)); + } }
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java index 0c96120..030497d 100644 --- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java +++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -3,10 +3,14 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.graph; +import static kotlinx.metadata.FlagsKt.flagsOf; + import com.android.tools.r8.dex.Constants; import com.google.common.collect.ImmutableList; +import java.util.ArrayList; import java.util.List; import java.util.function.BooleanSupplier; +import kotlinx.metadata.Flag; public class ClassAccessFlags extends AccessFlags<ClassAccessFlags> { @@ -83,6 +87,11 @@ } @Override + public int getAsCfAccessFlags() { + return materialize(); + } + + @Override public int getAsDexAccessFlags() { // We unset the super flag here, as it is meaningless in DEX. Furthermore, we add missing // abstract to interfaces to work around a javac bug when generating package-info classes. @@ -94,8 +103,25 @@ } @Override - public int getAsCfAccessFlags() { - return materialize(); + public int getAsKotlinFlags() { + int flag = super.getAsKotlinFlags(); + List<Flag> flags = new ArrayList<>(); + if (isAbstract()) { + flags.add(Flag.IS_ABSTRACT); + } + if (isClass()) { + flags.add(Flag.Class.IS_CLASS); + } + if (isInterface()) { + flags.add(Flag.Class.IS_INTERFACE); + } + if (isAnnotation()) { + flags.add(Flag.Class.IS_ANNOTATION_CLASS); + } + if (isEnum()) { + flags.add(Flag.Class.IS_ENUM_CLASS); + } + return flag | flagsOf(flags.toArray(EMPTY_FLAG)); } /** @@ -121,6 +147,10 @@ } } + private boolean isClass() { + return !isInterface() && !isAnnotation() && !isEnum(); + } + public boolean isInterface() { return isSet(Constants.ACC_INTERFACE); }
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 358af04..2a87607 100644 --- a/src/main/java/com/android/tools/r8/graph/DexClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -16,6 +16,7 @@ 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; @@ -26,6 +27,7 @@ import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; +import kotlinx.metadata.KmProperty; public abstract class DexClass extends DexDefinition { @@ -208,6 +210,16 @@ 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); + } + } + return functions; + } + public void appendVirtualMethod(DexEncodedMethod method) { DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + 1]; System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java index a5b1097..d32abdf 100644 --- a/src/main/java/com/android/tools/r8/graph/DexCode.java +++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -32,8 +32,8 @@ // DexCode corresponds to code item in dalvik/dex-format.html public class DexCode extends Code { - private static final String FAKE_THIS_PREFIX = "_"; - private static final String FAKE_THIS_SUFFIX = "this"; + static final String FAKE_THIS_PREFIX = "_"; + static final String FAKE_THIS_SUFFIX = "this"; public final int registerSize; public final int incomingRegisterSize; @@ -132,7 +132,7 @@ return new DexDebugInfo(debugInfo.startLine, newParameters, debugInfo.events); } - private static int getLargestPrefix(DexItemFactory factory, DexString name) { + public static int getLargestPrefix(DexItemFactory factory, DexString name) { if (name != null && name.endsWith(factory.thisName)) { String string = name.toString(); for (int i = 0; i < string.length(); i++) {
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 7fe4555..e4fff90 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -73,6 +73,7 @@ import java.util.Map; import java.util.function.Consumer; import java.util.function.IntPredicate; +import kotlinx.metadata.KmProperty; import org.objectweb.asm.Opcodes; public class DexEncodedMethod extends KeyedDexItem<DexMethod> { @@ -348,6 +349,41 @@ return accessFlags.isSynthetic(); } + boolean isKotlinFunction(List<KmProperty> properties) { + return !isStaticMember() && !isKotlinProperty(properties); + } + + // E.g., property `prop: T` is mapped to `getProp()T`, `setProp(T)V`, `prop$annotations()V`. + // TODO(b/70169921): Handle different name patterns via @JvmName. + boolean isKotlinProperty(List<KmProperty> properties) { + // TODO(b/70169921): Avoid decoding. + String methodName = method.name.toString(); + if (!methodName.startsWith("get") + && !methodName.startsWith("set") + && !methodName.endsWith("$annotations")) { + return false; + } + for (KmProperty property : properties) { + String propertyName = property.getName(); + assert propertyName.length() > 0; + String annotations = propertyName + "$annotations"; + if (methodName.equals(annotations)) { + return true; + } + String capitalized = + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); + String getter = "get" + capitalized; + if (methodName.equals(getter)) { + return true; + } + String setter = "set" + capitalized; + if (methodName.equals(setter)) { + return true; + } + } + return false; + } + public boolean isOnlyInlinedIntoNestMembers() { return compilationState == PROCESSED_INLINING_CANDIDATE_SAME_NEST; } @@ -722,8 +758,11 @@ assert (dexCode.getDebugInfo() == null) || (arity == dexCode.getDebugInfo().parameters.length); } else { - // TODO(b/134732760): Patch Cf debug info. - assert appView.options().isDesugaredLibraryCompilation(); + assert appView.options().isDesugaredLibraryCompilation() + || appView.options().enableCfInterfaceMethodDesugaring; + assert code.isCfCode(); + CfCode cfCode = code.asCfCode(); + cfCode.addFakeThisParameter(appView.dexItemFactory()); } }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java index bb39765..ca16abe 100644 --- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java +++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.graph; import com.android.tools.r8.dex.Constants; +import com.android.tools.r8.errors.Unreachable; import com.google.common.collect.ImmutableList; import java.util.List; import java.util.function.BooleanSupplier; @@ -77,6 +78,11 @@ return materialize(); } + @Override + public int getAsKotlinFlags() { + throw new Unreachable("Kotlin property is not directly mapped to JVM field."); + } + public boolean isVolatile() { return isSet(Constants.ACC_VOLATILE); }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java index 2173ed0..1b8e3a0 100644 --- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java +++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -3,10 +3,14 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.graph; +import static kotlinx.metadata.FlagsKt.flagsOf; + import com.android.tools.r8.dex.Constants; import com.google.common.collect.ImmutableList; +import java.util.ArrayList; import java.util.List; import java.util.function.BooleanSupplier; +import kotlinx.metadata.Flag; public class MethodAccessFlags extends AccessFlags<MethodAccessFlags> { @@ -92,6 +96,11 @@ } @Override + public int getAsCfAccessFlags() { + return materialize() & ~Constants.ACC_CONSTRUCTOR; + } + + @Override public int getAsDexAccessFlags() { MethodAccessFlags copy = copy(); if (copy.isSynchronized() && !copy.isNative()) { @@ -102,8 +111,13 @@ } @Override - public int getAsCfAccessFlags() { - return materialize() & ~Constants.ACC_CONSTRUCTOR; + public int getAsKotlinFlags() { + int flag = super.getAsKotlinFlags(); + List<Flag> flags = new ArrayList<>(); + if (isAbstract()) { + flags.add(Flag.IS_ABSTRACT); + } + return flag | flagsOf(flags.toArray(EMPTY_FLAG)); } public boolean isSynchronized() {
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 5041e1d..32b7cbe 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,6 +6,7 @@ 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; @@ -86,11 +87,9 @@ import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.LibraryMethodOverrideAnalysis; import com.android.tools.r8.shaking.MainDexClasses; -import com.android.tools.r8.utils.Action; import com.android.tools.r8.utils.CfgPrinter; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.InternalOptions; -import com.android.tools.r8.utils.InternalOptions.AssertionProcessing; import com.android.tools.r8.utils.InternalOptions.OutlineOptions; import com.android.tools.r8.utils.StringDiagnostic; import com.android.tools.r8.utils.ThreadUtils; @@ -171,7 +170,7 @@ OptimizationFeedbackSimple.getInstance(); private DexString highestSortingString; - private List<Action> onWaveDoneActions = null; + private List<com.android.tools.r8.utils.Action> onWaveDoneActions = null; private final List<DexString> neverMergePrefixes; boolean seenNotNeverMergePrefix = false; @@ -819,11 +818,11 @@ private void waveDone() { delayedOptimizationFeedback.updateVisibleOptimizationInfo(); - onWaveDoneActions.forEach(Action::execute); + onWaveDoneActions.forEach(com.android.tools.r8.utils.Action::execute); onWaveDoneActions = null; } - public void addWaveDoneAction(Action action) { + public void addWaveDoneAction(com.android.tools.r8.utils.Action action) { if (!appView.enableWholeProgramOptimizations()) { throw new Unreachable("addWaveDoneAction() should never be used in D8."); } @@ -1177,7 +1176,7 @@ assert appView.enableWholeProgramOptimizations(); codeRewriter.removeSwitchMaps(code); } - if (options.assertionProcessing != AssertionProcessing.LEAVE) { + if (options.assertionTransformation != AssertionTransformation.PASSTHROUGH) { codeRewriter.processAssertions(appView, method, code, feedback); }
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 bdc98f0..fa68de4 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
@@ -1417,6 +1417,49 @@ addProvider( new MethodGenerator( method, BackportedMethods::CharacterMethods_toStringCodepoint, "toStringCodepoint")); + + // String + type = factory.stringType; + + // String String.repeat(int) + name = factory.createString("repeat"); + proto = factory.createProto(factory.stringType, factory.intType); + method = factory.createMethod(type, proto, name); + addProvider( + new StatifyingMethodGenerator( + method, BackportedMethods::StringMethods_repeat, "repeat", type)); + + // boolean String.isBlank() + name = factory.createString("isBlank"); + proto = factory.createProto(factory.booleanType); + method = factory.createMethod(type, proto, name); + addProvider( + new StatifyingMethodGenerator( + method, BackportedMethods::StringMethods_isBlank, "isBlank", type)); + + // String String.strip() + name = factory.createString("strip"); + proto = factory.createProto(factory.stringType); + method = factory.createMethod(type, proto, name); + addProvider( + new StatifyingMethodGenerator( + method, BackportedMethods::StringMethods_strip, "strip", type)); + + // String String.stripLeading() + name = factory.createString("stripLeading"); + proto = factory.createProto(factory.stringType); + method = factory.createMethod(type, proto, name); + addProvider( + new StatifyingMethodGenerator( + method, BackportedMethods::StringMethods_stripLeading, "stripLeading", type)); + + // String String.stripTrailing() + name = factory.createString("stripTrailing"); + proto = factory.createProto(factory.stringType); + method = factory.createMethod(type, proto, name); + addProvider( + new StatifyingMethodGenerator( + method, BackportedMethods::StringMethods_stripTrailing, "stripTrailing", type)); } private void initializeJava9OptionalMethodProviders(DexItemFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java index e7ba0b5..ce77849 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -29,6 +29,7 @@ import com.android.tools.r8.ir.code.Invoke.Type; import com.android.tools.r8.ir.synthetic.SynthesizedCode; import com.android.tools.r8.origin.SynthesizedOrigin; +import com.android.tools.r8.utils.InternalOptions; import com.google.common.base.Suppliers; import com.google.common.primitives.Longs; import java.nio.ByteBuffer; @@ -494,7 +495,8 @@ } boolean holderIsInterface() { - if (!rewriter.converter.appView.options().isGeneratingClassFiles()) { + InternalOptions options = rewriter.converter.appView.options(); + if (!options.isGeneratingClassFiles()) { // When generating dex the value of this flag on invokes does not matter (unused). // We cannot know if definitionFor(implMethod.holder) is null or not in that case, // so we cannot set the flag and just return false. @@ -503,14 +505,11 @@ // The only case where we do Lambda desugaring with Cf to Cf is in L8. // If the compilation is not coreLibraryCompilation, then the assertion // implMethodHolder != null may fail, hence the assertion. - assert rewriter.converter.appView.options().isDesugaredLibraryCompilation(); + assert options.isDesugaredLibraryCompilation() || options.enableCfInterfaceMethodDesugaring; DexMethod implMethod = descriptor.implHandle.asMethod(); DexClass implMethodHolder = definitionFor(implMethod.holder); if (implMethodHolder == null) { - assert rewriter - .converter - .appView - .options() + assert options .desugaredLibraryConfiguration .getBackportCoreLibraryMember() .containsKey(implMethod.holder);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java index 8440fc2..744b10c 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -5695,6 +5695,88 @@ ImmutableList.of()); } + public static CfCode StringMethods_isBlank(InternalOptions options, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + return new CfCode( + method.holder, + 2, + 4, + ImmutableList.of( + label0, + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 1), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/String;"), + options.itemFactory.createProto(options.itemFactory.createType("I")), + options.itemFactory.createString("length")), + false), + new CfStore(ValueType.INT, 2), + label2, + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfIfCmp(If.Type.GE, ValueType.INT, label8), + label3, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/String;"), + options.itemFactory.createProto( + options.itemFactory.createType("I"), options.itemFactory.createType("I")), + options.itemFactory.createString("codePointAt")), + false), + new CfStore(ValueType.INT, 3), + label4, + new CfLoad(ValueType.INT, 3), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/Character;"), + options.itemFactory.createProto( + options.itemFactory.createType("Z"), options.itemFactory.createType("I")), + options.itemFactory.createString("isWhitespace")), + false), + new CfIf(If.Type.NE, ValueType.INT, label6), + label5, + new CfConstNumber(0, ValueType.INT), + new CfReturn(ValueType.INT), + label6, + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 3), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/Character;"), + options.itemFactory.createProto( + options.itemFactory.createType("I"), options.itemFactory.createType("I")), + options.itemFactory.createString("charCount")), + false), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfStore(ValueType.INT, 1), + label7, + new CfGoto(label2), + label8, + new CfConstNumber(1, ValueType.INT), + new CfReturn(ValueType.INT), + label9), + ImmutableList.of(), + ImmutableList.of()); + } + public static CfCode StringMethods_joinArray(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); @@ -5960,4 +6042,484 @@ ImmutableList.of(), ImmutableList.of()); } + + public static CfCode StringMethods_repeat(InternalOptions options, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + CfLabel label12 = new CfLabel(); + CfLabel label13 = new CfLabel(); + return new CfCode( + method.holder, + 4, + 5, + ImmutableList.of( + label0, + new CfLoad(ValueType.INT, 1), + new CfIf(If.Type.GE, ValueType.INT, label2), + label1, + new CfNew(options.itemFactory.createType("Ljava/lang/IllegalArgumentException;")), + new CfStackInstruction(CfStackInstruction.Opcode.Dup), + new CfNew(options.itemFactory.createType("Ljava/lang/StringBuilder;")), + new CfStackInstruction(CfStackInstruction.Opcode.Dup), + new CfInvoke( + 183, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createProto(options.itemFactory.createType("V")), + options.itemFactory.createString("<init>")), + false), + new CfConstString(options.itemFactory.createString("count is negative: ")), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createType("Ljava/lang/String;")), + options.itemFactory.createString("append")), + false), + new CfLoad(ValueType.INT, 1), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createType("I")), + options.itemFactory.createString("append")), + false), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/String;")), + options.itemFactory.createString("toString")), + false), + new CfInvoke( + 183, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/IllegalArgumentException;"), + options.itemFactory.createProto( + options.itemFactory.createType("V"), + options.itemFactory.createType("Ljava/lang/String;")), + options.itemFactory.createString("<init>")), + false), + new CfThrow(), + label2, + new CfLoad(ValueType.OBJECT, 0), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/String;"), + options.itemFactory.createProto(options.itemFactory.createType("I")), + options.itemFactory.createString("length")), + false), + new CfStore(ValueType.INT, 2), + label3, + new CfLoad(ValueType.INT, 1), + new CfIf(If.Type.EQ, ValueType.INT, label4), + new CfLoad(ValueType.INT, 2), + new CfIf(If.Type.NE, ValueType.INT, label5), + label4, + new CfConstString(options.itemFactory.createString("")), + new CfReturn(ValueType.OBJECT), + label5, + new CfLoad(ValueType.INT, 1), + new CfConstNumber(1, ValueType.INT), + new CfIfCmp(If.Type.NE, ValueType.INT, label7), + label6, + new CfLoad(ValueType.OBJECT, 0), + new CfReturn(ValueType.OBJECT), + label7, + new CfNew(options.itemFactory.createType("Ljava/lang/StringBuilder;")), + new CfStackInstruction(CfStackInstruction.Opcode.Dup), + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Mul, NumericType.INT), + new CfInvoke( + 183, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createProto( + options.itemFactory.createType("V"), options.itemFactory.createType("I")), + options.itemFactory.createString("<init>")), + false), + new CfStore(ValueType.OBJECT, 3), + label8, + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 4), + label9, + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 1), + new CfIfCmp(If.Type.GE, ValueType.INT, label12), + label10, + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.OBJECT, 0), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createType("Ljava/lang/String;")), + options.itemFactory.createString("append")), + false), + new CfStackInstruction(CfStackInstruction.Opcode.Pop), + label11, + new CfIinc(4, 1), + new CfGoto(label9), + label12, + new CfLoad(ValueType.OBJECT, 3), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/String;")), + options.itemFactory.createString("toString")), + false), + new CfReturn(ValueType.OBJECT), + label13), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode StringMethods_strip(InternalOptions options, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + CfLabel label12 = new CfLabel(); + CfLabel label13 = new CfLabel(); + CfLabel label14 = new CfLabel(); + CfLabel label15 = new CfLabel(); + return new CfCode( + method.holder, + 3, + 4, + ImmutableList.of( + label0, + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 1), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/String;"), + options.itemFactory.createProto(options.itemFactory.createType("I")), + options.itemFactory.createString("length")), + false), + new CfStore(ValueType.INT, 2), + label2, + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfIfCmp(If.Type.GE, ValueType.INT, label8), + label3, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/String;"), + options.itemFactory.createProto( + options.itemFactory.createType("I"), options.itemFactory.createType("I")), + options.itemFactory.createString("codePointAt")), + false), + new CfStore(ValueType.INT, 3), + label4, + new CfLoad(ValueType.INT, 3), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/Character;"), + options.itemFactory.createProto( + options.itemFactory.createType("Z"), options.itemFactory.createType("I")), + options.itemFactory.createString("isWhitespace")), + false), + new CfIf(If.Type.NE, ValueType.INT, label6), + label5, + new CfGoto(label8), + label6, + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 3), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/Character;"), + options.itemFactory.createProto( + options.itemFactory.createType("I"), options.itemFactory.createType("I")), + options.itemFactory.createString("charCount")), + false), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfStore(ValueType.INT, 1), + label7, + new CfGoto(label2), + label8, + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfIfCmp(If.Type.LE, ValueType.INT, label14), + label9, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 2), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/Character;"), + options.itemFactory.createProto( + options.itemFactory.createType("I"), + options.itemFactory.createType("Ljava/lang/CharSequence;"), + options.itemFactory.createType("I")), + options.itemFactory.createString("codePointBefore")), + false), + new CfStore(ValueType.INT, 3), + label10, + new CfLoad(ValueType.INT, 3), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/Character;"), + options.itemFactory.createProto( + options.itemFactory.createType("Z"), options.itemFactory.createType("I")), + options.itemFactory.createString("isWhitespace")), + false), + new CfIf(If.Type.NE, ValueType.INT, label12), + label11, + new CfGoto(label14), + label12, + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 3), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/Character;"), + options.itemFactory.createProto( + options.itemFactory.createType("I"), options.itemFactory.createType("I")), + options.itemFactory.createString("charCount")), + false), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 2), + label13, + new CfGoto(label8), + label14, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/String;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/String;"), + options.itemFactory.createType("I"), + options.itemFactory.createType("I")), + options.itemFactory.createString("substring")), + false), + new CfReturn(ValueType.OBJECT), + label15), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode StringMethods_stripLeading(InternalOptions options, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + return new CfCode( + method.holder, + 3, + 4, + ImmutableList.of( + label0, + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 1), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/String;"), + options.itemFactory.createProto(options.itemFactory.createType("I")), + options.itemFactory.createString("length")), + false), + new CfStore(ValueType.INT, 2), + label2, + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfIfCmp(If.Type.GE, ValueType.INT, label8), + label3, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/String;"), + options.itemFactory.createProto( + options.itemFactory.createType("I"), options.itemFactory.createType("I")), + options.itemFactory.createString("codePointAt")), + false), + new CfStore(ValueType.INT, 3), + label4, + new CfLoad(ValueType.INT, 3), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/Character;"), + options.itemFactory.createProto( + options.itemFactory.createType("Z"), options.itemFactory.createType("I")), + options.itemFactory.createString("isWhitespace")), + false), + new CfIf(If.Type.NE, ValueType.INT, label6), + label5, + new CfGoto(label8), + label6, + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 3), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/Character;"), + options.itemFactory.createProto( + options.itemFactory.createType("I"), options.itemFactory.createType("I")), + options.itemFactory.createString("charCount")), + false), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfStore(ValueType.INT, 1), + label7, + new CfGoto(label2), + label8, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/String;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/String;"), + options.itemFactory.createType("I"), + options.itemFactory.createType("I")), + options.itemFactory.createString("substring")), + false), + new CfReturn(ValueType.OBJECT), + label9), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode StringMethods_stripTrailing(InternalOptions options, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + return new CfCode( + method.holder, + 3, + 3, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/String;"), + options.itemFactory.createProto(options.itemFactory.createType("I")), + options.itemFactory.createString("length")), + false), + new CfStore(ValueType.INT, 1), + label1, + new CfLoad(ValueType.INT, 1), + new CfIf(If.Type.LE, ValueType.INT, label7), + label2, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/Character;"), + options.itemFactory.createProto( + options.itemFactory.createType("I"), + options.itemFactory.createType("Ljava/lang/CharSequence;"), + options.itemFactory.createType("I")), + options.itemFactory.createString("codePointBefore")), + false), + new CfStore(ValueType.INT, 2), + label3, + new CfLoad(ValueType.INT, 2), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/Character;"), + options.itemFactory.createProto( + options.itemFactory.createType("Z"), options.itemFactory.createType("I")), + options.itemFactory.createString("isWhitespace")), + false), + new CfIf(If.Type.NE, ValueType.INT, label5), + label4, + new CfGoto(label7), + label5, + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/Character;"), + options.itemFactory.createProto( + options.itemFactory.createType("I"), options.itemFactory.createType("I")), + options.itemFactory.createString("charCount")), + false), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 1), + label6, + new CfGoto(label1), + label7, + new CfLoad(ValueType.OBJECT, 0), + new CfConstNumber(0, ValueType.INT), + new CfLoad(ValueType.INT, 1), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/String;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/String;"), + options.itemFactory.createType("I"), + options.itemFactory.createType("I")), + options.itemFactory.createString("substring")), + false), + new CfReturn(ValueType.OBJECT), + label8), + ImmutableList.of(), + ImmutableList.of()); + } }
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 04dd590..527874a 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,6 +9,7 @@ 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; @@ -83,7 +84,6 @@ import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo; import com.android.tools.r8.utils.InternalOptions; -import com.android.tools.r8.utils.InternalOptions.AssertionProcessing; import com.android.tools.r8.utils.InternalOutputMode; import com.android.tools.r8.utils.LongInterval; import com.android.tools.r8.utils.SetUtils; @@ -1320,7 +1320,7 @@ */ public void processAssertions( AppView<?> appView, DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) { - assert appView.options().assertionProcessing != AssertionProcessing.LEAVE; + 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. @@ -1356,7 +1356,9 @@ if (staticGet.getField().name == dexItemFactory.assertionsDisabled) { iterator.replaceCurrentInstruction( code.createIntConstant( - appView.options().assertionProcessing == AssertionProcessing.REMOVE ? 1 : 0)); + appView.options().assertionTransformation == AssertionTransformation.DISABLE + ? 1 + : 0)); } } }
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java index 6aee26c..1600057 100644 --- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java +++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -113,7 +113,9 @@ } String markerString = marker.toString(); for (DexProgramClass clazz : application.classes()) { - if (clazz.getSynthesizedFrom().isEmpty() || options.isDesugaredLibraryCompilation()) { + if (clazz.getSynthesizedFrom().isEmpty() + || options.isDesugaredLibraryCompilation() + || options.enableCfInterfaceMethodDesugaring) { writeClass(clazz, consumer, markerString); } else { throw new Unimplemented("No support for synthetics in the Java bytecode backend."); @@ -201,8 +203,11 @@ // In this case bridges have been introduced for the Cf back-end, // which do not have class file version. assert options.testing.enableForceNestBasedAccessDesugaringForTest - || options.isDesugaredLibraryCompilation(); - return 0; + || options.isDesugaredLibraryCompilation() + || options.enableCfInterfaceMethodDesugaring; + // TODO(b/146424042): We may call static methods on interface classes so we have to go for + // version 52. + return options.enableCfInterfaceMethodDesugaring ? 52 : 0; } return method.getClassFileVersion(); }
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java index 73ab96a..1b34adf 100644 --- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java +++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -11,8 +11,10 @@ import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import it.unimi.dsi.fastutil.objects.Object2IntArrayMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; +import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -37,12 +39,30 @@ public final Intrinsics intrinsics; public final Metadata metadata; + final Map<DexType, DexType> knownTypeConversion; + public Kotlin(DexItemFactory factory) { this.factory = factory; this.functional = new Functional(); this.intrinsics = new Intrinsics(); this.metadata = new Metadata(); + + this.knownTypeConversion = + ImmutableMap.<DexType, DexType>builder() + // https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/index.html + .put(factory.booleanType, factory.createType(addKotlinPrefix("Boolean;"))) + .put(factory.byteType, factory.createType(addKotlinPrefix("Byte;"))) + .put(factory.charType, factory.createType(addKotlinPrefix("Character;"))) + .put(factory.shortType, factory.createType(addKotlinPrefix("Short;"))) + .put(factory.intType, factory.createType(addKotlinPrefix("Int;"))) + .put(factory.longType, factory.createType(addKotlinPrefix("Long;"))) + .put(factory.floatType, factory.createType(addKotlinPrefix("Float;"))) + .put(factory.doubleType, factory.createType(addKotlinPrefix("Double;"))) + .put(factory.voidType, factory.createType(addKotlinPrefix("Unit;"))) + .put(factory.stringType, factory.createType(addKotlinPrefix("String;"))) + // TODO(b/70169921): Collections? + .build(); } public final class Functional {
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 9f77f7a..e76bbcf 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -4,15 +4,21 @@ package com.android.tools.r8.kotlin; +import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix; +import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toKmType; +import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunction; import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmType; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; import java.util.List; import kotlinx.metadata.KmClass; +import kotlinx.metadata.KmFunction; +import kotlinx.metadata.KmProperty; import kotlinx.metadata.KmType; import kotlinx.metadata.jvm.KotlinClassHeader; import kotlinx.metadata.jvm.KotlinClassMetadata; @@ -50,9 +56,23 @@ } } assert clazz.superType != null; - KmType kmTypeForSupertype = toRenamedKmType(clazz.superType, appView, lens); - if (kmTypeForSupertype != null) { - superTypes.add(kmTypeForSupertype); + if (clazz.superType != appView.dexItemFactory().objectType) { + KmType kmTypeForSupertype = toRenamedKmType(clazz.superType, appView, lens); + if (kmTypeForSupertype != null) { + superTypes.add(kmTypeForSupertype); + } + } else if (clazz.isInterface()) { + superTypes.add(toKmType(addKotlinPrefix("Any;"))); + } + + List<KmFunction> functions = kmClass.getFunctions(); + 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); + } } }
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 4400273..0e195e7 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -3,17 +3,44 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.kotlin; +import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix; +import static com.android.tools.r8.utils.DescriptorUtils.descriptorToInternalName; +import static kotlinx.metadata.FlagsKt.flagsOf; + import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DebugLocalInfo; import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.DescriptorUtils; +import java.util.List; +import kotlinx.metadata.KmFunction; import kotlinx.metadata.KmType; +import kotlinx.metadata.KmValueParameter; class KotlinMetadataSynthesizer { + static KmType toKmType(String descriptor) { + KmType kmType = new KmType(flagsOf()); + kmType.visitClass(descriptorToInternalName(descriptor)); + return kmType; + } + static KmType toRenamedKmType( DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) { + // E.g., [Ljava/lang/String; -> Lkotlin/Array; + if (type.isArrayType()) { + return toKmType(addKotlinPrefix("Array;")); + } + // E.g., void -> Lkotlin/Unit; + if (appView.dexItemFactory().kotlin.knownTypeConversion.containsKey(type)) { + KmType kmType = new KmType(flagsOf()); + DexType convertedType = appView.dexItemFactory().kotlin.knownTypeConversion.get(type); + assert convertedType != null; + kmType.visitClass(descriptorToInternalName(convertedType.toDescriptorString())); + return kmType; + } DexClass clazz = appView.definitionFor(type); if (clazz == null) { return null; @@ -27,9 +54,48 @@ // For library or classpath class, we should not have renamed it. assert clazz.isProgramClass() || renamedType == type : type.toSourceString() + " -> " + renamedType.toSourceString(); - // TODO(b/70169921): Consult kotlinx.metadata.Flag for kotlin-specific flags (e.g., sealed). - KmType kmType = new KmType(clazz.accessFlags.getAsCfAccessFlags()); - kmType.visitClass(DescriptorUtils.descriptorToInternalName(renamedType.toDescriptorString())); + // TODO(b/70169921): Mysterious, why attempts to properly set flags bothers kotlinc? + // and/or why wiping out flags works for KmType but not KmFunction?! + KmType kmType = new KmType(flagsOf()); + kmType.visitClass(descriptorToInternalName(renamedType.toDescriptorString())); return kmType; } + + static KmFunction toRenamedKmFunction( + DexMethod method, AppView<AppInfoWithLiveness> appView, NamingLens lens) { + DexEncodedMethod encodedMethod = appView.definitionFor(method); + if (encodedMethod == null) { + return null; + } + // For library overrides, synthesize @Metadata always. + // For regular methods, make sure it is live. + if (!encodedMethod.isLibraryMethodOverride().isTrue() + && !appView.appInfo().liveMethods.contains(method)) { + return null; + } + DexMethod renamedMethod = lens.lookupMethod(method, appView.dexItemFactory()); + // For a library method override, we should not have renamed it. + assert !encodedMethod.isLibraryMethodOverride().isTrue() || renamedMethod == method + : method.toSourceString() + " -> " + renamedMethod.toSourceString(); + // TODO(b/70169921): Consult kotlinx.metadata.Flag.Function for kind (e.g., suspend). + KmFunction kmFunction = + new KmFunction(encodedMethod.accessFlags.getAsKotlinFlags(), renamedMethod.name.toString()); + KmType kmReturnType = toRenamedKmType(method.proto.returnType, appView, lens); + assert kmReturnType != null; + kmFunction.setReturnType(kmReturnType); + 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); + // TODO(b/70169921): Consult kotlinx.metadata.Flag.ValueParameter. + KmValueParameter kmValueParameter = new KmValueParameter(flagsOf(), parameterName); + KmType kmParamType = toRenamedKmType(paramType, appView, lens); + assert kmParamType != null; + kmValueParameter.setType(kmParamType); + parameters.add(kmValueParameter); + } + return kmFunction; + } }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java index 4778c35..ab9e92f 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -214,8 +214,14 @@ do { skipWhitespace(); } while (parseOption()); + // This may be unknown, but we want to always ensure that we don't attribute lines to the + // wrong configuration. + configurationBuilder.addParsedConfiguration( + "# The proguard configuration file for the following section is " + origin.toString()); + // Collect the parsed configuration. configurationBuilder.addParsedConfiguration(contents.substring(positionAfterInclude)); + configurationBuilder.addParsedConfiguration("# End of content from " + origin.toString()); } private boolean parseOption() throws ProguardRuleParserException {
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 a5f672e..c4815ae 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -5,6 +5,7 @@ import static com.google.common.base.Predicates.not; +import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation; import com.android.tools.r8.ClassFileConsumer; import com.android.tools.r8.CompilationMode; import com.android.tools.r8.DataResourceConsumer; @@ -74,15 +75,6 @@ ON } - public enum AssertionProcessing { - /** Leave the conditional javac generated assertion code untouched. */ - LEAVE, - /** Remove the javac generated assertion code completely. */ - REMOVE, - /** Enable the javac generated assertion code unconditionally at compile time. */ - ENABLE - } - public static final int SUPPORTED_CF_MAJOR_VERSION = Opcodes.V11; public static final int SUPPORTED_DEX_VERSION = AndroidApiLevel.LATEST.getDexVersion().getIntValue(); @@ -225,6 +217,7 @@ public boolean encodeChecksums = false; public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true; public boolean enableSourceDebugExtensionRewriter = false; + public boolean enableCfInterfaceMethodDesugaring = false; public int callGraphLikelySpuriousCallEdgeThreshold = 50; @@ -442,7 +435,7 @@ public boolean ignoreMissingClasses = false; // EXPERIMENTAL flag to get behaviour as close to Proguard as possible. public boolean forceProguardCompatibility = false; - public AssertionProcessing assertionProcessing = AssertionProcessing.REMOVE; + public AssertionTransformation assertionTransformation = null; public boolean configurationDebugging = false; // Don't convert Code objects to IRCode. @@ -1132,7 +1125,7 @@ } return enableDesugaring && interfaceMethodDesugaring == OffOrAuto.Auto - && !canUseDefaultAndStaticInterfaceMethods(); + && (!canUseDefaultAndStaticInterfaceMethods() || enableCfInterfaceMethodDesugaring); } public boolean isStringSwitchConversionEnabled() {
diff --git a/src/main/java/com/android/tools/r8/utils/SegmentTree.java b/src/main/java/com/android/tools/r8/utils/SegmentTree.java new file mode 100644 index 0000000..1fc95b0 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/SegmentTree.java
@@ -0,0 +1,82 @@ +// 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 java.util.Map; +import java.util.TreeMap; + +/** + * Implementation of a discrete segment tree where intervals are specified by their start end and + * point. Both points are considered part of the interval. + */ +public class SegmentTree<V> { + + private final TreeMap<Integer, V> internalTree = new TreeMap<>(); + private final boolean allowIntervalOverwrites; + + private int size = 0; + + public SegmentTree(boolean allowIntervalOverwrites) { + this.allowIntervalOverwrites = allowIntervalOverwrites; + } + + public V find(int point) { + Map.Entry<Integer, V> entry = findEntry(point); + return entry != null ? entry.getValue() : null; + } + + public Map.Entry<Integer, V> findEntry(Integer point) { + Map.Entry<Integer, V> kvEntry = internalTree.floorEntry(point); + return (kvEntry == null || kvEntry.getValue() == null) ? null : kvEntry; + } + + public SegmentTree<V> add(int start, int end, V value) { + Map.Entry<Integer, V> existingEndRange = findEntry(end); + Box<Integer> removedIntervals = new Box<>(0); + boolean removedKeys = + internalTree + .navigableKeySet() + .removeIf( + key -> { + if (start < key && key <= end) { + assert allowIntervalOverwrites; + if (internalTree.get(key) != null) { + removedIntervals.set(removedIntervals.get() + 1); + } + return true; + } + return false; + }); + if (existingEndRange != null) { + assert allowIntervalOverwrites; + if (removedKeys) { + // We have counted a removed interval where it was actually just shortened: + // I1 |----------------------------------| + // I2 |--------| + // R |--------||---------------------------| + // I1.start has been removed and counted, but it is actually just moved, so we decrease the + // removed counter by 1. + removedIntervals.set(removedIntervals.get() - 1); + } + } + internalTree.put(start, value); + if (!internalTree.containsKey(end + 1)) { + internalTree.put(end + 1, existingEndRange == null ? null : existingEndRange.getValue()); + } + // We only count unique intervals, thus the following is two and not three intervals when + // adding I2: + // I1 |-------------------------------| + // I2 |------------| + // R |------||------------||---------| + // However, if the order was reversed, we should remove one from the size count since I1 + // completely shadows I2. + size = (size - removedIntervals.get()) + 1; + return this; + } + + public int size() { + return size; + } +}
diff --git a/src/test/examplesJava11/backport/StringBackportJava11Main.java b/src/test/examplesJava11/backport/StringBackportJava11Main.java new file mode 100644 index 0000000..6af33fa --- /dev/null +++ b/src/test/examplesJava11/backport/StringBackportJava11Main.java
@@ -0,0 +1,131 @@ +package backport; + +public final class StringBackportJava11Main { + public static void main(String[] args) { + testRepeat(); + testIsBlank(); + testStrip(); + testStripLeading(); + testStripTrailing(); + } + + private static void testRepeat() { + try { + throw new AssertionError("hey".repeat(-1)); + } catch (IllegalArgumentException e) { + assertEquals("count is negative: -1", e.getMessage()); + } + + assertEquals("", "".repeat(0)); + assertEquals("", "".repeat(1)); + assertEquals("", "".repeat(2)); + + assertEquals("", "hey".repeat(0)); + assertEquals("hey", "hey".repeat(1)); + assertEquals("heyhey", "hey".repeat(2)); + assertEquals("heyheyhey", "hey".repeat(3)); + assertEquals("heyheyheyhey", "hey".repeat(4)); + } + + /** Per {@link Character#isWhitespace(int)} */ + private static final String WHITESPACE = "" + // Unicode "Zs" category: + + "\u0020" + + "\u1680" + //+ "\u00A0" Exception per Javadoc + + "\u1680" + + "\u2000" + + "\u2001" + + "\u2002" + + "\u2003" + + "\u2004" + + "\u2005" + + "\u2006" + //+ "\u2007" Exception per Javadoc + + "\u2008" + + "\u2009" + + "\u200A" + //+ "\u200F" Exception per Javadoc + //+ "\u205F" Not honored on Android 4.0.4 + + "\u3000" + // Unicode "Zl" category: + + "\u2028" + // Unicode "Zp" category: + + "\u2029" + // Others: + + "\t" + + "\n" + + "\u000B" + + "\f" + + "\r" + + "\u001C" + + "\u001D" + + "\u001E" + + "\u001F" + ; + + public static void testIsBlank() { + assertEquals(true, "".isBlank()); + assertEquals(true, WHITESPACE.isBlank()); + + // Android <=4.0.4 does not recognize this as whitespace. Just ensure local consistency. + assertEquals(Character.isWhitespace(0x205F), "\u205F".isBlank()); + + assertEquals(false, "a".isBlank()); + assertEquals(false, "å".isBlank()); + assertEquals(false, "a\u030A".isBlank()); + assertEquals(false, "\uD83D\uDE00".isBlank()); + assertEquals(false, (WHITESPACE + "a").isBlank()); + assertEquals(false, ("a" + WHITESPACE).isBlank()); + } + + public static void testStrip() { + assertEquals("", "".strip()); + assertEquals("", WHITESPACE.strip()); + assertEquals("a", "a".strip()); + assertEquals("a", (WHITESPACE + "a").strip()); + assertEquals("a", ("a" + WHITESPACE).strip()); + assertEquals("a", (WHITESPACE + "a" + WHITESPACE).strip()); + assertEquals("a" + WHITESPACE + "a", ("a" + WHITESPACE + "a").strip()); + assertEquals("a" + WHITESPACE + "a", (WHITESPACE + "a" + WHITESPACE + "a").strip()); + assertEquals("a" + WHITESPACE + "a", ("a" + WHITESPACE + "a" + WHITESPACE).strip()); + assertEquals("a" + WHITESPACE + "a", + (WHITESPACE + "a" + WHITESPACE + "a" + WHITESPACE).strip()); + } + + public static void testStripLeading() { + assertEquals("", "".stripLeading()); + assertEquals("", WHITESPACE.stripLeading()); + assertEquals("a", "a".stripLeading()); + assertEquals("a", (WHITESPACE + "a").stripLeading()); + assertEquals("a" + WHITESPACE, ("a" + WHITESPACE).stripLeading()); + assertEquals("a" + WHITESPACE, (WHITESPACE + "a" + WHITESPACE).stripLeading()); + assertEquals("a" + WHITESPACE + "a", ("a" + WHITESPACE + "a").stripLeading()); + assertEquals("a" + WHITESPACE + "a", (WHITESPACE + "a" + WHITESPACE + "a").stripLeading()); + assertEquals("a" + WHITESPACE + "a" + WHITESPACE, + ("a" + WHITESPACE + "a" + WHITESPACE).stripLeading()); + assertEquals("a" + WHITESPACE + "a" + WHITESPACE, + (WHITESPACE + "a" + WHITESPACE + "a" + WHITESPACE).stripLeading()); + } + + public static void testStripTrailing() { + assertEquals("", "".stripTrailing()); + assertEquals("", WHITESPACE.stripTrailing()); + assertEquals("a", "a".stripTrailing()); + assertEquals(WHITESPACE + "a", (WHITESPACE + "a").stripTrailing()); + assertEquals("a", ("a" + WHITESPACE).stripTrailing()); + assertEquals(WHITESPACE + "a", (WHITESPACE + "a" + WHITESPACE).stripTrailing()); + assertEquals("a" + WHITESPACE + "a", ("a" + WHITESPACE + "a").stripTrailing()); + assertEquals(WHITESPACE + "a" + WHITESPACE + "a", + (WHITESPACE + "a" + WHITESPACE + "a").stripTrailing()); + assertEquals("a" + WHITESPACE + "a", ("a" + WHITESPACE + "a" + WHITESPACE).stripTrailing()); + assertEquals(WHITESPACE + "a" + WHITESPACE + "a", + (WHITESPACE + "a" + WHITESPACE + "a" + WHITESPACE).stripTrailing()); + } + + private static void assertEquals(Object expected, Object actual) { + if (expected != actual && (expected == null || !expected.equals(actual))) { + throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>'); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java index e3f52a1..e04a2c1 100644 --- a/src/test/java/com/android/tools/r8/TestCompileResult.java +++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -204,6 +204,24 @@ return self(); } + public CR enableRuntimeAssertions(boolean enable) { + if (getBackend() == Backend.CF) { + if (enable) { + enableRuntimeAssertions(); + } + } else { + // Assertions cannot be enabled on dex VMs. + assert !enable; + } + + if (enable) { + if (!this.vmArguments.contains("-ea")) { + this.vmArguments.add("-ea"); + } + } + return self(); + } + public Path writeToZip() throws IOException { Path file = state.getNewTempFolder().resolve("out.zip"); writeToZip(file);
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java index 3b368b6..0f613d5 100644 --- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java +++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -21,6 +21,7 @@ import java.util.List; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; public abstract class TestCompilerBuilder< @@ -42,7 +43,7 @@ // Default initialized setup. Can be overwritten if needed. private boolean useDefaultRuntimeLibrary = true; - final List<Path> additionalRunClassPath = new ArrayList<>(); + private final List<Path> additionalRunClassPath = new ArrayList<>(); private ProgramConsumer programConsumer; private StringConsumer mainDexListConsumer; private AndroidApiLevel defaultMinApiLevel = ToolHelper.getMinApiLevelForDexVm(); @@ -279,12 +280,6 @@ return self(); } - @Deprecated - public T enableCoreLibraryDesugaring() { - // TODO(b/134732760): Use the other API instead. - return enableCoreLibraryDesugaring(AndroidApiLevel.B); - } - public T enableCoreLibraryDesugaring(AndroidApiLevel minAPILevel) { return enableCoreLibraryDesugaring(minAPILevel, null); } @@ -299,4 +294,11 @@ additionalRunClassPath.addAll(files); return self(); } + + public T addAssertionsConfiguration( + Function<AssertionsConfiguration.Builder, AssertionsConfiguration> + assertionsConfigurationGenerator) { + builder.addAssertionsConfiguration(assertionsConfigurationGenerator); + return self(); + } }
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java index 10d74a8..feda70f 100644 --- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java +++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java
@@ -3,8 +3,15 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.desugar; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.CompilationMode; import com.android.tools.r8.D8TestCompileResult; import com.android.tools.r8.JvmTestBuilder; +import com.android.tools.r8.R8FullTestBuilder; +import com.android.tools.r8.R8TestCompileResult; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.debug.DebugTestBase; import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command; import com.android.tools.r8.debug.DebugTestConfig; @@ -17,25 +24,43 @@ import java.util.Collections; 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 DefaultLambdaWithUnderscoreThisTestRunner extends DebugTestBase { + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public DefaultLambdaWithUnderscoreThisTestRunner(TestParameters parameters) { + this.parameters = parameters; + } + final Class<?> CLASS = DefaultLambdaWithUnderscoreThisTest.class; final String EXPECTED = StringUtils .lines("stateful(My _this variable foo Another ___this variable)"); - private void runDebugger(DebugTestConfig config) throws Throwable { + private void runDebugger(DebugTestConfig config, boolean desugared) throws Throwable { MethodReference main = Reference.methodFromMethod(CLASS.getMethod("main", String[].class)); MethodReference stateful = Reference.methodFromMethod(I.class.getMethod("stateful")); - Function<String, Command> checkThis = (String desugarThis) -> conditional((state) -> - state.isCfRuntime() - ? Collections.singletonList(checkLocal("this")) - : ImmutableList.of( - checkNoLocal("this"), - checkLocal(desugarThis))); + Function<String, Command> checkThis = + (String desugarThis) -> + conditional( + (state) -> + !desugared + ? Collections.singletonList(checkLocal("this")) + : ImmutableList.of(checkNoLocal("this"), checkLocal(desugarThis))); - runDebugTest(config, CLASS, + runDebugTest( + config, + CLASS, breakpoint(main, 22), run(), checkLine(22), @@ -55,27 +80,53 @@ checkLine(15), // Desugaring will insert '____this' in place of 'this' here. checkThis.apply("____this"), - checkLocals("_this", "___this"), + checkLocals("_this", "___this"), run()); } @Test public void testJvm() throws Throwable { + assumeTrue(parameters.isCfRuntime()); JvmTestBuilder builder = testForJvm().addTestClasspath(); - builder.run(CLASS).assertSuccessWithOutput(EXPECTED); - runDebugger(builder.debugConfig()); + builder.run(parameters.getRuntime(), CLASS).assertSuccessWithOutput(EXPECTED); + runDebugger(builder.debugConfig(), false); } @Test public void testD8() throws Throwable { - D8TestCompileResult compileResult = testForD8() - .addProgramClassesAndInnerClasses(CLASS) - .setMinApiThreshold(AndroidApiLevel.K) - .compile(); + assumeTrue(parameters.isDexRuntime()); + D8TestCompileResult compileResult = + testForD8() + .addProgramClassesAndInnerClasses(CLASS) + .setMinApiThreshold(AndroidApiLevel.K) + .compile(); compileResult // TODO(b/123506120): Add .assertNoMessages() - .run(CLASS) + .run(parameters.getRuntime(), CLASS) .assertSuccessWithOutput(EXPECTED); - runDebugger(compileResult.debugConfig()); + runDebugger(compileResult.debugConfig(), true); + } + + @Test + public void testR8() throws Throwable { + R8FullTestBuilder r8FullTestBuilder = + testForR8(parameters.getBackend()) + .addProgramClassesAndInnerClasses(CLASS) + .addKeepAllClassesRule() + .noMinification() + .setMode(CompilationMode.DEBUG) + .addOptionsModification( + internalOptions -> { + if (parameters.isCfRuntime()) { + internalOptions.enableDesugaring = true; + internalOptions.enableCfInterfaceMethodDesugaring = true; + } + }); + if (parameters.isDexRuntime()) { + r8FullTestBuilder.setMinApiThreshold(AndroidApiLevel.K); + } + R8TestCompileResult compileResult = r8FullTestBuilder.compile(); + compileResult.run(parameters.getRuntime(), CLASS).assertSuccessWithOutput(EXPECTED); + runDebugger(compileResult.debugConfig(), true); } }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/StringBackportJava11Test.java b/src/test/java/com/android/tools/r8/desugar/backports/StringBackportJava11Test.java new file mode 100644 index 0000000..b7f4b6d --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/backports/StringBackportJava11Test.java
@@ -0,0 +1,37 @@ +// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.desugar.backports; + +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.ToolHelper; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION; + +@RunWith(Parameterized.class) +public final class StringBackportJava11Test extends AbstractBackportTest { + @Parameters(name = "{0}") + public static Iterable<?> data() { + return getTestParameters() + .withDexRuntimes() + .withAllApiLevels() + .withCfRuntimesStartingFromIncluding(CfVm.JDK11) + .build(); + } + + private static final Path TEST_JAR = + Paths.get(ToolHelper.EXAMPLES_JAVA11_JAR_DIR).resolve("backport" + JAR_EXTENSION); + + public StringBackportJava11Test(TestParameters parameters) { + super(parameters, String.class, TEST_JAR, "backport.StringBackportJava11Main"); + // Note: None of the methods in this test exist in the latest android.jar. If/when they ship in + // an actual API level, migrate these tests to CharacterBackportTest. + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FeatureSplitTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FeatureSplitTest.java new file mode 100644 index 0000000..d4f08fc --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FeatureSplitTest.java
@@ -0,0 +1,264 @@ +// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.desugar.desugaredlibrary; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.ToolHelper.ArtCommandBuilder; +import com.android.tools.r8.ToolHelper.ProcessResult; +import com.android.tools.r8.dexsplitter.SplitterTestBase; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.BooleanUtils; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import dalvik.system.PathClassLoader; +import java.io.IOException; +import java.net.MalformedURLException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class FeatureSplitTest extends DesugaredLibraryTestBase { + + private final TestParameters parameters; + private final boolean shrinkDesugaredLibrary; + + @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}") + public static List<Object[]> data() { + return buildParameters( + BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build()); + } + + public FeatureSplitTest(boolean shrinkDesugaredLibrary, TestParameters parameters) { + this.shrinkDesugaredLibrary = shrinkDesugaredLibrary; + this.parameters = parameters; + } + + @Test + public void testTwoFeatures() throws CompilationFailedException, IOException, ExecutionException { + CompiledWithFeature compiledWithFeature = new CompiledWithFeature().invoke(this); + Path basePath = compiledWithFeature.getBasePath(); + Path feature1Path = compiledWithFeature.getFeature1Path(); + Path feature2Path = compiledWithFeature.getFeature2Path(); + Path desugaredLibrary = compiledWithFeature.getDesugaredLibrary(); + + assertKeepThe3StreamMethods(compiledWithFeature.getKeepRules()); + + assertClassPresent(basePath, BaseClass.class); + assertClassPresent(feature1Path, FeatureClass.class); + assertClassPresent(feature2Path, FeatureClass2.class); + + verifyRun(BaseClass.class, basePath, desugaredLibrary, null, "42"); + verifyRun(FeatureClass.class, basePath, desugaredLibrary, feature1Path, "1"); + verifyRun(FeatureClass2.class, basePath, desugaredLibrary, feature2Path, "7"); + } + + private void assertKeepThe3StreamMethods(String keepRules) { + // Stream desugaring is not needed >= N. + if (parameters.getApiLevel().getLevel() >= AndroidApiLevel.N.getLevel()) { + return; + } + // Ensure count, toArray and forEach are kept. + assertTrue( + keepRules.contains( + "-keep class j$.lang.Iterable$-EL {\n" + + " void forEach(java.lang.Iterable, j$.util.function.Consumer);")); + assertTrue( + keepRules.contains( + "-keep class j$.util.stream.Stream {\n" + + " long count();\n" + + " java.lang.Object[] toArray();")); + } + + private void assertClassPresent(Path appPath, Class<?> present) + throws IOException, ExecutionException { + CodeInspector inspector = new CodeInspector(appPath); + assertTrue(inspector.clazz(present).isPresent()); + } + + private void verifyRun( + Class<?> toRun, + Path basePath, + Path desugaredLibrary, + Path splitterFeatureDexFile, + String expectedResult) + throws IOException { + ProcessResult result = + runFeatureOnArt( + toRun, desugaredLibrary, basePath, splitterFeatureDexFile, parameters.getRuntime()); + assertEquals(result.exitCode, 0); + assertEquals(result.stdout, StringUtils.lines(expectedResult)); + } + + protected ProcessResult runFeatureOnArt( + Class toRun, + Path desugaredLibrary, + Path splitterBaseDexFile, + Path splitterFeatureDexFile, + TestRuntime runtime) + throws IOException { + assumeTrue(runtime.isDex()); + ArtCommandBuilder commandBuilder = new ArtCommandBuilder(runtime.asDex().getVm()); + commandBuilder.appendClasspath(splitterBaseDexFile.toString()); + if (desugaredLibrary != null) { + commandBuilder.appendClasspath(desugaredLibrary.toString()); + } + commandBuilder.appendProgramArgument(toRun.getName()); + if (splitterFeatureDexFile != null) { + commandBuilder.appendProgramArgument(splitterFeatureDexFile.toString()); + } + commandBuilder.setMainClass(SplitRunner.class.getName()); + return ToolHelper.runArtRaw(commandBuilder); + } + + public interface RunInterface { + + void run(); + } + + // Base using ForEach. + public static class BaseClass implements RunInterface { + + @Override + public void run() { + ArrayList<Integer> list = new ArrayList<>(); + list.add(42); + list.forEach(System.out::println); + } + } + + // Feature using count. + public static class FeatureClass implements RunInterface { + + @SuppressWarnings("ReplaceInefficientStreamCount") + @Override + public void run() { + ArrayList<Object> list = new ArrayList<>(); + list.add(new Object()); + System.out.println(list.stream().count()); + } + } + + // Feature using toArray. + public static class FeatureClass2 implements RunInterface { + + @SuppressWarnings("SimplifyStreamApiCallChains") + @Override + public void run() { + ArrayList<Integer> list = new ArrayList<>(); + list.add(7); + System.out.println(list.stream().toArray()[0]); + } + } + + static class SplitRunner { + + /* We support two different modes: + * - One argument to main: + * Pass in the class to be loaded, must implement RunInterface, run will be called. + * - Two arguments to main: + * Pass in the class to be loaded, must implement RunInterface, run will be called. + * Pass in the feature split that we class load. + */ + public static void main(String[] args) { + if (args.length < 1 || args.length > 2) { + throw new RuntimeException("Unsupported number of arguments"); + } + String classToRun = args[0]; + ClassLoader loader = SplitRunner.class.getClassLoader(); + // In the case where we simulate splits, we pass in the feature as the second argument + if (args.length == 2) { + try { + loader = new PathClassLoader(args[1], SplitRunner.class.getClassLoader()); + } catch (MalformedURLException e) { + throw new RuntimeException("Failed reading input URL"); + } + } + + try { + Class<?> aClass = loader.loadClass(classToRun); + RunInterface b = (RunInterface) aClass.newInstance(); + b.run(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { + throw new RuntimeException("Failed loading class"); + } + } + } + + private class CompiledWithFeature { + + private Path basePath; + private Path feature1Path; + private Path feature2Path; + private Path desugaredLibrary; + private String keepRules = ""; + + public Path getBasePath() { + return basePath; + } + + public Path getFeature1Path() { + return feature1Path; + } + + public Path getFeature2Path() { + return feature2Path; + } + + public Path getDesugaredLibrary() { + return desugaredLibrary; + } + + public String getKeepRules() { + return keepRules; + } + + public CompiledWithFeature invoke(FeatureSplitTest tester) + throws IOException, CompilationFailedException { + + basePath = temp.newFile("base.zip").toPath(); + feature1Path = temp.newFile("feature1.zip").toPath(); + feature2Path = temp.newFile("feature2.zip").toPath(); + + KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); + testForR8(parameters.getBackend()) + .addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class) + .setMinApi(parameters.getApiLevel()) + .addFeatureSplit( + builder -> + SplitterTestBase.simpleSplitProvider( + builder, feature1Path, temp, FeatureClass.class)) + .addFeatureSplit( + builder -> + SplitterTestBase.simpleSplitProvider( + builder, feature2Path, temp, FeatureClass2.class)) + .addKeepAllClassesRule() + .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) + .compile() + .writeToZip(basePath); + // Stream desugaring is not needed >= N. + if (parameters.getApiLevel().getLevel() >= AndroidApiLevel.N.getLevel()) { + return this; + } + desugaredLibrary = + tester.buildDesugaredLibrary( + parameters.getApiLevel(), keepRuleConsumer.get(), shrinkDesugaredLibrary); + keepRules = keepRuleConsumer.get(); + return this; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/StaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/StaticInterfaceMethodTest.java index 725bf3f..84917f1 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/StaticInterfaceMethodTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/StaticInterfaceMethodTest.java
@@ -5,10 +5,12 @@ package com.android.tools.r8.desugar.desugaredlibrary; import com.android.tools.r8.TestParameters; -import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.StringUtils; import java.time.chrono.Chronology; +import java.util.List; import java.util.Map; +import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -17,27 +19,65 @@ @RunWith(Parameterized.class) public class StaticInterfaceMethodTest extends DesugaredLibraryTestBase { - private final TestParameters parameters; + private static final String EXPECTED_OUTPUT = StringUtils.lines("false", "java.util.HashSet"); - @Parameters(name = "{0}") - public static TestParametersCollection data() { - return getTestParameters().withDexRuntimes().withAllApiLevels().build(); + private final TestParameters parameters; + private final boolean shrinkDesugaredLibrary; + + @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values()); } - public StaticInterfaceMethodTest(TestParameters parameters) { + public StaticInterfaceMethodTest(TestParameters parameters, boolean shrinkDesugaredLibrary) { + this.shrinkDesugaredLibrary = shrinkDesugaredLibrary; this.parameters = parameters; } @Test - public void testStaticInterfaceMethods() throws Exception { + public void testStaticInterfaceMethodsD8() throws Exception { + if (parameters.isCfRuntime()) { + testForJvm() + .addInnerClasses(StaticInterfaceMethodTest.class) + .run(parameters.getRuntime(), Executor.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + return; + } + KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); testForD8() .addInnerClasses(StaticInterfaceMethodTest.class) .setMinApi(parameters.getApiLevel()) - .enableCoreLibraryDesugaring(parameters.getApiLevel()) + .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) .compile() - .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel()) + .addDesugaredCoreLibraryRunClassPath( + this::buildDesugaredLibrary, + parameters.getApiLevel(), + keepRuleConsumer.get(), + shrinkDesugaredLibrary) .run(parameters.getRuntime(), Executor.class) - .assertSuccessWithOutput(StringUtils.lines("false", "java.util.HashSet")); + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testStaticInterfaceMethodsR8() throws Exception { + // Desugared library tests do not make sense in the Cf to Cf, and the JVM is already tested + // in the D8 test. Just return. + Assume.assumeFalse(parameters.isCfRuntime()); + KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); + testForR8(parameters.getBackend()) + .addKeepMainRule(Executor.class) + .addInnerClasses(StaticInterfaceMethodTest.class) + .setMinApi(parameters.getApiLevel()) + .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) + .compile() + .addDesugaredCoreLibraryRunClassPath( + this::buildDesugaredLibrary, + parameters.getApiLevel(), + keepRuleConsumer.get(), + shrinkDesugaredLibrary) + .run(parameters.getRuntime(), Executor.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); } static class Executor {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SynchronizedCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SynchronizedCollectionTest.java index a9df25b..2ad9b4f 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SynchronizedCollectionTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SynchronizedCollectionTest.java
@@ -5,11 +5,14 @@ package com.android.tools.r8.desugar.desugaredlibrary; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime.CfVm; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.StringUtils; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -18,26 +21,43 @@ @RunWith(Parameterized.class) public class SynchronizedCollectionTest extends DesugaredLibraryTestBase { + private static final Path INPUT_JAR = + Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR + "desugaredlib.jar"); + private static final String EXPECTED_OUTPUT = + StringUtils.lines("[1]", "2", "[2, 3]", "true", "2", "2", "2"); + private static final String MAIN_CLASS = "desugaredlib.SynchronizedCollectionMain"; + private final TestParameters parameters; private final boolean shrinkDesugaredLibrary; - @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}") + @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}") public static List<Object[]> data() { return buildParameters( - BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build()); + getTestParameters() + .withDexRuntimes() + .withAllApiLevels() + .withCfRuntimesStartingFromIncluding(CfVm.JDK9) + .build(), + BooleanUtils.values()); } - public SynchronizedCollectionTest(boolean shrinkDesugaredLibrary, TestParameters parameters) { + public SynchronizedCollectionTest(TestParameters parameters, boolean shrinkDesugaredLibrary) { this.shrinkDesugaredLibrary = shrinkDesugaredLibrary; this.parameters = parameters; } @Test - public void testExecution() throws Exception { + public void testExecutionD8() throws Exception { + if (parameters.isCfRuntime()) { + testForJvm() + .addProgramFiles(INPUT_JAR) + .run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + return; + } KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); testForD8() - .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR + "desugaredlib.jar")) - .addInnerClasses(SynchronizedCollectionTest.class) + .addProgramFiles(INPUT_JAR) .setMinApi(parameters.getApiLevel()) .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) .compile() @@ -46,7 +66,28 @@ parameters.getApiLevel(), keepRuleConsumer.get(), shrinkDesugaredLibrary) - .run(parameters.getRuntime(), "desugaredlib.SynchronizedCollectionMain") - .assertSuccessWithOutput(StringUtils.lines("[1]", "2", "[2, 3]", "true", "2", "2", "2")); + .run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testExecutionR8() throws Exception { + // Desugared library tests do not make sense in the Cf to Cf, and the JVM is already tested + // in the D8 test. Just return. + Assume.assumeFalse(parameters.isCfRuntime()); + KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); + testForR8(parameters.getBackend()) + .addProgramFiles(INPUT_JAR) + .addKeepMainRule(MAIN_CLASS) + .setMinApi(parameters.getApiLevel()) + .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) + .compile() + .addDesugaredCoreLibraryRunClassPath( + this::buildDesugaredLibrary, + parameters.getApiLevel(), + keepRuleConsumer.get(), + shrinkDesugaredLibrary) + .run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutput(EXPECTED_OUTPUT); } }
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java index fea2bce..1e31022 100644 --- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java +++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -41,12 +41,12 @@ public class SplitterTestBase extends TestBase { - protected static FeatureSplit simpleSplitProvider( + public static FeatureSplit simpleSplitProvider( FeatureSplit.Builder builder, Path outputPath, TemporaryFolder temp, Class... classes) { return simpleSplitProvider(builder, outputPath, temp, Arrays.asList(classes)); } - protected static FeatureSplit simpleSplitProvider( + public static FeatureSplit simpleSplitProvider( FeatureSplit.Builder builder, Path outputPath, TemporaryFolder temp,
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/StringMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/StringMethods.java index 566f6d6..9a2fc52 100644 --- a/src/test/java/com/android/tools/r8/ir/desugar/backports/StringMethods.java +++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/StringMethods.java
@@ -35,4 +35,78 @@ } return builder.toString(); } + + public static String repeat(String receiver, int count) { + if (count < 0) { + throw new IllegalArgumentException("count is negative: " + count); + } + int length = receiver.length(); + if (count == 0 || length == 0) { + return ""; + } + if (count == 1) { + return receiver; + } + StringBuilder builder = new StringBuilder(length * count); + for (int i = 0; i < count; i++) { + builder.append(receiver); + } + return builder.toString(); + } + + public static boolean isBlank(String receiver) { + for (int i = 0, length = receiver.length(); i < length; ) { + int codePoint = receiver.codePointAt(i); + if (!Character.isWhitespace(codePoint)) { + return false; + } + i += Character.charCount(codePoint); + } + return true; + } + + public static String strip(String receiver) { + int start = 0; + int end = receiver.length(); + while (start < end) { + int codePoint = receiver.codePointAt(start); + if (!Character.isWhitespace(codePoint)) { + break; + } + start += Character.charCount(codePoint); + } + while (end > start) { + int codePoint = Character.codePointBefore(receiver, end); + if (!Character.isWhitespace(codePoint)) { + break; + } + end -= Character.charCount(codePoint); + } + return receiver.substring(start, end); + } + + public static String stripLeading(String receiver) { + int start = 0; + int end = receiver.length(); + while (start < end) { + int codePoint = receiver.codePointAt(start); + if (!Character.isWhitespace(codePoint)) { + break; + } + start += Character.charCount(codePoint); + } + return receiver.substring(start, end); + } + + public static String stripTrailing(String receiver) { + int end = receiver.length(); + while (end > 0) { + int codePoint = Character.codePointBefore(receiver, end); + if (!Character.isWhitespace(codePoint)) { + break; + } + end -= Character.charCount(codePoint); + } + return receiver.substring(0, end); + } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParameterTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParameterTypeTest.java index 3fedef1..b470bb6 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParameterTypeTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParameterTypeTest.java
@@ -6,16 +6,14 @@ import static com.android.tools.r8.KotlinCompilerTool.KOTLINC; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed; -import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import com.android.tools.r8.R8TestCompileResult; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.KotlinTargetVersion; -import com.android.tools.r8.ToolHelper.ProcessResult; import com.android.tools.r8.shaking.ProguardKeepAttributes; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.KmClassSubject; @@ -85,26 +83,25 @@ supertype -> supertype.getFinalDescriptor().contains("Itf"))); assertTrue(superTypes.stream().anyMatch( supertype -> supertype.getFinalDescriptor().equals(itf.getFinalDescriptor()))); - // TODO(b/70169921): should not refer to Itf List<ClassSubject> parameterTypes = kmClass.getParameterTypesInFunctions(); assertTrue(parameterTypes.stream().anyMatch( - parameterType -> parameterType.getOriginalDescriptor().contains("Itf"))); + parameterType -> parameterType.getFinalDescriptor().equals(itf.getFinalDescriptor()))); }); Path libJar = compileResult.writeToZip(); String appFolder = PKG_PREFIX + "/parametertype_app"; - ProcessResult processResult = + Path output = kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8) .addClasspathFiles(libJar) .addSourceFiles(getKotlinFileInTest(appFolder, "main")) .setOutputPath(temp.newFolder().toPath()) - // TODO(b/70169921): update to just .compile() once fixed. - .compileRaw(); - // TODO(b/70169921): should be able to compile! - assertNotEquals(0, processResult.exitCode); - assertThat( - processResult.stderr, - containsString("cannot access class '" + pkg + ".parametertype_lib.Itf'")); + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), pkg + ".parametertype_app.MainKt") + .assertSuccessWithOutputLines("Impl::bar", "Program::bar"); } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturnTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturnTypeTest.java index e7efcae..bcf2181 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturnTypeTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturnTypeTest.java
@@ -6,16 +6,14 @@ import static com.android.tools.r8.KotlinCompilerTool.KOTLINC; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed; -import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import com.android.tools.r8.R8TestCompileResult; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.KotlinTargetVersion; -import com.android.tools.r8.ToolHelper.ProcessResult; import com.android.tools.r8.shaking.ProguardKeepAttributes; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.KmClassSubject; @@ -84,27 +82,26 @@ supertype -> supertype.getFinalDescriptor().contains("Itf"))); assertTrue(superTypes.stream().anyMatch( supertype -> supertype.getFinalDescriptor().equals(itf.getFinalDescriptor()))); - // TODO(b/70169921): should not refer to Itf List<ClassSubject> functionReturnTypes = kmClass.getReturnTypesInFunctions(); assertTrue(functionReturnTypes.stream().anyMatch( - returnType -> returnType.getOriginalDescriptor().contains("Itf"))); + returnType -> returnType.getFinalDescriptor().equals(itf.getFinalDescriptor()))); }); Path libJar = compileResult.writeToZip(); String appFolder = PKG_PREFIX + "/returntype_app"; - ProcessResult processResult = + Path output = kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8) .addClasspathFiles(libJar) .addSourceFiles(getKotlinFileInTest(appFolder, "main")) .setOutputPath(temp.newFolder().toPath()) - // TODO(b/70169921): update to just .compile() once fixed. - .compileRaw(); - // TODO(b/70169921): should be able to compile! - assertNotEquals(0, processResult.exitCode); - assertThat( - processResult.stderr, - containsString("cannot access class '" + pkg + ".returntype_lib.Itf'")); + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), pkg + ".returntype_app.MainKt") + .assertSuccessWithOutputLines("Impl::foo", "Program::foo", "true"); } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt index 3c1585a..83caed2 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt
@@ -4,8 +4,8 @@ package com.android.tools.r8.kotlin.metadata.classpath_app import com.android.tools.r8.kotlin.metadata.classpath_lib_ext.Extra -import com.android.tools.r8.kotlin.metadata.classpath_lib_ext.extension +import com.android.tools.r8.kotlin.metadata.classpath_lib_ext.fooExt fun main() { - Extra().extension() -} \ No newline at end of file + Extra().fooExt() +}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt index 975865b..501c583 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt
@@ -13,6 +13,6 @@ class Extra : Impl() -fun Extra.extension() { +fun Extra.fooExt() { foo() -} \ No newline at end of file +}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/returntype_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/returntype_app/main.kt index 37d446e..fd4ce90 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/returntype_app/main.kt +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/returntype_app/main.kt
@@ -4,10 +4,9 @@ package com.android.tools.r8.kotlin.metadata.returntype_app import com.android.tools.r8.kotlin.metadata.returntype_lib.Impl -import com.android.tools.r8.kotlin.metadata.returntype_lib.Itf class ProgramClass : Impl() { - override fun foo(): Itf { + override fun foo(): Impl { super.foo() println("Program::foo") return this
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 new file mode 100644 index 0000000..af84f69 --- /dev/null +++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
@@ -0,0 +1,287 @@ +// 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; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.AssertionsConfiguration; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.ThrowingConsumer; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.InstructionSubject; +import com.google.common.collect.ImmutableList; +import java.util.List; +import java.util.function.Function; +import org.junit.Assume; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class AssertionsConfigurationTest extends TestBase { + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withAllApiLevels().build(); + } + + public AssertionsConfigurationTest(TestParameters parameters) { + this.parameters = parameters; + } + + private void runD8Test( + Function<AssertionsConfiguration.Builder, AssertionsConfiguration> + assertionsConfigurationBuilder, + ThrowingConsumer<CodeInspector, RuntimeException> inspector, + 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) + .setMinApi(parameters.getApiLevel()) + .addAssertionsConfiguration(assertionsConfigurationBuilder) + .compile() + .inspect(inspector) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(StringUtils.lines(outputLines)); + } + + public void runR8Test( + Function<AssertionsConfiguration.Builder, AssertionsConfiguration> + assertionsConfigurationBuilder, + ThrowingConsumer<CodeInspector, RuntimeException> inspector, + List<String> outputLines) + throws Exception { + runR8Test(assertionsConfigurationBuilder, inspector, outputLines, false); + } + + public void runR8Test( + Function<AssertionsConfiguration.Builder, AssertionsConfiguration> + assertionsConfigurationBuilder, + ThrowingConsumer<CodeInspector, RuntimeException> inspector, + List<String> outputLines, + boolean enableJvmAssertions) + 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) + .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) + .setMinApi(parameters.getApiLevel()) + .addAssertionsConfiguration(assertionsConfigurationBuilder) + .compile() + .inspect(inspector) + .enableRuntimeAssertions(enableJvmAssertions) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(StringUtils.lines(outputLines)); + } + + private AssertionsConfiguration enableAllAssertions(AssertionsConfiguration.Builder builder) { + return builder.enable().build(); + } + + private AssertionsConfiguration disableAllAssertions(AssertionsConfiguration.Builder builder) { + return builder.disable().build(); + } + + private AssertionsConfiguration leaveAllAssertions(AssertionsConfiguration.Builder builder) { + return builder.passthrough().build(); + } + + private List<String> allAssertionsExpectedLines() { + return ImmutableList.of( + "AssertionError in testclasses.Class1", + "AssertionError in testclasses.Class2", + "AssertionError in testclasses.subpackage.Class1", + "AssertionError in testclasses.subpackage.Class2", + "DONE"); + } + + private List<String> noAllAssertionsExpectedLines() { + return ImmutableList.of("DONE"); + } + + private void checkAssertionCodeRemoved(CodeInspector inspector, Class<?> clazz) { + ClassSubject subject = inspector.clazz(clazz); + assertThat(subject, isPresent()); + // <clinit> is removed by R8 as it becomes empty. + if (subject.uniqueMethodWithName("<clinit>").isPresent()) { + assertFalse( + subject + .uniqueMethodWithName("<clinit>") + .streamInstructions() + .anyMatch(InstructionSubject::isStaticPut)); + } + assertFalse( + subject + .uniqueMethodWithName("m") + .streamInstructions() + .anyMatch(InstructionSubject::isThrow)); + } + + private void checkAssertionCodeEnabled(CodeInspector inspector, Class<?> clazz) { + ClassSubject subject = inspector.clazz(clazz); + assertThat(subject, isPresent()); + // <clinit> is removed by R8. + if (subject.uniqueMethodWithName("<clinit>").isPresent()) { + assertFalse( + subject + .uniqueMethodWithName("<clinit>") + .streamInstructions() + .anyMatch(InstructionSubject::isStaticPut)); + } + assertTrue( + subject + .uniqueMethodWithName("m") + .streamInstructions() + .anyMatch(InstructionSubject::isThrow)); + } + + private void checkAssertionCodeLeft(CodeInspector inspector, Class<?> clazz) { + ClassSubject subject = inspector.clazz(clazz); + assertThat(subject, isPresent()); + assertTrue( + subject + .uniqueMethodWithName("<clinit>") + .streamInstructions() + .anyMatch(InstructionSubject::isStaticPut)); + assertTrue( + subject + .uniqueMethodWithName("m") + .streamInstructions() + .anyMatch(InstructionSubject::isThrow)); + } + + 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); + } + + 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); + } + + 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); + } + + @Test + public void testEnableAllAssertionsForDex() throws Exception { + Assume.assumeTrue(parameters.isDexRuntime()); + // Leaving assertions in or disabling them on Dalvik/Art means no assertions. + runD8Test( + this::leaveAllAssertions, this::checkAssertionCodeLeft, noAllAssertionsExpectedLines()); + runR8Test( + this::leaveAllAssertions, this::checkAssertionCodeLeft, noAllAssertionsExpectedLines()); + runD8Test( + this::disableAllAssertions, + this::checkAssertionCodeRemoved, + noAllAssertionsExpectedLines()); + runR8Test( + this::disableAllAssertions, + this::checkAssertionCodeRemoved, + noAllAssertionsExpectedLines()); + // Compile time enabling assertions gives assertions on Dalvik/Art. + runD8Test( + this::enableAllAssertions, this::checkAssertionCodeEnabled, allAssertionsExpectedLines()); + runR8Test( + this::enableAllAssertions, this::checkAssertionCodeEnabled, allAssertionsExpectedLines()); + } + + @Test + public void testAssertionsForCf() throws Exception { + Assume.assumeTrue(parameters.isCfRuntime()); + // Leaving assertion code means assertions are controlled by the -ea flag. + runR8Test( + this::leaveAllAssertions, this::checkAssertionCodeLeft, noAllAssertionsExpectedLines()); + runR8Test( + this::leaveAllAssertions, this::checkAssertionCodeLeft, allAssertionsExpectedLines(), true); + // Compile time enabling or disabling assertions means the -ea flag has no effect. + runR8Test( + this::enableAllAssertions, this::checkAssertionCodeEnabled, allAssertionsExpectedLines()); + runR8Test( + this::enableAllAssertions, + this::checkAssertionCodeEnabled, + allAssertionsExpectedLines(), + true); + runR8Test( + this::disableAllAssertions, + this::checkAssertionCodeRemoved, + noAllAssertionsExpectedLines()); + runR8Test( + this::disableAllAssertions, + this::checkAssertionCodeRemoved, + noAllAssertionsExpectedLines(), + true); + } + + static class TestClass { + public static void main(String[] args) { + try { + com.android.tools.r8.rewrite.assertions.testclasses.Class1.m(); + } catch (AssertionError e) { + System.out.println("AssertionError in testclasses.Class1"); + } + try { + com.android.tools.r8.rewrite.assertions.testclasses.Class2.m(); + } catch (AssertionError e) { + System.out.println("AssertionError in testclasses.Class2"); + } + try { + com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.m(); + } catch (AssertionError e) { + System.out.println("AssertionError in testclasses.subpackage.Class1"); + } + try { + com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.m(); + } catch (AssertionError e) { + System.out.println("AssertionError in testclasses.subpackage.Class2"); + } + System.out.println("DONE"); + } + } +}
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 298134a..446821a 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
@@ -7,6 +7,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; +import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation; import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.D8TestCompileResult; import com.android.tools.r8.R8TestCompileResult; @@ -18,7 +19,6 @@ import com.android.tools.r8.naming.MemberNaming.MethodSignature; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.InternalOptions; -import com.android.tools.r8.utils.InternalOptions.AssertionProcessing; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; @@ -192,14 +192,14 @@ .compile(); } - private static R8TestCompileResult compileCf(InternalOptions.AssertionProcessing assertionsState) + private static R8TestCompileResult compileCf(AssertionTransformation transformation) throws CompilationFailedException { return testForR8(staticTemp, Backend.CF) .addProgramClasses(ClassWithAssertions.class) .debug() .noTreeShaking() .noMinification() - .addOptionsModification(o -> o.assertionProcessing = assertionsState) + .addOptionsModification(o -> o.assertionTransformation = transformation) .compile(); } @@ -232,9 +232,9 @@ if (backend == Backend.CF) { return new CompilationResults( withAccess, - compileCf(AssertionProcessing.LEAVE), - compileCf(AssertionProcessing.REMOVE), - compileCf(AssertionProcessing.ENABLE)); + compileCf(AssertionTransformation.PASSTHROUGH), + compileCf(AssertionTransformation.DISABLE), + compileCf(AssertionTransformation.ENABLE)); } return new CompilationResults( withAccess, @@ -331,18 +331,18 @@ checkResultWithChromiumAssertions(results.withAssertions); } - private D8TestCompileResult compileD8(InternalOptions.AssertionProcessing assertionsState) + private D8TestCompileResult compileD8(AssertionTransformation transformation) throws CompilationFailedException { return testForD8() .addProgramClasses(ClassWithAssertions.class) .debug() .setMinApi(AndroidApiLevel.B) - .addOptionsModification(o -> o.assertionProcessing = assertionsState) + .addOptionsModification(o -> o.assertionTransformation = transformation) .compile(); } - private D8TestCompileResult compileR8FollowedByD8( - InternalOptions.AssertionProcessing assertionsState) throws Exception { + private D8TestCompileResult compileR8FollowedByD8(AssertionTransformation transformation) + throws Exception { Path program = testForR8(Backend.CF) .addProgramClasses(ClassWithAssertions.class) @@ -357,16 +357,16 @@ .addProgramFiles(program) .debug() .setMinApi(AndroidApiLevel.B) - .addOptionsModification(o -> o.assertionProcessing = assertionsState) + .addOptionsModification(o -> o.assertionTransformation = transformation) .compile(); } @Test public void testD8() throws Exception { assumeTrue(parameters.isDexRuntime()); - checkResultWithAssertionsInactive(compileD8(AssertionProcessing.REMOVE)); - checkResultWithAssertionsInactive(compileD8(AssertionProcessing.LEAVE)); - checkResultWithAssertionsEnabledAtCompileTime(compileD8(AssertionProcessing.ENABLE)); + checkResultWithAssertionsInactive(compileD8(AssertionTransformation.DISABLE)); + checkResultWithAssertionsInactive(compileD8(AssertionTransformation.PASSTHROUGH)); + checkResultWithAssertionsEnabledAtCompileTime(compileD8(AssertionTransformation.ENABLE)); } private D8TestCompileResult compileD8Regress110887293(Function<byte[], byte[]> rewriter) @@ -390,9 +390,9 @@ @Test public void testR8FollowedByD8() throws Exception { assumeTrue(parameters.isDexRuntime()); - checkResultWithAssertionsInactive(compileR8FollowedByD8(AssertionProcessing.REMOVE)); - checkResultWithAssertionsInactive(compileR8FollowedByD8(AssertionProcessing.LEAVE)); + checkResultWithAssertionsInactive(compileR8FollowedByD8(AssertionTransformation.DISABLE)); + checkResultWithAssertionsInactive(compileR8FollowedByD8(AssertionTransformation.PASSTHROUGH)); checkResultWithAssertionsEnabledAtCompileTime( - compileR8FollowedByD8(AssertionProcessing.ENABLE)); + compileR8FollowedByD8(AssertionTransformation.ENABLE)); } }
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/Class1.java b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/Class1.java new file mode 100644 index 0000000..9de87c9 --- /dev/null +++ b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/Class1.java
@@ -0,0 +1,11 @@ +// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.rewrite.assertions.testclasses; + +public class Class1 { + public static void m() { + assert false; + } +}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/Class2.java b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/Class2.java new file mode 100644 index 0000000..93b24e7 --- /dev/null +++ b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/Class2.java
@@ -0,0 +1,11 @@ +// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.rewrite.assertions.testclasses; + +public class Class2 { + public static void m() { + assert false; + } +}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/subpackage/Class1.java b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/subpackage/Class1.java new file mode 100644 index 0000000..79d0949 --- /dev/null +++ b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/subpackage/Class1.java
@@ -0,0 +1,11 @@ +// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.rewrite.assertions.testclasses.subpackage; + +public class Class1 { + public static void m() { + assert false; + } +}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/subpackage/Class2.java b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/subpackage/Class2.java new file mode 100644 index 0000000..e001cf0 --- /dev/null +++ b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/subpackage/Class2.java
@@ -0,0 +1,11 @@ +// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.rewrite.assertions.testclasses.subpackage; + +public class Class2 { + public static void m() { + assert false; + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java b/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java index 4ea48cb..3fc3434 100644 --- a/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java +++ b/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java
@@ -17,6 +17,7 @@ import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import java.nio.file.Path; +import java.util.stream.Collectors; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -52,7 +53,22 @@ .addProgramClasses(PrintConfigurationTestClass.class) .addKeepRules(proguardConfig) .compile(); - assertEquals(proguardConfig, FileUtils.readTextFile(printConfigurationFile, Charsets.UTF_8)); + assertEqualsStripOrigin( + proguardConfig, FileUtils.readTextFile(printConfigurationFile, Charsets.UTF_8)); + } + + private String removeOriginComments(String s) { + return StringUtils.lines( + StringUtils.splitLines(s).stream() + .filter(line -> !line.startsWith("# The proguard")) + .filter(line -> !line.startsWith("# End of content")) + .filter(line -> !line.equals("")) + .collect(Collectors.toList())); + } + + private void assertEqualsStripOrigin(String a, String b) { + String expected = removeOriginComments(a); + assertEquals(expected, removeOriginComments(b)); } @Test @@ -72,7 +88,12 @@ .addKeepRuleFiles(proguardConfigFile) .compile(); - assertEquals(proguardConfig, FileUtils.readTextFile(proguardConfigOutFile, Charsets.UTF_8)); + String outFileContent = FileUtils.readTextFile(proguardConfigOutFile, Charsets.UTF_8); + assertEqualsStripOrigin(proguardConfig, outFileContent); + + // We should have added the proguard-config.txt file as the origin in the config output + String firstLine = StringUtils.splitLines(outFileContent).get(0); + assertThat(outFileContent, containsString(proguardConfigFile.toString())); } @Test @@ -129,6 +150,7 @@ "-printconfiguration " + printConfigurationFile.toString() )); compileWithR8(ImmutableList.of(mainClass), proguardConfig); - assertEquals(expected, FileUtils.readTextFile(printConfigurationFile, Charsets.UTF_8)); + assertEqualsStripOrigin( + expected, FileUtils.readTextFile(printConfigurationFile, Charsets.UTF_8)); } }
diff --git a/src/test/java/com/android/tools/r8/utils/SegmentTreeTests.java b/src/test/java/com/android/tools/r8/utils/SegmentTreeTests.java new file mode 100644 index 0000000..bb2ea17 --- /dev/null +++ b/src/test/java/com/android/tools/r8/utils/SegmentTreeTests.java
@@ -0,0 +1,106 @@ +// 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import java.util.Map; +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 SegmentTreeTests extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public SegmentTreeTests(TestParameters parameters) { + this.parameters = parameters; + } + + private SegmentTree<Integer> tree(boolean allowOverrides) { + return new SegmentTree<>(allowOverrides); + } + + @Test + public void testInsertSimpleRange() { + SegmentTree<Integer> tree = tree(false).add(1, 100, 3); + Map.Entry<Integer, Integer> entry = tree.findEntry(50); + assertEntry(tree, 50, 1, 3); + assertNull(tree.find(0)); + assertNull(tree.find(101)); + } + + @Test + public void testInsertMultipleRanges() { + SegmentTree<Integer> tree = tree(false).add(1, 10, 3).add(20, 30, 7).add(31, 100, 9); + assertEntry(tree, 10, 1, 3); + assertNull(tree.findEntry(11)); + assertEntry(tree, 20, 20, 7); + assertEquals(tree.findEntry(31), tree.findEntry(100)); + assertNull(tree.find(101)); + } + + @Test(expected = AssertionError.class) + public void testAssertingNoOverrides() { + tree(false).add(1, 10, 3).add(7, 2, 4); + } + + @Test + public void testOverrideLeft() { + SegmentTree<Integer> tree = tree(true).add(7, 9, 2).add(6, 7, 1); + assertNull(tree.findEntry(5)); + assertEntry(tree, 7, 6, 1); + assertEntry(tree, 9, 8, 2); + assertNull(tree.findEntry(10)); + assertEquals(2, tree.size()); + } + + @Test + public void testOverrideRight() { + SegmentTree<Integer> tree = tree(true).add(6, 7, 1).add(7, 9, 2); + assertNull(tree.findEntry(5)); + assertEntry(tree, 6, 6, 1); + assertEntry(tree, 8, 7, 2); + assertNull(tree.findEntry(10)); + assertEquals(2, tree.size()); + } + + @Test + public void testOverrideContained() { + SegmentTree<Integer> tree = tree(true).add(0, 100, 1).add(25, 75, 2); + assertEntry(tree, 24, 0, 1); + assertEntry(tree, 75, 25, 2); + assertEntry(tree, 100, 76, 1); + assertNull(tree.findEntry(101)); + assertEquals(2, tree.size()); + } + + @Test + public void testOverrideContainer() { + SegmentTree<Integer> tree = tree(true).add(25, 75, 2).add(0, 100, 1); + assertEntry(tree, 24, 0, 1); + assertEntry(tree, 75, 0, 1); + assertEntry(tree, 100, 0, 1); + assertNull(tree.findEntry(101)); + assertEquals(1, tree.size()); + } + + private void assertEntry( + SegmentTree<Integer> tree, int index, int expectedStart, int expectedValue) { + assertEquals(expectedStart, (int) tree.findEntry(index).getKey()); + assertEquals(expectedValue, (int) tree.findEntry(index).getValue()); + } +}
diff --git a/tools/r8_release.py b/tools/r8_release.py index bdb8242..a694ae0 100755 --- a/tools/r8_release.py +++ b/tools/r8_release.py
@@ -518,6 +518,10 @@ print ' --gcs_bucket_path=$BUCKET_PATH \\' print ' --port=1480' print + print 'The path for BUCKET_PATH has to be taken from the admrt info line:' + print ' INFO: Stage Available at: ...' + print '(without the /bigstore prefix).' + print print "When the 'redir' server is running use the following commands" print 'to retreive the artifact:' print @@ -551,7 +555,7 @@ def admrt(archives, action): cmd = [ADMRT, '--archives'] - cmd.extend(archives) + cmd.append(','.join(archives)) cmd.extend(['--action', action]) subprocess.check_call(cmd)