Introduce ApiLevelException as a checked exception
It extends CompilationException which is already part of the API.
Bug: 63692875
Change-Id: I79d37675f04c0707383c7315b4e1a423f440579e
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 0000000..0087086
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ApiLevelException.java
@@ -0,0 +1,40 @@
+// 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 {
+
+ private final int minApiLevel;
+ private final String minApiLevelString;
+ private final String unsupportedFeatures;
+ private final String sourceString;
+
+ public ApiLevelException(
+ int minApiLevel, String minApiLevelString, String unsupportedFeatures, String sourceString) {
+ super("");
+ assert minApiLevel > 0;
+ assert minApiLevelString != null;
+ assert unsupportedFeatures != null;
+ this.minApiLevel = minApiLevel;
+ this.minApiLevelString = minApiLevelString;
+ this.unsupportedFeatures = unsupportedFeatures;
+ this.sourceString = sourceString;
+ }
+
+ @Override
+ public String getMessage() {
+ 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 6988b06..cbbec9e 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -62,7 +62,7 @@
* @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;
@@ -83,7 +83,8 @@
* @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);
@@ -142,7 +143,7 @@
}
static CompilationResult runForTesting(AndroidApp inputApp, InternalOptions options)
- throws IOException {
+ throws IOException, CompilationException {
ExecutorService executor = ThreadUtils.getExecutorService(options);
try {
return runForTesting(inputApp, options, executor);
@@ -152,7 +153,8 @@
}
static CompilationResult runForTesting(
- AndroidApp inputApp, InternalOptions options, ExecutorService executor) throws IOException {
+ AndroidApp inputApp, InternalOptions options, ExecutorService executor)
+ throws IOException, CompilationException {
try {
assert !inputApp.hasPackageDistribution();
@@ -187,6 +189,8 @@
} catch (ExecutionException e) {
if (e.getCause() instanceof CompilationError) {
throw (CompilationError) e.getCause();
+ } else if (e.getCause() instanceof CompilationException) {
+ throw (CompilationException) e.getCause();
} else {
throw new RuntimeException(e.getMessage(), e.getCause());
}
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 6586a57..76d434c 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,7 @@
// 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;
@@ -196,7 +197,7 @@
}
}
- 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 332f292..90a5dcf 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -7,6 +7,7 @@
import com.google.common.collect.Sets;
+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;
@@ -186,7 +187,7 @@
return this;
}
- public byte[] generate() {
+ public byte[] generate() throws ApiLevelException {
// Check restrictions on interface methods.
checkInterfaceMethods();
@@ -271,10 +272,15 @@
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);
+ }
}
}
}
@@ -285,15 +291,18 @@
// -- 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 {
@@ -303,9 +312,11 @@
}
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());
}
}
@@ -313,9 +324,11 @@
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()) {
@@ -356,13 +369,21 @@
}
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);
@@ -663,7 +684,7 @@
}
}
- private void writeMethodHandle(DexMethodHandle methodHandle) {
+ private void writeMethodHandle(DexMethodHandle methodHandle) throws ApiLevelException {
checkThatInvokeCustomIsAllowed();
MethodHandleType methodHandleDexType;
switch (methodHandle.type) {
@@ -692,7 +713,7 @@
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()));
@@ -1342,10 +1363,13 @@
}
}
- 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/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index d0032cd..1e5c073 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -505,12 +505,12 @@
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 4b2ee49..46f2fd3 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -49,7 +49,7 @@
}
public AndroidApp runAndCheckVerification(D8Command command, String referenceApk)
- throws IOException, ExecutionException {
+ throws IOException, ExecutionException, CompilationException {
return checkVerification(ToolHelper.runD8(command), referenceApk);
}