1 /* 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 17 package com.android.dx.stock; 18 19 import com.android.dx.Code; 20 import com.android.dx.Comparison; 21 import com.android.dx.DexMaker; 22 import com.android.dx.FieldId; 23 import com.android.dx.Label; 24 import com.android.dx.Local; 25 import com.android.dx.MethodId; 26 import com.android.dx.TypeId; 27 28 import java.io.File; 29 import java.io.IOException; 30 import java.lang.reflect.Constructor; 31 import java.lang.reflect.Field; 32 import java.lang.reflect.InvocationHandler; 33 import java.lang.reflect.InvocationTargetException; 34 import java.lang.reflect.Method; 35 import java.lang.reflect.Modifier; 36 import java.lang.reflect.UndeclaredThrowableException; 37 import java.util.Arrays; 38 import java.util.Collections; 39 import java.util.Comparator; 40 import java.util.HashMap; 41 import java.util.HashSet; 42 import java.util.Map; 43 import java.util.Set; 44 import java.util.concurrent.CopyOnWriteArraySet; 45 46 import static java.lang.reflect.Modifier.PRIVATE; 47 import static java.lang.reflect.Modifier.PUBLIC; 48 import static java.lang.reflect.Modifier.STATIC; 49 50 /** 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 * @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 114 * internally calls setSeed during the constructor. The proxy will not intercept this call during 115 * 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 */ 120 public final class ProxyBuilder<T> { 121 // 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 125 private static final String FIELD_NAME_HANDLER = "$__handler"; 126 private static final String FIELD_NAME_METHODS = "$__methodArray"; 127 128 /** 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 136 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]; 142 private Set<Class<?>> interfaces = new HashSet<>(); 143 ProxyBuilder(Class<T> clazz)144 private ProxyBuilder(Class<T> clazz) { 145 baseClass = clazz; 146 } 147 forClass(Class<T> clazz)148 public static <T> ProxyBuilder<T> forClass(Class<T> clazz) { 149 return new ProxyBuilder<T>(clazz); 150 } 151 152 /** 153 * Specifies the parent ClassLoader to use when creating the proxy. 154 * 155 * <p>If null, {@code ProxyBuilder.class.getClassLoader()} will be used. 156 */ parentClassLoader(ClassLoader parent)157 public ProxyBuilder<T> parentClassLoader(ClassLoader parent) { 158 parentClassLoader = parent; 159 return this; 160 } 161 handler(InvocationHandler handler)162 public ProxyBuilder<T> handler(InvocationHandler handler) { 163 this.handler = handler; 164 return this; 165 } 166 167 /** 168 * Sets the directory where executable code is stored. See {@link 169 * DexMaker#generateAndLoad DexMaker.generateAndLoad()} for guidance on 170 * choosing a secure location for the dex cache. 171 */ dexCache(File dexCacheParent)172 public ProxyBuilder<T> dexCache(File dexCacheParent) { 173 dexCache = new File(dexCacheParent, "v" + Integer.toString(VERSION)); 174 dexCache.mkdir(); 175 return this; 176 } 177 implementing(Class<?>.... interfaces)178 public ProxyBuilder<T> implementing(Class<?>... interfaces) { 179 for (Class<?> i : interfaces) { 180 if (!i.isInterface()) { 181 throw new IllegalArgumentException("Not an interface: " + i.getName()); 182 } 183 this.interfaces.add(i); 184 } 185 return this; 186 } 187 constructorArgValues(Object... constructorArgValues)188 public ProxyBuilder<T> constructorArgValues(Object... constructorArgValues) { 189 this.constructorArgValues = constructorArgValues; 190 return this; 191 } 192 constructorArgTypes(Class<?>.... constructorArgTypes)193 public ProxyBuilder<T> constructorArgTypes(Class<?>... constructorArgTypes) { 194 this.constructorArgTypes = constructorArgTypes; 195 return this; 196 } 197 198 /** 199 * Create a new instance of the class to proxy. 200 * 201 * @throws UnsupportedOperationException if the class we are trying to create a proxy for is 202 * not accessible. 203 * @throws IOException if an exception occurred writing to the {@code dexCache} directory. 204 * @throws UndeclaredThrowableException if the constructor for the base class to proxy throws 205 * a declared exception during construction. 206 * @throws IllegalArgumentException if the handler is null, if the constructor argument types 207 * do not match the constructor argument values, or if no such constructor exists. 208 */ build()209 public T build() throws IOException { 210 check(handler != null, "handler == null"); 211 check(constructorArgTypes.length == constructorArgValues.length, 212 "constructorArgValues.length != constructorArgTypes.length"); 213 Class<? extends T> proxyClass = buildProxyClass(); 214 Constructor<? extends T> constructor; 215 try { 216 constructor = proxyClass.getConstructor(constructorArgTypes); 217 } catch (NoSuchMethodException e) { 218 throw new IllegalArgumentException("No constructor for " + baseClass.getName() 219 + " with parameter types " + Arrays.toString(constructorArgTypes)); 220 } 221 T result; 222 try { 223 result = constructor.newInstance(constructorArgValues); 224 } catch (InstantiationException e) { 225 // Should not be thrown, generated class is not abstract. 226 throw new AssertionError(e); 227 } catch (IllegalAccessException e) { 228 // Should not be thrown, the generated constructor is accessible. 229 throw new AssertionError(e); 230 } catch (InvocationTargetException e) { 231 // Thrown when the base class constructor throws an exception. 232 throw launderCause(e); 233 } 234 setInvocationHandler(result, handler); 235 return result; 236 } 237 238 // TODO: test coverage for this 239 240 /** 241 * Generate a proxy class. Note that new instances of this class will not automatically have an 242 * an invocation handler, even if {@link #handler(InvocationHandler)} was called. The handler 243 * must be set on each instance after it is created, using 244 * {@link #setInvocationHandler(Object, InvocationHandler)}. 245 */ buildProxyClass()246 public Class<? extends T> buildProxyClass() throws IOException { 247 // try the cache to see if we've generated this one before 248 // we only populate the map with matching types 249 @SuppressWarnings("unchecked") 250 Class<? extends T> proxyClass = (Class) generatedProxyClasses.get(baseClass); 251 if (proxyClass != null) { 252 boolean shareClassLoader = Boolean.parseBoolean(System.getProperty( 253 "dexmaker.share_classloader", "false")); 254 boolean validClassLoader; 255 if (shareClassLoader) { 256 ClassLoader parent = parentClassLoader != null ? parentClassLoader 257 : baseClass.getClassLoader(); 258 validClassLoader = proxyClass.getClassLoader() == parent; 259 } else { 260 validClassLoader = proxyClass.getClassLoader().getParent() == parentClassLoader; 261 } 262 if (validClassLoader && interfaces.equals(asSet(proxyClass.getInterfaces()))) { 263 return proxyClass; // cache hit! 264 } 265 } 266 267 // the cache missed; generate the class 268 DexMaker dexMaker = new DexMaker(); 269 String generatedName = getMethodNameForProxyOf(baseClass); 270 TypeId<? extends T> generatedType = TypeId.get("L" + generatedName + ";"); 271 TypeId<T> superType = TypeId.get(baseClass); 272 generateConstructorsAndFields(dexMaker, generatedType, superType, baseClass); 273 Method[] methodsToProxy = getMethodsToProxyRecursive(); 274 generateCodeForAllMethods(dexMaker, generatedType, methodsToProxy, superType); 275 dexMaker.declare(generatedType, generatedName + ".generated", PUBLIC, superType, getInterfacesAsTypeIds()); 276 ClassLoader classLoader = dexMaker.generateAndLoad(baseClass.getClassLoader(), 277 parentClassLoader, dexCache); 278 try { 279 proxyClass = loadClass(classLoader, generatedName); 280 } catch (IllegalAccessError e) { 281 // Thrown when the base class is not accessible. 282 throw new UnsupportedOperationException( 283 "cannot proxy inaccessible class " + baseClass, e); 284 } catch (ClassNotFoundException e) { 285 // Should not be thrown, we're sure to have generated this class. 286 throw new AssertionError(e); 287 } 288 setMethodsStaticField(proxyClass, methodsToProxy); 289 generatedProxyClasses.put(baseClass, proxyClass); 290 return proxyClass; 291 } 292 293 // The type cast is safe: the generated type will extend the base class type. 294 @SuppressWarnings("unchecked") loadClass(ClassLoader classLoader, String generatedName)295 private Class<? extends T> loadClass(ClassLoader classLoader, String generatedName) 296 throws ClassNotFoundException { 297 return (Class<? extends T>) classLoader.loadClass(generatedName); 298 } 299 launderCause(InvocationTargetException e)300 private static RuntimeException launderCause(InvocationTargetException e) { 301 Throwable cause = e.getCause(); 302 // Errors should be thrown as they are. 303 if (cause instanceof Error) { 304 throw (Error) cause; 305 } 306 // RuntimeException can be thrown as-is. 307 if (cause instanceof RuntimeException) { 308 throw (RuntimeException) cause; 309 } 310 // Declared exceptions will have to be wrapped. 311 throw new UndeclaredThrowableException(cause); 312 } 313 setMethodsStaticField(Class<?> proxyClass, Method[] methodsToProxy)314 private static void setMethodsStaticField(Class<?> proxyClass, Method[] methodsToProxy) { 315 try { 316 Field methodArrayField = proxyClass.getDeclaredField(FIELD_NAME_METHODS); 317 methodArrayField.setAccessible(true); 318 methodArrayField.set(null, methodsToProxy); 319 } catch (NoSuchFieldException e) { 320 // Should not be thrown, generated proxy class has been generated with this field. 321 throw new AssertionError(e); 322 } catch (IllegalAccessException e) { 323 // Should not be thrown, we just set the field to accessible. 324 throw new AssertionError(e); 325 } 326 } 327 328 /** 329 * Returns the proxy's {@link InvocationHandler}. 330 * 331 * @throws IllegalArgumentException if the object supplied is not a proxy created by this class. 332 */ getInvocationHandler(Object instance)333 public static InvocationHandler getInvocationHandler(Object instance) { 334 try { 335 Field field = instance.getClass().getDeclaredField(FIELD_NAME_HANDLER); 336 field.setAccessible(true); 337 return (InvocationHandler) field.get(instance); 338 } catch (NoSuchFieldException e) { 339 throw new IllegalArgumentException("Not a valid proxy instance", e); 340 } catch (IllegalAccessException e) { 341 // Should not be thrown, we just set the field to accessible. 342 throw new AssertionError(e); 343 } 344 } 345 346 /** 347 * Sets the proxy's {@link InvocationHandler}. 348 * <p> 349 * If you create a proxy with {@link #build()}, the proxy will already have a handler set, 350 * provided that you configured one with {@link #handler(InvocationHandler)}. 351 * <p> 352 * If you generate a proxy class with {@link #buildProxyClass()}, instances of the proxy class 353 * will not automatically have a handler set, and it is necessary to use this method with each 354 * instance. 355 * 356 * @throws IllegalArgumentException if the object supplied is not a proxy created by this class. 357 */ setInvocationHandler(Object instance, InvocationHandler handler)358 public static void setInvocationHandler(Object instance, InvocationHandler handler) { 359 try { 360 Field handlerField = instance.getClass().getDeclaredField(FIELD_NAME_HANDLER); 361 handlerField.setAccessible(true); 362 handlerField.set(instance, handler); 363 } catch (NoSuchFieldException e) { 364 throw new IllegalArgumentException("Not a valid proxy instance", e); 365 } catch (IllegalAccessException e) { 366 // Should not be thrown, we just set the field to accessible. 367 throw new AssertionError(e); 368 } 369 } 370 371 // TODO: test coverage for isProxyClass 372 373 /** 374 * Returns true if {@code c} is a proxy class created by this builder. 375 */ isProxyClass(Class<?> c)376 public static boolean isProxyClass(Class<?> c) { 377 // TODO: use a marker interface instead? 378 try { 379 c.getDeclaredField(FIELD_NAME_HANDLER); 380 return true; 381 } catch (NoSuchFieldException e) { 382 return false; 383 } 384 } 385 generateCodeForAllMethods(DexMaker dexMaker, TypeId<G> generatedType, Method[] methodsToProxy, TypeId<T> superclassType)386 private static <T, G extends T> void generateCodeForAllMethods(DexMaker dexMaker, 387 TypeId<G> generatedType, Method[] methodsToProxy, TypeId<T> superclassType) { 388 TypeId<InvocationHandler> handlerType = TypeId.get(InvocationHandler.class); 389 TypeId<Method[]> methodArrayType = TypeId.get(Method[].class); 390 FieldId<G, InvocationHandler> handlerField = 391 generatedType.getField(handlerType, FIELD_NAME_HANDLER); 392 FieldId<G, Method[]> allMethods = 393 generatedType.getField(methodArrayType, FIELD_NAME_METHODS); 394 TypeId<Method> methodType = TypeId.get(Method.class); 395 TypeId<Object[]> objectArrayType = TypeId.get(Object[].class); 396 MethodId<InvocationHandler, Object> methodInvoke = handlerType.getMethod(TypeId.OBJECT, 397 "invoke", TypeId.OBJECT, methodType, objectArrayType); 398 for (int m = 0; m < methodsToProxy.length; ++m) { 399 /* 400 * If the 5th method on the superclass Example that can be overridden were to look like 401 * this: 402 * 403 * public int doSomething(Bar param0, int param1) { 404 * ... 405 * } 406 * 407 * Then the following code will generate a method on the proxy that looks something 408 * like this: 409 * 410 * public int doSomething(Bar param0, int param1) { 411 * int methodIndex = 4; 412 * Method[] allMethods = Example_Proxy.$__methodArray; 413 * Method thisMethod = allMethods[methodIndex]; 414 * int argsLength = 2; 415 * Object[] args = new Object[argsLength]; 416 * InvocationHandler localHandler = this.$__handler; 417 * // for-loop begins 418 * int p = 0; 419 * Bar parameter0 = param0; 420 * args[p] = parameter0; 421 * p = 1; 422 * int parameter1 = param1; 423 * Integer boxed1 = Integer.valueOf(parameter1); 424 * args[p] = boxed1; 425 * // for-loop ends 426 * Object result = localHandler.invoke(this, thisMethod, args); 427 * Integer castResult = (Integer) result; 428 * int unboxedResult = castResult.intValue(); 429 * return unboxedResult; 430 * } 431 * 432 * Or, in more idiomatic Java: 433 * 434 * public int doSomething(Bar param0, int param1) { 435 * if ($__handler == null) { 436 * return super.doSomething(param0, param1); 437 * } 438 * return __handler.invoke(this, __methodArray[4], 439 * new Object[] { param0, Integer.valueOf(param1) }); 440 * } 441 */ 442 Method method = methodsToProxy[m]; 443 String name = method.getName(); 444 Class<?>[] argClasses = method.getParameterTypes(); 445 TypeId<?>[] argTypes = new TypeId<?>[argClasses.length]; 446 for (int i = 0; i < argTypes.length; ++i) { 447 argTypes[i] = TypeId.get(argClasses[i]); 448 } 449 Class<?> returnType = method.getReturnType(); 450 TypeId<?> resultType = TypeId.get(returnType); 451 MethodId<T, ?> superMethod = superclassType.getMethod(resultType, name, argTypes); 452 MethodId<?, ?> methodId = generatedType.getMethod(resultType, name, argTypes); 453 Code code = dexMaker.declare(methodId, PUBLIC); 454 Local<G> localThis = code.getThis(generatedType); 455 Local<InvocationHandler> localHandler = code.newLocal(handlerType); 456 Local<Object> invokeResult = code.newLocal(TypeId.OBJECT); 457 Local<Integer> intValue = code.newLocal(TypeId.INT); 458 Local<Object[]> args = code.newLocal(objectArrayType); 459 Local<Integer> argsLength = code.newLocal(TypeId.INT); 460 Local<Object> temp = code.newLocal(TypeId.OBJECT); 461 Local<?> resultHolder = code.newLocal(resultType); 462 Local<Method[]> methodArray = code.newLocal(methodArrayType); 463 Local<Method> thisMethod = code.newLocal(methodType); 464 Local<Integer> methodIndex = code.newLocal(TypeId.INT); 465 Class<?> aBoxedClass = PRIMITIVE_TO_BOXED.get(returnType); 466 Local<?> aBoxedResult = null; 467 if (aBoxedClass != null) { 468 aBoxedResult = code.newLocal(TypeId.get(aBoxedClass)); 469 } 470 Local<?>[] superArgs2 = new Local<?>[argClasses.length]; 471 Local<?> superResult2 = code.newLocal(resultType); 472 Local<InvocationHandler> nullHandler = code.newLocal(handlerType); 473 474 code.loadConstant(methodIndex, m); 475 code.sget(allMethods, methodArray); 476 code.aget(thisMethod, methodArray, methodIndex); 477 code.loadConstant(argsLength, argTypes.length); 478 code.newArray(args, argsLength); 479 code.iget(handlerField, localHandler, localThis); 480 481 // if (proxy == null) 482 code.loadConstant(nullHandler, null); 483 Label handlerNullCase = new Label(); 484 code.compare(Comparison.EQ, handlerNullCase, nullHandler, localHandler); 485 486 // This code is what we execute when we have a valid proxy: delegate to invocation 487 // handler. 488 for (int p = 0; p < argTypes.length; ++p) { 489 code.loadConstant(intValue, p); 490 Local<?> parameter = code.getParameter(p, argTypes[p]); 491 Local<?> unboxedIfNecessary = boxIfRequired(code, parameter, temp); 492 code.aput(args, intValue, unboxedIfNecessary); 493 } 494 code.invokeInterface(methodInvoke, invokeResult, localHandler, 495 localThis, thisMethod, args); 496 generateCodeForReturnStatement(code, returnType, invokeResult, resultHolder, 497 aBoxedResult); 498 499 // This code is executed if proxy is null: call the original super method. 500 // This is required to handle the case of construction of an object which leaks the 501 // "this" pointer. 502 code.mark(handlerNullCase); 503 for (int i = 0; i < superArgs2.length; ++i) { 504 superArgs2[i] = code.getParameter(i, argTypes[i]); 505 } 506 if (void.class.equals(returnType)) { 507 code.invokeSuper(superMethod, null, localThis, superArgs2); 508 code.returnVoid(); 509 } else { 510 invokeSuper(superMethod, code, localThis, superArgs2, superResult2); 511 code.returnValue(superResult2); 512 } 513 514 /* 515 * And to allow calling the original super method, the following is also generated: 516 * 517 * public String super$doSomething$java_lang_String(Bar param0, int param1) { 518 * int result = super.doSomething(param0, param1); 519 * return result; 520 * } 521 */ 522 // TODO: don't include a super_ method if the target is abstract! 523 MethodId<G, ?> callsSuperMethod = generatedType.getMethod( 524 resultType, superMethodName(method), argTypes); 525 Code superCode = dexMaker.declare(callsSuperMethod, PUBLIC); 526 Local<G> superThis = superCode.getThis(generatedType); 527 Local<?>[] superArgs = new Local<?>[argClasses.length]; 528 for (int i = 0; i < superArgs.length; ++i) { 529 superArgs[i] = superCode.getParameter(i, argTypes[i]); 530 } 531 if (void.class.equals(returnType)) { 532 superCode.invokeSuper(superMethod, null, superThis, superArgs); 533 superCode.returnVoid(); 534 } else { 535 Local<?> superResult = superCode.newLocal(resultType); 536 invokeSuper(superMethod, superCode, superThis, superArgs, superResult); 537 superCode.returnValue(superResult); 538 } 539 } 540 } 541 542 @SuppressWarnings({"unchecked", "rawtypes"}) invokeSuper(MethodId superMethod, Code superCode, Local superThis, Local[] superArgs, Local superResult)543 private static void invokeSuper(MethodId superMethod, Code superCode, 544 Local superThis, Local[] superArgs, Local superResult) { 545 superCode.invokeSuper(superMethod, superResult, superThis, superArgs); 546 } 547 boxIfRequired(Code code, Local<?> parameter, Local<Object> temp)548 private static Local<?> boxIfRequired(Code code, Local<?> parameter, Local<Object> temp) { 549 MethodId<?, ?> unboxMethod = PRIMITIVE_TYPE_TO_UNBOX_METHOD.get(parameter.getType()); 550 if (unboxMethod == null) { 551 return parameter; 552 } 553 code.invokeStatic(unboxMethod, temp, parameter); 554 return temp; 555 } 556 callSuper(Object proxy, Method method, Object... args)557 public static Object callSuper(Object proxy, Method method, Object... args) throws Throwable { 558 try { 559 return proxy.getClass() 560 .getMethod(superMethodName(method), method.getParameterTypes()) 561 .invoke(proxy, args); 562 } catch (InvocationTargetException e) { 563 throw e.getCause(); 564 } 565 } 566 567 /** 568 * The super method must include the return type, otherwise its ambiguous 569 * for methods with covariant return types. 570 */ superMethodName(Method method)571 private static String superMethodName(Method method) { 572 String returnType = method.getReturnType().getName(); 573 return "super$" + method.getName() + "$" 574 + returnType.replace('.', '_').replace('[', '_').replace(';', '_'); 575 } 576 check(boolean condition, String message)577 private static void check(boolean condition, String message) { 578 if (!condition) { 579 throw new IllegalArgumentException(message); 580 } 581 } 582 generateConstructorsAndFields(DexMaker dexMaker, TypeId<G> generatedType, TypeId<T> superType, Class<T> superClass)583 private static <T, G extends T> void generateConstructorsAndFields(DexMaker dexMaker, 584 TypeId<G> generatedType, TypeId<T> superType, Class<T> superClass) { 585 TypeId<InvocationHandler> handlerType = TypeId.get(InvocationHandler.class); 586 TypeId<Method[]> methodArrayType = TypeId.get(Method[].class); 587 FieldId<G, InvocationHandler> handlerField = generatedType.getField( 588 handlerType, FIELD_NAME_HANDLER); 589 dexMaker.declare(handlerField, PRIVATE, null); 590 FieldId<G, Method[]> allMethods = generatedType.getField( 591 methodArrayType, FIELD_NAME_METHODS); 592 dexMaker.declare(allMethods, PRIVATE | STATIC, null); 593 for (Constructor<T> constructor : getConstructorsToOverwrite(superClass)) { 594 if (constructor.getModifiers() == Modifier.FINAL) { 595 continue; 596 } 597 TypeId<?>[] types = classArrayToTypeArray(constructor.getParameterTypes()); 598 MethodId<?, ?> method = generatedType.getConstructor(types); 599 Code constructorCode = dexMaker.declare(method, PUBLIC); 600 Local<G> thisRef = constructorCode.getThis(generatedType); 601 Local<?>[] params = new Local[types.length]; 602 for (int i = 0; i < params.length; ++i) { 603 params[i] = constructorCode.getParameter(i, types[i]); 604 } 605 MethodId<T, ?> superConstructor = superType.getConstructor(types); 606 constructorCode.invokeDirect(superConstructor, null, thisRef, params); 607 constructorCode.returnVoid(); 608 } 609 } 610 611 // The type parameter on Constructor is the class in which the constructor is declared. 612 // The getDeclaredConstructors() method gets constructors declared only in the given class, 613 // hence this cast is safe. 614 @SuppressWarnings("unchecked") getConstructorsToOverwrite(Class<T> clazz)615 private static <T> Constructor<T>[] getConstructorsToOverwrite(Class<T> clazz) { 616 return (Constructor<T>[]) clazz.getDeclaredConstructors(); 617 } 618 getInterfacesAsTypeIds()619 private TypeId<?>[] getInterfacesAsTypeIds() { 620 TypeId<?>[] result = new TypeId<?>[interfaces.size()]; 621 int i = 0; 622 for (Class<?> implemented : interfaces) { 623 result[i++] = TypeId.get(implemented); 624 } 625 return result; 626 } 627 628 /** 629 * Gets all {@link Method} objects we can proxy in the hierarchy of the 630 * supplied class. 631 */ getMethodsToProxyRecursive()632 private Method[] getMethodsToProxyRecursive() { 633 Set<MethodSetEntry> methodsToProxy = new HashSet<>(); 634 Set<MethodSetEntry> seenFinalMethods = new HashSet<>(); 635 // Traverse the class hierarchy to ensure that all concrete methods (which could be marked 636 // as final) are visited before any abstract methods from interfaces. 637 for (Class<?> c = baseClass; c != null; c = c.getSuperclass()) { 638 getMethodsToProxy(methodsToProxy, seenFinalMethods, c); 639 } 640 // Now traverse the interface hierarchy, starting with the ones implemented by the class, 641 // followed by any extra interfaces. 642 for (Class<?> c = baseClass; c != null; c = c.getSuperclass()) { 643 for (Class<?> i : c.getInterfaces()) { 644 getMethodsToProxy(methodsToProxy, seenFinalMethods, i); 645 } 646 } 647 for (Class<?> c : interfaces) { 648 getMethodsToProxy(methodsToProxy, seenFinalMethods, c); 649 } 650 651 Method[] results = new Method[methodsToProxy.size()]; 652 int i = 0; 653 for (MethodSetEntry entry : methodsToProxy) { 654 results[i++] = entry.originalMethod; 655 } 656 657 // Sort the results array so that they are returned by this method 658 // in a deterministic fashion. 659 Arrays.sort(results, new Comparator<Method>() { 660 @Override 661 public int compare(Method method1, Method method2) { 662 return method1.toString().compareTo(method2.toString()); 663 } 664 }); 665 666 return results; 667 } 668 getMethodsToProxy(Set<MethodSetEntry> sink, Set<MethodSetEntry> seenFinalMethods, Class<?> c)669 private void getMethodsToProxy(Set<MethodSetEntry> sink, Set<MethodSetEntry> seenFinalMethods, 670 Class<?> c) { 671 boolean shareClassLoader = Boolean.parseBoolean(System.getProperty( 672 "dexmaker.share_classloader", "false")); 673 for (Method method : c.getDeclaredMethods()) { 674 if ((method.getModifiers() & Modifier.FINAL) != 0) { 675 // Skip final methods, we can't override them. We 676 // also need to remember them, in case the same 677 // method exists in a parent class. 678 MethodSetEntry entry = new MethodSetEntry(method); 679 seenFinalMethods.add(entry); 680 // We may have seen this method already, from an interface 681 // implemented by a child class. We need to remove it here. 682 sink.remove(entry); 683 continue; 684 } 685 if ((method.getModifiers() & STATIC) != 0) { 686 // Skip static methods, overriding them has no effect. 687 continue; 688 } 689 if (!Modifier.isPublic(method.getModifiers()) 690 && !Modifier.isProtected(method.getModifiers()) 691 && (!shareClassLoader || Modifier.isPrivate(method.getModifiers()))) { 692 // Skip private methods, since they are invoked through direct 693 // invocation (as opposed to virtual). Therefore, it would not 694 // be possible to intercept any private method defined inside 695 // the proxy class except through reflection. 696 697 // Skip package-private methods as well (for non-shared class 698 // loaders). The proxy class does 699 // not actually inherit package-private methods from the parent 700 // class because it is not a member of the parent's package. 701 // This is even true if the two classes have the same package 702 // name, as they use different class loaders. 703 continue; 704 } 705 if (method.getName().equals("finalize") && method.getParameterTypes().length == 0) { 706 // Skip finalize method, it's likely important that it execute as normal. 707 continue; 708 } 709 MethodSetEntry entry = new MethodSetEntry(method); 710 if (seenFinalMethods.contains(entry)) { 711 // This method is final in a child class. 712 // We can't override it. 713 continue; 714 } 715 sink.add(entry); 716 } 717 718 // Only visit the interfaces of this class if it is itself an interface. That prevents 719 // visiting interfaces of a class before its super classes. 720 if (c.isInterface()) { 721 for (Class<?> i : c.getInterfaces()) { 722 getMethodsToProxy(sink, seenFinalMethods, i); 723 } 724 } 725 } 726 getMethodNameForProxyOf(Class<T> clazz)727 private static <T> String getMethodNameForProxyOf(Class<T> clazz) { 728 return clazz.getName().replace(".", "/") + "_Proxy"; 729 } 730 classArrayToTypeArray(Class<?>[] input)731 private static TypeId<?>[] classArrayToTypeArray(Class<?>[] input) { 732 TypeId<?>[] result = new TypeId[input.length]; 733 for (int i = 0; i < input.length; ++i) { 734 result[i] = TypeId.get(input[i]); 735 } 736 return result; 737 } 738 739 /** 740 * Calculates the correct return statement code for a method. 741 * <p> 742 * A void method will not return anything. A method that returns a primitive will need to 743 * unbox the boxed result. Otherwise we will cast the result. 744 */ 745 // This one is tricky to fix, I gave up. 746 @SuppressWarnings({ "rawtypes", "unchecked" }) generateCodeForReturnStatement(Code code, Class methodReturnType, Local localForResultOfInvoke, Local localOfMethodReturnType, Local aBoxedResult)747 private static void generateCodeForReturnStatement(Code code, Class methodReturnType, 748 Local localForResultOfInvoke, Local localOfMethodReturnType, Local aBoxedResult) { 749 if (PRIMITIVE_TO_UNBOX_METHOD.containsKey(methodReturnType)) { 750 code.cast(aBoxedResult, localForResultOfInvoke); 751 MethodId unboxingMethodFor = getUnboxMethodForPrimitive(methodReturnType); 752 code.invokeVirtual(unboxingMethodFor, localOfMethodReturnType, aBoxedResult); 753 code.returnValue(localOfMethodReturnType); 754 } else if (void.class.equals(methodReturnType)) { 755 code.returnVoid(); 756 } else { 757 code.cast(localOfMethodReturnType, localForResultOfInvoke); 758 code.returnValue(localOfMethodReturnType); 759 } 760 } 761 asSet(T... array)762 private static <T> Set<T> asSet(T... array) { 763 return new CopyOnWriteArraySet<>(Arrays.asList(array)); 764 } 765 getUnboxMethodForPrimitive(Class<?> methodReturnType)766 private static MethodId<?, ?> getUnboxMethodForPrimitive(Class<?> methodReturnType) { 767 return PRIMITIVE_TO_UNBOX_METHOD.get(methodReturnType); 768 } 769 770 private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_BOXED; 771 static { 772 PRIMITIVE_TO_BOXED = new HashMap<>(); PRIMITIVE_TO_BOXED.put(boolean.class, Boolean.class)773 PRIMITIVE_TO_BOXED.put(boolean.class, Boolean.class); PRIMITIVE_TO_BOXED.put(int.class, Integer.class)774 PRIMITIVE_TO_BOXED.put(int.class, Integer.class); PRIMITIVE_TO_BOXED.put(byte.class, Byte.class)775 PRIMITIVE_TO_BOXED.put(byte.class, Byte.class); PRIMITIVE_TO_BOXED.put(long.class, Long.class)776 PRIMITIVE_TO_BOXED.put(long.class, Long.class); PRIMITIVE_TO_BOXED.put(short.class, Short.class)777 PRIMITIVE_TO_BOXED.put(short.class, Short.class); PRIMITIVE_TO_BOXED.put(float.class, Float.class)778 PRIMITIVE_TO_BOXED.put(float.class, Float.class); PRIMITIVE_TO_BOXED.put(double.class, Double.class)779 PRIMITIVE_TO_BOXED.put(double.class, Double.class); PRIMITIVE_TO_BOXED.put(char.class, Character.class)780 PRIMITIVE_TO_BOXED.put(char.class, Character.class); 781 } 782 783 private static final Map<TypeId<?>, MethodId<?, ?>> PRIMITIVE_TYPE_TO_UNBOX_METHOD; 784 static { 785 PRIMITIVE_TYPE_TO_UNBOX_METHOD = new HashMap<>(); 786 for (Map.Entry<Class<?>, Class<?>> entry : PRIMITIVE_TO_BOXED.entrySet()) { 787 TypeId<?> primitiveType = TypeId.get(entry.getKey()); 788 TypeId<?> boxedType = TypeId.get(entry.getValue()); 789 MethodId<?, ?> valueOfMethod = boxedType.getMethod(boxedType, "valueOf", primitiveType); PRIMITIVE_TYPE_TO_UNBOX_METHOD.put(primitiveType, valueOfMethod)790 PRIMITIVE_TYPE_TO_UNBOX_METHOD.put(primitiveType, valueOfMethod); 791 } 792 } 793 794 /** 795 * Map from primitive type to method used to unbox a boxed version of the primitive. 796 * <p> 797 * This is required for methods whose return type is primitive, since the 798 * {@link InvocationHandler} will return us a boxed result, and we'll need to convert it back to 799 * primitive value. 800 */ 801 private static final Map<Class<?>, MethodId<?, ?>> PRIMITIVE_TO_UNBOX_METHOD; 802 static { 803 Map<Class<?>, MethodId<?, ?>> map = new HashMap<>(); map.put(boolean.class, TypeId.get(Boolean.class).getMethod(TypeId.BOOLEAN, "booleanValue"))804 map.put(boolean.class, TypeId.get(Boolean.class).getMethod(TypeId.BOOLEAN, "booleanValue")); map.put(int.class, TypeId.get(Integer.class).getMethod(TypeId.INT, "intValue"))805 map.put(int.class, TypeId.get(Integer.class).getMethod(TypeId.INT, "intValue")); map.put(byte.class, TypeId.get(Byte.class).getMethod(TypeId.BYTE, "byteValue"))806 map.put(byte.class, TypeId.get(Byte.class).getMethod(TypeId.BYTE, "byteValue")); map.put(long.class, TypeId.get(Long.class).getMethod(TypeId.LONG, "longValue"))807 map.put(long.class, TypeId.get(Long.class).getMethod(TypeId.LONG, "longValue")); map.put(short.class, TypeId.get(Short.class).getMethod(TypeId.SHORT, "shortValue"))808 map.put(short.class, TypeId.get(Short.class).getMethod(TypeId.SHORT, "shortValue")); map.put(float.class, TypeId.get(Float.class).getMethod(TypeId.FLOAT, "floatValue"))809 map.put(float.class, TypeId.get(Float.class).getMethod(TypeId.FLOAT, "floatValue")); map.put(double.class, TypeId.get(Double.class).getMethod(TypeId.DOUBLE, "doubleValue"))810 map.put(double.class, TypeId.get(Double.class).getMethod(TypeId.DOUBLE, "doubleValue")); map.put(char.class, TypeId.get(Character.class).getMethod(TypeId.CHAR, "charValue"))811 map.put(char.class, TypeId.get(Character.class).getMethod(TypeId.CHAR, "charValue")); 812 PRIMITIVE_TO_UNBOX_METHOD = map; 813 } 814 815 /** 816 * Wrapper class to let us disambiguate {@link Method} objects. 817 * <p> 818 * The purpose of this class is to override the {@link #equals(Object)} and {@link #hashCode()} 819 * methods so we can use a {@link Set} to remove duplicate methods that are overrides of one 820 * another. For these purposes, we consider two methods to be equal if they have the same 821 * name, return type, and parameter types. 822 */ 823 private static class MethodSetEntry { 824 private final String name; 825 private final Class<?>[] paramTypes; 826 private final Class<?> returnType; 827 private final Method originalMethod; 828 MethodSetEntry(Method method)829 public MethodSetEntry(Method method) { 830 originalMethod = method; 831 name = method.getName(); 832 paramTypes = method.getParameterTypes(); 833 returnType = method.getReturnType(); 834 } 835 836 @Override equals(Object o)837 public boolean equals(Object o) { 838 if (o instanceof MethodSetEntry) { 839 MethodSetEntry other = (MethodSetEntry) o; 840 return name.equals(other.name) 841 && returnType.equals(other.returnType) 842 && Arrays.equals(paramTypes, other.paramTypes); 843 } 844 return false; 845 } 846 847 @Override hashCode()848 public int hashCode() { 849 int result = 17; 850 result += 31 * result + name.hashCode(); 851 result += 31 * result + returnType.hashCode(); 852 result += 31 * result + Arrays.hashCode(paramTypes); 853 return result; 854 } 855 } 856 } 857