blob: 13638945469b0791e6e78bd741786d04e7bda7da [file] [log] [blame]
Jesse Wilson579d7732012-01-03 16:12:39 -05001/*
2 * Copyright (C) 2011 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 */
16
Paul Duffinb8a58962017-03-15 14:14:35 +000017package com.android.dx.stock;
Jesse Wilson579d7732012-01-03 16:12:39 -050018
Paul Duffinb8a58962017-03-15 14:14:35 +000019import com.android.dx.Code;
20import com.android.dx.Comparison;
21import com.android.dx.DexMaker;
22import com.android.dx.FieldId;
23import com.android.dx.Label;
24import com.android.dx.Local;
25import com.android.dx.MethodId;
26import com.android.dx.TypeId;
27
Jesse Wilson579d7732012-01-03 16:12:39 -050028import java.io.File;
29import java.io.IOException;
30import java.lang.reflect.Constructor;
31import java.lang.reflect.Field;
32import java.lang.reflect.InvocationHandler;
33import java.lang.reflect.InvocationTargetException;
34import java.lang.reflect.Method;
35import java.lang.reflect.Modifier;
36import java.lang.reflect.UndeclaredThrowableException;
37import java.util.Arrays;
Jesse Wilson2e28a222012-01-10 12:50:37 -050038import java.util.Collections;
Andrew Yousef8d53a062015-01-23 13:59:10 -080039import java.util.Comparator;
Jesse Wilson579d7732012-01-03 16:12:39 -050040import java.util.HashMap;
41import java.util.HashSet;
42import java.util.Map;
43import java.util.Set;
Jesse Wilson1af1da62012-01-12 19:12:46 -050044import java.util.concurrent.CopyOnWriteArraySet;
Jesse Wilson579d7732012-01-03 16:12:39 -050045
Paul Duffinb8a58962017-03-15 14:14:35 +000046import static java.lang.reflect.Modifier.PRIVATE;
47import static java.lang.reflect.Modifier.PUBLIC;
48import static java.lang.reflect.Modifier.STATIC;
49
Jesse Wilson579d7732012-01-03 16:12:39 -050050/**
51 * Creates dynamic proxies of concrete classes.
52 * <p>
53 * This is similar to the {@code java.lang.reflect.Proxy} class, but works for classes instead of
54 * interfaces.
55 * <h3>Example</h3>
56 * The following example demonstrates the creation of a dynamic proxy for {@code java.util.Random}
57 * which will always return 4 when asked for integers, and which logs method calls to every method.
58 * <pre>
59 * InvocationHandler handler = new InvocationHandler() {
60 * &#64;Override
61 * public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
62 * if (method.getName().equals("nextInt")) {
63 * // Chosen by fair dice roll, guaranteed to be random.
64 * return 4;
65 * }
66 * Object result = ProxyBuilder.callSuper(proxy, method, args);
67 * System.out.println("Method: " + method.getName() + " args: "
68 * + Arrays.toString(args) + " result: " + result);
69 * return result;
70 * }
71 * };
72 * Random debugRandom = ProxyBuilder.forClass(Random.class)
73 * .dexCache(getInstrumentation().getTargetContext().getDir("dx", Context.MODE_PRIVATE))
74 * .handler(handler)
75 * .build();
76 * assertEquals(4, debugRandom.nextInt());
77 * debugRandom.setSeed(0);
78 * assertTrue(debugRandom.nextBoolean());
79 * </pre>
80 * <h3>Usage</h3>
81 * Call {@link #forClass(Class)} for the Class you wish to proxy. Call
82 * {@link #handler(InvocationHandler)} passing in an {@link InvocationHandler}, and then call
83 * {@link #build()}. The returned instance will be a dynamically generated subclass where all method
84 * calls will be delegated to the invocation handler, except as noted below.
85 * <p>
86 * The static method {@link #callSuper(Object, Method, Object...)} allows you to access the original
87 * super method for a given proxy. This allows the invocation handler to selectively override some
88 * methods but not others.
89 * <p>
90 * By default, the {@link #build()} method will call the no-arg constructor belonging to the class
91 * being proxied. If you wish to call a different constructor, you must provide arguments for both
92 * {@link #constructorArgTypes(Class[])} and {@link #constructorArgValues(Object[])}.
93 * <p>
94 * This process works only for classes with public and protected level of visibility.
95 * <p>
96 * You may proxy abstract classes. You may not proxy final classes.
97 * <p>
98 * Only non-private, non-final, non-static methods will be dispatched to the invocation handler.
99 * Private, static or final methods will always call through to the superclass as normal.
100 * <p>
101 * The {@link #finalize()} method on {@code Object} will not be proxied.
102 * <p>
103 * You must provide a dex cache directory via the {@link #dexCache(File)} method. You should take
104 * care not to make this a world-writable directory, so that third parties cannot inject code into
105 * your application. A suitable parameter for these output directories would be something like
106 * this:
107 * <pre>{@code
108 * getApplicationContext().getDir("dx", Context.MODE_PRIVATE);
109 * }</pre>
110 * <p>
111 * If the base class to be proxied leaks the {@code this} pointer in the constructor (bad practice),
112 * that is to say calls a non-private non-final method from the constructor, the invocation handler
113 * will not be invoked. As a simple concrete example, when proxying Random we discover that it
Paul Duffinb8a58962017-03-15 14:14:35 +0000114 * internally calls setSeed during the constructor. The proxy will not intercept this call during
Jesse Wilson579d7732012-01-03 16:12:39 -0500115 * proxy construction, but will intercept as normal afterwards. This behaviour may be subject to
116 * change in future releases.
117 * <p>
118 * This class is <b>not thread safe</b>.
119 */
120public final class ProxyBuilder<T> {
Andrew Yousef054604d2015-03-12 13:13:43 -0700121 // Version of ProxyBuilder. It should be updated if the implementation
122 // of the generated proxy class changes.
123 public static final int VERSION = 1;
124
Jesse Wilson579d7732012-01-03 16:12:39 -0500125 private static final String FIELD_NAME_HANDLER = "$__handler";
126 private static final String FIELD_NAME_METHODS = "$__methodArray";
127
Jesse Wilson2e28a222012-01-10 12:50:37 -0500128 /**
129 * A cache of all proxy classes ever generated. At the time of writing,
130 * Android's runtime doesn't support class unloading so there's little
131 * value in using weak references.
132 */
133 private static final Map<Class<?>, Class<?>> generatedProxyClasses
134 = Collections.synchronizedMap(new HashMap<Class<?>, Class<?>>());
135
Jesse Wilson579d7732012-01-03 16:12:39 -0500136 private final Class<T> baseClass;
137 private ClassLoader parentClassLoader = ProxyBuilder.class.getClassLoader();
138 private InvocationHandler handler;
139 private File dexCache;
140 private Class<?>[] constructorArgTypes = new Class[0];
141 private Object[] constructorArgValues = new Object[0];
Paul Duffinb8a58962017-03-15 14:14:35 +0000142 private Set<Class<?>> interfaces = new HashSet<>();
Philip P. Moltmann171f0972017-11-20 09:38:54 -0800143 private Method[] methods;
144 private boolean sharedClassLoader;
Jesse Wilson579d7732012-01-03 16:12:39 -0500145
146 private ProxyBuilder(Class<T> clazz) {
147 baseClass = clazz;
148 }
149
150 public static <T> ProxyBuilder<T> forClass(Class<T> clazz) {
151 return new ProxyBuilder<T>(clazz);
152 }
153
154 /**
155 * Specifies the parent ClassLoader to use when creating the proxy.
156 *
157 * <p>If null, {@code ProxyBuilder.class.getClassLoader()} will be used.
158 */
159 public ProxyBuilder<T> parentClassLoader(ClassLoader parent) {
160 parentClassLoader = parent;
161 return this;
162 }
163
164 public ProxyBuilder<T> handler(InvocationHandler handler) {
165 this.handler = handler;
166 return this;
167 }
168
Jesse Wilson73cfa442012-01-11 15:33:57 -0500169 /**
170 * Sets the directory where executable code is stored. See {@link
171 * DexMaker#generateAndLoad DexMaker.generateAndLoad()} for guidance on
172 * choosing a secure location for the dex cache.
173 */
Andrew Yousef054604d2015-03-12 13:13:43 -0700174 public ProxyBuilder<T> dexCache(File dexCacheParent) {
175 dexCache = new File(dexCacheParent, "v" + Integer.toString(VERSION));
176 dexCache.mkdir();
Jesse Wilson579d7732012-01-03 16:12:39 -0500177 return this;
178 }
Paul Duffinb8a58962017-03-15 14:14:35 +0000179
Jesse Wilson1af1da62012-01-12 19:12:46 -0500180 public ProxyBuilder<T> implementing(Class<?>... interfaces) {
181 for (Class<?> i : interfaces) {
182 if (!i.isInterface()) {
183 throw new IllegalArgumentException("Not an interface: " + i.getName());
184 }
185 this.interfaces.add(i);
186 }
187 return this;
188 }
Jesse Wilson579d7732012-01-03 16:12:39 -0500189
190 public ProxyBuilder<T> constructorArgValues(Object... constructorArgValues) {
191 this.constructorArgValues = constructorArgValues;
192 return this;
193 }
194
195 public ProxyBuilder<T> constructorArgTypes(Class<?>... constructorArgTypes) {
196 this.constructorArgTypes = constructorArgTypes;
197 return this;
198 }
199
Philip P. Moltmann171f0972017-11-20 09:38:54 -0800200 public ProxyBuilder<T> onlyMethods(Method[] methods) {
201 this.methods = methods;
202 return this;
203 }
204
205 public ProxyBuilder<T> withSharedClassLoader() {
206 this.sharedClassLoader = true;
207 return this;
208 }
209
Jesse Wilson579d7732012-01-03 16:12:39 -0500210 /**
211 * Create a new instance of the class to proxy.
212 *
213 * @throws UnsupportedOperationException if the class we are trying to create a proxy for is
214 * not accessible.
Jesse Wilson19775852012-01-03 17:06:00 -0500215 * @throws IOException if an exception occurred writing to the {@code dexCache} directory.
Jesse Wilson579d7732012-01-03 16:12:39 -0500216 * @throws UndeclaredThrowableException if the constructor for the base class to proxy throws
217 * a declared exception during construction.
218 * @throws IllegalArgumentException if the handler is null, if the constructor argument types
219 * do not match the constructor argument values, or if no such constructor exists.
220 */
Jesse Wilson19775852012-01-03 17:06:00 -0500221 public T build() throws IOException {
Jesse Wilson579d7732012-01-03 16:12:39 -0500222 check(handler != null, "handler == null");
223 check(constructorArgTypes.length == constructorArgValues.length,
224 "constructorArgValues.length != constructorArgTypes.length");
Jesse Wilson1af1da62012-01-12 19:12:46 -0500225 Class<? extends T> proxyClass = buildProxyClass();
Jesse Wilson579d7732012-01-03 16:12:39 -0500226 Constructor<? extends T> constructor;
227 try {
228 constructor = proxyClass.getConstructor(constructorArgTypes);
229 } catch (NoSuchMethodException e) {
Jesse Wilson1af1da62012-01-12 19:12:46 -0500230 throw new IllegalArgumentException("No constructor for " + baseClass.getName()
Jesse Wilson679fb662012-01-12 00:31:04 -0500231 + " with parameter types " + Arrays.toString(constructorArgTypes));
Jesse Wilson579d7732012-01-03 16:12:39 -0500232 }
233 T result;
234 try {
235 result = constructor.newInstance(constructorArgValues);
236 } catch (InstantiationException e) {
237 // Should not be thrown, generated class is not abstract.
238 throw new AssertionError(e);
239 } catch (IllegalAccessException e) {
240 // Should not be thrown, the generated constructor is accessible.
241 throw new AssertionError(e);
242 } catch (InvocationTargetException e) {
Jesse Wilson2e28a222012-01-10 12:50:37 -0500243 // Thrown when the base class constructor throws an exception.
Jesse Wilson579d7732012-01-03 16:12:39 -0500244 throw launderCause(e);
245 }
Paul Duffina13e8e92016-07-22 12:16:26 +0100246 setInvocationHandler(result, handler);
Jesse Wilson579d7732012-01-03 16:12:39 -0500247 return result;
248 }
249
Jesse Wilson1af1da62012-01-12 19:12:46 -0500250 // TODO: test coverage for this
Paul Duffina13e8e92016-07-22 12:16:26 +0100251
252 /**
253 * Generate a proxy class. Note that new instances of this class will not automatically have an
254 * an invocation handler, even if {@link #handler(InvocationHandler)} was called. The handler
255 * must be set on each instance after it is created, using
256 * {@link #setInvocationHandler(Object, InvocationHandler)}.
257 */
Jesse Wilson1af1da62012-01-12 19:12:46 -0500258 public Class<? extends T> buildProxyClass() throws IOException {
Jesse Wilson2e28a222012-01-10 12:50:37 -0500259 // try the cache to see if we've generated this one before
Paul Duffinb8a58962017-03-15 14:14:35 +0000260 // we only populate the map with matching types
261 @SuppressWarnings("unchecked")
Jesse Wilson2e28a222012-01-10 12:50:37 -0500262 Class<? extends T> proxyClass = (Class) generatedProxyClasses.get(baseClass);
Paul Duffindb20bbc2017-03-15 16:15:29 +0000263 if (proxyClass != null) {
Paul Duffindb20bbc2017-03-15 16:15:29 +0000264 boolean validClassLoader;
Philip P. Moltmann171f0972017-11-20 09:38:54 -0800265 if (sharedClassLoader) {
266 ClassLoader parent = parentClassLoader != null ? parentClassLoader : baseClass
267 .getClassLoader();
Paul Duffindb20bbc2017-03-15 16:15:29 +0000268 validClassLoader = proxyClass.getClassLoader() == parent;
269 } else {
270 validClassLoader = proxyClass.getClassLoader().getParent() == parentClassLoader;
271 }
272 if (validClassLoader && interfaces.equals(asSet(proxyClass.getInterfaces()))) {
273 return proxyClass; // cache hit!
274 }
Jesse Wilson2e28a222012-01-10 12:50:37 -0500275 }
276
277 // the cache missed; generate the class
278 DexMaker dexMaker = new DexMaker();
279 String generatedName = getMethodNameForProxyOf(baseClass);
280 TypeId<? extends T> generatedType = TypeId.get("L" + generatedName + ";");
281 TypeId<T> superType = TypeId.get(baseClass);
282 generateConstructorsAndFields(dexMaker, generatedType, superType, baseClass);
Philip P. Moltmann171f0972017-11-20 09:38:54 -0800283
284 Method[] methodsToProxy;
285 if (methods == null) {
286 methodsToProxy = getMethodsToProxyRecursive();
287 } else {
288 methodsToProxy = methods;
289 }
290
291 // Sort the results array so that they are in a deterministic fashion.
292 Arrays.sort(methodsToProxy, new Comparator<Method>() {
293 @Override
294 public int compare(Method method1, Method method2) {
295 return method1.toString().compareTo(method2.toString());
296 }
297 });
298
Jesse Wilson2e28a222012-01-10 12:50:37 -0500299 generateCodeForAllMethods(dexMaker, generatedType, methodsToProxy, superType);
Paul Duffinb8a58962017-03-15 14:14:35 +0000300 dexMaker.declare(generatedType, generatedName + ".generated", PUBLIC, superType, getInterfacesAsTypeIds());
Philip P. Moltmann171f0972017-11-20 09:38:54 -0800301 if (sharedClassLoader) {
302 dexMaker.setSharedClassLoader(baseClass.getClassLoader());
303 }
304 ClassLoader classLoader = dexMaker.generateAndLoad(parentClassLoader, dexCache);
Jesse Wilson2e28a222012-01-10 12:50:37 -0500305 try {
306 proxyClass = loadClass(classLoader, generatedName);
307 } catch (IllegalAccessError e) {
308 // Thrown when the base class is not accessible.
Jesse Wilson1af1da62012-01-12 19:12:46 -0500309 throw new UnsupportedOperationException(
310 "cannot proxy inaccessible class " + baseClass, e);
Jesse Wilson2e28a222012-01-10 12:50:37 -0500311 } catch (ClassNotFoundException e) {
312 // Should not be thrown, we're sure to have generated this class.
313 throw new AssertionError(e);
314 }
315 setMethodsStaticField(proxyClass, methodsToProxy);
316 generatedProxyClasses.put(baseClass, proxyClass);
317 return proxyClass;
318 }
319
Jesse Wilson579d7732012-01-03 16:12:39 -0500320 // The type cast is safe: the generated type will extend the base class type.
321 @SuppressWarnings("unchecked")
322 private Class<? extends T> loadClass(ClassLoader classLoader, String generatedName)
323 throws ClassNotFoundException {
324 return (Class<? extends T>) classLoader.loadClass(generatedName);
325 }
326
327 private static RuntimeException launderCause(InvocationTargetException e) {
328 Throwable cause = e.getCause();
329 // Errors should be thrown as they are.
330 if (cause instanceof Error) {
331 throw (Error) cause;
332 }
333 // RuntimeException can be thrown as-is.
334 if (cause instanceof RuntimeException) {
335 throw (RuntimeException) cause;
336 }
337 // Declared exceptions will have to be wrapped.
338 throw new UndeclaredThrowableException(cause);
339 }
340
Jesse Wilson579d7732012-01-03 16:12:39 -0500341 private static void setMethodsStaticField(Class<?> proxyClass, Method[] methodsToProxy) {
342 try {
343 Field methodArrayField = proxyClass.getDeclaredField(FIELD_NAME_METHODS);
344 methodArrayField.setAccessible(true);
345 methodArrayField.set(null, methodsToProxy);
346 } catch (NoSuchFieldException e) {
347 // Should not be thrown, generated proxy class has been generated with this field.
348 throw new AssertionError(e);
349 } catch (IllegalAccessException e) {
350 // Should not be thrown, we just set the field to accessible.
351 throw new AssertionError(e);
352 }
353 }
354
355 /**
356 * Returns the proxy's {@link InvocationHandler}.
357 *
358 * @throws IllegalArgumentException if the object supplied is not a proxy created by this class.
359 */
360 public static InvocationHandler getInvocationHandler(Object instance) {
361 try {
362 Field field = instance.getClass().getDeclaredField(FIELD_NAME_HANDLER);
363 field.setAccessible(true);
364 return (InvocationHandler) field.get(instance);
365 } catch (NoSuchFieldException e) {
366 throw new IllegalArgumentException("Not a valid proxy instance", e);
367 } catch (IllegalAccessException e) {
368 // Should not be thrown, we just set the field to accessible.
369 throw new AssertionError(e);
370 }
371 }
372
Paul Duffina13e8e92016-07-22 12:16:26 +0100373 /**
374 * Sets the proxy's {@link InvocationHandler}.
375 * <p>
376 * If you create a proxy with {@link #build()}, the proxy will already have a handler set,
377 * provided that you configured one with {@link #handler(InvocationHandler)}.
378 * <p>
379 * If you generate a proxy class with {@link #buildProxyClass()}, instances of the proxy class
380 * will not automatically have a handler set, and it is necessary to use this method with each
381 * instance.
382 *
383 * @throws IllegalArgumentException if the object supplied is not a proxy created by this class.
384 */
385 public static void setInvocationHandler(Object instance, InvocationHandler handler) {
386 try {
387 Field handlerField = instance.getClass().getDeclaredField(FIELD_NAME_HANDLER);
388 handlerField.setAccessible(true);
389 handlerField.set(instance, handler);
390 } catch (NoSuchFieldException e) {
391 throw new IllegalArgumentException("Not a valid proxy instance", e);
392 } catch (IllegalAccessException e) {
393 // Should not be thrown, we just set the field to accessible.
394 throw new AssertionError(e);
395 }
396 }
397
Jesse Wilson1af1da62012-01-12 19:12:46 -0500398 // TODO: test coverage for isProxyClass
Paul Duffinb8a58962017-03-15 14:14:35 +0000399
Jesse Wilson73cfa442012-01-11 15:33:57 -0500400 /**
401 * Returns true if {@code c} is a proxy class created by this builder.
402 */
403 public static boolean isProxyClass(Class<?> c) {
404 // TODO: use a marker interface instead?
405 try {
406 c.getDeclaredField(FIELD_NAME_HANDLER);
407 return true;
408 } catch (NoSuchFieldException e) {
409 return false;
410 }
411 }
412
Jesse Wilsonab220f02012-01-05 15:18:59 -0500413 private static <T, G extends T> void generateCodeForAllMethods(DexMaker dexMaker,
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500414 TypeId<G> generatedType, Method[] methodsToProxy, TypeId<T> superclassType) {
415 TypeId<InvocationHandler> handlerType = TypeId.get(InvocationHandler.class);
416 TypeId<Method[]> methodArrayType = TypeId.get(Method[].class);
Jesse Wilson579d7732012-01-03 16:12:39 -0500417 FieldId<G, InvocationHandler> handlerField =
418 generatedType.getField(handlerType, FIELD_NAME_HANDLER);
419 FieldId<G, Method[]> allMethods =
420 generatedType.getField(methodArrayType, FIELD_NAME_METHODS);
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500421 TypeId<Method> methodType = TypeId.get(Method.class);
422 TypeId<Object[]> objectArrayType = TypeId.get(Object[].class);
423 MethodId<InvocationHandler, Object> methodInvoke = handlerType.getMethod(TypeId.OBJECT,
424 "invoke", TypeId.OBJECT, methodType, objectArrayType);
Jesse Wilson579d7732012-01-03 16:12:39 -0500425 for (int m = 0; m < methodsToProxy.length; ++m) {
426 /*
427 * If the 5th method on the superclass Example that can be overridden were to look like
428 * this:
429 *
430 * public int doSomething(Bar param0, int param1) {
431 * ...
432 * }
433 *
434 * Then the following code will generate a method on the proxy that looks something
435 * like this:
436 *
437 * public int doSomething(Bar param0, int param1) {
438 * int methodIndex = 4;
439 * Method[] allMethods = Example_Proxy.$__methodArray;
440 * Method thisMethod = allMethods[methodIndex];
441 * int argsLength = 2;
442 * Object[] args = new Object[argsLength];
443 * InvocationHandler localHandler = this.$__handler;
444 * // for-loop begins
445 * int p = 0;
446 * Bar parameter0 = param0;
447 * args[p] = parameter0;
448 * p = 1;
449 * int parameter1 = param1;
450 * Integer boxed1 = Integer.valueOf(parameter1);
451 * args[p] = boxed1;
452 * // for-loop ends
453 * Object result = localHandler.invoke(this, thisMethod, args);
454 * Integer castResult = (Integer) result;
455 * int unboxedResult = castResult.intValue();
456 * return unboxedResult;
457 * }
458 *
459 * Or, in more idiomatic Java:
460 *
461 * public int doSomething(Bar param0, int param1) {
462 * if ($__handler == null) {
463 * return super.doSomething(param0, param1);
464 * }
465 * return __handler.invoke(this, __methodArray[4],
466 * new Object[] { param0, Integer.valueOf(param1) });
467 * }
468 */
469 Method method = methodsToProxy[m];
470 String name = method.getName();
471 Class<?>[] argClasses = method.getParameterTypes();
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500472 TypeId<?>[] argTypes = new TypeId<?>[argClasses.length];
Jesse Wilson579d7732012-01-03 16:12:39 -0500473 for (int i = 0; i < argTypes.length; ++i) {
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500474 argTypes[i] = TypeId.get(argClasses[i]);
Jesse Wilson579d7732012-01-03 16:12:39 -0500475 }
476 Class<?> returnType = method.getReturnType();
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500477 TypeId<?> resultType = TypeId.get(returnType);
Jesse Wilson579d7732012-01-03 16:12:39 -0500478 MethodId<T, ?> superMethod = superclassType.getMethod(resultType, name, argTypes);
479 MethodId<?, ?> methodId = generatedType.getMethod(resultType, name, argTypes);
Jesse Wilsonab220f02012-01-05 15:18:59 -0500480 Code code = dexMaker.declare(methodId, PUBLIC);
Jesse Wilson579d7732012-01-03 16:12:39 -0500481 Local<G> localThis = code.getThis(generatedType);
482 Local<InvocationHandler> localHandler = code.newLocal(handlerType);
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500483 Local<Object> invokeResult = code.newLocal(TypeId.OBJECT);
484 Local<Integer> intValue = code.newLocal(TypeId.INT);
Jesse Wilson579d7732012-01-03 16:12:39 -0500485 Local<Object[]> args = code.newLocal(objectArrayType);
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500486 Local<Integer> argsLength = code.newLocal(TypeId.INT);
487 Local<Object> temp = code.newLocal(TypeId.OBJECT);
Jesse Wilson579d7732012-01-03 16:12:39 -0500488 Local<?> resultHolder = code.newLocal(resultType);
489 Local<Method[]> methodArray = code.newLocal(methodArrayType);
490 Local<Method> thisMethod = code.newLocal(methodType);
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500491 Local<Integer> methodIndex = code.newLocal(TypeId.INT);
Jesse Wilson579d7732012-01-03 16:12:39 -0500492 Class<?> aBoxedClass = PRIMITIVE_TO_BOXED.get(returnType);
493 Local<?> aBoxedResult = null;
494 if (aBoxedClass != null) {
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500495 aBoxedResult = code.newLocal(TypeId.get(aBoxedClass));
Jesse Wilson579d7732012-01-03 16:12:39 -0500496 }
497 Local<?>[] superArgs2 = new Local<?>[argClasses.length];
498 Local<?> superResult2 = code.newLocal(resultType);
499 Local<InvocationHandler> nullHandler = code.newLocal(handlerType);
500
501 code.loadConstant(methodIndex, m);
502 code.sget(allMethods, methodArray);
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500503 code.aget(thisMethod, methodArray, methodIndex);
Jesse Wilson579d7732012-01-03 16:12:39 -0500504 code.loadConstant(argsLength, argTypes.length);
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500505 code.newArray(args, argsLength);
506 code.iget(handlerField, localHandler, localThis);
Jesse Wilson579d7732012-01-03 16:12:39 -0500507
508 // if (proxy == null)
509 code.loadConstant(nullHandler, null);
Jesse Wilson23abc2f2012-01-06 14:58:00 -0500510 Label handlerNullCase = new Label();
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500511 code.compare(Comparison.EQ, handlerNullCase, nullHandler, localHandler);
Jesse Wilson579d7732012-01-03 16:12:39 -0500512
513 // This code is what we execute when we have a valid proxy: delegate to invocation
514 // handler.
515 for (int p = 0; p < argTypes.length; ++p) {
516 code.loadConstant(intValue, p);
517 Local<?> parameter = code.getParameter(p, argTypes[p]);
Jesse Wilson19775852012-01-03 17:06:00 -0500518 Local<?> unboxedIfNecessary = boxIfRequired(code, parameter, temp);
Jesse Wilson579d7732012-01-03 16:12:39 -0500519 code.aput(args, intValue, unboxedIfNecessary);
520 }
521 code.invokeInterface(methodInvoke, invokeResult, localHandler,
522 localThis, thisMethod, args);
523 generateCodeForReturnStatement(code, returnType, invokeResult, resultHolder,
524 aBoxedResult);
525
526 // This code is executed if proxy is null: call the original super method.
527 // This is required to handle the case of construction of an object which leaks the
528 // "this" pointer.
529 code.mark(handlerNullCase);
530 for (int i = 0; i < superArgs2.length; ++i) {
531 superArgs2[i] = code.getParameter(i, argTypes[i]);
532 }
533 if (void.class.equals(returnType)) {
534 code.invokeSuper(superMethod, null, localThis, superArgs2);
535 code.returnVoid();
536 } else {
537 invokeSuper(superMethod, code, localThis, superArgs2, superResult2);
538 code.returnValue(superResult2);
539 }
540
541 /*
542 * And to allow calling the original super method, the following is also generated:
543 *
Jesse Wilson1af1da62012-01-12 19:12:46 -0500544 * public String super$doSomething$java_lang_String(Bar param0, int param1) {
Jesse Wilson579d7732012-01-03 16:12:39 -0500545 * int result = super.doSomething(param0, param1);
546 * return result;
547 * }
548 */
Jesse Wilson1af1da62012-01-12 19:12:46 -0500549 // TODO: don't include a super_ method if the target is abstract!
Jesse Wilson579d7732012-01-03 16:12:39 -0500550 MethodId<G, ?> callsSuperMethod = generatedType.getMethod(
Jesse Wilson1af1da62012-01-12 19:12:46 -0500551 resultType, superMethodName(method), argTypes);
Jesse Wilsonab220f02012-01-05 15:18:59 -0500552 Code superCode = dexMaker.declare(callsSuperMethod, PUBLIC);
Jesse Wilson579d7732012-01-03 16:12:39 -0500553 Local<G> superThis = superCode.getThis(generatedType);
554 Local<?>[] superArgs = new Local<?>[argClasses.length];
555 for (int i = 0; i < superArgs.length; ++i) {
556 superArgs[i] = superCode.getParameter(i, argTypes[i]);
557 }
558 if (void.class.equals(returnType)) {
559 superCode.invokeSuper(superMethod, null, superThis, superArgs);
560 superCode.returnVoid();
561 } else {
562 Local<?> superResult = superCode.newLocal(resultType);
563 invokeSuper(superMethod, superCode, superThis, superArgs, superResult);
564 superCode.returnValue(superResult);
565 }
566 }
567 }
568
Jesse Wilson19775852012-01-03 17:06:00 -0500569 @SuppressWarnings({"unchecked", "rawtypes"})
570 private static void invokeSuper(MethodId superMethod, Code superCode,
Jesse Wilson579d7732012-01-03 16:12:39 -0500571 Local superThis, Local[] superArgs, Local superResult) {
572 superCode.invokeSuper(superMethod, superResult, superThis, superArgs);
573 }
574
Jesse Wilson19775852012-01-03 17:06:00 -0500575 private static Local<?> boxIfRequired(Code code, Local<?> parameter, Local<Object> temp) {
Jesse Wilson579d7732012-01-03 16:12:39 -0500576 MethodId<?, ?> unboxMethod = PRIMITIVE_TYPE_TO_UNBOX_METHOD.get(parameter.getType());
577 if (unboxMethod == null) {
578 return parameter;
579 }
580 code.invokeStatic(unboxMethod, temp, parameter);
581 return temp;
582 }
583
Jesse Wilson1af1da62012-01-12 19:12:46 -0500584 public static Object callSuper(Object proxy, Method method, Object... args) throws Throwable {
585 try {
586 return proxy.getClass()
587 .getMethod(superMethodName(method), method.getParameterTypes())
588 .invoke(proxy, args);
589 } catch (InvocationTargetException e) {
590 throw e.getCause();
591 }
592 }
593
594 /**
595 * The super method must include the return type, otherwise its ambiguous
596 * for methods with covariant return types.
597 */
598 private static String superMethodName(Method method) {
599 String returnType = method.getReturnType().getName();
600 return "super$" + method.getName() + "$"
601 + returnType.replace('.', '_').replace('[', '_').replace(';', '_');
Jesse Wilson579d7732012-01-03 16:12:39 -0500602 }
603
604 private static void check(boolean condition, String message) {
605 if (!condition) {
606 throw new IllegalArgumentException(message);
607 }
608 }
609
Jesse Wilsonab220f02012-01-05 15:18:59 -0500610 private static <T, G extends T> void generateConstructorsAndFields(DexMaker dexMaker,
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500611 TypeId<G> generatedType, TypeId<T> superType, Class<T> superClass) {
612 TypeId<InvocationHandler> handlerType = TypeId.get(InvocationHandler.class);
613 TypeId<Method[]> methodArrayType = TypeId.get(Method[].class);
Jesse Wilson579d7732012-01-03 16:12:39 -0500614 FieldId<G, InvocationHandler> handlerField = generatedType.getField(
615 handlerType, FIELD_NAME_HANDLER);
Jesse Wilsonab220f02012-01-05 15:18:59 -0500616 dexMaker.declare(handlerField, PRIVATE, null);
Jesse Wilson579d7732012-01-03 16:12:39 -0500617 FieldId<G, Method[]> allMethods = generatedType.getField(
618 methodArrayType, FIELD_NAME_METHODS);
Jesse Wilsonab220f02012-01-05 15:18:59 -0500619 dexMaker.declare(allMethods, PRIVATE | STATIC, null);
Jesse Wilson579d7732012-01-03 16:12:39 -0500620 for (Constructor<T> constructor : getConstructorsToOverwrite(superClass)) {
621 if (constructor.getModifiers() == Modifier.FINAL) {
622 continue;
623 }
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500624 TypeId<?>[] types = classArrayToTypeArray(constructor.getParameterTypes());
Jesse Wilson579d7732012-01-03 16:12:39 -0500625 MethodId<?, ?> method = generatedType.getConstructor(types);
Jesse Wilsonc0271e92012-01-10 11:17:32 -0500626 Code constructorCode = dexMaker.declare(method, PUBLIC);
Jesse Wilson579d7732012-01-03 16:12:39 -0500627 Local<G> thisRef = constructorCode.getThis(generatedType);
628 Local<?>[] params = new Local[types.length];
629 for (int i = 0; i < params.length; ++i) {
630 params[i] = constructorCode.getParameter(i, types[i]);
631 }
632 MethodId<T, ?> superConstructor = superType.getConstructor(types);
633 constructorCode.invokeDirect(superConstructor, null, thisRef, params);
634 constructorCode.returnVoid();
635 }
636 }
637
638 // The type parameter on Constructor is the class in which the constructor is declared.
639 // The getDeclaredConstructors() method gets constructors declared only in the given class,
640 // hence this cast is safe.
641 @SuppressWarnings("unchecked")
642 private static <T> Constructor<T>[] getConstructorsToOverwrite(Class<T> clazz) {
643 return (Constructor<T>[]) clazz.getDeclaredConstructors();
644 }
645
Jesse Wilson1af1da62012-01-12 19:12:46 -0500646 private TypeId<?>[] getInterfacesAsTypeIds() {
647 TypeId<?>[] result = new TypeId<?>[interfaces.size()];
648 int i = 0;
649 for (Class<?> implemented : interfaces) {
650 result[i++] = TypeId.get(implemented);
Jesse Wilson579d7732012-01-03 16:12:39 -0500651 }
Jesse Wilson1af1da62012-01-12 19:12:46 -0500652 return result;
653 }
654
655 /**
656 * Gets all {@link Method} objects we can proxy in the hierarchy of the
657 * supplied class.
658 */
659 private Method[] getMethodsToProxyRecursive() {
Paul Duffinb8a58962017-03-15 14:14:35 +0000660 Set<MethodSetEntry> methodsToProxy = new HashSet<>();
661 Set<MethodSetEntry> seenFinalMethods = new HashSet<>();
Paul Duffin309cc662016-08-05 13:24:03 +0100662 // Traverse the class hierarchy to ensure that all concrete methods (which could be marked
663 // as final) are visited before any abstract methods from interfaces.
Jesse Wilson1af1da62012-01-12 19:12:46 -0500664 for (Class<?> c = baseClass; c != null; c = c.getSuperclass()) {
Mark Brophy95689a72012-10-12 18:40:08 +0100665 getMethodsToProxy(methodsToProxy, seenFinalMethods, c);
Jesse Wilson1af1da62012-01-12 19:12:46 -0500666 }
Paul Duffin309cc662016-08-05 13:24:03 +0100667 // Now traverse the interface hierarchy, starting with the ones implemented by the class,
668 // followed by any extra interfaces.
Paul Duffincb6e5222016-08-05 12:48:49 +0100669 for (Class<?> c = baseClass; c != null; c = c.getSuperclass()) {
670 for (Class<?> i : c.getInterfaces()) {
671 getMethodsToProxy(methodsToProxy, seenFinalMethods, i);
672 }
673 }
Jesse Wilson1af1da62012-01-12 19:12:46 -0500674 for (Class<?> c : interfaces) {
Mark Brophy95689a72012-10-12 18:40:08 +0100675 getMethodsToProxy(methodsToProxy, seenFinalMethods, c);
Jesse Wilson1af1da62012-01-12 19:12:46 -0500676 }
677
Jesse Wilson579d7732012-01-03 16:12:39 -0500678 Method[] results = new Method[methodsToProxy.size()];
679 int i = 0;
680 for (MethodSetEntry entry : methodsToProxy) {
681 results[i++] = entry.originalMethod;
682 }
Andrew Yousef8d53a062015-01-23 13:59:10 -0800683
Jesse Wilson579d7732012-01-03 16:12:39 -0500684 return results;
685 }
686
Mark Brophy95689a72012-10-12 18:40:08 +0100687 private void getMethodsToProxy(Set<MethodSetEntry> sink, Set<MethodSetEntry> seenFinalMethods,
688 Class<?> c) {
Jesse Wilson1af1da62012-01-12 19:12:46 -0500689 for (Method method : c.getDeclaredMethods()) {
690 if ((method.getModifiers() & Modifier.FINAL) != 0) {
Mark Brophy95689a72012-10-12 18:40:08 +0100691 // Skip final methods, we can't override them. We
692 // also need to remember them, in case the same
693 // method exists in a parent class.
Samuel Tanab38abd2016-02-24 15:54:57 -0800694 MethodSetEntry entry = new MethodSetEntry(method);
695 seenFinalMethods.add(entry);
696 // We may have seen this method already, from an interface
697 // implemented by a child class. We need to remove it here.
698 sink.remove(entry);
Jesse Wilson1af1da62012-01-12 19:12:46 -0500699 continue;
700 }
701 if ((method.getModifiers() & STATIC) != 0) {
702 // Skip static methods, overriding them has no effect.
703 continue;
704 }
Paul Duffina13e8e92016-07-22 12:16:26 +0100705 if (!Modifier.isPublic(method.getModifiers())
Philip P. Moltmann171f0972017-11-20 09:38:54 -0800706 && !Modifier.isProtected(method.getModifiers())
707 && (!sharedClassLoader || Modifier.isPrivate(method.getModifiers()))) {
Paul Duffina13e8e92016-07-22 12:16:26 +0100708 // Skip private methods, since they are invoked through direct
709 // invocation (as opposed to virtual). Therefore, it would not
710 // be possible to intercept any private method defined inside
711 // the proxy class except through reflection.
712
Paul Duffindb20bbc2017-03-15 16:15:29 +0000713 // Skip package-private methods as well (for non-shared class
714 // loaders). The proxy class does
Paul Duffina13e8e92016-07-22 12:16:26 +0100715 // not actually inherit package-private methods from the parent
716 // class because it is not a member of the parent's package.
717 // This is even true if the two classes have the same package
718 // name, as they use different class loaders.
719 continue;
720 }
Jesse Wilson1af1da62012-01-12 19:12:46 -0500721 if (method.getName().equals("finalize") && method.getParameterTypes().length == 0) {
722 // Skip finalize method, it's likely important that it execute as normal.
723 continue;
724 }
Mark Brophy95689a72012-10-12 18:40:08 +0100725 MethodSetEntry entry = new MethodSetEntry(method);
726 if (seenFinalMethods.contains(entry)) {
727 // This method is final in a child class.
728 // We can't override it.
729 continue;
730 }
731 sink.add(entry);
Jesse Wilson1af1da62012-01-12 19:12:46 -0500732 }
Paul Duffin309cc662016-08-05 13:24:03 +0100733
734 // Only visit the interfaces of this class if it is itself an interface. That prevents
735 // visiting interfaces of a class before its super classes.
736 if (c.isInterface()) {
737 for (Class<?> i : c.getInterfaces()) {
738 getMethodsToProxy(sink, seenFinalMethods, i);
739 }
740 }
Jesse Wilson1af1da62012-01-12 19:12:46 -0500741 }
742
Jesse Wilson579d7732012-01-03 16:12:39 -0500743 private static <T> String getMethodNameForProxyOf(Class<T> clazz) {
Paul Duffindb20bbc2017-03-15 16:15:29 +0000744 return clazz.getName().replace(".", "/") + "_Proxy";
Jesse Wilson579d7732012-01-03 16:12:39 -0500745 }
746
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500747 private static TypeId<?>[] classArrayToTypeArray(Class<?>[] input) {
748 TypeId<?>[] result = new TypeId[input.length];
Jesse Wilson579d7732012-01-03 16:12:39 -0500749 for (int i = 0; i < input.length; ++i) {
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500750 result[i] = TypeId.get(input[i]);
Jesse Wilson579d7732012-01-03 16:12:39 -0500751 }
752 return result;
753 }
754
755 /**
756 * Calculates the correct return statement code for a method.
757 * <p>
758 * A void method will not return anything. A method that returns a primitive will need to
759 * unbox the boxed result. Otherwise we will cast the result.
760 */
761 // This one is tricky to fix, I gave up.
762 @SuppressWarnings({ "rawtypes", "unchecked" })
763 private static void generateCodeForReturnStatement(Code code, Class methodReturnType,
764 Local localForResultOfInvoke, Local localOfMethodReturnType, Local aBoxedResult) {
765 if (PRIMITIVE_TO_UNBOX_METHOD.containsKey(methodReturnType)) {
Jesse Wilson97b0be62012-01-07 19:21:41 -0500766 code.cast(aBoxedResult, localForResultOfInvoke);
Jesse Wilson579d7732012-01-03 16:12:39 -0500767 MethodId unboxingMethodFor = getUnboxMethodForPrimitive(methodReturnType);
768 code.invokeVirtual(unboxingMethodFor, localOfMethodReturnType, aBoxedResult);
769 code.returnValue(localOfMethodReturnType);
770 } else if (void.class.equals(methodReturnType)) {
771 code.returnVoid();
772 } else {
Jesse Wilson97b0be62012-01-07 19:21:41 -0500773 code.cast(localOfMethodReturnType, localForResultOfInvoke);
Jesse Wilson579d7732012-01-03 16:12:39 -0500774 code.returnValue(localOfMethodReturnType);
775 }
776 }
777
Jesse Wilson1af1da62012-01-12 19:12:46 -0500778 private static <T> Set<T> asSet(T... array) {
Paul Duffinb8a58962017-03-15 14:14:35 +0000779 return new CopyOnWriteArraySet<>(Arrays.asList(array));
Jesse Wilson1af1da62012-01-12 19:12:46 -0500780 }
781
Jesse Wilson579d7732012-01-03 16:12:39 -0500782 private static MethodId<?, ?> getUnboxMethodForPrimitive(Class<?> methodReturnType) {
783 return PRIMITIVE_TO_UNBOX_METHOD.get(methodReturnType);
784 }
785
786 private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_BOXED;
787 static {
Paul Duffinb8a58962017-03-15 14:14:35 +0000788 PRIMITIVE_TO_BOXED = new HashMap<>();
Jesse Wilson579d7732012-01-03 16:12:39 -0500789 PRIMITIVE_TO_BOXED.put(boolean.class, Boolean.class);
790 PRIMITIVE_TO_BOXED.put(int.class, Integer.class);
791 PRIMITIVE_TO_BOXED.put(byte.class, Byte.class);
792 PRIMITIVE_TO_BOXED.put(long.class, Long.class);
793 PRIMITIVE_TO_BOXED.put(short.class, Short.class);
794 PRIMITIVE_TO_BOXED.put(float.class, Float.class);
795 PRIMITIVE_TO_BOXED.put(double.class, Double.class);
796 PRIMITIVE_TO_BOXED.put(char.class, Character.class);
797 }
798
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500799 private static final Map<TypeId<?>, MethodId<?, ?>> PRIMITIVE_TYPE_TO_UNBOX_METHOD;
Jesse Wilson579d7732012-01-03 16:12:39 -0500800 static {
Paul Duffinb8a58962017-03-15 14:14:35 +0000801 PRIMITIVE_TYPE_TO_UNBOX_METHOD = new HashMap<>();
Jesse Wilson579d7732012-01-03 16:12:39 -0500802 for (Map.Entry<Class<?>, Class<?>> entry : PRIMITIVE_TO_BOXED.entrySet()) {
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500803 TypeId<?> primitiveType = TypeId.get(entry.getKey());
804 TypeId<?> boxedType = TypeId.get(entry.getValue());
Jesse Wilson579d7732012-01-03 16:12:39 -0500805 MethodId<?, ?> valueOfMethod = boxedType.getMethod(boxedType, "valueOf", primitiveType);
806 PRIMITIVE_TYPE_TO_UNBOX_METHOD.put(primitiveType, valueOfMethod);
807 }
808 }
809
810 /**
811 * Map from primitive type to method used to unbox a boxed version of the primitive.
812 * <p>
813 * This is required for methods whose return type is primitive, since the
814 * {@link InvocationHandler} will return us a boxed result, and we'll need to convert it back to
815 * primitive value.
816 */
817 private static final Map<Class<?>, MethodId<?, ?>> PRIMITIVE_TO_UNBOX_METHOD;
818 static {
Paul Duffinb8a58962017-03-15 14:14:35 +0000819 Map<Class<?>, MethodId<?, ?>> map = new HashMap<>();
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500820 map.put(boolean.class, TypeId.get(Boolean.class).getMethod(TypeId.BOOLEAN, "booleanValue"));
821 map.put(int.class, TypeId.get(Integer.class).getMethod(TypeId.INT, "intValue"));
822 map.put(byte.class, TypeId.get(Byte.class).getMethod(TypeId.BYTE, "byteValue"));
823 map.put(long.class, TypeId.get(Long.class).getMethod(TypeId.LONG, "longValue"));
824 map.put(short.class, TypeId.get(Short.class).getMethod(TypeId.SHORT, "shortValue"));
825 map.put(float.class, TypeId.get(Float.class).getMethod(TypeId.FLOAT, "floatValue"));
826 map.put(double.class, TypeId.get(Double.class).getMethod(TypeId.DOUBLE, "doubleValue"));
827 map.put(char.class, TypeId.get(Character.class).getMethod(TypeId.CHAR, "charValue"));
Jesse Wilson579d7732012-01-03 16:12:39 -0500828 PRIMITIVE_TO_UNBOX_METHOD = map;
829 }
830
831 /**
832 * Wrapper class to let us disambiguate {@link Method} objects.
833 * <p>
834 * The purpose of this class is to override the {@link #equals(Object)} and {@link #hashCode()}
835 * methods so we can use a {@link Set} to remove duplicate methods that are overrides of one
836 * another. For these purposes, we consider two methods to be equal if they have the same
837 * name, return type, and parameter types.
838 */
Philip P. Moltmann171f0972017-11-20 09:38:54 -0800839 public static class MethodSetEntry {
840 public final String name;
841 public final Class<?>[] paramTypes;
842 public final Class<?> returnType;
843 public final Method originalMethod;
Jesse Wilson579d7732012-01-03 16:12:39 -0500844
845 public MethodSetEntry(Method method) {
846 originalMethod = method;
847 name = method.getName();
848 paramTypes = method.getParameterTypes();
849 returnType = method.getReturnType();
850 }
851
852 @Override
853 public boolean equals(Object o) {
854 if (o instanceof MethodSetEntry) {
855 MethodSetEntry other = (MethodSetEntry) o;
856 return name.equals(other.name)
857 && returnType.equals(other.returnType)
858 && Arrays.equals(paramTypes, other.paramTypes);
859 }
860 return false;
861 }
862
863 @Override
864 public int hashCode() {
865 int result = 17;
866 result += 31 * result + name.hashCode();
867 result += 31 * result + returnType.hashCode();
868 result += 31 * result + Arrays.hashCode(paramTypes);
869 return result;
870 }
871 }
872}