diff options
Diffstat (limited to 'test/952-invoke-custom/util-src')
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])); + } +} |