Add D8 and R8 API to control assertion code at compile time
Second step:
* Support setting ENABLE, DISABLE or FULL for pacakge and class as well
* Tests for inner classes when enabling for class on the JVM.
Bug: 139898386
Change-Id: Iead67e7f5bf8511ff27561b1bb10e1b0a1a6da60
diff --git a/src/main/java/com/android/tools/r8/AssertionsConfiguration.java b/src/main/java/com/android/tools/r8/AssertionsConfiguration.java
index f77bbce..15acd6f 100644
--- a/src/main/java/com/android/tools/r8/AssertionsConfiguration.java
+++ b/src/main/java/com/android/tools/r8/AssertionsConfiguration.java
@@ -4,12 +4,14 @@
package com.android.tools.r8;
-import com.android.tools.r8.utils.StringDiagnostic;
+import java.util.ArrayList;
+import java.util.List;
@Keep
public class AssertionsConfiguration {
/** The possible transformations of the javac generated assertion code during compilation. */
+ @Keep
public enum AssertionTransformation {
/** Unconditionally enable the javac generated assertion code. */
ENABLE,
@@ -22,14 +24,55 @@
PASSTHROUGH
}
- private AssertionTransformation transformation;
-
- private AssertionsConfiguration(AssertionTransformation transformation) {
- this.transformation = transformation;
+ public enum ConfigurationType {
+ ALL,
+ PACKAGE,
+ CLASS
}
- static Builder builder(DiagnosticsHandler handler) {
- return new Builder(handler);
+ public static class ConfigurationEntry {
+ private final AssertionTransformation transformation;
+ private final ConfigurationType type;
+ private final String value;
+
+ private ConfigurationEntry(
+ AssertionTransformation transformation, ConfigurationType type, String value) {
+ assert value != null || type == ConfigurationType.ALL;
+ this.transformation = transformation;
+ this.type = type;
+ this.value = value;
+ }
+
+ public AssertionTransformation getTransformation() {
+ return transformation;
+ }
+
+ public ConfigurationType getType() {
+ return type;
+ }
+
+ public String getValue() {
+ return value;
+ }
+ }
+
+ // Methods which need to be public.
+ public static class InternalAssertionConfiguration {
+
+ public static List<ConfigurationEntry> getConfiguration(AssertionsConfiguration configuration) {
+ return configuration.entries;
+ }
+ }
+
+ private final List<ConfigurationEntry> entries;
+
+ private AssertionsConfiguration(List<ConfigurationEntry> entries) {
+ this.entries = entries;
+ }
+
+ static AssertionsConfiguration.Builder builder(AssertionsConfiguration previous) {
+ return new AssertionsConfiguration.Builder(
+ previous != null ? previous.entries : new ArrayList<>());
}
/**
@@ -38,24 +81,35 @@
* <p>A builder is obtained by calling {@link
* BaseCompilerCommand.Builder#addAssertionsConfiguration}.
*/
- public AssertionTransformation getTransformation() {
- return transformation;
- }
-
@Keep
public static class Builder {
- private AssertionTransformation transformation = null;
+ private final List<ConfigurationEntry> entries;
- private final DiagnosticsHandler handler;
+ private Builder(List<ConfigurationEntry> previousEntries) {
+ assert previousEntries != null;
+ this.entries = previousEntries;
+ }
- private Builder(DiagnosticsHandler handler) {
- this.handler = handler;
+ private void addEntry(
+ AssertionTransformation transformation, ConfigurationType type, String value) {
+ entries.add(new ConfigurationEntry(transformation, type, value));
}
/** Set how to handle javac generated assertion code. */
public AssertionsConfiguration.Builder setTransformation(
AssertionTransformation transformation) {
- this.transformation = transformation;
+ addEntry(transformation, ConfigurationType.ALL, null);
+ return this;
+ }
+
+ AssertionsConfiguration.Builder setDefault(AssertionTransformation transformation) {
+ // Add the default by inserting a transform all entry at the beginning of the list, if there
+ // isn't already one.
+ ConfigurationEntry defaultEntry =
+ new ConfigurationEntry(transformation, ConfigurationType.ALL, null);
+ if (entries.size() == 0 || entries.get(0).type != ConfigurationType.ALL) {
+ entries.listIterator().add(defaultEntry);
+ }
return this;
}
@@ -86,7 +140,7 @@
/** Set how to handle javac generated assertion code in package and all subpackages. */
public AssertionsConfiguration.Builder setTransformationForPackage(
String packageName, AssertionTransformation transformation) {
- handler.error(new StringDiagnostic("Unsupported"));
+ addEntry(transformation, ConfigurationType.PACKAGE, packageName);
return this;
}
@@ -123,7 +177,7 @@
/** Set how to handle javac generated assertion code in class. */
public AssertionsConfiguration.Builder setTransformationForClass(
String className, AssertionTransformation transformation) {
- handler.error(new StringDiagnostic("Unsupported"));
+ addEntry(transformation, ConfigurationType.CLASS, className);
return this;
}
@@ -151,7 +205,7 @@
/** Build and return the {@link AssertionsConfiguration}. */
public AssertionsConfiguration build() {
- return new AssertionsConfiguration(transformation);
+ return new AssertionsConfiguration(entries);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index ce60661..2b94a54 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexItemFactory;
@@ -134,8 +135,11 @@
return optimizeMultidexForLinearAlloc;
}
- public AssertionsConfiguration getAssertionsConfiguration() {
- return assertionsConfiguration;
+ AssertionsConfiguration getAssertionsConfiguration(
+ AssertionTransformation defaultTransformation) {
+ return AssertionsConfiguration.builder(assertionsConfiguration)
+ .setDefault(defaultTransformation)
+ .build();
}
Reporter getReporter() {
@@ -491,7 +495,8 @@
Function<AssertionsConfiguration.Builder, AssertionsConfiguration>
assertionsConfigurationGenerator) {
assertionsConfiguration =
- assertionsConfigurationGenerator.apply(AssertionsConfiguration.builder(getReporter()));
+ assertionsConfigurationGenerator.apply(
+ AssertionsConfiguration.builder(assertionsConfiguration));
return self();
}
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 2214e2d..d3ec378 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -6,7 +6,6 @@
import static com.android.tools.r8.D8Command.USAGE_MESSAGE;
import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.dex.Marker;
@@ -19,6 +18,7 @@
import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
+import com.android.tools.r8.ir.optimize.AssertionsRewriter;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.naming.PrefixRewritingNamingLens;
import com.android.tools.r8.origin.CommandLineOrigin;
@@ -159,7 +159,7 @@
final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
- if (options.assertionTransformation != AssertionTransformation.PASSTHROUGH) {
+ if (!AssertionsRewriter.isPassthroughAll(options.assertionsConfiguration)) {
// Run analysis to mark all <clinit> methods having the javac generated assertion
// enabling code.
ClassInitializerAssertionEnablingAnalysis analysis =
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 6bdc35c..2401aa5 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -361,15 +361,10 @@
internal.desugaredLibraryConfiguration = libraryConfiguration;
internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
- assert internal.assertionTransformation == null;
- if (getAssertionsConfiguration() != null) {
- internal.assertionTransformation = getAssertionsConfiguration().getTransformation();
- }
- if (internal.assertionTransformation == null) {
- // Default, when no configuration is provided, is to disable all javac generated assertion
- // code when generating dex.
- internal.assertionTransformation = AssertionTransformation.DISABLE;
- }
+ assert internal.assertionsConfiguration == null;
+ // Default, when no configuration is provided, is to remove all javac generated assertion
+ // code when generating dex.
+ internal.assertionsConfiguration = getAssertionsConfiguration(AssertionTransformation.DISABLE);
return internal;
}
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index ff64d62..1346623 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
import com.android.tools.r8.graph.DexItemFactory;
@@ -141,6 +142,11 @@
// TODO(134732760): This is still work in progress.
internal.desugaredLibraryConfiguration = libraryConfiguration;
+ assert internal.assertionsConfiguration == null;
+ // Default, when no configuration is provided, is to remove all javac generated assertion
+ // code when generating dex.
+ internal.assertionsConfiguration = getAssertionsConfiguration(AssertionTransformation.DISABLE);
+
return internal;
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 03235a4..8cacb12 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -6,7 +6,6 @@
import static com.android.tools.r8.R8Command.USAGE_MESSAGE;
import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.dex.Marker;
@@ -33,6 +32,7 @@
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.SourceDebugExtensionRewriter;
import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
+import com.android.tools.r8.ir.optimize.AssertionsRewriter;
import com.android.tools.r8.ir.optimize.EnumInfoMapCollector;
import com.android.tools.r8.ir.optimize.MethodPoolCollection;
import com.android.tools.r8.ir.optimize.NestReducer;
@@ -813,7 +813,7 @@
if (appView.options().enableInitializedClassesInInstanceMethodsAnalysis) {
enqueuer.registerAnalysis(new InitializedClassesInInstanceMethodsAnalysis(appView));
}
- if (appView.options().assertionTransformation != AssertionTransformation.PASSTHROUGH) {
+ if (!AssertionsRewriter.isPassthroughAll(appView.options().assertionsConfiguration)) {
enqueuer.registerAnalysis(
new ClassInitializerAssertionEnablingAnalysis(
appView.dexItemFactory(), OptimizationFeedbackSimple.getInstance()));
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 45f9e84..265fa38 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -870,19 +870,14 @@
internal.syntheticProguardRulesConsumer = syntheticProguardRulesConsumer;
- // Default is to remove Java assertion code as Dalvik and Art does not reliable support
- // Java assertions. When generation class file output always keep the Java assertions code.
- assert internal.assertionTransformation == null;
- if (getAssertionsConfiguration() == null) {
- // Default, when no configuration is provided, is to disable all javac generated assertion
- // code when generating dex and leave it when generating class files.
- internal.assertionTransformation =
- internal.isGeneratingClassFiles()
- ? AssertionTransformation.PASSTHROUGH
- : AssertionTransformation.DISABLE;
- } else {
- internal.assertionTransformation = getAssertionsConfiguration().getTransformation();
- }
+ assert internal.assertionsConfiguration == null;
+ // Default, when no configuration is provided, is to remove all javac generated assertion
+ // code when generating dex and leave it when generating class files.
+ internal.assertionsConfiguration =
+ getAssertionsConfiguration(
+ internal.isGeneratingClassFiles()
+ ? AssertionTransformation.PASSTHROUGH
+ : AssertionTransformation.DISABLE);
// When generating class files the build is "intermediate" and we cannot pollute the namespace
// with the a hard-coded outline class. Doing so would prohibit subsequent merging of two
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 5d85611..ab96c0d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -119,6 +119,7 @@
public final DexString longDescriptor = createString("J");
public final DexString shortDescriptor = createString("S");
public final DexString voidDescriptor = createString("V");
+ public final DexString descriptorSeparator = createString("/");
public final DexString boxedBooleanDescriptor = createString("Ljava/lang/Boolean;");
public final DexString boxedByteDescriptor = createString("Ljava/lang/Byte;");
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index 131cee6..f3a829c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -442,6 +442,22 @@
return true;
}
+ public boolean contains(DexString s) {
+ // TODO(b/146621590): This does not handle character boundaries correctly.
+ int index = 0;
+ while (content.length - index >= s.content.length) {
+ int i = 0;
+ while (i < s.content.length - 1 && content[index + i] == s.content[i]) {
+ i++;
+ }
+ if (i == s.content.length - 1) {
+ return true;
+ }
+ index++;
+ }
+ return false;
+ }
+
public boolean endsWith(DexString suffix) {
if (content.length < suffix.content.length) {
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index e28aa62..00d2f79 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -6,7 +6,6 @@
import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.IncludeAllResources;
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
@@ -49,6 +48,7 @@
import com.android.tools.r8.ir.desugar.StringConcatRewriter;
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import com.android.tools.r8.ir.optimize.AliasIntroducer;
+import com.android.tools.r8.ir.optimize.AssertionsRewriter;
import com.android.tools.r8.ir.optimize.Assumer;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization;
import com.android.tools.r8.ir.optimize.CodeRewriter;
@@ -160,6 +160,7 @@
public final Collection<Assumer> assumers = new ArrayList<>();
private final DynamicTypeOptimization dynamicTypeOptimization;
+ final AssertionsRewriter assertionsRewriter;
final DeadCodeRemover deadCodeRemover;
private final MethodOptimizationInfoCollector methodOptimizationInfoCollector;
@@ -198,6 +199,7 @@
this.stringOptimizer = new StringOptimizer(appView);
this.stringBuilderOptimizer = new StringBuilderOptimizer(appView);
this.deadCodeRemover = new DeadCodeRemover(appView, codeRewriter);
+ this.assertionsRewriter = new AssertionsRewriter(appView);
this.idempotentFunctionCallCanonicalizer = new IdempotentFunctionCallCanonicalizer(appView);
this.neverMergePrefixes =
options.neverMergePrefixes.stream()
@@ -1172,13 +1174,13 @@
if (memberValuePropagation != null) {
memberValuePropagation.rewriteWithConstantValues(code, method.method.holder);
}
+
if (options.enableEnumValueOptimization) {
assert appView.enableWholeProgramOptimizations();
codeRewriter.removeSwitchMaps(code);
}
- if (options.assertionTransformation != AssertionTransformation.PASSTHROUGH) {
- codeRewriter.processAssertions(appView, method, code, feedback);
- }
+
+ assertionsRewriter.run(method, code);
previous = printMethod(code, "IR after disable assertions (SSA)", previous);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
new file mode 100644
index 0000000..2292b0a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -0,0 +1,268 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.AssertionsConfiguration;
+import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
+import com.android.tools.r8.AssertionsConfiguration.ConfigurationEntry;
+import com.android.tools.r8.AssertionsConfiguration.ConfigurationType;
+import com.android.tools.r8.AssertionsConfiguration.InternalAssertionConfiguration;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ThrowingCharIterator;
+import java.io.UTFDataFormatException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class AssertionsRewriter {
+
+ private static class ConfigurationEntryWithDexString {
+
+ private ConfigurationEntry entry;
+ private final DexString value;
+
+ private ConfigurationEntryWithDexString(
+ ConfigurationEntry entry, DexItemFactory dexItemFactory) {
+ this.entry = entry;
+ switch (entry.getType()) {
+ case PACKAGE:
+ if (entry.getValue().length() == 0) {
+ value = dexItemFactory.createString("");
+ } else {
+ value =
+ dexItemFactory.createString(
+ "L"
+ + entry
+ .getValue()
+ .replace(
+ DescriptorUtils.JAVA_PACKAGE_SEPARATOR,
+ DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR)
+ + "/");
+ }
+ break;
+ case CLASS:
+ value =
+ dexItemFactory.createString(
+ "L"
+ + entry
+ .getValue()
+ .replace(
+ DescriptorUtils.JAVA_PACKAGE_SEPARATOR,
+ DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR)
+ + ";");
+ break;
+ case ALL:
+ value = null;
+ break;
+ default:
+ throw new Unreachable();
+ }
+ }
+ }
+
+ private final AppView<?> appView;
+ private final DexItemFactory dexItemFactory;
+ private final List<ConfigurationEntryWithDexString> configuration;
+ private final boolean enabled;
+
+ public AssertionsRewriter(AppView<?> appView) {
+ this.appView = appView;
+ this.dexItemFactory = appView.dexItemFactory();
+ if (appView.options().assertionsConfiguration == null) {
+ this.configuration = null;
+ this.enabled = false;
+ } else {
+ List<ConfigurationEntry> configuration =
+ InternalAssertionConfiguration
+ .getConfiguration(appView.options().assertionsConfiguration);
+ this.configuration =
+ configuration.stream()
+ .map(entry -> new ConfigurationEntryWithDexString(entry, appView.dexItemFactory()))
+ .collect(Collectors.toList());
+ this.enabled = !isPassthroughAll(appView.options().assertionsConfiguration);
+ }
+ }
+
+ public static boolean isPassthroughAll(AssertionsConfiguration assertionsConfiguration) {
+ List<ConfigurationEntry> configuration =
+ InternalAssertionConfiguration.getConfiguration(assertionsConfiguration);
+ return configuration.size() == 1
+ && configuration.get(0).getTransformation() == AssertionTransformation.PASSTHROUGH
+ && configuration.get(0).getType() == ConfigurationType.ALL;
+ }
+
+ private AssertionTransformation getTransformationForMethod(DexEncodedMethod method) {
+ AssertionTransformation transformation = null;
+ for (ConfigurationEntryWithDexString entry : configuration) {
+ switch (entry.entry.getType()) {
+ case ALL:
+ transformation = entry.entry.getTransformation();
+ break;
+ case PACKAGE:
+ if (entry.value.size == 0) {
+ if (!method.method.holder.descriptor.contains(dexItemFactory.descriptorSeparator)) {
+ transformation = entry.entry.getTransformation();
+ }
+ } else if (method.method.holder.descriptor.startsWith(entry.value)) {
+ transformation = entry.entry.getTransformation();
+ }
+ break;
+ case CLASS:
+ if (method.method.holder.descriptor.equals(entry.value)) {
+ transformation = entry.entry.getTransformation();
+ }
+ if (isDescriptorForClassOrInnerClass(entry.value, method.method.holder.descriptor)) {
+ transformation = entry.entry.getTransformation();
+ }
+ break;
+ default:
+ throw new Unreachable();
+ }
+ }
+ assert transformation != null; // Default transformation are always added.
+ return transformation;
+ }
+
+ private boolean isDescriptorForClassOrInnerClass(
+ DexString classDescriptor, DexString classOrInnerClassDescriptor) {
+ // Same string same class.
+ if (classOrInnerClassDescriptor == classDescriptor) {
+ return true;
+ }
+
+ // Check for inner class name by checking if the prefix is the class descriptor,
+ // where ';' is replaced whit '$' and no '/' after that.
+ if (classOrInnerClassDescriptor.size < classDescriptor.size) {
+ return false;
+ }
+ ThrowingCharIterator<UTFDataFormatException> i1 = classDescriptor.iterator();
+ ThrowingCharIterator<UTFDataFormatException> i2 = classOrInnerClassDescriptor.iterator();
+ try {
+ while (i1.hasNext()) {
+ char c1 = i1.nextChar();
+ char c2 = i2.nextChar();
+ // The Java VM behaviour is including all inner classes as well when a class is specified.
+ if (c1 == ';' && c2 == DescriptorUtils.INNER_CLASS_SEPARATOR) {
+ // If there is a '/' after the '$' this is not an inner class after all.
+ while (i2.hasNext()) {
+ if (i2.nextChar() == DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR) {
+ return false;
+ }
+ }
+ return true;
+ }
+ if (c1 != c2) {
+ return false;
+ }
+ }
+ assert i2.hasNext();
+ return false;
+ } catch (UTFDataFormatException e) {
+ return false;
+ }
+ }
+
+ /**
+ * For supporting assert javac adds the static field $assertionsDisabled to all classes which have
+ * methods with assertions. This is used to support the Java VM -ea flag.
+ *
+ * <p>The class:
+ *
+ * <pre>
+ * class A {
+ * void m() {
+ * assert xxx;
+ * }
+ * }
+ * </pre>
+ *
+ * Is compiled into:
+ *
+ * <pre>
+ * class A {
+ * static boolean $assertionsDisabled;
+ * static {
+ * $assertionsDisabled = A.class.desiredAssertionStatus();
+ * }
+ *
+ * // method with "assert xxx";
+ * void m() {
+ * if (!$assertionsDisabled) {
+ * if (xxx) {
+ * throw new AssertionError(...);
+ * }
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * With the rewriting below (and other rewritings) the resulting code is:
+ *
+ * <pre>
+ * class A {
+ * void m() {
+ * }
+ * }
+ * </pre>
+ */
+ public void run(DexEncodedMethod method, IRCode code) {
+ if (!enabled) {
+ return;
+ }
+ AssertionTransformation transformation = getTransformationForMethod(method);
+ if (transformation == AssertionTransformation.PASSTHROUGH) {
+ return;
+ }
+ DexEncodedMethod clinit;
+ // If the <clinit> of this class did not have have code to turn on assertions don't try to
+ // remove assertion code from the method (including <clinit> itself.
+ if (method.isClassInitializer()) {
+ clinit = method;
+ } else {
+ DexClass clazz = appView.definitionFor(method.method.holder);
+ if (clazz == null) {
+ return;
+ }
+ clinit = clazz.getClassInitializer();
+ }
+ if (clinit == null || !clinit.getOptimizationInfo().isInitializerEnablingJavaAssertions()) {
+ return;
+ }
+
+ // This code will process the assertion code in all methods including <clinit>.
+ InstructionListIterator iterator = code.instructionListIterator();
+ while (iterator.hasNext()) {
+ Instruction current = iterator.next();
+ if (current.isInvokeMethod()) {
+ InvokeMethod invoke = current.asInvokeMethod();
+ if (invoke.getInvokedMethod() == dexItemFactory.classMethods.desiredAssertionStatus) {
+ iterator.replaceCurrentInstruction(code.createIntConstant(0));
+ }
+ } else if (current.isStaticPut()) {
+ StaticPut staticPut = current.asStaticPut();
+ if (staticPut.getField().name == dexItemFactory.assertionsDisabled) {
+ iterator.remove();
+ }
+ } else if (current.isStaticGet()) {
+ StaticGet staticGet = current.asStaticGet();
+ if (staticGet.getField().name == dexItemFactory.assertionsDisabled) {
+ iterator.replaceCurrentInstruction(
+ code.createIntConstant(transformation == AssertionTransformation.DISABLE ? 1 : 0));
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 527874a..65e86b7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -9,7 +9,6 @@
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isTypeVisibleFromContext;
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
@@ -72,7 +71,6 @@
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Switch;
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.Value;
@@ -80,7 +78,6 @@
import com.android.tools.r8.ir.code.Xor;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
import com.android.tools.r8.utils.InternalOptions;
@@ -1275,95 +1272,6 @@
assert code.isConsistentSSA();
}
- /**
- * For supporting assert javac adds the static field $assertionsDisabled to all classes which have
- * methods with assertions. This is used to support the Java VM -ea flag.
- *
- * <p>The class:
- *
- * <pre>
- * class A {
- * void m() {
- * assert xxx;
- * }
- * }
- * </pre>
- *
- * Is compiled into:
- *
- * <pre>
- * class A {
- * static boolean $assertionsDisabled;
- * static {
- * $assertionsDisabled = A.class.desiredAssertionStatus();
- * }
- *
- * // method with "assert xxx";
- * void m() {
- * if (!$assertionsDisabled) {
- * if (xxx) {
- * throw new AssertionError(...);
- * }
- * }
- * }
- * }
- * </pre>
- *
- * With the rewriting below (and other rewritings) the resulting code is:
- *
- * <pre>
- * class A {
- * void m() {
- * }
- * }
- * </pre>
- */
- public void processAssertions(
- AppView<?> appView, DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
- assert appView.options().assertionTransformation != AssertionTransformation.PASSTHROUGH;
- DexEncodedMethod clinit;
- // If the <clinit> of this class did not have have code to turn on assertions don't try to
- // remove assertion code from the method (including <clinit> itself.
- if (method.isClassInitializer()) {
- clinit = method;
- } else {
- DexClass clazz = appView.definitionFor(method.method.holder);
- if (clazz == null) {
- return;
- }
- clinit = clazz.getClassInitializer();
- }
- if (clinit == null || !clinit.getOptimizationInfo().isInitializerEnablingJavaAssertions()) {
- return;
- }
-
- // This code will process the assertion code in all methods including <clinit>.
- InstructionListIterator iterator = code.instructionListIterator();
- while (iterator.hasNext()) {
- Instruction current = iterator.next();
- if (current.isInvokeMethod()) {
- InvokeMethod invoke = current.asInvokeMethod();
- if (invoke.getInvokedMethod() == dexItemFactory.classMethods.desiredAssertionStatus) {
- iterator.replaceCurrentInstruction(code.createIntConstant(0));
- }
- } else if (current.isStaticPut()) {
- StaticPut staticPut = current.asStaticPut();
- if (staticPut.getField().name == dexItemFactory.assertionsDisabled) {
- iterator.remove();
- }
- } else if (current.isStaticGet()) {
- StaticGet staticGet = current.asStaticGet();
- if (staticGet.getField().name == dexItemFactory.assertionsDisabled) {
- iterator.replaceCurrentInstruction(
- code.createIntConstant(
- appView.options().assertionTransformation == AssertionTransformation.DISABLE
- ? 1
- : 0));
- }
- }
- }
- }
-
enum RemoveCheckCastInstructionIfTrivialResult {
NO_REMOVALS,
REMOVED_CAST_DO_NARROW
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index c4815ae..128c18b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -5,7 +5,7 @@
import static com.google.common.base.Predicates.not;
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
+import com.android.tools.r8.AssertionsConfiguration;
import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.DataResourceConsumer;
@@ -435,7 +435,7 @@
public boolean ignoreMissingClasses = false;
// EXPERIMENTAL flag to get behaviour as close to Proguard as possible.
public boolean forceProguardCompatibility = false;
- public AssertionTransformation assertionTransformation = null;
+ public AssertionsConfiguration assertionsConfiguration = null;
public boolean configurationDebugging = false;
// Don't convert Code objects to IRCode.
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
index af84f69..ce9f1c5 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.rewrite.assertions.testclasses.TestClassForInnerClass;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -25,12 +26,35 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
@RunWith(Parameterized.class)
-public class AssertionsConfigurationTest extends TestBase {
+public class AssertionsConfigurationTest extends TestBase implements Opcodes {
private final TestParameters parameters;
+ private Class<?> class1 = com.android.tools.r8.rewrite.assertions.testclasses.Class1.class;
+ private Class<?> class2 = com.android.tools.r8.rewrite.assertions.testclasses.Class2.class;
+ private Class<?> subpackageClass1 =
+ com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class;
+ private Class<?> subpackageClass2 =
+ com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class;
+
+ private List<Class<?>> testClasses =
+ ImmutableList.of(TestClass.class, class1, class2, subpackageClass1, subpackageClass2);
+
+ private String packageName =
+ com.android.tools.r8.rewrite.assertions.testclasses.Class1.class.getPackage().getName();
+ private String subPackageName =
+ com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class
+ .getPackage()
+ .getName();
+
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimes().withAllApiLevels().build();
@@ -47,12 +71,7 @@
List<String> outputLines)
throws Exception {
testForD8()
- .addProgramClasses(
- TestClass.class,
- com.android.tools.r8.rewrite.assertions.testclasses.Class1.class,
- com.android.tools.r8.rewrite.assertions.testclasses.Class2.class,
- com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class,
- com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class)
+ .addProgramClasses(testClasses)
.setMinApi(parameters.getApiLevel())
.addAssertionsConfiguration(assertionsConfigurationBuilder)
.compile()
@@ -79,18 +98,9 @@
throws Exception {
testForR8(parameters.getBackend())
- .addProgramClasses(
- TestClass.class,
- com.android.tools.r8.rewrite.assertions.testclasses.Class1.class,
- com.android.tools.r8.rewrite.assertions.testclasses.Class2.class,
- com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class,
- com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class)
+ .addProgramClasses(testClasses)
.addKeepMainRule(TestClass.class)
- .addKeepClassAndMembersRules(
- com.android.tools.r8.rewrite.assertions.testclasses.Class1.class,
- com.android.tools.r8.rewrite.assertions.testclasses.Class2.class,
- com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class,
- com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class)
+ .addKeepClassAndMembersRules(class1, class2, subpackageClass1, subpackageClass2)
.setMinApi(parameters.getApiLevel())
.addAssertionsConfiguration(assertionsConfigurationBuilder)
.compile()
@@ -125,8 +135,7 @@
return ImmutableList.of("DONE");
}
- private void checkAssertionCodeRemoved(CodeInspector inspector, Class<?> clazz) {
- ClassSubject subject = inspector.clazz(clazz);
+ private void checkAssertionCodeRemoved(ClassSubject subject) {
assertThat(subject, isPresent());
// <clinit> is removed by R8 as it becomes empty.
if (subject.uniqueMethodWithName("<clinit>").isPresent()) {
@@ -143,8 +152,11 @@
.anyMatch(InstructionSubject::isThrow));
}
- private void checkAssertionCodeEnabled(CodeInspector inspector, Class<?> clazz) {
- ClassSubject subject = inspector.clazz(clazz);
+ private void checkAssertionCodeRemoved(CodeInspector inspector, Class<?> clazz) {
+ checkAssertionCodeRemoved(inspector.clazz(clazz));
+ }
+
+ private void checkAssertionCodeEnabled(ClassSubject subject) {
assertThat(subject, isPresent());
// <clinit> is removed by R8.
if (subject.uniqueMethodWithName("<clinit>").isPresent()) {
@@ -161,6 +173,10 @@
.anyMatch(InstructionSubject::isThrow));
}
+ private void checkAssertionCodeEnabled(CodeInspector inspector, Class<?> clazz) {
+ checkAssertionCodeEnabled(inspector.clazz(clazz));
+ }
+
private void checkAssertionCodeLeft(CodeInspector inspector, Class<?> clazz) {
ClassSubject subject = inspector.clazz(clazz);
assertThat(subject, isPresent());
@@ -177,40 +193,28 @@
}
private void checkAssertionCodeRemoved(CodeInspector inspector) {
- checkAssertionCodeRemoved(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class1.class);
- checkAssertionCodeRemoved(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class2.class);
- checkAssertionCodeRemoved(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class);
- checkAssertionCodeRemoved(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class);
+ checkAssertionCodeRemoved(inspector, class1);
+ checkAssertionCodeRemoved(inspector, class2);
+ checkAssertionCodeRemoved(inspector, subpackageClass1);
+ checkAssertionCodeRemoved(inspector, subpackageClass2);
}
private void checkAssertionCodeEnabled(CodeInspector inspector) {
- checkAssertionCodeEnabled(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class1.class);
- checkAssertionCodeEnabled(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class2.class);
- checkAssertionCodeEnabled(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class);
- checkAssertionCodeEnabled(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class);
+ checkAssertionCodeEnabled(inspector, class1);
+ checkAssertionCodeEnabled(inspector, class2);
+ checkAssertionCodeEnabled(inspector, subpackageClass1);
+ checkAssertionCodeEnabled(inspector, subpackageClass2);
}
private void checkAssertionCodeLeft(CodeInspector inspector) {
- checkAssertionCodeLeft(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class1.class);
- checkAssertionCodeLeft(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class2.class);
- checkAssertionCodeLeft(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class);
- checkAssertionCodeLeft(
- inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class);
+ checkAssertionCodeLeft(inspector, class1);
+ checkAssertionCodeLeft(inspector, class2);
+ checkAssertionCodeLeft(inspector, subpackageClass1);
+ checkAssertionCodeLeft(inspector, subpackageClass2);
}
@Test
- public void testEnableAllAssertionsForDex() throws Exception {
+ public void testAssertionsForDex() throws Exception {
Assume.assumeTrue(parameters.isDexRuntime());
// Leaving assertions in or disabling them on Dalvik/Art means no assertions.
runD8Test(
@@ -230,6 +234,15 @@
this::enableAllAssertions, this::checkAssertionCodeEnabled, allAssertionsExpectedLines());
runR8Test(
this::enableAllAssertions, this::checkAssertionCodeEnabled, allAssertionsExpectedLines());
+ // Enabling for the package should enable all.
+ runD8Test(
+ builder -> builder.enableForPackage(packageName).build(),
+ this::checkAssertionCodeEnabled,
+ allAssertionsExpectedLines());
+ runR8Test(
+ builder -> builder.enableForPackage(packageName).build(),
+ this::checkAssertionCodeEnabled,
+ allAssertionsExpectedLines());
}
@Test
@@ -259,6 +272,415 @@
true);
}
+ @Test
+ public void testEnableForPackageForDex() throws Exception {
+ Assume.assumeTrue(parameters.isDexRuntime());
+ runD8Test(
+ builder -> builder.enableForPackage(subPackageName).build(),
+ inspector -> {
+ checkAssertionCodeEnabled(inspector, subpackageClass1);
+ checkAssertionCodeEnabled(inspector, subpackageClass2);
+ },
+ ImmutableList.of(
+ "AssertionError in testclasses.subpackage.Class1",
+ "AssertionError in testclasses.subpackage.Class2",
+ "DONE"));
+ runR8Test(
+ builder -> builder.enableForPackage(subPackageName).build(),
+ inspector -> {
+ checkAssertionCodeEnabled(inspector, subpackageClass1);
+ checkAssertionCodeEnabled(inspector, subpackageClass2);
+ },
+ ImmutableList.of(
+ "AssertionError in testclasses.subpackage.Class1",
+ "AssertionError in testclasses.subpackage.Class2",
+ "DONE"));
+ }
+
+ @Test
+ public void testEnableForClassForDex() throws Exception {
+ Assume.assumeTrue(parameters.isDexRuntime());
+ runD8Test(
+ builder ->
+ builder
+ .enableForClass(class1.getCanonicalName())
+ .enableForClass(subpackageClass2.getCanonicalName())
+ .build(),
+ inspector -> {
+ // checkAssertionCodeEnabled(inspector, class1);
+ // checkAssertionCodeEnabled(inspector, subpackageClass2);
+ },
+ ImmutableList.of(
+ "AssertionError in testclasses.Class1",
+ "AssertionError in testclasses.subpackage.Class2",
+ "DONE"));
+ runR8Test(
+ builder ->
+ builder
+ .enableForClass(class1.getCanonicalName())
+ .enableForClass(subpackageClass2.getCanonicalName())
+ .build(),
+ inspector -> {
+ checkAssertionCodeEnabled(inspector, class1);
+ checkAssertionCodeEnabled(inspector, subpackageClass2);
+ },
+ ImmutableList.of(
+ "AssertionError in testclasses.Class1",
+ "AssertionError in testclasses.subpackage.Class2",
+ "DONE"));
+ }
+
+ @Test
+ public void testMixedForDex() throws Exception {
+ Assume.assumeTrue(parameters.isDexRuntime());
+ runD8Test(
+ builder ->
+ builder
+ .enableForPackage(packageName)
+ .disableForClass(class2.getCanonicalName())
+ .disableForClass(subpackageClass1.getCanonicalName())
+ .build(),
+ inspector -> {
+ checkAssertionCodeEnabled(inspector, class1);
+ checkAssertionCodeRemoved(inspector, class2);
+ checkAssertionCodeRemoved(inspector, subpackageClass1);
+ checkAssertionCodeEnabled(inspector, subpackageClass2);
+ },
+ ImmutableList.of(
+ "AssertionError in testclasses.Class1",
+ "AssertionError in testclasses.subpackage.Class2",
+ "DONE"));
+ runR8Test(
+ builder ->
+ builder
+ .enableForPackage(packageName)
+ .disableForClass(class2.getCanonicalName())
+ .disableForClass(subpackageClass1.getCanonicalName())
+ .build(),
+ inspector -> {
+ checkAssertionCodeEnabled(inspector, class1);
+ checkAssertionCodeRemoved(inspector, class2);
+ checkAssertionCodeRemoved(inspector, subpackageClass1);
+ checkAssertionCodeEnabled(inspector, subpackageClass2);
+ },
+ ImmutableList.of(
+ "AssertionError in testclasses.Class1",
+ "AssertionError in testclasses.subpackage.Class2",
+ "DONE"));
+ }
+
+ @Test
+ public void testUnnamedPackageForDex() throws Exception {
+ Assume.assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addProgramClasses(class1, class2)
+ .addProgramClassFileData(
+ testClassForUnknownPackage(),
+ classInUnnamedPackage("Class1"),
+ classInUnnamedPackage("Class2"))
+ .setMinApi(parameters.getApiLevel())
+ .addAssertionsConfiguration(builder -> builder.enableForPackage("").build())
+ .compile()
+ .inspect(
+ inspector -> {
+ checkAssertionCodeEnabled(inspector.clazz("Class1"));
+ checkAssertionCodeEnabled(inspector.clazz("Class2"));
+ checkAssertionCodeRemoved(inspector.clazz(class1));
+ checkAssertionCodeRemoved(inspector.clazz(class2));
+ })
+ .run(parameters.getRuntime(), "Main")
+ .assertSuccessWithOutputLines(
+ "AssertionError in Class1", "AssertionError in Class2", "DONE");
+ }
+
+ @Test
+ public void testInnerClassForJvm() throws Exception {
+ Assume.assumeTrue(parameters.isCfRuntime());
+ // Pointing to the outer class enables assertions for the inner as well.
+ testForJvm()
+ .addProgramClasses(TestClassForInnerClass.class, TestClassForInnerClass.InnerClass.class)
+ .addVmArguments("-ea:" + TestClassForInnerClass.class.getCanonicalName())
+ .run(parameters.getRuntime(), TestClassForInnerClass.class)
+ .assertSuccessWithOutputLines(
+ "AssertionError in TestClassForInnerClass",
+ "AssertionError in TestClassForInnerClass.InnerClass",
+ "DONE");
+
+ // Pointing to the inner class enables no assertions.
+ testForJvm()
+ .addProgramClasses(TestClassForInnerClass.class, TestClassForInnerClass.InnerClass.class)
+ .addVmArguments("-ea:" + TestClassForInnerClass.InnerClass.class.getCanonicalName())
+ .run(parameters.getRuntime(), TestClassForInnerClass.class)
+ .assertSuccessWithOutputLines("DONE");
+ testForJvm()
+ .addProgramClasses(TestClassForInnerClass.class, TestClassForInnerClass.InnerClass.class)
+ .addVmArguments("-ea:" + TestClassForInnerClass.InnerClass.class.getTypeName())
+ .run(parameters.getRuntime(), TestClassForInnerClass.class)
+ .assertSuccessWithOutputLines("DONE");
+ }
+
+ @Test
+ public void testInnerClassForDex() throws Exception {
+ Assume.assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addProgramClasses(TestClassForInnerClass.class, TestClassForInnerClass.InnerClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .addAssertionsConfiguration(
+ builder ->
+ builder.enableForClass(TestClassForInnerClass.class.getCanonicalName()).build())
+ .compile()
+ .inspect(
+ inspector -> {
+ checkAssertionCodeEnabled(inspector.clazz(TestClassForInnerClass.class));
+ checkAssertionCodeEnabled(inspector.clazz(TestClassForInnerClass.InnerClass.class));
+ })
+ .run(parameters.getRuntime(), TestClassForInnerClass.class)
+ .assertSuccessWithOutputLines(
+ "AssertionError in TestClassForInnerClass",
+ "AssertionError in TestClassForInnerClass.InnerClass",
+ "DONE");
+ }
+ /**
+ * Code for the following class in the unnamed package:
+ *
+ * <p>public class Main { public static void main(String[] args) { try { Class1.m(); } catch
+ * (AssertionError e) { System.out.println("AssertionError in Class1"); } try { Class2.m(); }
+ * catch (AssertionError e) { System.out.println("AssertionError in Class2"); } try {
+ * com.android.tools.r8.rewrite.assertions.Class1.m(); } catch (AssertionError e) {
+ * System.out.println("AssertionError in Class1"); } try {
+ * com.android.tools.r8.rewrite.assertions.Class2.m(); } catch (AssertionError e) {
+ * System.out.println("AssertionError in Class2"); } System.out.println("DONE"); } }
+ */
+ public static byte[] testClassForUnknownPackage() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_FINAL | ACC_SUPER, "Main", null, "java/lang/Object", null);
+
+ classWriter.visitSource("Main.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(1, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ Label label1 = new Label();
+ Label label2 = new Label();
+ methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/AssertionError");
+ Label label3 = new Label();
+ Label label4 = new Label();
+ Label label5 = new Label();
+ methodVisitor.visitTryCatchBlock(label3, label4, label5, "java/lang/AssertionError");
+ Label label6 = new Label();
+ Label label7 = new Label();
+ Label label8 = new Label();
+ methodVisitor.visitTryCatchBlock(label6, label7, label8, "java/lang/AssertionError");
+ Label label9 = new Label();
+ Label label10 = new Label();
+ Label label11 = new Label();
+ methodVisitor.visitTryCatchBlock(label9, label10, label11, "java/lang/AssertionError");
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(4, label0);
+ methodVisitor.visitMethodInsn(INVOKESTATIC, "Class1", "m", "()V", false);
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(7, label1);
+ methodVisitor.visitJumpInsn(GOTO, label3);
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(5, label2);
+ methodVisitor.visitFrame(
+ Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/AssertionError"});
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ Label label12 = new Label();
+ methodVisitor.visitLabel(label12);
+ methodVisitor.visitLineNumber(6, label12);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("AssertionError in Class1");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(9, label3);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitMethodInsn(INVOKESTATIC, "Class2", "m", "()V", false);
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(12, label4);
+ methodVisitor.visitJumpInsn(GOTO, label6);
+ methodVisitor.visitLabel(label5);
+ methodVisitor.visitLineNumber(10, label5);
+ methodVisitor.visitFrame(
+ Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/AssertionError"});
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ Label label13 = new Label();
+ methodVisitor.visitLabel(label13);
+ methodVisitor.visitLineNumber(11, label13);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("AssertionError in Class2");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitLabel(label6);
+ methodVisitor.visitLineNumber(14, label6);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/rewrite/assertions/testclasses/Class1",
+ "m",
+ "()V",
+ false);
+ methodVisitor.visitLabel(label7);
+ methodVisitor.visitLineNumber(17, label7);
+ methodVisitor.visitJumpInsn(GOTO, label9);
+ methodVisitor.visitLabel(label8);
+ methodVisitor.visitLineNumber(15, label8);
+ methodVisitor.visitFrame(
+ Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/AssertionError"});
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ Label label14 = new Label();
+ methodVisitor.visitLabel(label14);
+ methodVisitor.visitLineNumber(16, label14);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("AssertionError in testclasses.Class1");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitLabel(label9);
+ methodVisitor.visitLineNumber(19, label9);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/rewrite/assertions/testclasses/Class2",
+ "m",
+ "()V",
+ false);
+ methodVisitor.visitLabel(label10);
+ methodVisitor.visitLineNumber(22, label10);
+ Label label15 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label15);
+ methodVisitor.visitLabel(label11);
+ methodVisitor.visitLineNumber(20, label11);
+ methodVisitor.visitFrame(
+ Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/AssertionError"});
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ Label label16 = new Label();
+ methodVisitor.visitLabel(label16);
+ methodVisitor.visitLineNumber(21, label16);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("AssertionError in testclasses.Class2");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitLabel(label15);
+ methodVisitor.visitLineNumber(24, label15);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("DONE");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label17 = new Label();
+ methodVisitor.visitLabel(label17);
+ methodVisitor.visitLineNumber(25, label17);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+
+ /**
+ * Code for the following class in the unnamed package:
+ *
+ * <p>public class <name> { public static void m() { assert false; } }
+ */
+ public static byte[] classInUnnamedPackage(String name) {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, name, null, "java/lang/Object", null);
+
+ classWriter.visitSource(name + ".java", null);
+
+ {
+ fieldVisitor =
+ classWriter.visitField(
+ ACC_FINAL | ACC_STATIC | ACC_SYNTHETIC, "$assertionsDisabled", "Z", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(1, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "m", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(3, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, name, "$assertionsDisabled", "Z");
+ Label label1 = new Label();
+ methodVisitor.visitJumpInsn(IFNE, label1);
+ methodVisitor.visitTypeInsn(NEW, "java/lang/AssertionError");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL, "java/lang/AssertionError", "<init>", "()V", false);
+ methodVisitor.visitInsn(ATHROW);
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(4, label1);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 0);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(1, label0);
+ methodVisitor.visitLdcInsn(Type.getType("L" + name + ";"));
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Class", "desiredAssertionStatus", "()Z", false);
+ Label label1 = new Label();
+ methodVisitor.visitJumpInsn(IFNE, label1);
+ methodVisitor.visitInsn(ICONST_1);
+ Label label2 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label2);
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitInsn(ICONST_0);
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {Opcodes.INTEGER});
+ methodVisitor.visitFieldInsn(PUTSTATIC, name, "$assertionsDisabled", "Z");
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 0);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+
static class TestClass {
public static void main(String[] args) {
try {
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
index 446821a..27e53ba 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
@@ -199,7 +199,7 @@
.debug()
.noTreeShaking()
.noMinification()
- .addOptionsModification(o -> o.assertionTransformation = transformation)
+ .addAssertionsConfiguration(builder -> builder.setTransformation(transformation).build())
.compile();
}
@@ -337,7 +337,7 @@
.addProgramClasses(ClassWithAssertions.class)
.debug()
.setMinApi(AndroidApiLevel.B)
- .addOptionsModification(o -> o.assertionTransformation = transformation)
+ .addAssertionsConfiguration(builder -> builder.setTransformation(transformation).build())
.compile();
}
@@ -357,7 +357,7 @@
.addProgramFiles(program)
.debug()
.setMinApi(AndroidApiLevel.B)
- .addOptionsModification(o -> o.assertionTransformation = transformation)
+ .addAssertionsConfiguration(builder -> builder.setTransformation(transformation).build())
.compile();
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/TestClassForInnerClass.java b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/TestClassForInnerClass.java
new file mode 100644
index 0000000..6a719f9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/TestClassForInnerClass.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.rewrite.assertions.testclasses;
+
+public class TestClassForInnerClass {
+ public static class InnerClass {
+ public static void m() {
+ assert false;
+ }
+ }
+
+ public static void m() {
+ assert false;
+ }
+
+ public static void main(String[] args) {
+ try {
+ m();
+ } catch (AssertionError e) {
+ System.out.println("AssertionError in TestClassForInnerClass");
+ }
+ try {
+ InnerClass.m();
+ } catch (AssertionError e) {
+ System.out.println("AssertionError in TestClassForInnerClass.InnerClass");
+ }
+ System.out.println("DONE");
+ }
+}