aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Lamarche <benoitlamarche@google.com>2017-08-21 14:49:19 +0200
committerBenoit Lamarche <benoitlamarche@google.com>2017-08-21 14:49:19 +0200
commitd483112cf7af2e256d7c9ed393e354bf51e72d5f (patch)
tree101dc16131989c0d9c8b97efbde3b3f3bb0b9513
parentfc0b1b9556a5f369193df1379d575db8c2807d3c (diff)
downloadr8-d483112cf7af2e256d7c9ed393e354bf51e72d5f.tar.gz
Introduce ApiLevelException as a checked exception
It extends CompilationException which is already part of the API. Bug: 63692875 Change-Id: I89418c80793f894b3438d69af9890e70b469ded2
-rw-r--r--src/main/java/com/android/tools/r8/ApiLevelException.java31
-rw-r--r--src/main/java/com/android/tools/r8/D8.java16
-rw-r--r--src/main/java/com/android/tools/r8/R8.java42
-rw-r--r--src/main/java/com/android/tools/r8/dex/ApplicationWriter.java6
-rw-r--r--src/main/java/com/android/tools/r8/dex/FileWriter.java61
-rw-r--r--src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java4
-rw-r--r--src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java13
-rw-r--r--src/test/java/com/android/tools/r8/ToolHelper.java10
-rw-r--r--src/test/java/com/android/tools/r8/internal/CompilationTestBase.java2
9 files changed, 140 insertions, 45 deletions
diff --git a/src/main/java/com/android/tools/r8/ApiLevelException.java b/src/main/java/com/android/tools/r8/ApiLevelException.java
new file mode 100644
index 000000000..ec9fe6885
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ApiLevelException.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2017, 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;
+
+/**
+ * Exception to signal features that are not supported until a given API level.
+ */
+public class ApiLevelException extends CompilationException {
+
+ public ApiLevelException(
+ int minApiLevel, String minApiLevelString, String unsupportedFeatures, String sourceString) {
+ super(makeMessage(minApiLevel, minApiLevelString, unsupportedFeatures, sourceString));
+ assert minApiLevel > 0;
+ assert minApiLevelString != null;
+ assert unsupportedFeatures != null;
+ }
+
+ private static String makeMessage(
+ int minApiLevel, String minApiLevelString, String unsupportedFeatures, String sourceString) {
+ String message =
+ unsupportedFeatures
+ + " are only supported starting with "
+ + minApiLevelString
+ + " (--min-api "
+ + minApiLevel
+ + ")";
+ message = (sourceString != null) ? message + ": " + sourceString : message;
+ return message;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index ed992a3d5..38b7acc4e 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -66,7 +66,7 @@ public final class D8 {
* @param command D8 command.
* @return the compilation result.
*/
- public static D8Output run(D8Command command) throws IOException {
+ public static D8Output run(D8Command command) throws IOException, CompilationException {
InternalOptions options = command.getInternalOptions();
CompilationResult result = runForTesting(command.getInputApp(), options);
assert result != null;
@@ -87,7 +87,8 @@ public final class D8 {
* @param executor executor service from which to get threads for multi-threaded processing.
* @return the compilation result.
*/
- public static D8Output run(D8Command command, ExecutorService executor) throws IOException {
+ public static D8Output run(D8Command command, ExecutorService executor)
+ throws IOException, CompilationException {
InternalOptions options = command.getInternalOptions();
CompilationResult result = runForTesting(
command.getInputApp(), options, executor);
@@ -146,7 +147,7 @@ public final class D8 {
}
static CompilationResult runForTesting(AndroidApp inputApp, InternalOptions options)
- throws IOException {
+ throws IOException, CompilationException {
ExecutorService executor = ThreadUtils.getExecutorService(options);
try {
return runForTesting(inputApp, options, executor);
@@ -166,7 +167,8 @@ public final class D8 {
}
private static CompilationResult runForTesting(
- AndroidApp inputApp, InternalOptions options, ExecutorService executor) throws IOException {
+ AndroidApp inputApp, InternalOptions options, ExecutorService executor)
+ throws IOException, CompilationException {
try {
assert !inputApp.hasPackageDistribution();
@@ -200,10 +202,8 @@ public final class D8 {
} catch (MainDexError mainDexError) {
throw new CompilationError(mainDexError.getMessageForD8());
} catch (ExecutionException e) {
- if (e.getCause() instanceof CompilationError) {
- throw (CompilationError) e.getCause();
- }
- throw new RuntimeException(e.getMessage(), e.getCause());
+ R8.unwrapExecutionException(e);
+ throw new AssertionError(e); // unwrapping method should have thrown
}
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index da90e8247..f28a07a68 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -207,7 +207,7 @@ public class R8 {
}
static CompilationResult runForTesting(AndroidApp app, InternalOptions options)
- throws ProguardRuleParserException, IOException {
+ throws ProguardRuleParserException, IOException, CompilationException {
ExecutorService executor = ThreadUtils.getExecutorService(options);
try {
return runForTesting(app, options, executor);
@@ -220,12 +220,12 @@ public class R8 {
AndroidApp app,
InternalOptions options,
ExecutorService executor)
- throws ProguardRuleParserException, IOException {
+ throws ProguardRuleParserException, IOException, CompilationException {
return new R8(options).run(app, executor);
}
private CompilationResult run(AndroidApp inputApp, ExecutorService executorService)
- throws IOException, ProguardRuleParserException {
+ throws IOException, ProguardRuleParserException, CompilationException {
if (options.quiet) {
System.setOut(new PrintStream(ByteStreams.nullOutputStream()));
}
@@ -389,10 +389,8 @@ public class R8 {
} catch (MainDexError mainDexError) {
throw new CompilationError(mainDexError.getMessageForR8());
} catch (ExecutionException e) {
- if (e.getCause() instanceof CompilationError) {
- throw (CompilationError) e.getCause();
- }
- throw new RuntimeException(e.getMessage(), e.getCause());
+ unwrapExecutionException(e);
+ throw new AssertionError(e); // unwrapping method should have thrown
} finally {
// Dump timings.
if (options.printTimes) {
@@ -401,6 +399,36 @@ public class R8 {
}
}
+ static void unwrapExecutionException(ExecutionException executionException)
+ throws CompilationException {
+ Throwable cause = executionException.getCause();
+ if (cause instanceof CompilationError) {
+ // add original exception as suppressed exception to provide the original stack trace
+ cause.addSuppressed(executionException);
+ throw (CompilationError) cause;
+ } else if (cause instanceof CompilationException) {
+ cause.addSuppressed(executionException);
+ throw (CompilationException) cause;
+ } else if (cause instanceof RuntimeException) {
+ // ForkJoinPool wraps checked exceptions in RuntimeExceptions
+ if (cause.getCause() != null
+ && cause.getCause() instanceof CompilationException) {
+ cause.addSuppressed(executionException);
+ throw (CompilationException) cause.getCause();
+ // ForkJoinPool sometimes uses 2 levels of RuntimeExceptions, to provide accurate stack traces
+ } else if (cause.getCause() != null && cause.getCause().getCause() != null
+ && cause.getCause().getCause() instanceof CompilationException) {
+ cause.addSuppressed(executionException);
+ throw (CompilationException) cause.getCause().getCause();
+ } else {
+ cause.addSuppressed(executionException);
+ throw (RuntimeException) cause;
+ }
+ } else {
+ throw new RuntimeException(executionException.getMessage(), cause);
+ }
+ }
+
/**
* Main API entry for the R8 compiler.
*
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 1e2e2c9c9..412b291a9 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -3,6 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.dex;
+import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.dex.VirtualFile.FilePerClassDistributor;
+import com.android.tools.r8.dex.VirtualFile.FillFilesDistributor;
+import com.android.tools.r8.dex.VirtualFile.PackageMapDistributor;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexAnnotation;
@@ -203,7 +207,7 @@ public class ApplicationWriter {
}
}
- private byte[] writeDexFile(VirtualFile vfile) {
+ private byte[] writeDexFile(VirtualFile vfile) throws ApiLevelException {
FileWriter fileWriter =
new FileWriter(
vfile.computeMapping(application), application, appInfo, options, namingLens);
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index f1162a929..623787270 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.dex;
import static com.android.tools.r8.utils.LebUtils.sizeAsUleb128;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppInfo;
@@ -183,7 +184,7 @@ public class FileWriter {
return this;
}
- public byte[] generate() {
+ public byte[] generate() throws ApiLevelException {
// Check restrictions on interface methods.
checkInterfaceMethods();
@@ -268,10 +269,15 @@ public class FileWriter {
Arrays.sort(methods, (DexEncodedMethod a, DexEncodedMethod b) -> a.method.compareTo(b.method));
}
- private void checkInterfaceMethods() {
+ private void checkInterfaceMethods() throws ApiLevelException {
for (DexProgramClass clazz : mapping.getClasses()) {
if (clazz.isInterface()) {
- clazz.forEachMethod(this::checkInterfaceMethod);
+ for (DexEncodedMethod method : clazz.directMethods()) {
+ checkInterfaceMethod(method);
+ }
+ for (DexEncodedMethod method : clazz.virtualMethods()) {
+ checkInterfaceMethod(method);
+ }
}
}
}
@@ -282,15 +288,17 @@ public class FileWriter {
// -- starting with N interfaces may also have public or private
// static methods, as well as public non-abstract (default)
// and private instance methods.
- private void checkInterfaceMethod(DexEncodedMethod method) {
+ private void checkInterfaceMethod(DexEncodedMethod method) throws ApiLevelException {
if (application.dexItemFactory.isClassConstructor(method.method)) {
return; // Class constructor is always OK.
}
if (method.accessFlags.isStatic()) {
if (!options.canUseDefaultAndStaticInterfaceMethods()) {
- throw new CompilationError("Static interface methods are only supported "
- + "starting with Android N (--min-api " + Constants.ANDROID_N_API + "): "
- + method.method.toSourceString());
+ throw new ApiLevelException(
+ Constants.ANDROID_N_API,
+ "Android N",
+ "Static interface methods",
+ method.method.toSourceString());
}
} else {
@@ -300,9 +308,11 @@ public class FileWriter {
}
if (!method.accessFlags.isAbstract() && !method.accessFlags.isPrivate() &&
!options.canUseDefaultAndStaticInterfaceMethods()) {
- throw new CompilationError("Default interface methods are only supported "
- + "starting with Android N (--min-api " + Constants.ANDROID_N_API + "): "
- + method.method.toSourceString());
+ throw new ApiLevelException(
+ Constants.ANDROID_N_API,
+ "Android N",
+ "Default interface methods",
+ method.method.toSourceString());
}
}
@@ -310,9 +320,11 @@ public class FileWriter {
if (options.canUsePrivateInterfaceMethods()) {
return;
}
- throw new CompilationError("Private interface methods are only supported "
- + "starting with Android N (--min-api " + Constants.ANDROID_N_API + "): "
- + method.method.toSourceString());
+ throw new ApiLevelException(
+ Constants.ANDROID_N_API,
+ "Android N",
+ "Private interface methods",
+ method.method.toSourceString());
}
if (!method.accessFlags.isPublic()) {
@@ -353,13 +365,21 @@ public class FileWriter {
}
private <T extends DexItem> void writeFixedSectionItems(T[] items, int offset,
- Consumer<T> writer) {
+ ItemWriter<T> writer) throws ApiLevelException {
assert dest.position() == offset;
for (T item : items) {
writer.accept(item);
}
}
+ /**
+ * Similar to a {@link Consumer} but throws an {@link ApiLevelException}.
+ */
+ @FunctionalInterface
+ private interface ItemWriter<T> {
+ void accept(T t) throws ApiLevelException;
+ }
+
private <T extends DexItem> void writeItems(Collection<T> items, Consumer<Integer> offsetSetter,
Consumer<T> writer) {
writeItems(items, offsetSetter, writer, 1);
@@ -660,7 +680,7 @@ public class FileWriter {
}
}
- private void writeMethodHandle(DexMethodHandle methodHandle) {
+ private void writeMethodHandle(DexMethodHandle methodHandle) throws ApiLevelException {
checkThatInvokeCustomIsAllowed();
MethodHandleType methodHandleDexType;
switch (methodHandle.type) {
@@ -689,7 +709,7 @@ public class FileWriter {
dest.putShort((short) 0); // unused
}
- private void writeCallSite(DexCallSite callSite) {
+ private void writeCallSite(DexCallSite callSite) throws ApiLevelException {
checkThatInvokeCustomIsAllowed();
assert dest.isAligned(4);
dest.putInt(mixedSectionOffsets.getOffsetFor(callSite.getEncodedArray()));
@@ -1339,10 +1359,13 @@ public class FileWriter {
}
}
- private void checkThatInvokeCustomIsAllowed() {
+ private void checkThatInvokeCustomIsAllowed() throws ApiLevelException {
if (!options.canUseInvokeCustom()) {
- throw new CompilationError("Invoke-custom is unsupported before Android O (--min-api "
- + Constants.ANDROID_O_API + ")");
+ throw new ApiLevelException(
+ Constants.ANDROID_O_API,
+ "Android O",
+ "Invoke-customs",
+ null /* sourceString */);
}
}
}
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
index d5f3a2e52..7594b0cf2 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
@@ -130,7 +130,7 @@ public abstract class RunExamplesAndroidNTest<B> {
@Test
public void staticInterfaceMethodsErrorDueToMinSdk() throws Throwable {
- thrown.expect(CompilationError.class);
+ thrown.expect(ApiLevelException.class);
test("staticinterfacemethods-error-due-to-min-sdk", "interfacemethods",
"StaticInterfaceMethods")
.run();
@@ -146,7 +146,7 @@ public abstract class RunExamplesAndroidNTest<B> {
@Test
public void defaultMethodsErrorDueToMinSdk() throws Throwable {
- thrown.expect(CompilationError.class);
+ thrown.expect(ApiLevelException.class);
test("defaultmethods-error-due-to-min-sdk", "interfacemethods",
"DefaultMethods")
.run();
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 38868b6e1..e8d3c9dcb 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -109,6 +109,9 @@ public abstract class RunExamplesAndroidOTest<B> {
if (compilationErrorExpected(testName)) {
thrown.expect(CompilationError.class);
}
+ if (minSdkErrorExpected(testName)) {
+ thrown.expect(ApiLevelException.class);
+ }
String qualifiedMainClass = packageName + "." + mainClass;
Path inputFile = Paths.get(EXAMPLE_DIR, packageName + JAR_EXTENSION);
@@ -152,8 +155,10 @@ public abstract class RunExamplesAndroidOTest<B> {
}
private static List<String> compilationErrorExpected =
- ImmutableList.of(
- "invokepolymorphic-error-due-to-min-sdk", "invokecustom-error-due-to-min-sdk");
+ ImmutableList.of("invokepolymorphic-error-due-to-min-sdk");
+
+ private static List<String> minSdkErrorExpected =
+ ImmutableList.of("invokecustom-error-due-to-min-sdk");
private static Map<DexVm, List<String>> failsOn =
ImmutableMap.of(
@@ -211,6 +216,10 @@ public abstract class RunExamplesAndroidOTest<B> {
return compilationErrorExpected.contains(testName);
}
+ boolean minSdkErrorExpected(String testName) {
+ return minSdkErrorExpected.contains(testName);
+ }
+
@Test
public void invokeCustom() throws Throwable {
test("invokecustom", "invokecustom", "InvokeCustom")
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index eee820f9e..663d6bd68 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -473,18 +473,18 @@ public class ToolHelper {
}
public static AndroidApp runR8(R8Command command)
- throws ProguardRuleParserException, ExecutionException, IOException {
+ throws ProguardRuleParserException, ExecutionException, IOException, CompilationException {
return runR8(command, null);
}
public static AndroidApp runR8(R8Command command, Consumer<InternalOptions> optionsConsumer)
- throws ProguardRuleParserException, ExecutionException, IOException {
+ throws ProguardRuleParserException, ExecutionException, IOException, CompilationException {
return runR8WithFullResult(command, optionsConsumer).androidApp;
}
public static CompilationResult runR8WithFullResult(
R8Command command, Consumer<InternalOptions> optionsConsumer)
- throws ProguardRuleParserException, ExecutionException, IOException {
+ throws ProguardRuleParserException, ExecutionException, IOException, CompilationException {
// TODO(zerny): Should we really be adding the android library in ToolHelper?
AndroidApp app = command.getInputApp();
if (app.getLibraryResourceProviders().isEmpty()) {
@@ -536,12 +536,12 @@ public class ToolHelper {
return runD8(D8Command.builder(app).build(), optionsConsumer);
}
- public static AndroidApp runD8(D8Command command) throws IOException {
+ public static AndroidApp runD8(D8Command command) throws IOException, CompilationException {
return runD8(command, null);
}
public static AndroidApp runD8(D8Command command, Consumer<InternalOptions> optionsConsumer)
- throws IOException {
+ throws IOException, CompilationException {
InternalOptions options = command.getInternalOptions();
if (optionsConsumer != null) {
optionsConsumer.accept(options);
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index 0b3726c86..91ecb4f92 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -57,7 +57,7 @@ public abstract class CompilationTestBase {
}
public AndroidApp runAndCheckVerification(D8Command command, String referenceApk)
- throws IOException, ExecutionException {
+ throws IOException, ExecutionException, CompilationException {
return checkVerification(ToolHelper.runD8(command), referenceApk);
}