Command line options for rewriting assertions to invoke assertion handler
Bug: 209445989
Change-Id: I2d2a232859f59855fd97b64b7e78c4eba0457cfc
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
index 1316be8..631b5b3 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
@@ -3,8 +3,11 @@
// 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.Unreachable;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ExceptionDiagnostic;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
@@ -38,7 +41,16 @@
" --force-pa[:[<class name>|<package name>...]]",
" # Don't change javac generated assertion code. This",
" # is the default handling of javac assertion code when",
- " # generating class file format.");
+ " # generating class file format.",
+ " --force-assertions-handler:<handler method>[:[<class name>|<package name>...]]",
+ " --force-ah:<handler method>[:[<class name>|<package name>...]]",
+ " # Change javac and kotlinc generated assertion code to invoke",
+ " # the method <handler method> with each assertion error",
+ " # instead of throwing it. The <handler method> is specified"
+ + " as",
+ " # a class name followed by a dot and the method name. The",
+ " # handler method must take a single argument of type",
+ " # java.lang.Throwable and have return type void.");
static final Iterable<String> THREAD_COUNT_USAGE_MESSAGE =
Arrays.asList(
@@ -82,23 +94,54 @@
private static String PACKAGE_ASSERTION_POSTFIX = "...";
+ private enum AssertionTransformationType {
+ ENABLE,
+ DISABLE,
+ PASSTHROUGH,
+ HANDLER
+ }
+
+ private AssertionsConfiguration.Builder prepareBuilderForScope(
+ AssertionsConfiguration.Builder builder,
+ AssertionTransformationType transformation,
+ MethodReference assertionHandler) {
+ switch (transformation) {
+ case ENABLE:
+ return builder.setCompileTimeEnable();
+ case DISABLE:
+ return builder.setCompileTimeDisable();
+ case PASSTHROUGH:
+ return builder.setPassthrough();
+ case HANDLER:
+ return builder.setAssertionHandler(assertionHandler);
+ default:
+ throw new Unreachable();
+ }
+ }
+
private void addAssertionTransformation(
- B builder, AssertionTransformation transformation, String scope) {
+ B builder,
+ AssertionTransformationType transformation,
+ MethodReference assertionHandler,
+ String scope) {
if (scope == null) {
builder.addAssertionsConfiguration(
- b -> b.setTransformation(transformation).setScopeAll().build());
+ b -> prepareBuilderForScope(b, transformation, assertionHandler).setScopeAll().build());
} else {
assert scope.length() > 0;
if (scope.endsWith(PACKAGE_ASSERTION_POSTFIX)) {
builder.addAssertionsConfiguration(
b ->
- b.setTransformation(transformation)
+ prepareBuilderForScope(b, transformation, assertionHandler)
.setScopePackage(
scope.substring(0, scope.length() - PACKAGE_ASSERTION_POSTFIX.length()))
.build());
} else {
builder.addAssertionsConfiguration(
- b -> b.setTransformation(transformation).setScopeClass(scope).build());
+ b ->
+ prepareBuilderForScope(b, transformation, assertionHandler)
+ .setScopeClass(scope)
+ .build());
}
}
}
@@ -110,34 +153,78 @@
String FORCE_DA = "--force-da";
String FORCE_PASSTHROUGH_ASSERTIONS = "--force-passthrough-assertions";
String FORCE_PA = "--force-pa";
+ String FORCE_ASSERTIONS_HANDLER = "--force-assertions-handler";
+ String FORCE_AH = "--force-ah";
- AssertionTransformation transformation = null;
+ AssertionTransformationType transformation = null;
+ MethodReference assertionsHandler = null;
String remaining = null;
if (arg.startsWith(FORCE_ENABLE_ASSERTIONS)) {
- transformation = AssertionTransformation.ENABLE;
+ transformation = AssertionTransformationType.ENABLE;
remaining = arg.substring(FORCE_ENABLE_ASSERTIONS.length());
} else if (arg.startsWith(FORCE_EA)) {
- transformation = AssertionTransformation.ENABLE;
+ transformation = AssertionTransformationType.ENABLE;
remaining = arg.substring(FORCE_EA.length());
} else if (arg.startsWith(FORCE_DISABLE_ASSERTIONS)) {
- transformation = AssertionTransformation.DISABLE;
+ transformation = AssertionTransformationType.DISABLE;
remaining = arg.substring(FORCE_DISABLE_ASSERTIONS.length());
} else if (arg.startsWith(FORCE_DA)) {
- transformation = AssertionTransformation.DISABLE;
+ transformation = AssertionTransformationType.DISABLE;
remaining = arg.substring(FORCE_DA.length());
} else if (arg.startsWith(FORCE_PASSTHROUGH_ASSERTIONS)) {
- transformation = AssertionTransformation.PASSTHROUGH;
+ transformation = AssertionTransformationType.PASSTHROUGH;
remaining = arg.substring(FORCE_PASSTHROUGH_ASSERTIONS.length());
} else if (arg.startsWith(FORCE_PA)) {
- transformation = AssertionTransformation.PASSTHROUGH;
+ transformation = AssertionTransformationType.PASSTHROUGH;
remaining = arg.substring(FORCE_PA.length());
+ } else if (arg.startsWith(FORCE_ASSERTIONS_HANDLER)) {
+ transformation = AssertionTransformationType.HANDLER;
+ remaining = arg.substring(FORCE_ASSERTIONS_HANDLER.length());
+ } else if (arg.startsWith(FORCE_AH)) {
+ transformation = AssertionTransformationType.HANDLER;
+ remaining = arg.substring(FORCE_AH.length());
+ }
+ if (transformation == AssertionTransformationType.HANDLER) {
+ if (remaining.length() == 0 || (remaining.length() == 1 && remaining.charAt(0) == ':')) {
+ throw builder.fatalError(
+ new StringDiagnostic("Missing required argument <handler method>", origin));
+ }
+ if (remaining.charAt(0) != ':') {
+ return false;
+ }
+ remaining = remaining.substring(1);
+ int index = remaining.indexOf(':');
+ if (index == 0) {
+ throw builder.fatalError(
+ new StringDiagnostic("Missing required argument <handler method>", origin));
+ }
+ String assertionsHandlerString = index > 0 ? remaining.substring(0, index) : remaining;
+ int lastDotIndex = assertionsHandlerString.lastIndexOf('.');
+ if (assertionsHandlerString.length() < 3
+ || lastDotIndex <= 0
+ || lastDotIndex == assertionsHandlerString.length() - 1
+ || !DescriptorUtils.isValidJavaType(assertionsHandlerString.substring(0, lastDotIndex))) {
+ throw builder.fatalError(
+ new StringDiagnostic(
+ "Invalid argument <handler method>: " + assertionsHandlerString, origin));
+ }
+ assertionsHandler =
+ Reference.methodFromDescriptor(
+ DescriptorUtils.javaTypeToDescriptor(
+ assertionsHandlerString.substring(0, lastDotIndex)),
+ assertionsHandlerString.substring(lastDotIndex + 1),
+ "(Ljava/lang/Throwable;)V");
+ remaining = remaining.substring(assertionsHandlerString.length());
}
if (transformation != null) {
if (remaining.length() == 0) {
- addAssertionTransformation(builder, transformation, null);
+ addAssertionTransformation(builder, transformation, assertionsHandler, null);
return true;
} else {
- if (remaining.length() == 1 || remaining.charAt(0) != ':') {
+ if (remaining.length() == 1 && remaining.charAt(0) == ':') {
+ throw builder.fatalError(new StringDiagnostic("Missing optional argument", origin));
+ }
+ if (remaining.charAt(0) != ':') {
return false;
}
String classOrPackageScope = remaining.substring(1);
@@ -147,7 +234,8 @@
builder.error(
new StringDiagnostic("Illegal assertion scope: " + classOrPackageScope, origin));
}
- addAssertionTransformation(builder, transformation, remaining.substring(1));
+ addAssertionTransformation(
+ builder, transformation, assertionsHandler, remaining.substring(1));
return true;
}
} else {
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index cf095b1..7a8c4e4 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.origin.EmbeddedOrigin;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.FileUtils;
@@ -36,6 +37,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Set;
+import java.util.function.Function;
import java.util.zip.ZipFile;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -586,6 +588,13 @@
assertEquals(AssertionTransformationScope.ALL, entries.get(0).getScope());
}
+ private void checkSingleForceAllAssertion(
+ List<AssertionsConfiguration> entries, Function<AssertionsConfiguration, Boolean> check) {
+ assertEquals(1, entries.size());
+ assertTrue(check.apply(entries.get(0)));
+ assertEquals(AssertionTransformationScope.ALL, entries.get(0).getScope());
+ }
+
private void checkSingleForceClassAndPackageAssertion(
List<AssertionsConfiguration> entries, AssertionTransformation transformation) {
assertEquals(2, entries.size());
@@ -597,6 +606,19 @@
assertEquals("PackageName", entries.get(1).getValue());
}
+ private void checkSingleForceClassAndPackageAssertion(
+ List<AssertionsConfiguration> entries,
+ Function<AssertionsConfiguration, Boolean> checkClass,
+ Function<AssertionsConfiguration, Boolean> checkPackage) {
+ assertEquals(2, entries.size());
+ assertTrue(checkClass.apply(entries.get(0)));
+ assertEquals(AssertionTransformationScope.CLASS, entries.get(0).getScope());
+ assertEquals("ClassName", entries.get(0).getValue());
+ assertTrue(checkPackage.apply(entries.get(1)));
+ assertEquals(AssertionTransformationScope.PACKAGE, entries.get(1).getScope());
+ assertEquals("PackageName", entries.get(1).getValue());
+ }
+
@Test
public void forceAssertionOption() throws Exception {
checkSingleForceAllAssertion(
@@ -622,6 +644,47 @@
"--force-passthrough-assertions:PackageName...")
.getAssertionsConfiguration(),
AssertionTransformation.PASSTHROUGH);
+ checkSingleForceAllAssertion(
+ parse("--force-assertions-handler:com.example.MyHandler.handler")
+ .getAssertionsConfiguration(),
+ configuration ->
+ configuration.isAssertionHandler()
+ && configuration
+ .getAssertionHandler()
+ .getHolderClass()
+ .equals(Reference.classFromDescriptor("Lcom/example/MyHandler;"))
+ && configuration.getAssertionHandler().getMethodName().equals("handler")
+ && configuration
+ .getAssertionHandler()
+ .getMethodDescriptor()
+ .equals("(Ljava/lang/Throwable;)V"));
+ checkSingleForceClassAndPackageAssertion(
+ parse(
+ "--force-assertions-handler:com.example.MyHandler.handler1:ClassName",
+ "--force-assertions-handler:com.example.MyHandler.handler2:PackageName...")
+ .getAssertionsConfiguration(),
+ configuration ->
+ configuration.isAssertionHandler()
+ && configuration
+ .getAssertionHandler()
+ .getHolderClass()
+ .equals(Reference.classFromDescriptor("Lcom/example/MyHandler;"))
+ && configuration.getAssertionHandler().getMethodName().equals("handler1")
+ && configuration
+ .getAssertionHandler()
+ .getMethodDescriptor()
+ .equals("(Ljava/lang/Throwable;)V"),
+ configuration ->
+ configuration.isAssertionHandler()
+ && configuration
+ .getAssertionHandler()
+ .getHolderClass()
+ .equals(Reference.classFromDescriptor("Lcom/example/MyHandler;"))
+ && configuration.getAssertionHandler().getMethodName().equals("handler2")
+ && configuration
+ .getAssertionHandler()
+ .getMethodDescriptor()
+ .equals("(Ljava/lang/Throwable;)V"));
}
@Test(expected = CompilationFailedException.class)
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java
index 358d863..2e54294 100644
--- a/src/test/java/com/android/tools/r8/L8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.origin.EmbeddedOrigin;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -30,6 +31,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.function.Function;
import org.hamcrest.Matcher;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -375,6 +377,13 @@
assertEquals(AssertionTransformationScope.ALL, entries.get(0).getScope());
}
+ private void checkSingleForceAllAssertion(
+ List<AssertionsConfiguration> entries, Function<AssertionsConfiguration, Boolean> check) {
+ assertEquals(1, entries.size());
+ assertTrue(check.apply(entries.get(0)));
+ assertEquals(AssertionTransformationScope.ALL, entries.get(0).getScope());
+ }
+
private void checkSingleForceClassAndPackageAssertion(
List<AssertionsConfiguration> entries, AssertionTransformation transformation) {
assertEquals(2, entries.size());
@@ -386,6 +395,19 @@
assertEquals("PackageName", entries.get(1).getValue());
}
+ private void checkSingleForceClassAndPackageAssertion(
+ List<AssertionsConfiguration> entries,
+ Function<AssertionsConfiguration, Boolean> checkClass,
+ Function<AssertionsConfiguration, Boolean> checkPackage) {
+ assertEquals(2, entries.size());
+ assertTrue(checkClass.apply(entries.get(0)));
+ assertEquals(AssertionTransformationScope.CLASS, entries.get(0).getScope());
+ assertEquals("ClassName", entries.get(0).getValue());
+ assertTrue(checkPackage.apply(entries.get(1)));
+ assertEquals(AssertionTransformationScope.PACKAGE, entries.get(1).getScope());
+ assertEquals("PackageName", entries.get(1).getValue());
+ }
+
@Test
public void forceAssertionOption() throws Exception {
checkSingleForceAllAssertion(
@@ -433,6 +455,52 @@
ToolHelper.getDesugarLibJsonForTesting().toString())
.getAssertionsConfiguration(),
AssertionTransformation.PASSTHROUGH);
+ checkSingleForceAllAssertion(
+ parse(
+ "--force-assertions-handler:com.example.MyHandler.handler",
+ "--desugared-lib",
+ ToolHelper.getDesugarLibJsonForTesting().toString())
+ .getAssertionsConfiguration(),
+ configuration ->
+ configuration.isAssertionHandler()
+ && configuration
+ .getAssertionHandler()
+ .getHolderClass()
+ .equals(Reference.classFromDescriptor("Lcom/example/MyHandler;"))
+ && configuration.getAssertionHandler().getMethodName().equals("handler")
+ && configuration
+ .getAssertionHandler()
+ .getMethodDescriptor()
+ .equals("(Ljava/lang/Throwable;)V"));
+ checkSingleForceClassAndPackageAssertion(
+ parse(
+ "--force-assertions-handler:com.example.MyHandler.handler1:ClassName",
+ "--force-assertions-handler:com.example.MyHandler.handler2:PackageName...",
+ "--desugared-lib",
+ ToolHelper.getDesugarLibJsonForTesting().toString())
+ .getAssertionsConfiguration(),
+ configuration ->
+ configuration.isAssertionHandler()
+ && configuration
+ .getAssertionHandler()
+ .getHolderClass()
+ .equals(Reference.classFromDescriptor("Lcom/example/MyHandler;"))
+ && configuration.getAssertionHandler().getMethodName().equals("handler1")
+ && configuration
+ .getAssertionHandler()
+ .getMethodDescriptor()
+ .equals("(Ljava/lang/Throwable;)V"),
+ configuration ->
+ configuration.isAssertionHandler()
+ && configuration
+ .getAssertionHandler()
+ .getHolderClass()
+ .equals(Reference.classFromDescriptor("Lcom/example/MyHandler;"))
+ && configuration.getAssertionHandler().getMethodName().equals("handler2")
+ && configuration
+ .getAssertionHandler()
+ .getMethodDescriptor()
+ .equals("(Ljava/lang/Throwable;)V"));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 325344f..536f5bb 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.origin.EmbeddedOrigin;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.ThreadUtils;
@@ -36,6 +37,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.function.Function;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
@@ -717,6 +719,13 @@
assertEquals(AssertionTransformationScope.ALL, entries.get(0).getScope());
}
+ private void checkSingleForceAllAssertion(
+ List<AssertionsConfiguration> entries, Function<AssertionsConfiguration, Boolean> check) {
+ assertEquals(1, entries.size());
+ assertTrue(check.apply(entries.get(0)));
+ assertEquals(AssertionTransformationScope.ALL, entries.get(0).getScope());
+ }
+
private void checkSingleForceClassAndPackageAssertion(
List<AssertionsConfiguration> entries, AssertionTransformation transformation) {
assertEquals(2, entries.size());
@@ -728,6 +737,19 @@
assertEquals("PackageName", entries.get(1).getValue());
}
+ private void checkSingleForceClassAndPackageAssertion(
+ List<AssertionsConfiguration> entries,
+ Function<AssertionsConfiguration, Boolean> checkClass,
+ Function<AssertionsConfiguration, Boolean> checkPackage) {
+ assertEquals(2, entries.size());
+ assertTrue(checkClass.apply(entries.get(0)));
+ assertEquals(AssertionTransformationScope.CLASS, entries.get(0).getScope());
+ assertEquals("ClassName", entries.get(0).getValue());
+ assertTrue(checkPackage.apply(entries.get(1)));
+ assertEquals(AssertionTransformationScope.PACKAGE, entries.get(1).getScope());
+ assertEquals("PackageName", entries.get(1).getValue());
+ }
+
@Test
public void forceAssertionOption() throws Exception {
checkSingleForceAllAssertion(
@@ -753,6 +775,47 @@
"--force-passthrough-assertions:PackageName...")
.getAssertionsConfiguration(),
AssertionTransformation.PASSTHROUGH);
+ checkSingleForceAllAssertion(
+ parse("--force-assertions-handler:com.example.MyHandler.handler")
+ .getAssertionsConfiguration(),
+ configuration ->
+ configuration.isAssertionHandler()
+ && configuration
+ .getAssertionHandler()
+ .getHolderClass()
+ .equals(Reference.classFromDescriptor("Lcom/example/MyHandler;"))
+ && configuration.getAssertionHandler().getMethodName().equals("handler")
+ && configuration
+ .getAssertionHandler()
+ .getMethodDescriptor()
+ .equals("(Ljava/lang/Throwable;)V"));
+ checkSingleForceClassAndPackageAssertion(
+ parse(
+ "--force-assertions-handler:com.example.MyHandler.handler1:ClassName",
+ "--force-assertions-handler:com.example.MyHandler.handler2:PackageName...")
+ .getAssertionsConfiguration(),
+ configuration ->
+ configuration.isAssertionHandler()
+ && configuration
+ .getAssertionHandler()
+ .getHolderClass()
+ .equals(Reference.classFromDescriptor("Lcom/example/MyHandler;"))
+ && configuration.getAssertionHandler().getMethodName().equals("handler1")
+ && configuration
+ .getAssertionHandler()
+ .getMethodDescriptor()
+ .equals("(Ljava/lang/Throwable;)V"),
+ configuration ->
+ configuration.isAssertionHandler()
+ && configuration
+ .getAssertionHandler()
+ .getHolderClass()
+ .equals(Reference.classFromDescriptor("Lcom/example/MyHandler;"))
+ && configuration.getAssertionHandler().getMethodName().equals("handler2")
+ && configuration
+ .getAssertionHandler()
+ .getMethodDescriptor()
+ .equals("(Ljava/lang/Throwable;)V"));
}
@Test(expected = CompilationFailedException.class)