blob: 286c09859deffee7579c5ced68415864a78946ba [file] [log] [blame]
Orion Hodson76e6adb2018-02-23 13:15:55 +00001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package transformer;
17
18import annotations.CalledByIndy;
19import annotations.Constant;
20import annotations.LinkerFieldHandle;
21import annotations.LinkerMethodHandle;
22import annotations.MethodHandleKind;
23import java.io.InputStream;
24import java.io.OutputStream;
25import java.lang.invoke.MethodType;
26import java.lang.reflect.Method;
27import java.lang.reflect.Modifier;
28import java.net.URL;
29import java.net.URLClassLoader;
30import java.nio.file.Files;
31import java.nio.file.Path;
32import java.nio.file.Paths;
33import java.util.HashMap;
34import java.util.Map;
35import org.objectweb.asm.ClassReader;
36import org.objectweb.asm.ClassVisitor;
37import org.objectweb.asm.ClassWriter;
38import org.objectweb.asm.Handle;
39import org.objectweb.asm.MethodVisitor;
40import org.objectweb.asm.Opcodes;
41import org.objectweb.asm.Type;
42
43/**
44 * Class for inserting invoke-dynamic instructions in annotated Java class files.
45 *
46 * This class replaces static method invocations of annotated methods
47 * with invoke-dynamic instructions. Suppose a method is annotated as:
48 *
49 * @CalledByIndy(
50 * invokeMethodHandle =
51 * @LinkerMethodHandle(
52 * kind = MethodHandleKind.INVOKE_STATIC,
53 * enclosingType = TestLinkerMethodMinimalArguments.class,
54 * argumentTypes = {MethodHandles.Lookup.class, String.class, MethodType.class},
55 * name = "linkerMethod"
56 * ),
57 * name = "magicAdd",
58 * returnType = int.class,
59 * argumentTypes = {int.class, int.class}
60 * )
61 * private int add(int x, int y) {
62 * throw new UnsupportedOperationException(e);
63 * }
64 *
65 * private int magicAdd(int x, int y) {
66 * return x + y;
67 * }
68 *
69 * Then invokestatic bytecodes targeting the add() method will be
70 * replaced invokedynamic instructions targetting the CallSite that is
71 * construction by the bootstrap method described by the @CalledByIndy
72 * annotation.
73 *
74 * In the example above, this results in add() being replaced by
75 * invocations of magicAdd().
76 */
77class IndyTransformer {
78
79 static class BootstrapBuilder extends ClassVisitor {
80
81 private final Map<String, CalledByIndy> callsiteMap;
82 private final Map<String, Handle> bsmMap = new HashMap<>();
83
84 public BootstrapBuilder(int api, Map<String, CalledByIndy> callsiteMap) {
85 this(api, null, callsiteMap);
86 }
87
88 public BootstrapBuilder(int api, ClassVisitor cv, Map<String, CalledByIndy> callsiteMap) {
89 super(api, cv);
90 this.callsiteMap = callsiteMap;
91 }
92
93 @Override
94 public MethodVisitor visitMethod(
95 int access, String name, String desc, String signature, String[] exceptions) {
96 MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
97 return new MethodVisitor(this.api, mv) {
98 @Override
99 public void visitMethodInsn(
100 int opcode, String owner, String name, String desc, boolean itf) {
101 if (opcode == org.objectweb.asm.Opcodes.INVOKESTATIC) {
102 CalledByIndy callsite = callsiteMap.get(name);
103 if (callsite != null) {
104 insertIndy(callsite.name(), desc, callsite);
105 return;
106 }
107 }
108 mv.visitMethodInsn(opcode, owner, name, desc, itf);
109 }
110
111 private void insertIndy(String name, String desc, CalledByIndy callsite) {
112 Handle bsm = buildBootstrapMethodHandle(callsite);
113 Object[] bsmArgs = buildBootstrapArguments(callsite);
114 mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
115 }
116
117 private Handle buildBootstrapMethodHandle(CalledByIndy callsite) {
118 MethodHandleKind kind;
119 if (callsite.fieldMethodHandle().length != 0) {
120 return buildBootstrapMethodHandleForField(callsite.fieldMethodHandle()[0]);
121 } else if (callsite.invokeMethodHandle().length != 0) {
122 return buildBootstrapMethodHandleForMethod(
123 callsite.invokeMethodHandle()[0]);
124 } else {
125 throw new Error("Missing linker method handle in CalledByIndy annotation");
126 }
127 }
128
129 private Handle buildBootstrapMethodHandleForField(LinkerFieldHandle fieldHandle) {
130 int handleKind;
131 switch (fieldHandle.kind()) {
132 case GET_FIELD:
133 handleKind = Opcodes.H_GETFIELD;
134 break;
135 case GET_STATIC:
136 handleKind = Opcodes.H_GETSTATIC;
137 break;
138 case PUT_FIELD:
139 handleKind = Opcodes.H_PUTFIELD;
140 break;
141 case PUT_STATIC:
142 handleKind = Opcodes.H_PUTSTATIC;
143 break;
144 default:
145 throw new Error("Unknown field invocation kind: " + fieldHandle.kind());
146 }
147 Class<?> resolverClass = fieldHandle.enclosingType();
148 String resolverMethod = fieldHandle.name();
149 Class<?> resolverReturnType = fieldHandle.type();
150
151 // TODO: arguments types to invoke resolver with (default + extra args).
152 throw new Error("WIP");
153 }
154
155 private Handle buildBootstrapMethodHandleForMethod(
156 LinkerMethodHandle methodHandle) {
157 int handleKind;
158 switch (methodHandle.kind()) {
159 case INVOKE_CONSTRUCTOR:
160 handleKind = Opcodes.H_NEWINVOKESPECIAL;
161 break;
162 case INVOKE_INTERFACE:
163 handleKind = Opcodes.H_INVOKEINTERFACE;
164 break;
165 case INVOKE_SPECIAL:
166 handleKind = Opcodes.H_INVOKESPECIAL;
167 break;
168 case INVOKE_STATIC:
169 handleKind = Opcodes.H_INVOKESTATIC;
170 break;
171 case INVOKE_VIRTUAL:
172 handleKind = Opcodes.H_INVOKEVIRTUAL;
173 break;
174 default:
175 throw new Error(
176 "Unknown method invocation kind: " + methodHandle.kind());
177 }
178 String className = Type.getInternalName(methodHandle.enclosingType());
179 String methodName = methodHandle.name();
180 String methodType =
181 MethodType.methodType(
182 methodHandle.returnType(), methodHandle.argumentTypes())
183 .toMethodDescriptorString();
184 return new Handle(
185 handleKind, className, methodName, methodType, false /* itf */);
186 }
187
188 private Object decodeConstant(int index, Constant constant) {
189 if (constant.booleanValue().length == 1) {
190 return constant.booleanValue()[0];
191 } else if (constant.byteValue().length == 1) {
192 return constant.byteValue()[0];
193 } else if (constant.charValue().length == 1) {
194 return constant.charValue()[0];
195 } else if (constant.shortValue().length == 1) {
196 return constant.shortValue()[0];
197 } else if (constant.intValue().length == 1) {
198 return constant.intValue()[0];
199 } else if (constant.longValue().length == 1) {
200 return constant.longValue()[0];
201 } else if (constant.floatValue().length == 1) {
202 return constant.floatValue()[0];
203 } else if (constant.doubleValue().length == 1) {
204 return constant.doubleValue()[0];
205 } else if (constant.stringValue().length == 1) {
206 return constant.stringValue()[0];
207 } else if (constant.classValue().length == 1) {
208 return Type.getType(constant.classValue()[0]);
209 } else {
210 throw new Error("Bad constant at index " + index);
211 }
212 }
213
214 private Object[] buildBootstrapArguments(CalledByIndy callsite) {
215 Constant[] rawArgs = callsite.methodHandleExtraArgs();
216 Object[] args = new Object[rawArgs.length];
217 for (int i = 0; i < rawArgs.length; ++i) {
218 args[i] = decodeConstant(i, rawArgs[i]);
219 }
220 return args;
221 }
222 };
223 }
224 }
225
226 private static void transform(Path inputClassPath, Path outputClassPath) throws Throwable {
227 URLClassLoader classLoader =
228 new URLClassLoader(
229 new URL[] {inputClassPath.toUri().toURL()},
230 ClassLoader.getSystemClassLoader());
231 String inputClassName = inputClassPath.getFileName().toString().replace(".class", "");
232 Class<?> inputClass = classLoader.loadClass(inputClassName);
233 Map<String, CalledByIndy> callsiteMap = new HashMap<>();
234
235 for (Method m : inputClass.getDeclaredMethods()) {
236 CalledByIndy calledByIndy = m.getAnnotation(CalledByIndy.class);
237 if (calledByIndy == null) {
238 continue;
239 }
240 if (calledByIndy.name() == null) {
241 throw new Error("CallByIndy annotation does not specify name");
242 }
243 final int PRIVATE_STATIC = Modifier.STATIC | Modifier.PRIVATE;
244 if ((m.getModifiers() & PRIVATE_STATIC) != PRIVATE_STATIC) {
245 throw new Error(
246 "Method whose invocations should be replaced should be private and static");
247 }
248 callsiteMap.put(m.getName(), calledByIndy);
249 }
250 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
251 try (InputStream is = Files.newInputStream(inputClassPath)) {
252 ClassReader cr = new ClassReader(is);
253 cr.accept(new BootstrapBuilder(Opcodes.ASM6, cw, callsiteMap), 0);
254 }
255 try (OutputStream os = Files.newOutputStream(outputClassPath)) {
256 os.write(cw.toByteArray());
257 }
258 }
259
260 public static void main(String[] args) throws Throwable {
261 transform(Paths.get(args[0]), Paths.get(args[1]));
262 }
263}