ART: Sources for 979-const-method-handle

Removes binary dependencies from 979-const-method-handle test and uses
ASM instead to build test cases.

Test: art/test/run-test --host 979
Bug: 66890674
Change-Id: I239e389b4623b5cd33d80f5ba8d624678bdb614a
diff --git a/test/979-const-method-handle/build b/test/979-const-method-handle/build
old mode 100644
new mode 100755
index 495557e..ce931a9
--- a/test/979-const-method-handle/build
+++ b/test/979-const-method-handle/build
@@ -1,12 +1,12 @@
 #!/bin/bash
 #
-# Copyright (C) 2017 The Android Open Source Project
+# Copyright 2018 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
 #
-#     http://www.apache.org/licenses/LICENSE-2.0
+#      http://www.apache.org/licenses/LICENSE-2.0
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,42 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Stop if something fails.
+# make us exit on a failure
 set -e
 
-${DX} --dex --min-sdk-version=28 --output=classes.dex classes
+ASM_JAR="${ANDROID_BUILD_TOP}/prebuilts/misc/common/asm/asm-6.0.jar"
+INTERMEDIATE_CLASSES=classes-intermediate
+TRANSFORMER_CLASSES=classes-transformer
+CLASSES=classes
 
-zip $TEST_NAME.jar classes.dex
+DEXER="${DX:-dx}"
+if [ "${USE_D8=false}" = "true" ]; then
+  DEXER="${ANDROID_HOST_OUT}/bin/d8-compat-dx"
+fi
+
+# Create directories for classes
+for class_dir in "${INTERMEDIATE_CLASSES}" "${TRANSFORMER_CLASSES}" "${CLASSES}"; do
+  rm -rf "${class_dir}"
+  mkdir "${class_dir}"
+done
+
+# Build transformer
+${JAVAC:-javac} ${JAVAC_ARGS} -cp "${ASM_JAR}" -d ${TRANSFORMER_CLASSES} $(find util-src -name '*.java')
+
+# Generate intermediate classes that will allow transform to be applied to test classes
+JAVAC_ARGS="${JAVAC_ARGS} -source 1.8 -target 1.8"
+${JAVAC:-javac} ${JAVAC_ARGS} -cp ${TRANSFORMER_CLASSES} -d ${INTERMEDIATE_CLASSES} $(find src -name '*.java')
+
+# Run transform
+for class in ${INTERMEDIATE_CLASSES}/*.class ; do
+  transformed_class=${CLASSES}/$(basename ${class})
+  ${JAVA:-java} -cp "${ASM_JAR}:${TRANSFORMER_CLASSES}" \
+      transformer.ConstantTransformer ${class} ${transformed_class}
+done
+
+# Create DEX
+DX_FLAGS="${DX_FLAGS} --min-sdk-version=28 --debug --dump-width=1000"
+${DEXER} -JXmx256m --dex ${DX_FLAGS} --dump-to=${CLASSES}.lst --output=classes.dex ${CLASSES} ${TRANSFORMER_CLASSES}
+
+# Zip DEX to file name expected by test runner
+zip ${TEST_NAME:-classes-dex}.jar classes.dex
diff --git a/test/979-const-method-handle/classes/Main.class b/test/979-const-method-handle/classes/Main.class
deleted file mode 100644
index 8d6b7d8..0000000
--- a/test/979-const-method-handle/classes/Main.class
+++ /dev/null
Binary files differ
diff --git a/test/979-const-method-handle/classes/constmethodhandle/ConstTest.class b/test/979-const-method-handle/classes/constmethodhandle/ConstTest.class
deleted file mode 100644
index a21b0a33..0000000
--- a/test/979-const-method-handle/classes/constmethodhandle/ConstTest.class
+++ /dev/null
Binary files differ
diff --git a/test/979-const-method-handle/expected.txt b/test/979-const-method-handle/expected.txt
index 573b80d..335e5cb 100644
--- a/test/979-const-method-handle/expected.txt
+++ b/test/979-const-method-handle/expected.txt
@@ -1,2 +1,5 @@
-MethodHandle MethodHandle(Object)Class => class java.lang.Float
-MethodType (char,short,int,long,float,double,Object)boolean
+(int,Integer,System)String
+Hello World! And Hello Zog
+Hello World! And Hello Zorba
+name is HoverFly
+2.718281828459045
diff --git a/test/979-const-method-handle/info.txt b/test/979-const-method-handle/info.txt
index e8514ce..fc909db 100644
--- a/test/979-const-method-handle/info.txt
+++ b/test/979-const-method-handle/info.txt
@@ -1,7 +1 @@
 This test checks const-method-handle and const-method-type bytecodes.
-
-The class files in this test come from:
-
-  dalvik/dx/tests/142-const-method-handle
-
-and are built using ASM bytecode manipulation library.
diff --git a/test/979-const-method-handle/src/Main.java b/test/979-const-method-handle/src/Main.java
new file mode 100644
index 0000000..ddac1c8
--- /dev/null
+++ b/test/979-const-method-handle/src/Main.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import annotations.ConstantMethodHandle;
+import annotations.ConstantMethodType;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodType;
+
+class Main {
+    private static String name = "default";
+
+    private static void unreachable() {
+        throw new Error("Unreachable");
+    }
+
+    @ConstantMethodType(
+        returnType = String.class,
+        parameterTypes = {int.class, Integer.class, System.class}
+    )
+    private static MethodType methodType0() {
+        unreachable();
+        return null;
+    }
+
+    static void helloWorld(String who) {
+        System.out.print("Hello World! And Hello ");
+        System.out.println(who);
+    }
+
+    @ConstantMethodHandle(
+        kind = ConstantMethodHandle.INVOKE_STATIC,
+        owner = "Main",
+        fieldOrMethodName = "helloWorld",
+        descriptor = "(Ljava/lang/String;)V"
+    )
+    private static MethodHandle printHelloHandle() {
+        unreachable();
+        return null;
+    }
+
+    @ConstantMethodHandle(
+        kind = ConstantMethodHandle.STATIC_PUT,
+        owner = "Main",
+        fieldOrMethodName = "name",
+        descriptor = "Ljava/lang/String;"
+    )
+    private static MethodHandle setNameHandle() {
+        unreachable();
+        return null;
+    }
+
+    @ConstantMethodHandle(
+        kind = ConstantMethodHandle.STATIC_GET,
+        owner = "java/lang/Math",
+        fieldOrMethodName = "E",
+        descriptor = "D"
+    )
+    private static MethodHandle getMathE() {
+        unreachable();
+        return null;
+    }
+
+    public static void main(String[] args) throws Throwable {
+        System.out.println(methodType0());
+        printHelloHandle().invokeExact("Zog");
+        printHelloHandle().invokeExact("Zorba");
+        setNameHandle().invokeExact("HoverFly");
+        System.out.print("name is ");
+        System.out.println(name);
+        System.out.println(getMathE().invoke());
+    }
+}
diff --git a/test/979-const-method-handle/util-src/annotations/ConstantMethodHandle.java b/test/979-const-method-handle/util-src/annotations/ConstantMethodHandle.java
new file mode 100644
index 0000000..40785eb
--- /dev/null
+++ b/test/979-const-method-handle/util-src/annotations/ConstantMethodHandle.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation can be set on method to specify that if this method
+ * is statically invoked then the invocation is replaced by a
+ * load-constant bytecode with the MethodHandle constant described by
+ * the annotation.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface ConstantMethodHandle {
+    /* Method handle kinds */
+    public static final int STATIC_PUT = 0;
+    public static final int STATIC_GET = 1;
+    public static final int INSTANCE_PUT = 2;
+    public static final int INSTANCE_GET = 3;
+    public static final int INVOKE_STATIC = 4;
+    public static final int INVOKE_VIRTUAL = 5;
+    public static final int INVOKE_SPECIAL = 6;
+    public static final int NEW_INVOKE_SPECIAL = 7;
+    public static final int INVOKE_INTERFACE = 8;
+
+    /** Kind of method handle. */
+    int kind();
+
+    /** Class name owning the field or method. */
+    String owner();
+
+    /** The field or method name addressed by the MethodHandle. */
+    String fieldOrMethodName();
+
+    /** Descriptor for the field (type) or method (method-type) */
+    String descriptor();
+
+    /** Whether the owner is an interface. */
+    boolean ownerIsInterface() default false;
+}
diff --git a/test/979-const-method-handle/util-src/annotations/ConstantMethodType.java b/test/979-const-method-handle/util-src/annotations/ConstantMethodType.java
new file mode 100644
index 0000000..c89fa01
--- /dev/null
+++ b/test/979-const-method-handle/util-src/annotations/ConstantMethodType.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation can be set on method to specify that if this method
+ * is statically invoked then the invocation is replaced by a
+ * load-constant bytecode with the MethodType constant described by
+ * the annotation.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface ConstantMethodType {
+    /** Return type of method() or field getter() */
+    Class<?> returnType() default void.class;
+
+    /** Types of parameters for method or field setter() */
+    Class<?>[] parameterTypes() default {};
+}
diff --git a/test/979-const-method-handle/util-src/transformer/ConstantTransformer.java b/test/979-const-method-handle/util-src/transformer/ConstantTransformer.java
new file mode 100644
index 0000000..9356426
--- /dev/null
+++ b/test/979-const-method-handle/util-src/transformer/ConstantTransformer.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package transformer;
+
+import annotations.ConstantMethodHandle;
+import annotations.ConstantMethodType;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+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;
+
+/**
+ * Class for transforming invoke static bytecodes into constant method handle loads and and constant
+ * method type loads.
+ *
+ * <p>When a parameterless private static method returning a MethodHandle is defined and annotated
+ * with {@code ConstantMethodHandle}, this transformer will replace static invocations of the method
+ * with a load constant bytecode with a method handle in the constant pool.
+ *
+ * <p>Suppose a method is annotated as: <code>
+ *  @ConstantMethodHandle(
+ *      kind = ConstantMethodHandle.STATIC_GET,
+ *      owner = "java/lang/Math",
+ *      fieldOrMethodName = "E",
+ *      descriptor = "D"
+ *  )
+ *  private static MethodHandle getMathE() {
+ *      unreachable();
+ *      return null;
+ *  }
+ * </code> Then invocations of {@code getMathE} will be replaced by a load from the constant pool
+ * with the constant method handle described in the {@code ConstantMethodHandle} annotation.
+ *
+ * <p>Similarly, a parameterless private static method returning a {@code MethodType} and annotated
+ * with {@code ConstantMethodType}, will have invocations replaced by a load constant bytecode with
+ * a method type in the constant pool.
+ */
+class ConstantTransformer {
+    static class ConstantBuilder extends ClassVisitor {
+        private final Map<String, ConstantMethodHandle> constantMethodHandles;
+        private final Map<String, ConstantMethodType> constantMethodTypes;
+
+        ConstantBuilder(
+                int api,
+                ClassVisitor cv,
+                Map<String, ConstantMethodHandle> constantMethodHandles,
+                Map<String, ConstantMethodType> constantMethodTypes) {
+            super(api, cv);
+            this.constantMethodHandles = constantMethodHandles;
+            this.constantMethodTypes = constantMethodTypes;
+        }
+
+        @Override
+        public MethodVisitor visitMethod(
+                int access, String name, String desc, String signature, String[] exceptions) {
+            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
+            return new MethodVisitor(this.api, mv) {
+                @Override
+                public void visitMethodInsn(
+                        int opcode, String owner, String name, String desc, boolean itf) {
+                    if (opcode == org.objectweb.asm.Opcodes.INVOKESTATIC) {
+                        ConstantMethodHandle constantMethodHandle = constantMethodHandles.get(name);
+                        if (constantMethodHandle != null) {
+                            insertConstantMethodHandle(constantMethodHandle);
+                            return;
+                        }
+                        ConstantMethodType constantMethodType = constantMethodTypes.get(name);
+                        if (constantMethodType != null) {
+                            insertConstantMethodType(constantMethodType);
+                            return;
+                        }
+                    }
+                    mv.visitMethodInsn(opcode, owner, name, desc, itf);
+                }
+
+                private Type buildMethodType(Class<?> returnType, Class<?>[] parameterTypes) {
+                    Type rType = Type.getType(returnType);
+                    Type[] pTypes = new Type[parameterTypes.length];
+                    for (int i = 0; i < pTypes.length; ++i) {
+                        pTypes[i] = Type.getType(parameterTypes[i]);
+                    }
+                    return Type.getMethodType(rType, pTypes);
+                }
+
+                private int getHandleTag(int kind) {
+                    switch (kind) {
+                        case ConstantMethodHandle.STATIC_PUT:
+                            return Opcodes.H_PUTSTATIC;
+                        case ConstantMethodHandle.STATIC_GET:
+                            return Opcodes.H_GETSTATIC;
+                        case ConstantMethodHandle.INSTANCE_PUT:
+                            return Opcodes.H_PUTFIELD;
+                        case ConstantMethodHandle.INSTANCE_GET:
+                            return Opcodes.H_GETFIELD;
+                        case ConstantMethodHandle.INVOKE_STATIC:
+                            return Opcodes.H_INVOKESTATIC;
+                        case ConstantMethodHandle.INVOKE_VIRTUAL:
+                            return Opcodes.H_INVOKEVIRTUAL;
+                        case ConstantMethodHandle.INVOKE_SPECIAL:
+                            return Opcodes.H_INVOKESPECIAL;
+                        case ConstantMethodHandle.NEW_INVOKE_SPECIAL:
+                            return Opcodes.H_NEWINVOKESPECIAL;
+                        case ConstantMethodHandle.INVOKE_INTERFACE:
+                            return Opcodes.H_INVOKEINTERFACE;
+                    }
+                    throw new Error("Unhandled kind " + kind);
+                }
+
+                private void insertConstantMethodHandle(ConstantMethodHandle constantMethodHandle) {
+                    Handle handle =
+                            new Handle(
+                                    getHandleTag(constantMethodHandle.kind()),
+                                    constantMethodHandle.owner(),
+                                    constantMethodHandle.fieldOrMethodName(),
+                                    constantMethodHandle.descriptor(),
+                                    constantMethodHandle.ownerIsInterface());
+                    mv.visitLdcInsn(handle);
+                }
+
+                private void insertConstantMethodType(ConstantMethodType constantMethodType) {
+                    Type methodType =
+                            buildMethodType(
+                                    constantMethodType.returnType(),
+                                    constantMethodType.parameterTypes());
+                    mv.visitLdcInsn(methodType);
+                }
+            };
+        }
+    }
+
+    private static void throwAnnotationError(
+            Method method, Class<?> annotationClass, String reason) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Error in annotation ")
+                .append(annotationClass)
+                .append(" on method ")
+                .append(method)
+                .append(": ")
+                .append(reason);
+        throw new Error(sb.toString());
+    }
+
+    private static void checkMethodToBeReplaced(
+            Method method, Class<?> annotationClass, Class<?> returnType) {
+        final int PRIVATE_STATIC = Modifier.STATIC | Modifier.PRIVATE;
+        if ((method.getModifiers() & PRIVATE_STATIC) != PRIVATE_STATIC) {
+            throwAnnotationError(method, annotationClass, " method is not private and static");
+        }
+        if (method.getTypeParameters().length != 0) {
+            throwAnnotationError(method, annotationClass, " method expects parameters");
+        }
+        if (!method.getReturnType().equals(returnType)) {
+            throwAnnotationError(method, annotationClass, " wrong return type");
+        }
+    }
+
+    private static void transform(Path inputClassPath, Path outputClassPath) throws Throwable {
+        Path classLoadPath = inputClassPath.toAbsolutePath().getParent();
+        URLClassLoader classLoader =
+                new URLClassLoader(new URL[] {classLoadPath.toUri().toURL()},
+                                   ClassLoader.getSystemClassLoader());
+        String inputClassName = inputClassPath.getFileName().toString().replace(".class", "");
+        Class<?> inputClass = classLoader.loadClass(inputClassName);
+
+        final Map<String, ConstantMethodHandle> constantMethodHandles = new HashMap<>();
+        final Map<String, ConstantMethodType> constantMethodTypes = new HashMap<>();
+
+        for (Method m : inputClass.getDeclaredMethods()) {
+            ConstantMethodHandle constantMethodHandle = m.getAnnotation(ConstantMethodHandle.class);
+            if (constantMethodHandle != null) {
+                checkMethodToBeReplaced(m, ConstantMethodHandle.class, MethodHandle.class);
+                constantMethodHandles.put(m.getName(), constantMethodHandle);
+                continue;
+            }
+
+            ConstantMethodType constantMethodType = m.getAnnotation(ConstantMethodType.class);
+            if (constantMethodType != null) {
+                checkMethodToBeReplaced(m, ConstantMethodType.class, MethodType.class);
+                constantMethodTypes.put(m.getName(), constantMethodType);
+                continue;
+            }
+        }
+        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+        try (InputStream is = Files.newInputStream(inputClassPath)) {
+            ClassReader cr = new ClassReader(is);
+            ConstantBuilder cb =
+                    new ConstantBuilder(
+                            Opcodes.ASM6, cw, constantMethodHandles, constantMethodTypes);
+            cr.accept(cb, 0);
+        }
+        try (OutputStream os = Files.newOutputStream(outputClassPath)) {
+            os.write(cw.toByteArray());
+        }
+    }
+
+    public static void main(String[] args) throws Throwable {
+        transform(Paths.get(args[0]), Paths.get(args[1]));
+    }
+}