summaryrefslogtreecommitdiff
path: root/test/952-invoke-custom/util-src
diff options
context:
space:
mode:
Diffstat (limited to 'test/952-invoke-custom/util-src')
-rw-r--r--test/952-invoke-custom/util-src/annotations/BootstrapMethod.java45
-rw-r--r--test/952-invoke-custom/util-src/annotations/CalledByIndy.java44
-rw-r--r--test/952-invoke-custom/util-src/annotations/Constant.java47
-rw-r--r--test/952-invoke-custom/util-src/transformer/IndyTransformer.java202
4 files changed, 338 insertions, 0 deletions
diff --git a/test/952-invoke-custom/util-src/annotations/BootstrapMethod.java b/test/952-invoke-custom/util-src/annotations/BootstrapMethod.java
new file mode 100644
index 0000000000..c16783007f
--- /dev/null
+++ b/test/952-invoke-custom/util-src/annotations/BootstrapMethod.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
+
+/**
+ * Describes a bootstrap method that performs method handle resolution on behalf of an
+ * invoke-dynamic instruction.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+public @interface BootstrapMethod {
+ /** The class containing the bootstrap method. */
+ Class<?> enclosingType();
+
+ /** The bootstrap method name. */
+ String name();
+
+ /** The return type of the bootstrap method. */
+ Class<?> returnType() default CallSite.class;
+
+ /** The parameter types of the bootstrap method. */
+ Class<?>[] parameterTypes() default {Lookup.class, String.class, MethodType.class};
+}
diff --git a/test/952-invoke-custom/util-src/annotations/CalledByIndy.java b/test/952-invoke-custom/util-src/annotations/CalledByIndy.java
new file mode 100644
index 0000000000..c4d13a2af4
--- /dev/null
+++ b/test/952-invoke-custom/util-src/annotations/CalledByIndy.java
@@ -0,0 +1,44 @@
+/*
+ * 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 called then it must be
+ * called by an invokedynamic instruction.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface CalledByIndy {
+ /** Resolver metadata for bootstrapping */
+ BootstrapMethod[] bootstrapMethod() default {};
+
+ /** Field or method name. */
+ String fieldOrMethodName();
+
+ /** Return type of method() or field getter() */
+ Class<?> returnType() default void.class;
+
+ /** Types of parameters for method or field setter() */
+ Class<?>[] parameterTypes() default {};
+
+ Constant[] constantArgumentsForBootstrapMethod() default {};
+}
diff --git a/test/952-invoke-custom/util-src/annotations/Constant.java b/test/952-invoke-custom/util-src/annotations/Constant.java
new file mode 100644
index 0000000000..7966a524ba
--- /dev/null
+++ b/test/952-invoke-custom/util-src/annotations/Constant.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+/** Describes an annotation that allows passing a constant extra argument to a linker method. */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+public @interface Constant {
+ boolean[] booleanValue() default {};
+
+ byte[] byteValue() default {};
+
+ char[] charValue() default {};
+
+ short[] shortValue() default {};
+
+ int[] intValue() default {};
+
+ float[] floatValue() default {};
+
+ double[] doubleValue() default {};
+
+ long[] longValue() default {};
+
+ Class<?>[] classValue() default {};
+
+ String[] stringValue() default {};
+}
diff --git a/test/952-invoke-custom/util-src/transformer/IndyTransformer.java b/test/952-invoke-custom/util-src/transformer/IndyTransformer.java
new file mode 100644
index 0000000000..d21dbbeabc
--- /dev/null
+++ b/test/952-invoke-custom/util-src/transformer/IndyTransformer.java
@@ -0,0 +1,202 @@
+/*
+ * 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.BootstrapMethod;
+import annotations.CalledByIndy;
+import annotations.Constant;
+import java.io.InputStream;
+import java.io.OutputStream;
+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 inserting invoke-dynamic instructions in annotated Java class files.
+ *
+ * <p>This class replaces static method invocations of annotated methods with invoke-dynamic
+ * instructions. Suppose a method is annotated as: <code>
+ *
+ * @CalledByIndy(
+ * bootstrapMethod =
+ * @BootstrapMethod(
+ * enclosingType = TestLinkerMethodMinimalArguments.class,
+ * parameterTypes = {MethodHandles.Lookup.class, String.class, MethodType.class},
+ * name = "linkerMethod"
+ * ),
+ * fieldOdMethodName = "magicAdd",
+ * returnType = int.class,
+ * argumentTypes = {int.class, int.class}
+ * )
+ * private int add(int x, int y) {
+ * throw new UnsupportedOperationException(e);
+ * }
+ *
+ * private int magicAdd(int x, int y) {
+ * return x + y;
+ * }
+ *
+ * </code> Then invokestatic bytecodes targeting the add() method will be replaced invokedynamic
+ * instructions targetting the CallSite that is construction by the bootstrap method described by
+ * the @CalledByIndy annotation.
+ *
+ * <p>In the example above, this results in add() being replaced by invocations of magicAdd().
+ */
+public class IndyTransformer {
+
+ static class BootstrapBuilder extends ClassVisitor {
+
+ private final Map<String, CalledByIndy> callsiteMap;
+ private final Map<String, Handle> bsmMap = new HashMap<>();
+
+ public BootstrapBuilder(int api, Map<String, CalledByIndy> callsiteMap) {
+ this(api, null, callsiteMap);
+ }
+
+ public BootstrapBuilder(int api, ClassVisitor cv, Map<String, CalledByIndy> callsiteMap) {
+ super(api, cv);
+ this.callsiteMap = callsiteMap;
+ }
+
+ @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) {
+ CalledByIndy callsite = callsiteMap.get(name);
+ if (callsite != null) {
+ insertIndy(callsite.fieldOrMethodName(), desc, callsite);
+ return;
+ }
+ }
+ mv.visitMethodInsn(opcode, owner, name, desc, itf);
+ }
+
+ private void insertIndy(String name, String desc, CalledByIndy callsite) {
+ Handle bsm = buildBootstrapMethodHandle(callsite.bootstrapMethod()[0]);
+ Object[] bsmArgs =
+ buildBootstrapArguments(callsite.constantArgumentsForBootstrapMethod());
+ mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
+ }
+
+ private Handle buildBootstrapMethodHandle(BootstrapMethod bootstrapMethod) {
+ String className = Type.getInternalName(bootstrapMethod.enclosingType());
+ String methodName = bootstrapMethod.name();
+ String methodType =
+ MethodType.methodType(
+ bootstrapMethod.returnType(),
+ bootstrapMethod.parameterTypes())
+ .toMethodDescriptorString();
+ return new Handle(
+ Opcodes.H_INVOKESTATIC,
+ className,
+ methodName,
+ methodType,
+ false /* itf */);
+ }
+
+ private Object decodeConstant(int index, Constant constant) {
+ if (constant.booleanValue().length == 1) {
+ return constant.booleanValue()[0];
+ } else if (constant.byteValue().length == 1) {
+ return constant.byteValue()[0];
+ } else if (constant.charValue().length == 1) {
+ return constant.charValue()[0];
+ } else if (constant.shortValue().length == 1) {
+ return constant.shortValue()[0];
+ } else if (constant.intValue().length == 1) {
+ return constant.intValue()[0];
+ } else if (constant.longValue().length == 1) {
+ return constant.longValue()[0];
+ } else if (constant.floatValue().length == 1) {
+ return constant.floatValue()[0];
+ } else if (constant.doubleValue().length == 1) {
+ return constant.doubleValue()[0];
+ } else if (constant.stringValue().length == 1) {
+ return constant.stringValue()[0];
+ } else if (constant.classValue().length == 1) {
+ return Type.getType(constant.classValue()[0]);
+ } else {
+ throw new Error("Bad constant at index " + index);
+ }
+ }
+
+ private Object[] buildBootstrapArguments(Constant[] bootstrapMethodArguments) {
+ Object[] args = new Object[bootstrapMethodArguments.length];
+ for (int i = 0; i < bootstrapMethodArguments.length; ++i) {
+ args[i] = decodeConstant(i, bootstrapMethodArguments[i]);
+ }
+ return args;
+ }
+ };
+ }
+ }
+
+ private static void transform(Path inputClassPath, Path outputClassPath) throws Throwable {
+ URL url = inputClassPath.getParent().toUri().toURL();
+ URLClassLoader classLoader =
+ new URLClassLoader(new URL[] {url}, ClassLoader.getSystemClassLoader());
+ String inputClassName = inputClassPath.getFileName().toString().replace(".class", "");
+ Class<?> inputClass = classLoader.loadClass(inputClassName);
+ Map<String, CalledByIndy> callsiteMap = new HashMap<>();
+
+ for (Method m : inputClass.getDeclaredMethods()) {
+ CalledByIndy calledByIndy = m.getAnnotation(CalledByIndy.class);
+ if (calledByIndy == null) {
+ continue;
+ }
+ if (calledByIndy.fieldOrMethodName() == null) {
+ throw new Error("CallByIndy annotation does not specify a field or method name");
+ }
+ final int PRIVATE_STATIC = Modifier.STATIC | Modifier.PRIVATE;
+ if ((m.getModifiers() & PRIVATE_STATIC) != PRIVATE_STATIC) {
+ throw new Error(
+ "Method whose invocations should be replaced should be private and static");
+ }
+ callsiteMap.put(m.getName(), calledByIndy);
+ }
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+ try (InputStream is = Files.newInputStream(inputClassPath)) {
+ ClassReader cr = new ClassReader(is);
+ cr.accept(new BootstrapBuilder(Opcodes.ASM6, cw, callsiteMap), 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]));
+ }
+}