Extend recognized set of signature-polymorphic methods
Bug: 66370036
Change-Id: Ib79a305263254a8b4e60acdc1d38e163e94a14dd
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 aeadd6e..153c2e6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -100,6 +100,7 @@
public final DexString annotationDescriptor = createString("Ljava/lang/annotation/Annotation;");
public final DexString throwableDescriptor = createString("Ljava/lang/Throwable;");
public final DexString objectsDescriptor = createString("Ljava/util/Objects;");
+ public final DexString varHandleDescriptor = createString("Ljava/lang/invoke/VarHandle;");
public final DexString constructorMethodName = createString(Constants.INSTANCE_INITIALIZER_NAME);
public final DexString classConstructorMethodName = createString(Constants.CLASS_INITIALIZER_NAME);
@@ -141,6 +142,8 @@
public final DexType stringBuilderType = createType("Ljava/lang/StringBuilder;");
public final DexType stringBufferType = createType("Ljava/lang/StringBuffer;");
+ public final DexType varHandleType = createType(varHandleDescriptor);
+
public final StringBuildingMethods stringBuilderMethods = new StringBuildingMethods(stringBuilderType);
public final StringBuildingMethods stringBufferMethods = new StringBuildingMethods(stringBufferType);
public final ObjectsMethods objectsMethods = new ObjectsMethods();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 4dc7e0a..258d8e6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -1058,11 +1058,20 @@
public void addInvoke(Type type, DexItem item, DexProto callSiteProto, List<Value> arguments)
throws ApiLevelException {
- if (type == Invoke.Type.POLYMORPHIC && !options.canUseInvokePolymorphic()) {
- throw new ApiLevelException(
- AndroidApiLevel.O,
- "MethodHandle.invoke and MethodHandle.invokeExact",
- null /* sourceString */);
+ if (type == Invoke.Type.POLYMORPHIC) {
+ assert item instanceof DexMethod;
+ if (!options.canUseInvokePolymorphic()) {
+ throw new ApiLevelException(
+ AndroidApiLevel.O,
+ "MethodHandle.invoke and MethodHandle.invokeExact",
+ null /* sourceString */);
+ } else if (!options.canUseInvokePolymorphicOnVarHandle()
+ && ((DexMethod) item).getHolder() == options.itemFactory.varHandleType) {
+ throw new ApiLevelException(
+ AndroidApiLevel.P,
+ "Call to polymorphic signature of VarHandle",
+ null /* sourceString */);
+ }
}
add(Invoke.create(type, item, callSiteProto, null, arguments));
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 7f640c3..97dce33 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -135,7 +135,7 @@
private static final String REFLECT_ARRAY_NEW_INSTANCE_NAME = "newInstance";
private static final String REFLECT_ARRAY_NEW_INSTANCE_DESC =
"(Ljava/lang/Class;[I)Ljava/lang/Object;";
- private static final String METHODHANDLE_INVOKE_OR_INVOKEEXACT_DESC =
+ private static final String POLYMORPHIC_SIGNATURE_DESC =
"([Ljava/lang/Object;)Ljava/lang/Object;";
// Language types.
@@ -2539,8 +2539,7 @@
DexProto callSiteProto = null;
DexMethod targetMethod = method;
if (invokeType == Invoke.Type.POLYMORPHIC) {
- targetMethod = application.getMethod(
- insn.owner, insn.name, METHODHANDLE_INVOKE_OR_INVOKEEXACT_DESC);
+ targetMethod = application.getMethod(insn.owner, insn.name, POLYMORPHIC_SIGNATURE_DESC);
callSiteProto = application.getProto(insn.desc);
}
builder.addInvoke(invokeType, targetMethod, callSiteProto, types, registers);
@@ -2970,7 +2969,52 @@
}
private boolean isCallToPolymorphicSignatureMethod(MethodInsnNode method) {
- return method.owner.equals("java/lang/invoke/MethodHandle")
- && (method.name.equals("invoke") || method.name.equals("invokeExact"));
+ if (method.owner.equals("java/lang/invoke/MethodHandle")) {
+ switch (method.name) {
+ case "invoke":
+ case "invokeExact":
+ return true;
+ default :
+ return false;
+ }
+ } else if (method.owner.equals("java/lang/invoke/VarHandle")) {
+ switch (method.name) {
+ case "compareAndExchange":
+ case "compareAndExchangeAcquire":
+ case "compareAndExchangeRelease":
+ case "compareAndSet":
+ case "get":
+ case "getAcquire":
+ case "getAndAdd":
+ case "getAndAddAcquire":
+ case "getAndAddRelease":
+ case "getAndBitwiseAnd":
+ case "getAndBitwiseAndAcquire":
+ case "getAndBitwiseAndRelease":
+ case "getAndBitwiseOr":
+ case "getAndBitwiseOrAcquire":
+ case "getAndBitwiseOrRelease":
+ case "getAndBitwiseXor":
+ case "getAndBitwiseXorAcquire":
+ case "getAndBitwiseXorRelease":
+ case "getAndSet":
+ case "getAndSetAcquire":
+ case "getAndSetRelease":
+ case "getOpaque":
+ case "getVolatile":
+ case "set":
+ case "setOpaque":
+ case "setRelease":
+ case "setVolatile":
+ case "weakCompareAndSet":
+ case "weakCompareAndSetAcquire":
+ case "weakCompareAndSetPlain":
+ case "weakCompareAndSetRelease":
+ return true;
+ default :
+ return false;
+ }
+ }
+ return false;
}
}
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 dd0a60c..8d7171f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -408,6 +408,10 @@
}
}
+ public boolean canUseInvokePolymorphicOnVarHandle() {
+ return minApiLevel >= AndroidApiLevel.P.getLevel();
+ }
+
public boolean canUseInvokePolymorphic() {
return minApiLevel >= AndroidApiLevel.O.getLevel();
}
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/D8RunExamplesJava9Test.java
new file mode 100644
index 0000000..2eb9eaf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesJava9Test.java
@@ -0,0 +1,72 @@
+// 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;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.InternalCompilerError;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.function.UnaryOperator;
+
+public class D8RunExamplesJava9Test extends RunExamplesJava9Test<D8Command.Builder> {
+
+ class D8TestRunner extends TestRunner<D8TestRunner> {
+
+ D8TestRunner(String testName, String packageName, String mainClass) {
+ super(testName, packageName, mainClass);
+ }
+
+ @Override
+ D8TestRunner withMinApiLevel(int minApiLevel) {
+ return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel));
+ }
+
+ D8TestRunner withClasspath(Path... classpath) {
+ return withBuilderTransformation(b -> {
+ try {
+ return b.addClasspathFiles(classpath);
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ });
+ }
+
+
+ @Override
+ void build(Path inputFile, Path out) throws Throwable {
+ D8Command.Builder builder = D8Command.builder();
+ for (UnaryOperator<D8Command.Builder> transformation : builderTransformations) {
+ builder = transformation.apply(builder);
+ }
+ // TODO(mikaelpeltier) Add new android.jar build from aosp and use it
+ builder.addLibraryFiles(Paths.get(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel())));
+ D8Command command = builder.addProgramFiles(inputFile).setOutputPath(out).build();
+ try {
+ ToolHelper.runD8(command, this::combinedOptionConsumer);
+ } catch (Unimplemented | CompilationError | InternalCompilerError re) {
+ throw re;
+ } catch (RuntimeException re) {
+ throw re.getCause() == null ? re : re.getCause();
+ }
+ }
+
+ D8TestRunner withIntermediate(boolean intermediate) {
+ return withBuilderTransformation(builder -> builder.setIntermediate(intermediate));
+ }
+
+ @Override
+ D8TestRunner self() {
+ return this;
+ }
+ }
+
+ @Override
+ D8TestRunner test(String testName, String packageName, String mainClass) {
+ return new D8TestRunner(testName, packageName, mainClass);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
new file mode 100644
index 0000000..9414a73
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
@@ -0,0 +1,53 @@
+// 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;
+
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Map;
+import java.util.function.UnaryOperator;
+import org.junit.Test;
+
+public class R8RunExamplesJava9Test extends RunExamplesJava9Test<R8Command.Builder> {
+
+ class R8TestRunner extends TestRunner<R8TestRunner> {
+
+ R8TestRunner(String testName, String packageName, String mainClass) {
+ super(testName, packageName, mainClass);
+ }
+
+ @Override
+ R8TestRunner withMinApiLevel(int minApiLevel) {
+ return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel));
+ }
+
+ @Override
+ void build(Path inputFile, Path out) throws Throwable {
+ R8Command.Builder builder = R8Command.builder();
+ for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
+ builder = transformation.apply(builder);
+ }
+ // TODO(mikaelpeltier) Add new android.jar build from aosp and use it
+ builder.addLibraryFiles(Paths.get(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel())));
+ R8Command command = builder.addProgramFiles(inputFile).setOutputPath(out).build();
+ ToolHelper.runR8(command, this::combinedOptionConsumer);
+ }
+
+ @Override
+ R8TestRunner self() {
+ return this;
+ }
+ }
+
+ @Override
+ R8TestRunner test(String testName, String packageName, String mainClass) {
+ return new R8TestRunner(testName, packageName, mainClass);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
new file mode 100644
index 0000000..37a1697
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -0,0 +1,272 @@
+// 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;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
+import com.android.tools.r8.utils.DexInspector.FoundMethodSubject;
+import com.android.tools.r8.utils.DexInspector.InstructionSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OffOrAuto;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+public abstract class RunExamplesJava9Test
+ <B extends BaseCommand.Builder<? extends BaseCommand, B>> {
+ static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_JAVA9_BUILD_DIR;
+
+ abstract class TestRunner<C extends TestRunner<C>> {
+ final String testName;
+ final String packageName;
+ final String mainClass;
+
+ Integer androidJarVersion = null;
+
+ final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>();
+ final List<Consumer<DexInspector>> dexInspectorChecks = new ArrayList<>();
+ final List<UnaryOperator<B>> builderTransformations = new ArrayList<>();
+
+ TestRunner(String testName, String packageName, String mainClass) {
+ this.testName = testName;
+ this.packageName = packageName;
+ this.mainClass = mainClass;
+ }
+
+ abstract C self();
+
+ C withDexCheck(Consumer<DexInspector> check) {
+ dexInspectorChecks.add(check);
+ return self();
+ }
+
+ C withClassCheck(Consumer<FoundClassSubject> check) {
+ return withDexCheck(inspector -> inspector.forAllClasses(check));
+ }
+
+ C withMethodCheck(Consumer<FoundMethodSubject> check) {
+ return withClassCheck(clazz -> clazz.forAllMethods(check));
+ }
+
+ <T extends InstructionSubject> C withInstructionCheck(
+ Predicate<InstructionSubject> filter, Consumer<T> check) {
+ return withMethodCheck(method -> {
+ if (method.isAbstract()) {
+ return;
+ }
+ Iterator<T> iterator = method.iterateInstructions(filter);
+ while (iterator.hasNext()) {
+ check.accept(iterator.next());
+ }
+ });
+ }
+
+ C withOptionConsumer(Consumer<InternalOptions> consumer) {
+ optionConsumers.add(consumer);
+ return self();
+ }
+
+ C withMainDexClass(String... classes) {
+ return withBuilderTransformation(builder -> builder.addMainDexClasses(classes));
+ }
+
+ C withInterfaceMethodDesugaring(OffOrAuto behavior) {
+ return withOptionConsumer(o -> o.interfaceMethodDesugaring = behavior);
+ }
+
+ C withTryWithResourcesDesugaring(OffOrAuto behavior) {
+ return withOptionConsumer(o -> o.tryWithResourcesDesugaring = behavior);
+ }
+
+ C withBuilderTransformation(UnaryOperator<B> builderTransformation) {
+ builderTransformations.add(builderTransformation);
+ return self();
+ }
+
+ void combinedOptionConsumer(InternalOptions options) {
+ for (Consumer<InternalOptions> consumer : optionConsumers) {
+ consumer.accept(options);
+ }
+ }
+
+ Path build() throws Throwable {
+ Path inputFile = getInputJar();
+ Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
+
+ build(inputFile, out);
+ return out;
+ }
+
+ Path getInputJar() {
+ return Paths.get(EXAMPLE_DIR, packageName + JAR_EXTENSION);
+ }
+
+ void run() throws Throwable {
+ if (minSdkErrorExpected(testName)) {
+ thrown.expect(ApiLevelException.class);
+ }
+
+ String qualifiedMainClass = packageName + "." + mainClass;
+ Path inputFile = getInputJar();
+ Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
+
+ build(inputFile, out);
+
+ if (!ToolHelper.artSupported()) {
+ return;
+ }
+
+ if (!dexInspectorChecks.isEmpty()) {
+ DexInspector inspector = new DexInspector(out);
+ for (Consumer<DexInspector> check : dexInspectorChecks) {
+ check.accept(inspector);
+ }
+ }
+
+ execute(testName, qualifiedMainClass, new Path[]{inputFile}, new Path[]{out});
+ }
+
+ abstract C withMinApiLevel(int minApiLevel);
+
+ C withAndroidJar(int androidJarVersion) {
+ assert this.androidJarVersion == null;
+ this.androidJarVersion = androidJarVersion;
+ return self();
+ }
+
+ abstract void build(Path inputFile, Path out) throws Throwable;
+ }
+
+ private static List<String> minSdkErrorExpected =
+ ImmutableList.of("varhandle-error-due-to-min-sdk");
+
+ private static Map<DexVm.Version, List<String>> failsOn =
+ ImmutableMap.of(
+ DexVm.Version.V4_4_4, ImmutableList.of(
+ // Dex version not supported
+ "varhandle"
+ ),
+ DexVm.Version.V5_1_1, ImmutableList.of(
+ // Dex version not supported
+ "varhandle"
+ ),
+ DexVm.Version.V6_0_1, ImmutableList.of(
+ // Dex version not supported
+ "varhandle"
+ ),
+ DexVm.Version.V7_0_0, ImmutableList.of(
+ // Dex version not supported
+ "varhandle"
+ ),
+ DexVm.Version.DEFAULT, ImmutableList.of(
+ // TODO(mikaelpeltier): Update runtime when the support will be ready
+ "varhandle"
+ )
+ );
+
+ @Rule
+ public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ boolean failsOn(Map<DexVm.Version, List<String>> failsOn, String name) {
+ DexVm.Version vmVersion = ToolHelper.getDexVm().getVersion();
+ return failsOn.containsKey(vmVersion)
+ && failsOn.get(vmVersion).contains(name);
+ }
+
+ boolean expectedToFail(String name) {
+ return failsOn(failsOn, name);
+ }
+
+ boolean minSdkErrorExpected(String testName) {
+ return minSdkErrorExpected.contains(testName);
+ }
+
+ @Test
+ public void varHAndle() throws Throwable {
+ test("varhandle", "varhandle", "VarHandleTests")
+ .withMinApiLevel(AndroidApiLevel.P.getLevel())
+ .run();
+ }
+
+ @Test
+ public void varHandleErrorDueToMinSdk() throws Throwable {
+ test("varhandle-error-due-to-min-sdk", "varhandle", "VarHandleTests")
+ .withMinApiLevel(AndroidApiLevel.O.getLevel())
+ .run();
+ }
+
+ abstract RunExamplesJava9Test<B>.TestRunner<?> test(String testName, String packageName,
+ String mainClass);
+
+ void execute(
+ String testName,
+ String qualifiedMainClass, Path[] jars, Path[] dexes)
+ throws IOException {
+
+ boolean expectedToFail = expectedToFail(testName);
+ if (expectedToFail) {
+ thrown.expect(Throwable.class);
+ }
+ String output = ToolHelper.runArtNoVerificationErrors(
+ Arrays.stream(dexes).map(path -> path.toString()).collect(Collectors.toList()),
+ qualifiedMainClass,
+ null);
+ if (!expectedToFail) {
+ ToolHelper.ProcessResult javaResult =
+ ToolHelper.runJava(
+ Arrays.stream(jars).map(path -> path.toString()).collect(Collectors.toList()),
+ qualifiedMainClass);
+ assertEquals("JVM run failed", javaResult.exitCode, 0);
+ assertTrue(
+ "JVM output does not match art output.\n\tjvm: "
+ + javaResult.stdout
+ + "\n\tart: "
+ + output.replace("\r", ""),
+ output.equals(javaResult.stdout.replace("\r", "")));
+ }
+ }
+
+ protected DexInspector getMainDexInspector(Path zip)
+ throws ZipException, IOException, ExecutionException {
+ try (ZipFile zipFile = new ZipFile(zip.toFile())) {
+ try (InputStream in =
+ zipFile.getInputStream(zipFile.getEntry(ToolHelper.DEFAULT_DEX_FILENAME))) {
+ return new DexInspector(AndroidApp.fromDexProgramData(ByteStreams.toByteArray(in)));
+ }
+ }
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 80c1e76..12d1d5a 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -64,6 +64,7 @@
public static final String EXAMPLES_ANDROID_N_BUILD_DIR = BUILD_DIR + "test/examplesAndroidN/";
public static final String EXAMPLES_ANDROID_O_BUILD_DIR = BUILD_DIR + "test/examplesAndroidO/";
public static final String EXAMPLES_ANDROID_P_BUILD_DIR = BUILD_DIR + "test/examplesAndroidP/";
+ public static final String EXAMPLES_JAVA9_BUILD_DIR = BUILD_DIR + "test/examplesJava9/";
public static final String SMALI_BUILD_DIR = BUILD_DIR + "test/smali/";
public static final String LINE_SEPARATOR = StringUtils.LINE_SEPARATOR;