Add new const-method-handle opcode Change-Id: I6d890810b5d76a1c6292f50717643a8391805d79
diff --git a/build.gradle b/build.gradle index 6be6012..7b83fcf 100644 --- a/build.gradle +++ b/build.gradle
@@ -88,6 +88,12 @@ } output.resourcesDir = 'build/classes/examplesAndroidO' } + examplesAndroidP { + java { + srcDirs = ['src/test/examplesAndroidP'] + } + output.resourcesDir = 'build/classes/examplesAndroidP' + } jctfCommon { java { srcDirs = [ @@ -145,6 +151,7 @@ jctfTestsCompile 'junit:junit:4.12' jctfTestsCompile sourceSets.jctfCommon.output examplesAndroidOCompile group: 'org.ow2.asm', name: 'asm', version: '6.0_BETA' + examplesAndroidPCompile group: 'org.ow2.asm', name: 'asm', version: '6.0_BETA' examplesCompile 'com.google.protobuf:protobuf-lite:3.0.0' examplesRuntime 'com.google.protobuf:protobuf-lite:3.0.0' supportLibs 'com.android.support:support-v4:25.4.0' @@ -887,6 +894,51 @@ } } +task buildExampleAndroidPJars { + dependsOn downloadDeps + def examplesDir = file("src/test/examplesAndroidP") + + task "compile_examplesAndroidP"(type: JavaCompile) { + source = fileTree(dir: examplesDir, include: '**/*.java') + destinationDir = file("build/test/examplesAndroidP/classes") + classpath = sourceSets.main.compileClasspath + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + options.compilerArgs += ["-Xlint:-options"] + } + examplesDir.eachDir { dir -> + def name = dir.getName(); + def destinationDir = file("build/test/examplesAndroidP/classes"); + if (file("src/test/examplesAndroidP/" + name + "/TestGenerator.java").isFile()) { + task "generate_examplesAndroidP_${name}"(type: JavaExec, + dependsOn: "compile_examplesAndroidP") { + main = name + ".TestGenerator" + classpath = files(destinationDir, sourceSets.main.compileClasspath) + args destinationDir + } + } else { + task "generate_examplesAndroidP_${name}" () {} + } + } + examplesDir.eachDir { dir -> + def name = dir.getName(); + def exampleOutputDir = file("build/test/examplesAndroidP"); + def jarName = "${name}.jar" + dependsOn "jar_examplesAndroidP_${name}" + task "jar_examplesAndroidP_${name}"(type: Jar, + dependsOn: ["compile_examplesAndroidP", + "generate_examplesAndroidP_${name}"]) { + archiveName = jarName + destinationDir = exampleOutputDir + from "build/test/examplesAndroidP/classes" // Java 1.8 classes + include "**/" + name + "/**/*.class" + // Do not include generator into the test runtime jar, it is not useful. + // Otherwise, shrinking will need ASM jars. + exclude "**/TestGenerator*" + } + } +} + task buildExamples { if (OperatingSystem.current().isMacOsX() || OperatingSystem.current().isWindows()) { logger.lifecycle("WARNING: Testing (including building examples) is only partially supported on your " + @@ -900,6 +952,7 @@ dependsOn buildExampleJars dependsOn buildExampleAndroidNJars dependsOn buildExampleAndroidOJars + dependsOn buildExampleAndroidPJars def examplesDir = file("src/test/examples") def noDexTests = [ "multidex",
diff --git a/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java new file mode 100644 index 0000000..e2c084b --- /dev/null +++ b/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
@@ -0,0 +1,77 @@ +// 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.code; + +import com.android.tools.r8.ApiLevelException; +import com.android.tools.r8.errors.InternalCompilerError; +import com.android.tools.r8.graph.DexMethodHandle; +import com.android.tools.r8.graph.ObjectToOffsetMapping; +import com.android.tools.r8.graph.OffsetToObjectMapping; +import com.android.tools.r8.graph.UseRegistry; +import com.android.tools.r8.ir.conversion.IRBuilder; +import com.android.tools.r8.naming.ClassNameMapper; +import java.nio.ShortBuffer; + +public class ConstMethodHandle extends Format21c { + + public static final int OPCODE = 0xfe; + public static final String NAME = "ConstMethodHandle"; + public static final String SMALI_NAME = "const-method-handle"; + + ConstMethodHandle(int high, BytecodeStream stream, OffsetToObjectMapping mapping) { + super(high, stream, mapping.getMethodHandleMap()); + } + + public ConstMethodHandle(int register, DexMethodHandle methodHandle) { + super(register, methodHandle); + } + + public DexMethodHandle getMethodHandle() { + return (DexMethodHandle) BBBB; + } + + public String getName() { + return NAME; + } + + public String getSmaliName() { + return SMALI_NAME; + } + + public int getOpcode() { + return OPCODE; + } + + public String toString(ClassNameMapper naming) { + return formatString("v" + AA + ", \"" + BBBB.toString() + "\""); + } + + public String toSmaliString(ClassNameMapper naming) { + return formatSmaliString("v" + AA + ", \"" + BBBB.toString() + "\""); + } + + @Override + public void registerUse(UseRegistry registry) { + registry.registerMethodHandle(getMethodHandle()); + } + + @Override + public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) { + int index = BBBB.getOffset(mapping); + if (index != (index & 0xffff)) { + throw new InternalCompilerError("MethodHandle-index overflow."); + } + super.write(dest, mapping); + } + + @Override + public void buildIR(IRBuilder builder) throws ApiLevelException { + builder.addConstMethodHandle(AA, (DexMethodHandle) BBBB); + } + + @Override + public boolean canThrow() { + return true; + } +}
diff --git a/src/main/java/com/android/tools/r8/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/code/InvokeCustom.java index 3c0b67c..c4a16b5 100644 --- a/src/main/java/com/android/tools/r8/code/InvokeCustom.java +++ b/src/main/java/com/android/tools/r8/code/InvokeCustom.java
@@ -4,8 +4,6 @@ package com.android.tools.r8.code; import com.android.tools.r8.graph.DexCallSite; -import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.graph.DexMethodHandle; import com.android.tools.r8.graph.DexValue; import com.android.tools.r8.graph.DexValue.DexValueMethodHandle; import com.android.tools.r8.graph.DexValue.DexValueType; @@ -61,54 +59,15 @@ } static void registerCallSite(UseRegistry registry, DexCallSite callSite) { - InvokeCustom.registerMethodHandle(registry, callSite.bootstrapMethod); + registry.registerMethodHandle(callSite.bootstrapMethod); // Register bootstrap method arguments, only Type and MethodHandle need to be register. for (DexValue arg : callSite.bootstrapArgs) { if (arg instanceof DexValueType) { registry.registerTypeReference(((DexValueType) arg).value); } else if (arg instanceof DexValueMethodHandle) { - InvokeCustom.registerMethodHandle(registry, ((DexValueMethodHandle) arg).value); + registry.registerMethodHandle(((DexValueMethodHandle) arg).value); } } } - - static void registerMethodHandle(UseRegistry registry, DexMethodHandle methodHandle) { - switch (methodHandle.type) { - case INSTANCE_GET: - registry.registerInstanceFieldRead(methodHandle.asField()); - break; - case INSTANCE_PUT: - registry.registerInstanceFieldWrite(methodHandle.asField()); - break; - case STATIC_GET: - registry.registerStaticFieldRead(methodHandle.asField()); - break; - case STATIC_PUT: - registry.registerStaticFieldWrite(methodHandle.asField()); - break; - case INVOKE_INSTANCE: - registry.registerInvokeVirtual(methodHandle.asMethod()); - break; - case INVOKE_STATIC: - registry.registerInvokeStatic(methodHandle.asMethod()); - break; - case INVOKE_CONSTRUCTOR: - DexMethod method = methodHandle.asMethod(); - registry.registerNewInstance(method.getHolder()); - registry.registerInvokeDirect(method); - break; - case INVOKE_INTERFACE: - registry.registerInvokeInterface(methodHandle.asMethod()); - break; - case INVOKE_SUPER: - registry.registerInvokeSuper(methodHandle.asMethod()); - break; - case INVOKE_DIRECT: - registry.registerInvokeDirect(methodHandle.asMethod()); - break; - default: - throw new AssertionError(); - } - } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java index eeba4e8..d64a893 100644 --- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java +++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -4,8 +4,10 @@ package com.android.tools.r8.graph; import com.android.tools.r8.dex.IndexedItemCollection; +import com.android.tools.r8.naming.NamingLens; -public class DexMethodHandle extends IndexedDexItem { +public class DexMethodHandle extends IndexedDexItem implements + PresortedComparable<DexMethodHandle> { public enum MethodHandleType { STATIC_PUT((short) 0x00), @@ -190,4 +192,51 @@ assert isFieldHandle(); return (DexField) fieldOrMethod; } + + @Override + public int slowCompareTo(DexMethodHandle other) { + int result = type.getValue() - other.type.getValue(); + if (result == 0) { + if (isFieldHandle()) { + result = asField().slowCompareTo(other.asField()); + } else { + assert isMethodHandle(); + result = asMethod().slowCompareTo(other.asMethod()); + } + } + return result; + } + + @Override + public int slowCompareTo(DexMethodHandle other, NamingLens namingLens) { + int result = type.getValue() - other.type.getValue(); + if (result == 0) { + if (isFieldHandle()) { + result = asField().slowCompareTo(other.asField(), namingLens); + } else { + assert isMethodHandle(); + result = asMethod().slowCompareTo(other.asMethod(), namingLens); + } + } + return result; + } + + @Override + public int layeredCompareTo(DexMethodHandle other, NamingLens namingLens) { + int result = type.getValue() - other.type.getValue(); + if (result == 0) { + if (isFieldHandle()) { + result = asField().layeredCompareTo(other.asField(), namingLens); + } else { + assert isMethodHandle(); + result = asMethod().layeredCompareTo(other.asMethod(), namingLens); + } + } + return result; + } + + @Override + public int compareTo(DexMethodHandle other) { + return sortedCompareTo(other.getSortedIndex()); + } }
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java index 45824c2..78b2f56 100644 --- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java +++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -26,4 +26,43 @@ public abstract boolean registerStaticFieldWrite(DexField field); public abstract boolean registerTypeReference(DexType type); + + public void registerMethodHandle(DexMethodHandle methodHandle) { + switch (methodHandle.type) { + case INSTANCE_GET: + registerInstanceFieldRead(methodHandle.asField()); + break; + case INSTANCE_PUT: + registerInstanceFieldWrite(methodHandle.asField()); + break; + case STATIC_GET: + registerStaticFieldRead(methodHandle.asField()); + break; + case STATIC_PUT: + registerStaticFieldWrite(methodHandle.asField()); + break; + case INVOKE_INSTANCE: + registerInvokeVirtual(methodHandle.asMethod()); + break; + case INVOKE_STATIC: + registerInvokeStatic(methodHandle.asMethod()); + break; + case INVOKE_CONSTRUCTOR: + DexMethod method = methodHandle.asMethod(); + registerNewInstance(method.getHolder()); + registerInvokeDirect(method); + break; + case INVOKE_INTERFACE: + registerInvokeInterface(methodHandle.asMethod()); + break; + case INVOKE_SUPER: + registerInvokeSuper(methodHandle.asMethod()); + break; + case INVOKE_DIRECT: + registerInvokeDirect(methodHandle.asMethod()); + break; + default: + throw new AssertionError(); + } + } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java new file mode 100644 index 0000000..108eb32 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -0,0 +1,80 @@ +// 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.ir.code; + +import com.android.tools.r8.dex.Constants; +import com.android.tools.r8.graph.DexMethodHandle; +import com.android.tools.r8.graph.DexString; +import com.android.tools.r8.ir.conversion.DexBuilder; + +public class ConstMethodHandle extends ConstInstruction { + + private final DexMethodHandle methodHandle; + + public ConstMethodHandle(Value dest, DexMethodHandle methodHandle) { + super(dest); + dest.markNeverNull(); + this.methodHandle = methodHandle; + } + + public Value dest() { + return outValue; + } + + public DexMethodHandle getValue() { + return methodHandle; + } + + @Override + public void buildDex(DexBuilder builder) { + int dest = builder.allocatedRegister(dest(), getNumber()); + builder.add(this, new com.android.tools.r8.code.ConstMethodHandle(dest, methodHandle)); + } + + @Override + public boolean identicalNonValueParts(Instruction other) { + return other.asConstMethodHandle().methodHandle == methodHandle; + } + + @Override + public int compareNonValueParts(Instruction other) { + return methodHandle.slowCompareTo(other.asConstMethodHandle().methodHandle); + } + + @Override + public int maxInValueRegister() { + assert false : "ConstMethodHandle has no register arguments."; + return 0; + } + + @Override + public int maxOutValueRegister() { + return Constants.U8BIT_MAX; + } + + @Override + public String toString() { + return super.toString() + " \"" + methodHandle + "\""; + } + + @Override + public boolean instructionTypeCanThrow() { + return true; + } + + @Override + public boolean isOutConstant() { + return true; + } + + @Override + public boolean isConstMethodHandle() { + return true; + } + + @Override + public ConstMethodHandle asConstMethodHandle() { + return this; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java index 21c2abf..24c3925 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java +++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -450,6 +450,14 @@ return null; } + public boolean isConstMethodHandle() { + return false; + } + + public ConstMethodHandle asConstMethodHandle() { + return null; + } + public boolean isConstString() { return false; }
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 35cd7fa..c84adcb 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
@@ -32,6 +32,7 @@ import com.android.tools.r8.ir.code.Cmp; import com.android.tools.r8.ir.code.Cmp.Bias; import com.android.tools.r8.ir.code.ConstClass; +import com.android.tools.r8.ir.code.ConstMethodHandle; import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.ConstString; import com.android.tools.r8.ir.code.ConstType; @@ -799,6 +800,19 @@ add(instruction); } + public void addConstMethodHandle(int dest, DexMethodHandle methodHandle) + throws ApiLevelException { + if (!options.canUseConstantMethodHandle()) { + throw new ApiLevelException( + AndroidApiLevel.P, + "Const-method-handle", + null /* sourceString */); + } + Value out = writeRegister(dest, MoveType.OBJECT, ThrowingInfo.CAN_THROW); + ConstMethodHandle instruction = new ConstMethodHandle(out, methodHandle); + add(instruction); + } + public void addConstString(int dest, DexString string) { Value out = writeRegister(dest, MoveType.OBJECT, ThrowingInfo.CAN_THROW); ConstString instruction = new ConstString(out, string);
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 e44ee80..a4203a4 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
@@ -142,6 +142,7 @@ static final Type STRING_TYPE = Type.getObjectType("java/lang/String"); static final Type INT_ARRAY_TYPE = Type.getObjectType(INT_ARRAY_DESC); static final Type THROWABLE_TYPE = Type.getObjectType("java/lang/Throwable"); + static final Type METHOD_HANDLE_TYPE = Type.getObjectType("java/lang/invoke/MethodHandle"); private static final int[] NO_TARGETS = {}; @@ -619,7 +620,7 @@ case Opcodes.LDC: { // const-class and const-string* may throw in dex. LdcInsnNode ldc = (LdcInsnNode) insn; - return ldc.cst instanceof String || ldc.cst instanceof Type; + return ldc.cst instanceof String || ldc.cst instanceof Type || ldc.cst instanceof Handle; } default: return false; @@ -1727,6 +1728,8 @@ state.push(Type.INT_TYPE); } else if (insn.cst instanceof Float) { state.push(Type.FLOAT_TYPE); + } else if (insn.cst instanceof Handle) { + state.push(METHOD_HANDLE_TYPE); } else { throw new CompilationError("Unsupported constant: " + insn.cst.toString()); } @@ -2724,7 +2727,7 @@ } } - private void build(LdcInsnNode insn, IRBuilder builder) { + private void build(LdcInsnNode insn, IRBuilder builder) throws ApiLevelException { if (insn.cst instanceof Type) { Type type = (Type) insn.cst; int dest = state.push(type); @@ -2744,6 +2747,10 @@ } else if (insn.cst instanceof Float) { int dest = state.push(Type.FLOAT_TYPE); builder.addFloatConst(dest, Float.floatToRawIntBits((Float) insn.cst)); + } else if (insn.cst instanceof Handle) { + Handle handle = (Handle) insn.cst; + int dest = state.push(METHOD_HANDLE_TYPE); + builder.addConstMethodHandle(dest, getMethodHandle(application, handle)); } else { throw new CompilationError("Unsupported constant: " + insn.cst.toString()); }
diff --git a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java index 2d38bf0..c718e91 100644 --- a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java +++ b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
@@ -49,6 +49,8 @@ public void visitLdcInsn(Object cst) { if (cst instanceof Type) { registry.registerTypeReference(application.getType((Type) cst)); + } else if (cst instanceof Handle) { + registerMethodHandleType((Handle) cst); } }
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 c8308a9..68a6502 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -348,6 +348,10 @@ return minApiLevel >= AndroidApiLevel.O.getLevel(); } + public boolean canUseConstantMethodHandle() { + return minApiLevel >= AndroidApiLevel.P.getLevel(); + } + public boolean canUseInvokeCustom() { return minApiLevel >= AndroidApiLevel.O.getLevel(); }
diff --git a/src/test/examplesAndroidP/invokecustom/InvokeCustom.java b/src/test/examplesAndroidP/invokecustom/InvokeCustom.java new file mode 100644 index 0000000..282b5ae --- /dev/null +++ b/src/test/examplesAndroidP/invokecustom/InvokeCustom.java
@@ -0,0 +1,34 @@ +// 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 invokecustom; + +import java.lang.invoke.CallSite; +import java.lang.invoke.ConstantCallSite; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + + +public class InvokeCustom { + + private static String staticField1 = "StaticField1"; + + private static void targetMethodTest1() { + System.out.println("Hello World!"); + } + + private static void targetMethodTest2(MethodHandle mhInvokeStatic, MethodHandle mhGetStatic) + throws Throwable { + mhInvokeStatic.invokeExact(); + System.out.println(mhGetStatic.invoke()); + } + + public static CallSite bsmLookupStatic(MethodHandles.Lookup caller, String name, MethodType type) + throws NoSuchMethodException, IllegalAccessException { + final MethodHandles.Lookup lookup = MethodHandles.lookup(); + final MethodHandle targetMH = lookup.findStatic(lookup.lookupClass(), name, type); + return new ConstantCallSite(targetMH.asType(type)); + } + +}
diff --git a/src/test/examplesAndroidP/invokecustom/TestGenerator.java b/src/test/examplesAndroidP/invokecustom/TestGenerator.java new file mode 100644 index 0000000..b9a5126 --- /dev/null +++ b/src/test/examplesAndroidP/invokecustom/TestGenerator.java
@@ -0,0 +1,84 @@ +// 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 invokecustom; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.invoke.CallSite; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Handle; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +public class TestGenerator { + + private final Path classNamePath; + + public static void main(String[] args) throws IOException { + assert args.length == 1; + TestGenerator testGenerator = new TestGenerator(Paths.get(args[0], + TestGenerator.class.getPackage().getName(), InvokeCustom.class.getSimpleName() + ".class")); + testGenerator.generateTests(); + } + + public TestGenerator(Path classNamePath) { + this.classNamePath = classNamePath; + } + + private void generateTests() throws IOException { + ClassReader cr = new ClassReader(new FileInputStream(classNamePath.toFile())); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + cr.accept( + new ClassVisitor(Opcodes.ASM6, cw) { + @Override + public void visitEnd() { + generateMethodTest1(cw); + generateMethodMain(cw); + super.visitEnd(); + } + }, 0); + new FileOutputStream(classNamePath.toFile()).write(cw.toByteArray()); + } + + /* Generate main method that only call all test methods. */ + private void generateMethodMain(ClassVisitor cv) { + MethodVisitor mv = cv.visitMethod( + Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, Type.getInternalName(InvokeCustom.class), "test1", "()V", false); + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(-1, -1); + } + + /** + * Generate test with an invokedynamic, a static bootstrap method without extra args and + * args to the target method. + */ + private void generateMethodTest1(ClassVisitor cv) { + MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test1", "()V", + null, null); + MethodType mt = MethodType.methodType( + CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class); + Handle bootstrap = new Handle( Opcodes.H_INVOKESTATIC, Type.getInternalName(InvokeCustom.class), + "bsmLookupStatic", mt.toMethodDescriptorString(), false); + mv.visitLdcInsn(new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(InvokeCustom.class), + "targetMethodTest1", "()V", false)); + mv.visitLdcInsn(new Handle(Opcodes.H_GETSTATIC, Type.getInternalName(InvokeCustom.class), + "staticField1", "Ljava/lang/String;", false)); + mv.visitInvokeDynamicInsn("targetMethodTest2", + "(Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)V", + bootstrap); + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(-1, -1); + } +}
diff --git a/src/test/examplesAndroidP/invokecustom/keep-rules.txt b/src/test/examplesAndroidP/invokecustom/keep-rules.txt new file mode 100644 index 0000000..dbc21fc --- /dev/null +++ b/src/test/examplesAndroidP/invokecustom/keep-rules.txt
@@ -0,0 +1,16 @@ +# 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. + +# Keep the application entry point and the target methods of invoke-custom because these methods +# can not be known at compile time. Get rid of everything that is not reachable from there. +-keep public class invokecustom.InvokeCustom { + public static void main(...); +} + +-keepclasseswithmembers class * { + *** targetMethodTest*(...); +} + +# allow access modification to enable minification +-allowaccessmodification
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java new file mode 100644 index 0000000..dde8a9c --- /dev/null +++ b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java
@@ -0,0 +1,86 @@ +// 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 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 com.android.tools.r8.utils.DexInspector; +import com.android.tools.r8.utils.OffOrAuto; +import com.android.tools.r8.utils.OutputMode; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.HashSet; +import java.util.function.UnaryOperator; +import org.hamcrest.core.CombinableMatcher; +import org.hamcrest.core.IsInstanceOf; +import org.hamcrest.core.StringContains; +import org.junit.Assert; +import org.junit.Test; +import org.junit.internal.matchers.ThrowableMessageMatcher; + +public class D8RunExamplesAndroidPTest extends RunExamplesAndroidPTest<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.O.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/R8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java new file mode 100644 index 0000000..0f7386e --- /dev/null +++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
@@ -0,0 +1,91 @@ +// 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.concurrent.ExecutionException; +import java.util.function.UnaryOperator; +import org.junit.Test; + +public class R8RunExamplesAndroidPTest extends RunExamplesAndroidPTest<R8Command.Builder> { + + private static Map<DexVm.Version, List<String>> alsoFailsOn = + ImmutableMap.of( + DexVm.Version.V4_4_4, ImmutableList.of( + "invokecustom-with-shrinking" + ), + DexVm.Version.V5_1_1, ImmutableList.of( + "invokecustom-with-shrinking" + ), + DexVm.Version.V6_0_1, ImmutableList.of( + "invokecustom-with-shrinking" + ), + DexVm.Version.V7_0_0, ImmutableList.of( + "invokecustom-with-shrinking" + ), + DexVm.Version.DEFAULT, ImmutableList.of( + ) + ); + + @Test + public void invokeCustomWithShrinking() throws Throwable { + test("invokecustom-with-shrinking", "invokecustom", "InvokeCustom") + .withMinApiLevel(AndroidApiLevel.P.getLevel()) + .withBuilderTransformation(builder -> + builder.addProguardConfigurationFiles( + Paths.get(ToolHelper.EXAMPLES_ANDROID_P_DIR, "invokecustom/keep-rules.txt"))) + .run(); + } + + 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 { + try { + 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.O.getLevel()))); + R8Command command = builder.addProgramFiles(inputFile).setOutputPath(out).build(); + ToolHelper.runR8(command, this::combinedOptionConsumer); + } catch (ExecutionException e) { + throw e.getCause(); + } + } + + @Override + R8TestRunner self() { + return this; + } + } + + @Override + R8TestRunner test(String testName, String packageName, String mainClass) { + return new R8TestRunner(testName, packageName, mainClass); + } + + @Override + boolean expectedToFail(String name) { + return super.expectedToFail(name) || failsOn(alsoFailsOn, name); + } +}
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java new file mode 100644 index 0000000..06916a8 --- /dev/null +++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
@@ -0,0 +1,273 @@ +// 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.DexInspector.InvokeInstructionSubject; +import com.android.tools.r8.utils.FileUtils; +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.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; + +public abstract class RunExamplesAndroidPTest + <B extends BaseCommand.Builder<? extends BaseCommand, B>> { + static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_ANDROID_P_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( + "invokepolymorphic-error-due-to-min-sdk", "invokecustom-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 + "invokecustom" + ), + DexVm.Version.V5_1_1, ImmutableList.of( + // Dex version not supported + "invokecustom" + ), + DexVm.Version.V6_0_1, ImmutableList.of( + // Dex version not supported + "invokecustom" + ), + DexVm.Version.V7_0_0, ImmutableList.of( + // Dex version not supported + "invokecustom" + ), + DexVm.Version.DEFAULT, ImmutableList.of() + ); + + @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 invokeCustom() throws Throwable { + test("invokecustom", "invokecustom", "InvokeCustom") + .withMinApiLevel(AndroidApiLevel.P.getLevel()) + .run(); + } + + @Test + public void invokeCustomErrorDueToMinSdk() throws Throwable { + test("invokecustom-error-due-to-min-sdk", "invokecustom", "InvokeCustom") + .withMinApiLevel(AndroidApiLevel.O.getLevel()) + .run(); + } + + abstract RunExamplesAndroidPTest<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(FileUtils.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 79be8a8..595b753 100644 --- a/src/test/java/com/android/tools/r8/ToolHelper.java +++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -59,9 +59,11 @@ public static final String BUILD_DIR = "build/"; public static final String EXAMPLES_DIR = "src/test/examples/"; public static final String EXAMPLES_ANDROID_O_DIR = "src/test/examplesAndroidO/"; + public static final String EXAMPLES_ANDROID_P_DIR = "src/test/examplesAndroidP/"; public static final String EXAMPLES_BUILD_DIR = BUILD_DIR + "test/examples/"; 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 SMALI_BUILD_DIR = BUILD_DIR + "test/smali/"; public static final String LINE_SEPARATOR = StringUtils.LINE_SEPARATOR;