Merge commit '51dd41e7e6a7d6b00cfd621fb5156a87a11cff14' into dev-release Change-Id: I1f5e30801109f8ae5a0102bd3c2eb9b8698756c4
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java index e95af33..02835ae 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -86,6 +86,8 @@ "Landroid/media/MediaMetadataRetriever;"; public static final String androidResourcesDescriptorString = "Landroid/content/res/Resources;"; public static final String androidContextDescriptorString = "Landroid/content/Context;"; + public static final String kotlinJvmInternalIntrinsicsDescriptor = + "Lkotlin/jvm/internal/Intrinsics;"; /** Set of types that may be synthesized during compilation. */ private final Set<DexType> possibleCompilerSynthesizedTypes = Sets.newIdentityHashSet(); @@ -649,6 +651,8 @@ public final DexType kotlinMetadataType = createStaticallyKnownType(kotlinMetadataDescriptor); public final DexType kotlinJvmNameType = createStaticallyKnownType(kotlinJvmNameDescriptor); + public final DexType kotlinJvmInternalIntrinsicsType = + createStaticallyKnownType(kotlinJvmInternalIntrinsicsDescriptor); public final DexType kotlinEnumEntriesList = createStaticallyKnownType("Lkotlin/enums/EnumEntriesList;"); @@ -944,6 +948,8 @@ public final IteratorMethods iteratorMethods = new IteratorMethods(); public final StringConcatFactoryMembers stringConcatFactoryMembers = new StringConcatFactoryMembers(); + public final KotlinJvmInternalIntrinsicsMethods kotlinJvmInternalIntrinsicsMethods = + new KotlinJvmInternalIntrinsicsMethods(); private final SyntheticNaming syntheticNaming = new SyntheticNaming(); @@ -3035,6 +3041,91 @@ createMethod(javaUtilIteratorType, createProto(objectType), nextName); } + public class KotlinJvmInternalIntrinsicsMethods { + /* + From javap: + + public class kotlin.jvm.internal.Intrinsics { + public static void checkNotNull(java.lang.Object); + public static void checkNotNull(java.lang.Object, java.lang.String); + public static void checkExpressionValueIsNotNull(java.lang.Object, java.lang.String); + public static void checkNotNullExpressionValue(java.lang.Object, java.lang.String); + public static void checkReturnedValueIsNotNull( + java.lang.Object, java.lang.String, java.lang.String); + public static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String); + public static void checkFieldIsNotNull( + java.lang.Object, java.lang.String, java.lang.String); + public static void checkFieldIsNotNull(java.lang.Object, java.lang.String); + public static void checkParameterIsNotNull(java.lang.Object, java.lang.String); + public static void checkNotNullParameter(java.lang.Object, java.lang.String); + } + */ + public final DexMethod checkNotNullObject = + createMethod( + kotlinJvmInternalIntrinsicsType, createProto(voidType, objectType), "checkNotNull"); + public final DexMethod checkNotNullObjectString = + createMethod( + kotlinJvmInternalIntrinsicsType, + createProto(voidType, objectType, stringType), + "checkNotNull"); + public final DexMethod checkExpressionValueIsNotNull = + createMethod( + kotlinJvmInternalIntrinsicsType, + createProto(voidType, objectType, stringType), + "checkExpressionValueIsNotNull"); + public final DexMethod checkNotNullExpressionValue = + createMethod( + kotlinJvmInternalIntrinsicsType, + createProto(voidType, objectType, stringType), + "checkNotNullExpressionValue"); + public final DexMethod checkReturnedValueIsNotNullObjectStringString = + createMethod( + kotlinJvmInternalIntrinsicsType, + createProto(voidType, objectType, stringType, stringType), + "checkReturnedValueIsNotNull"); + public final DexMethod checkReturnedValueIsNotNullObjectString = + createMethod( + kotlinJvmInternalIntrinsicsType, + createProto(voidType, objectType, stringType), + "checkReturnedValueIsNotNull"); + public final DexMethod checkFieldIsNotNullObjectStringString = + createMethod( + kotlinJvmInternalIntrinsicsType, + createProto(voidType, objectType, stringType, stringType), + "checkFieldIsNotNull"); + public final DexMethod checkFieldIsNotNullObjectString = + createMethod( + kotlinJvmInternalIntrinsicsType, + createProto(voidType, objectType, stringType), + "checkFieldIsNotNull"); + public final DexMethod checkParameterIsNotNull = + createMethod( + kotlinJvmInternalIntrinsicsType, + createProto(voidType, objectType, stringType), + "checkParameterIsNotNull"); + public final DexMethod checkNotNullParameter = + createMethod( + kotlinJvmInternalIntrinsicsType, + createProto(voidType, objectType, stringType), + "checkNotNullParameter"); + + public boolean isNullCheck(DexMethod method) { + if (!method.getHolderType().isIdenticalTo(kotlinJvmInternalIntrinsicsType)) { + return false; + } + return method.isIdenticalTo(checkNotNullObject) + || method.isIdenticalTo(checkNotNullObjectString) + || method.isIdenticalTo(checkExpressionValueIsNotNull) + || method.isIdenticalTo(checkNotNullExpressionValue) + || method.isIdenticalTo(checkReturnedValueIsNotNullObjectString) + || method.isIdenticalTo(checkReturnedValueIsNotNullObjectStringString) + || method.isIdenticalTo(checkFieldIsNotNullObjectString) + || method.isIdenticalTo(checkFieldIsNotNullObjectStringString) + || method.isIdenticalTo(checkParameterIsNotNull) + || method.isIdenticalTo(checkNotNullParameter); + } + } + private static <T extends DexItem> T canonicalize( Map<T, T> committedMap, Map<T, T> pendingMap, T item) { assert item != null;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java index 653bdaa..dd2cdac 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -178,6 +178,14 @@ return true; } + if (appView.options().getProguardConfiguration().getProcessKotlinNullChecks().isRemove() + && appView + .dexItemFactory() + .kotlinJvmInternalIntrinsicsMethods + .isNullCheck(getInvokedMethod())) { + return false; + } + AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness(); SingleResolutionResult<?> resolutionResult = appViewWithLiveness
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CheckNotNullConverter.java b/src/main/java/com/android/tools/r8/ir/optimize/CheckNotNullConverter.java index ded9ac0..7bbe9a1 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/CheckNotNullConverter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/CheckNotNullConverter.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClassAndMethod; +import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.BasicBlockIterator; @@ -46,6 +47,22 @@ } } + static boolean kotlinNullCheckLedgibleForMessageRemoval( + AppView<? extends AppInfoWithClassHierarchy> appView, DexMethod method) { + return appView + .options() + .getProguardConfiguration() + .getProcessKotlinNullChecks() + .isRemoveMessage() + && appView.dexItemFactory().kotlinJvmInternalIntrinsicsMethods.isNullCheck(method); + } + + private static boolean canConvertNullCheck( + AppView<? extends AppInfoWithClassHierarchy> appView, DexClassAndMethod singleTarget) { + return singleTarget.getOptimizationInfo().isConvertCheckNotNull() + || kotlinNullCheckLedgibleForMessageRemoval(appView, singleTarget.getReference()); + } + private static void rewriteInvoke( AppView<? extends AppInfoWithClassHierarchy> appView, IRCode code, @@ -53,7 +70,7 @@ InvokeMethod invoke) { ProgramMethod context = code.context(); DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context); - if (singleTarget == null || !singleTarget.getOptimizationInfo().isConvertCheckNotNull()) { + if (singleTarget == null || !canConvertNullCheck(appView, singleTarget)) { return; } Value checkNotNullValue = invoke.getFirstNonReceiverArgument();
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/FilteredKeepRulesBuilder.java b/src/main/java/com/android/tools/r8/processkeeprules/FilteredKeepRulesBuilder.java index 9ba68e1..504cfe1 100644 --- a/src/main/java/com/android/tools/r8/processkeeprules/FilteredKeepRulesBuilder.java +++ b/src/main/java/com/android/tools/r8/processkeeprules/FilteredKeepRulesBuilder.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.position.TextPosition; import com.android.tools.r8.shaking.FilteredClassPath; import com.android.tools.r8.shaking.ProguardClassNameList; +import com.android.tools.r8.shaking.ProguardConfiguration.ProcessKotlinNullChecks; import com.android.tools.r8.shaking.ProguardConfigurationParser.ProguardConfigurationSourceParser; import com.android.tools.r8.shaking.ProguardConfigurationParserConsumer; import com.android.tools.r8.shaking.ProguardConfigurationRule; @@ -214,6 +215,15 @@ } @Override + public void addProcessKotlinNullChecks( + ProcessKotlinNullChecks value, + ProguardConfigurationSourceParser parser, + Position position, + TextPosition positionStart) { + writeComment(parser, positionStart); + } + + @Override public void addKeepPackageNamesPattern( ProguardClassNameList proguardClassNameList, ProguardConfigurationSourceParser parser,
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java b/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java index e0bbeab..4b9b4bf 100644 --- a/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java +++ b/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.position.TextPosition; import com.android.tools.r8.shaking.FilteredClassPath; import com.android.tools.r8.shaking.ProguardClassNameList; +import com.android.tools.r8.shaking.ProguardConfiguration.ProcessKotlinNullChecks; import com.android.tools.r8.shaking.ProguardConfigurationParser.ProguardConfigurationSourceParser; import com.android.tools.r8.shaking.ProguardConfigurationParserConsumer; import com.android.tools.r8.shaking.ProguardConfigurationRule; @@ -154,6 +155,15 @@ ProguardConfigurationSourceParser parser, Position position, TextPosition positionStart) {} @Override + public void addProcessKotlinNullChecks( + ProcessKotlinNullChecks value, + ProguardConfigurationSourceParser parser, + Position position, + TextPosition positionStart) { + handleRule(parser, positionStart, "-processkotlinnullchecks"); + } + + @Override public void addKeepPackageNamesPattern( ProguardClassNameList proguardClassNameList, ProguardConfigurationSourceParser parser,
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java index ad2a5a2..8450bce 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.shaking; +import static com.android.tools.r8.shaking.ProguardConfiguration.ProcessKotlinNullChecks.DEFAULT; import static com.android.tools.r8.shaking.ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS; import com.android.tools.r8.errors.dontwarn.DontWarnConfiguration; @@ -28,6 +29,26 @@ public class ProguardConfiguration { + public enum ProcessKotlinNullChecks { + DEFAULT, + KEEP, + REMOVE_MESSAGE, + REMOVE; + + public boolean isRemoveMessage() { + return this == DEFAULT || this == REMOVE_MESSAGE; + } + + public boolean isRemove() { + return this == REMOVE; + } + + public ProcessKotlinNullChecks meet(ProcessKotlinNullChecks other) { + assert other != DEFAULT; + return other.ordinal() > ordinal() ? other : this; + } + } + public static class Builder implements ProguardConfigurationParserConsumer { private final StringBuilder parsedConfiguration = new StringBuilder(); @@ -74,6 +95,7 @@ private boolean forceProguardCompatibility = false; private boolean protoShrinking = false; private int maxRemovedAndroidLogLevel = MaximumRemovedAndroidLogLevelRule.NOT_SET; + private ProcessKotlinNullChecks processKotlinNullChecks = DEFAULT; PackageObfuscationMode packageObfuscationMode = PackageObfuscationMode.NONE; String packagePrefix = ""; @@ -274,6 +296,15 @@ Collections.singletonList(RUNTIME_VISIBLE_ANNOTATIONS), parser, position, positionStart); } + @Override + public void addProcessKotlinNullChecks( + ProcessKotlinNullChecks value, + ProguardConfigurationSourceParser parser, + Position position, + TextPosition positionStart) { + processKotlinNullChecks = processKotlinNullChecks.meet(value); + } + public Builder addKeepAttributePatterns(List<String> keepAttributePatterns) { this.keepAttributePatterns.addAll(keepAttributePatterns); return this; @@ -499,7 +530,8 @@ adaptResourceFileContents.build(), keepDirectories.build(), protoShrinking, - getMaxRemovedAndroidLogLevel()); + getMaxRemovedAndroidLogLevel(), + processKotlinNullChecks); reporter.failIfPendingErrors(); @@ -554,6 +586,7 @@ private final int maxRemovedAndroidLogLevel; private final boolean hasWhyAreYouNotInliningRule; private final boolean hasWhyAreYouNotObfuscatingRule; + private final ProcessKotlinNullChecks processKotlinNullChecks; private ProguardConfiguration( String parsedConfiguration, @@ -591,7 +624,8 @@ ProguardPathFilter adaptResourceFileContents, ProguardPathFilter keepDirectories, boolean protoShrinking, - int maxRemovedAndroidLogLevel) { + int maxRemovedAndroidLogLevel, + ProcessKotlinNullChecks processKotlinNullChecks) { this.parsedConfiguration = parsedConfiguration; this.dexItemFactory = factory; this.injars = ImmutableList.copyOf(injars); @@ -632,6 +666,7 @@ Iterables.any(rules, rule -> rule instanceof WhyAreYouNotInliningRule); this.hasWhyAreYouNotObfuscatingRule = Iterables.any(rules, rule -> rule instanceof WhyAreYouNotObfuscatingRule); + this.processKotlinNullChecks = processKotlinNullChecks; } /** @@ -811,6 +846,10 @@ return hasWhyAreYouNotObfuscatingRule; } + public ProcessKotlinNullChecks getProcessKotlinNullChecks() { + return processKotlinNullChecks; + } + @Override public String toString() { StringBuilder builder = new StringBuilder();
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 f67ed82..0060f9d3 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -3,6 +3,9 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.shaking; +import static com.android.tools.r8.shaking.ProguardConfiguration.ProcessKotlinNullChecks.KEEP; +import static com.android.tools.r8.shaking.ProguardConfiguration.ProcessKotlinNullChecks.REMOVE; +import static com.android.tools.r8.shaking.ProguardConfiguration.ProcessKotlinNullChecks.REMOVE_MESSAGE; import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor; import com.android.tools.r8.InputDependencyGraphConsumer; @@ -18,6 +21,7 @@ import com.android.tools.r8.position.TextPosition; import com.android.tools.r8.position.TextRange; import com.android.tools.r8.shaking.InlineRule.InlineRuleType; +import com.android.tools.r8.shaking.ProguardConfiguration.ProcessKotlinNullChecks; import com.android.tools.r8.shaking.ProguardTypeMatcher.ClassOrType; import com.android.tools.r8.shaking.ProguardWildcard.BackReference; import com.android.tools.r8.shaking.ProguardWildcard.Pattern; @@ -296,6 +300,8 @@ // Intentionally left empty. } else if (acceptString("keepkotlinmetadata")) { configurationConsumer.addKeepKotlinMetadata(this, getPosition(optionStart), optionStart); + } else if (acceptString("processkotlinnullchecks")) { + parseProcessKotlinNullChecks(optionStart); } else if (acceptString("renamesourcefileattribute")) { skipWhitespace(); String renameSourceFileAttribute = @@ -712,6 +718,30 @@ attributesPatterns, this, getPosition(start), start); } + private void parseProcessKotlinNullChecks(TextPosition start) + throws ProguardRuleParserException { + skipWhitespace(); + TextPosition argumentStart = getPosition(); + String processKotlinNullChecksValue = + isOptionalArgumentGiven() ? acceptQuotedOrUnquotedString() : ""; + ProcessKotlinNullChecks value = REMOVE_MESSAGE; + switch (processKotlinNullChecksValue) { + case "keep": + value = KEEP; + break; + case "": + case "remove_message": + value = REMOVE_MESSAGE; + break; + case "remove": + value = REMOVE; + break; + default: + throw parseError("Illegal value for -processkotlinnullchecks", argumentStart); + } + configurationConsumer.addProcessKotlinNullChecks(value, this, getPosition(start), start); + } + private boolean skipFlag(String name) { if (acceptString(name)) { return true;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java index b3067da..4ea4043 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.position.Position; import com.android.tools.r8.position.TextPosition; +import com.android.tools.r8.shaking.ProguardConfiguration.ProcessKotlinNullChecks; import com.android.tools.r8.shaking.ProguardConfigurationParser.ProguardConfigurationSourceParser; import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode; import java.nio.file.Path; @@ -39,6 +40,12 @@ void addKeepKotlinMetadata( ProguardConfigurationSourceParser parser, Position position, TextPosition positionStart); + void addProcessKotlinNullChecks( + ProcessKotlinNullChecks value, + ProguardConfigurationSourceParser parser, + Position position, + TextPosition positionStart); + void addKeepPackageNamesPattern( ProguardClassNameList proguardClassNameList, ProguardConfigurationSourceParser parser,
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java index 10d8bbc..be552b1 100644 --- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -4,14 +4,12 @@ package com.android.tools.r8.kotlin; -import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72; import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0; import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_9_21; import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_2_0_20; import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_2_1_10; import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -31,7 +29,6 @@ import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.InstructionSubject; import com.android.tools.r8.utils.codeinspector.MethodSubject; -import com.google.common.collect.Lists; import com.google.common.collect.Streams; import java.util.Collections; import java.util.List; @@ -110,9 +107,9 @@ return; } if (testParameters.isCfRuntime() && !hasKotlinCGeneratedLambdaClasses) { - assertEquals(5, inspector.allClasses().size()); + assertEquals(4, inspector.allClasses().size()); } else { - assertEquals(7, inspector.allClasses().size()); + assertEquals(6, inspector.allClasses().size()); } }); } @@ -138,9 +135,9 @@ return; } if (testParameters.isCfRuntime() && !hasKotlinCGeneratedLambdaClasses) { - assertEquals(5, inspector.allClasses().size()); + assertEquals(4, inspector.allClasses().size()); } else { - assertEquals(7, inspector.allClasses().size()); + assertEquals(6, inspector.allClasses().size()); } }); } @@ -264,14 +261,14 @@ assertThat( inspector.clazz( "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1"), - isPresentIf(testParameters.isDexRuntime())); + isPresent()); assertThat( inspector.clazz( "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1"), isAbsent()); assertThat( inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1"), - isPresentIf(testParameters.isCfRuntime())); + isAbsent()); } }); } @@ -298,15 +295,8 @@ clazz, "main", String[].class.getCanonicalName())); - String kotlinIntrinsics = "void kotlin.jvm.internal.Intrinsics"; assertEquals( - Lists.newArrayList( - kotlinIntrinsics - + (kotlinc.is(KOTLINC_1_3_72) - ? ".checkParameterIsNotNull" - : ".checkNotNullParameter") - + "(java.lang.Object, java.lang.String)"), - collectStaticCalls(clazz, "main", String[].class.getCanonicalName())); + 0, collectStaticCalls(clazz, "main", String[].class.getCanonicalName()).size()); }); }
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinNullChecksTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinNullChecksTest.java index 965918d..4dc5c4e 100644 --- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinNullChecksTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinNullChecksTest.java
@@ -9,11 +9,13 @@ import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.InstructionSubject; import com.google.common.collect.ImmutableMap; import java.io.IOException; +import java.nio.file.Path; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -31,9 +33,10 @@ return getTestParameters().withAllRuntimesAndApiLevels().build(); } - private static final String EXPECTED_OUTPUT_D8 = + private static final String EXPECTED_OUTPUT_WITH_INVOKE_OF_INTRINSICS = StringUtils.lines("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Hello, world!"); - private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!"); + private static final String EXPECTED_OUTPUT_WITHOUT_INVOKE_OF_INTRINSICS = + StringUtils.lines("Hello, world!"); private static final String[] RUN_ARGUMENTS = new String[] {"", "", "", "", "", "", "", "", "", ""}; @@ -86,7 +89,7 @@ .run(parameters.getRuntime(), TestClass.class, RUN_ARGUMENTS) .inspect(inspector -> assertKotlinIntrinsicsInvokes(inspector, 10)) .inspect(inspector -> assertObjectGetClassInvokes(inspector, 0)) - .assertSuccessWithOutput(EXPECTED_OUTPUT_D8); + .assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_INVOKE_OF_INTRINSICS); } @Test @@ -97,22 +100,64 @@ .run(parameters.getRuntime(), TestClass.class, RUN_ARGUMENTS) .inspect(inspector -> assertKotlinIntrinsicsInvokes(inspector, 10)) .inspect(inspector -> assertObjectGetClassInvokes(inspector, 0)) - .assertSuccessWithOutput(EXPECTED_OUTPUT_D8); + .assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_INVOKE_OF_INTRINSICS); } @Test public void testR8() throws Exception { - parameters.assumeDexRuntime(); testForR8(parameters) .addProgramClassFileData(getTransformedMain()) .addClasspathClassFileData(getTransformedKotlinIntrinsics()) .addKeepMainRule(TestClass.class) - .compile() - .addRunClasspathClassFileData(getTransformedKotlinIntrinsics()) + .run(parameters.getRuntime(), TestClass.class, RUN_ARGUMENTS) + .inspect(inspector -> assertKotlinIntrinsicsInvokes(inspector, 0)) + .inspect(inspector -> assertObjectGetClassInvokes(inspector, 10)) + .assertSuccessWithOutput(EXPECTED_OUTPUT_WITHOUT_INVOKE_OF_INTRINSICS); + } + + @Test + public void testR8ProcessKotlinNullChecksLeave() throws Exception { + Path fakeKotlinIntrinsicsLibrary = + testForD8(parameters) + .addProgramClassFileData(getTransformedKotlinIntrinsics()) + .compile() + .writeToZip(); + testForR8(parameters) + .addProgramClassFileData(getTransformedMain()) + .addClasspathClassFileData(getTransformedKotlinIntrinsics()) + .addKeepMainRule(TestClass.class) + .addKeepRules("-processkotlinnullchecks keep") + .addRunClasspathFiles(fakeKotlinIntrinsicsLibrary) .run(parameters.getRuntime(), TestClass.class, RUN_ARGUMENTS) .inspect(inspector -> assertKotlinIntrinsicsInvokes(inspector, 10)) .inspect(inspector -> assertObjectGetClassInvokes(inspector, 0)) - .assertSuccessWithOutput(EXPECTED_OUTPUT_D8); + .assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_INVOKE_OF_INTRINSICS); + } + + @Test + public void testR8ProcessKotlinNullChecksRemoveMessage() throws Exception { + testForR8(parameters) + .addProgramClassFileData(getTransformedMain()) + .addClasspathClassFileData(getTransformedKotlinIntrinsics()) + .addKeepMainRule(TestClass.class) + .addKeepRules("-processkotlinnullchecks remove_message") + .run(parameters.getRuntime(), TestClass.class, RUN_ARGUMENTS) + .inspect(inspector -> assertKotlinIntrinsicsInvokes(inspector, 0)) + .inspect(inspector -> assertObjectGetClassInvokes(inspector, 10)) + .assertSuccessWithOutput(EXPECTED_OUTPUT_WITHOUT_INVOKE_OF_INTRINSICS); + } + + @Test + public void testR8ProcessKotlinNullChecksRemove() throws Exception { + testForR8(parameters) + .addProgramClassFileData(getTransformedMain()) + .addClasspathClassFileData(getTransformedKotlinIntrinsics()) + .addKeepMainRule(TestClass.class) + .addKeepRules("-processkotlinnullchecks remove") + .run(parameters.getRuntime(), TestClass.class, RUN_ARGUMENTS) + .inspect(inspector -> assertKotlinIntrinsicsInvokes(inspector, 0)) + .inspect(inspector -> assertObjectGetClassInvokes(inspector, 0)) + .assertSuccessWithOutput(EXPECTED_OUTPUT_WITHOUT_INVOKE_OF_INTRINSICS); } private void addRuleForKotlinIntrinsics(String rule, R8TestBuilder<?, ?, ?> builder) { @@ -137,12 +182,13 @@ testForR8(parameters) .addProgramClassFileData(getTransformedMain()) .addClasspathClassFileData(getTransformedKotlinIntrinsics()) + .addKeepRules("-processkotlinnullchecks keep") .apply(b -> addRuleForKotlinIntrinsics("-convertchecknotnull", b)) .addKeepMainRule(TestClass.class) .run(parameters.getRuntime(), TestClass.class, RUN_ARGUMENTS) .inspect(inspector -> assertKotlinIntrinsicsInvokes(inspector, 0)) .inspect(inspector -> assertObjectGetClassInvokes(inspector, 10)) - .assertSuccessWithOutput(EXPECTED_OUTPUT); + .assertSuccessWithOutput(EXPECTED_OUTPUT_WITHOUT_INVOKE_OF_INTRINSICS); } @Test @@ -150,12 +196,13 @@ testForR8(parameters) .addProgramClassFileData(getTransformedMain()) .addClasspathClassFileData(getTransformedKotlinIntrinsics()) + .addKeepRules("-processkotlinnullchecks remove_message") .apply(b -> addRuleForKotlinIntrinsics("-assumenosideeffects", b)) .addKeepMainRule(TestClass.class) .run(parameters.getRuntime(), TestClass.class, RUN_ARGUMENTS) .inspect(inspector -> assertKotlinIntrinsicsInvokes(inspector, 0)) .inspect(inspector -> assertObjectGetClassInvokes(inspector, 0)) - .assertSuccessWithOutput(EXPECTED_OUTPUT); + .assertSuccessWithOutput(EXPECTED_OUTPUT_WITHOUT_INVOKE_OF_INTRINSICS); } private byte[] getTransformedMain() throws IOException { @@ -163,13 +210,13 @@ .replaceClassDescriptorInMethodInstructions( ImmutableMap.of( descriptor(KotlinJvmInternalIntrinsicsStub.class), - "Lkotlin/jvm/internal/Intrinsics;")) + DexItemFactory.kotlinJvmInternalIntrinsicsDescriptor)) .transform(); } private byte[] getTransformedKotlinIntrinsics() throws IOException { return transformer(KotlinJvmInternalIntrinsicsStub.class) - .setClassDescriptor("Lkotlin/jvm/internal/Intrinsics;") + .setClassDescriptor(DexItemFactory.kotlinJvmInternalIntrinsicsDescriptor) .transform(); }
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java index 45aad76..cc2b6c5 100644 --- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -129,6 +129,14 @@ testBuilder .addKeepRules(keepClassMethod(mainClassName, testMethodSignature)) .addOptionsModification(disableClassInliner)) - .inspect(inspector -> checkClassIsRemoved(inspector, TEST_DATA_CLASS.getClassName())); + .applyIf( + testParameters.isCfRuntime(), + b -> + b.inspect( + inspector -> checkClassIsRemoved(inspector, TEST_DATA_CLASS.getClassName())), + // TODO(b/461691875): Why is the class not removed (it is unused). + b -> + b.inspect( + inspector -> checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName()))); } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java index eb55f31..071df42 100644 --- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
@@ -49,7 +49,9 @@ "intrinsics", "intrinsics.IntrinsicsKt", testBuilder -> - testBuilder.addKeepRules(extraRules).noHorizontalClassMerging(Intrinsics.class)) + testBuilder + .addKeepRules(extraRules, "-processkotlinnullchecks keep") + .noHorizontalClassMerging(Intrinsics.class)) .inspect( inspector -> { ClassSubject intrinsicsClass =
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingCapturesKotlinStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingCapturesKotlinStyleTest.java index ea1031a..1ee6b63 100644 --- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingCapturesKotlinStyleTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingCapturesKotlinStyleTest.java
@@ -87,8 +87,7 @@ .addHorizontallyMergedClassesInspector( inspector -> { if (parameters.isDexRuntime() - && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L) - && kotlinParameters.getLambdaGeneration().isInvokeDynamic()) { + && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L)) { SyntheticItemsTestUtils syntheticItems = testBuilder.getState().getSyntheticItems(); inspector .assertIsCompleteMergeGroup(
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java index 8f4d567..e22c378 100644 --- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java
@@ -146,10 +146,7 @@ if (kotlinParameters.getLambdaGeneration().isClass()) { inspector .assertIsCompleteMergeGroup( - lambdasInInput.getKStyleLambdaReferenceFromTypeName(getTestName(), "MainKt$main$1"), - lambdasInInput.getKStyleLambdaReferenceFromTypeName(getTestName(), "MainKt$main$2"), lambdasInInput.getKStyleLambdaReferenceFromTypeName(getTestName(), "MainKt$main$3"), - lambdasInInput.getKStyleLambdaReferenceFromTypeName(getTestName(), "MainKt$main$4"), lambdasInInput.getKStyleLambdaReferenceFromTypeName( getTestName(), "MainKt$testFirst$1"), lambdasInInput.getKStyleLambdaReferenceFromTypeName( @@ -195,31 +192,8 @@ ClassReference mainKt = Reference.classFromTypeName(getMainClassName()); List<ClassReference> mergeGroup = ImmutableList.of( - syntheticItems.syntheticLambdaClass(mainKt, 1), - syntheticItems.syntheticLambdaClass(mainKt, 2), - syntheticItems.syntheticLambdaClass(mainKt, 3), - syntheticItems.syntheticLambdaClass(mainKt, 4), - syntheticItems.syntheticLambdaClass(mainKt, 5), - syntheticItems.syntheticLambdaClass(mainKt, 6), - syntheticItems.syntheticLambdaClass(mainKt, 7), - syntheticItems.syntheticLambdaClass(mainKt, 8), - syntheticItems.syntheticLambdaClass(mainKt, 9), - syntheticItems.syntheticLambdaClass(mainKt, 10), - syntheticItems.syntheticLambdaClass(mainKt, 11), - syntheticItems.syntheticLambdaClass(mainKt, 12), - syntheticItems.syntheticLambdaClass(mainKt, 13), - syntheticItems.syntheticLambdaClass(mainKt, 14), - syntheticItems.syntheticLambdaClass(mainKt, 15), - syntheticItems.syntheticLambdaClass(mainKt, 16), - syntheticItems.syntheticLambdaClass(mainKt, 17), - syntheticItems.syntheticLambdaClass(mainKt, 18), - syntheticItems.syntheticLambdaClass(mainKt, 19), - syntheticItems.syntheticLambdaClass(mainKt, 20), - syntheticItems.syntheticLambdaClass(mainKt, 21), - syntheticItems.syntheticLambdaClass(mainKt, 22), - syntheticItems.syntheticLambdaClass(mainKt, 23), - syntheticItems.syntheticLambdaClass(mainKt, 24), - syntheticItems.syntheticBottomUpOutlineClass(mainKt, 0)); + syntheticItems.syntheticBottomUpOutlineClass(mainKt, 0), + syntheticItems.syntheticBottomUpOutlineClass(mainKt, 1)); inspector.assertIsCompleteMergeGroup(mergeGroup).assertNoOtherClassesMerged(); } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java index c4a70db..6ee7339 100644 --- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java
@@ -13,7 +13,6 @@ import com.android.tools.r8.KotlinTestParameters; import com.android.tools.r8.TestParameters; import com.android.tools.r8.references.ClassReference; -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; @@ -95,9 +94,7 @@ if (kotlinParameters.getCompilerVersion().isLessThanOrEqualTo(KOTLINC_1_9_21)) { inspector.assertIsCompleteMergeGroup(lambdasInInput.getJStyleLambdas()); } else { - assertEquals( - parameters.isCfRuntime() || parameters.getApiLevel() == AndroidApiLevel.B ? 3 : 4, - inspector.getMergeGroups().size()); + assertEquals(3, inspector.getMergeGroups().size()); } // The remaining lambdas are not merged.
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialKotlinStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialKotlinStyleTest.java index 483460c..6c76091 100644 --- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialKotlinStyleTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialKotlinStyleTest.java
@@ -153,7 +153,7 @@ assertEquals( kotlinParameters.getLambdaGeneration().isInvokeDynamic() ? 0 - : allowAccessModification && parameters.isCfRuntime() ? 1 : 1, + : parameters.isDexRuntime() ? 1 : 0, lambdasInOutput.size()); }
diff --git a/src/test/java/com/android/tools/r8/kotlin/optimize/defaultarguments/KotlinDefaultArgumentsTest.java b/src/test/java/com/android/tools/r8/kotlin/optimize/defaultarguments/KotlinDefaultArgumentsTest.java index 4a39c83..aaed8b1 100644 --- a/src/test/java/com/android/tools/r8/kotlin/optimize/defaultarguments/KotlinDefaultArgumentsTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/optimize/defaultarguments/KotlinDefaultArgumentsTest.java
@@ -95,6 +95,29 @@ .addProgramFiles(kotlinc.getKotlinStdlibJar()) .addProgramFiles(kotlinc.getKotlinAnnotationJar()) .addKeepMainRule(MAIN) + // With default -processkotlinnullchecks inlining of "unrelated" Kotlin intrinsics cause + // an ArrayLength instruction to enter read$default + .enableProguardTestOptions() + .addKeepRules( + "-neverinline class kotlin.jvm.internal.Intrinsics { java.lang.Throwable" + + " sanitizeStackTrace(java.lang.Throwable); }") + .allowAccessModification() + .setMinApi(parameters) + .compile() + .inspect(inspector -> inspect(inspector, true)) + .run(parameters.getRuntime(), MAIN) + .assertSuccessWithOutputLines(EXPECTED_OUTPUT); + } + + @Test + public void testR8KeepKotlinNullChecks() + throws ExecutionException, CompilationFailedException, IOException { + testForR8(parameters.getBackend()) + .addProgramFiles(compilationResults.getForConfiguration(kotlinParameters)) + .addProgramFiles(kotlinc.getKotlinStdlibJar()) + .addProgramFiles(kotlinc.getKotlinAnnotationJar()) + .addKeepMainRule(MAIN) + .addKeepRules("-processkotlinnullchecks keep") .allowAccessModification() .setMinApi(parameters) .compile()
diff --git a/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java b/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java index f784755..e3b6656 100644 --- a/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java +++ b/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java
@@ -95,6 +95,18 @@ .put( "-whyareyounotinlining class * { *; }", "-whyareyounotinlining not allowed in library consumer rules.") + .put( + "-processkotlinnullchecks", + "-processkotlinnullchecks not allowed in library consumer rules.") + .put( + "-processkotlinnullchecks keep", + "-processkotlinnullchecks not allowed in library consumer rules.") + .put( + "-processkotlinnullchecks remove_message", + "-processkotlinnullchecks not allowed in library consumer rules.") + .put( + "-processkotlinnullchecks remove", + "-processkotlinnullchecks not allowed in library consumer rules.") .build(); @Parameter(1)
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java index 24490f3..91e3aa5 100644 --- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java +++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -27,6 +27,7 @@ import com.android.tools.r8.position.Position; import com.android.tools.r8.position.TextRange; import com.android.tools.r8.shaking.ProguardClassNameList.SingleClassNameList; +import com.android.tools.r8.shaking.ProguardConfiguration.ProcessKotlinNullChecks; import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards; import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType; import com.android.tools.r8.shaking.constructor.InitMatchingTest; @@ -3020,6 +3021,83 @@ } @Test + public void parseProcessKotlinNullChecks() { + { + String configuration = StringUtils.lines(""); + parser.parse(createConfigurationForTesting(configuration)); + verifyParserEndsCleanly(); + + ProguardConfiguration config = builder.build(); + assertEquals(ProcessKotlinNullChecks.DEFAULT, config.getProcessKotlinNullChecks()); + assertEquals(0, config.getRules().size()); + } + reset(); + + { + String configuration = StringUtils.lines("-processkotlinnullchecks"); + parser.parse(createConfigurationForTesting(configuration)); + verifyParserEndsCleanly(); + + ProguardConfiguration config = builder.build(); + assertEquals(ProcessKotlinNullChecks.REMOVE_MESSAGE, config.getProcessKotlinNullChecks()); + assertEquals(0, config.getRules().size()); + } + reset(); + + { + String configuration = StringUtils.lines("-processkotlinnullchecks keep"); + parser.parse(createConfigurationForTesting(configuration)); + verifyParserEndsCleanly(); + + ProguardConfiguration config = builder.build(); + assertEquals(ProcessKotlinNullChecks.KEEP, config.getProcessKotlinNullChecks()); + assertEquals(0, config.getRules().size()); + } + for (String configuration : + ImmutableList.of( + StringUtils.lines("-processkotlinnullchecks remove_message"), + StringUtils.lines( + "-processkotlinnullchecks remove_message", "-processkotlinnullchecks keep"), + StringUtils.lines( + "-processkotlinnullchecks keep", "-processkotlinnullchecks remove_message"))) { + reset(); + + parser.parse(createConfigurationForTesting(configuration)); + verifyParserEndsCleanly(); + + ProguardConfiguration config = builder.build(); + assertEquals(ProcessKotlinNullChecks.REMOVE_MESSAGE, config.getProcessKotlinNullChecks()); + assertEquals(0, config.getRules().size()); + } + + for (String configuration : + ImmutableList.of( + StringUtils.lines("-processkotlinnullchecks remove"), + StringUtils.lines( + "-processkotlinnullchecks remove", "-processkotlinnullchecks remove_message"), + StringUtils.lines( + "-processkotlinnullchecks remove", "-processkotlinnullchecks keep"))) { + reset(); + parser.parse(createConfigurationForTesting(configuration)); + verifyParserEndsCleanly(); + + ProguardConfiguration config = builder.build(); + assertEquals(ProcessKotlinNullChecks.REMOVE, config.getProcessKotlinNullChecks()); + assertEquals(0, config.getRules().size()); + } + + reset(); + + try { + String configuration = StringUtils.lines("-processkotlinnullchecks leave"); + parser.parse(createConfigurationForTesting(configuration)); + fail("Expect to fail due to unsupported value."); + } catch (RuntimeException e) { + checkDiagnostics(handler.errors, null, 1, 26, "Illegal value for -processkotlinnullchecks"); + } + } + + @Test public void testParsedConfigurationWithInclude() throws Exception { Path config = temp.newFile("config.txt").toPath().toAbsolutePath(); Path include1 = temp.newFile("include1.txt").toPath().toAbsolutePath();
diff --git a/tools/download_kotlin.py b/tools/download_kotlin.py index e07c86c..11576c5 100755 --- a/tools/download_kotlin.py +++ b/tools/download_kotlin.py
@@ -14,8 +14,8 @@ JETBRAINS_KOTLIN_STABLE_URL = "https://github.com/JetBrains/kotlin/releases/download" -JETBRAINS_KOTLIN_MAVEN_URL = "https://packages.jetbrains.team/maven/p/" \ - "kt/bootstrap/org/jetbrains/kotlin/" +JETBRAINS_KOTLIN_MAVEN_URL = "https://redirector.kotlinlang.org/maven/" \ + "bootstrap/org/jetbrains/kotlin/" KOTLIN_RELEASE_URL = JETBRAINS_KOTLIN_MAVEN_URL + "kotlin-compiler/" KOTLINC_LIB = os.path.join(utils.THIRD_PARTY, "kotlin", @@ -92,7 +92,7 @@ if (top_most_version_and_build is None): raise Exception('Url: %s \n returned %s' % - (KOTLIN_RELEASE_URL, response.getcode())) + (response.geturl(), response.getcode())) # Download checked in kotlin dev compiler before owerlaying with the new. # TODO(sgjesse): This should just ensure an empty directory instead of