Add support for -processkotlinnullchecks
RELNOTES:
Added new R8 option -processkotlinnullchecks to configure R8 for
processing Kotlin null checks. The option has three values: `keep`,
`remove_message` and `remove`.
The option will process the following null checks added by the Kotlin
compiler:
```
class kotlin.jvm.internal.Intrinsics {
void checkNotNull(java.lang.Object);
void checkNotNull(java.lang.Object, java.lang.String);
void checkExpressionValueIsNotNull(
java.lang.Object, java.lang.String);
void checkNotNullExpressionValue(
java.lang.Object, java.lang.String);
void checkReturnedValueIsNotNull(
java.lang.Object, java.lang.String);
void checkReturnedValueIsNotNull(
java.lang.Object, java.lang.String, java.lang.String);
void checkFieldIsNotNull(java.lang.Object, java.lang.String);
void checkFieldIsNotNull(
java.lang.Object, java.lang.String, java.lang.String);
void checkParameterIsNotNull(java.lang.Object, java.lang.String);
void checkNotNullParameter(java.lang.Object, java.lang.String);
}
```
When `keep` is used these checks are not changed, when
`remove_message` is used the method call is rewritten to an call to
getClass() on the first argument (effectively keeping the null check,
but without any message) and when `remove` is used they are
completely removed.
By default (without any `-processkotlinnullchecks` option) R8 will
use `remove_message` Any specification of `-processkotlinnullchecks`
will override that. If no value is specified the default is
`remove_message`. If specified multiple times the strongest value is
selected.
Note, that when the static analysis can prove that certain values in
the program are never `null` these checks will be removed independent
of using this option.
Bug: b/459441752
Change-Id: Ib72edbed0e369b3cffe33ff9cb59edf148ba2de0diff --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();