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 android.os.Build; 20 21 import com.android.dx.DexMakerTest; 22 import junit.framework.AssertionFailedError; 23 import org.junit.After; 24 import org.junit.Before; 25 import org.junit.Test; 26 27 import java.io.File; 28 import java.io.IOException; 29 import java.lang.reflect.Field; 30 import java.lang.reflect.InvocationHandler; 31 import java.lang.reflect.Method; 32 import java.lang.reflect.Modifier; 33 import java.lang.reflect.UndeclaredThrowableException; 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.Map; 37 import java.util.Random; 38 import java.util.concurrent.Callable; 39 import java.util.concurrent.atomic.AtomicInteger; 40 41 import static com.android.dx.util.TestUtil.DELTA_DOUBLE; 42 import static com.android.dx.util.TestUtil.DELTA_FLOAT; 43 import static junit.framework.Assert.assertFalse; 44 import static junit.framework.Assert.assertNotNull; 45 import static junit.framework.Assert.assertSame; 46 import static junit.framework.Assert.assertTrue; 47 import static junit.framework.Assert.fail; 48 import static org.junit.Assert.assertEquals; 49 import static org.junit.Assert.assertNotSame; 50 import static org.junit.Assume.assumeTrue; 51 52 public class ProxyBuilderTest { 53 private FakeInvocationHandler fakeHandler = new FakeInvocationHandler(); 54 private File versionedDxDir = new File(DexMakerTest.getDataDirectory(), "v1"); 55 56 @Before setUp()57 public void setUp() throws Exception { 58 versionedDxDir.mkdirs(); 59 clearVersionedDxDir(); 60 getGeneratedProxyClasses().clear(); 61 } 62 63 @After tearDown()64 public void tearDown() throws Exception { 65 getGeneratedProxyClasses().clear(); 66 clearVersionedDxDir(); 67 } 68 clearVersionedDxDir()69 private void clearVersionedDxDir() { 70 for (File file : versionedDxDir.listFiles()) { 71 file.delete(); 72 } 73 } 74 75 public static class SimpleClass { simpleMethod()76 public String simpleMethod() { 77 throw new AssertionFailedError(); 78 } 79 } 80 81 public static class ExampleClass { exampleMethod()82 public String exampleMethod() { 83 throw new AssertionFailedError(); 84 } 85 } 86 87 public static class ExampleOperationClass { exampleMethod()88 public String exampleMethod() { 89 throw new AssertionFailedError(); 90 } 91 } 92 93 @Test testExampleOperation()94 public void testExampleOperation() throws Throwable { 95 fakeHandler.setFakeResult("expected"); 96 ExampleClass proxy = proxyFor(ExampleClass.class).build(); 97 assertEquals("expected", proxy.exampleMethod()); 98 assertEquals(2, versionedDxDir.listFiles().length); 99 } 100 101 @Test testExampleOperation_DexMakerCaching()102 public void testExampleOperation_DexMakerCaching() throws Throwable { 103 fakeHandler.setFakeResult("expected"); 104 ExampleOperationClass proxy = proxyFor(ExampleOperationClass.class).build(); 105 assertEquals(2, versionedDxDir.listFiles().length); 106 assertEquals("expected", proxy.exampleMethod()); 107 108 // Force ProxyBuilder to create a DexMaker generator and call DexMaker.generateAndLoad(). 109 getGeneratedProxyClasses().clear(); 110 111 proxy = proxyFor(ExampleOperationClass.class).build(); 112 assertEquals(2, versionedDxDir.listFiles().length); 113 assertEquals("expected", proxy.exampleMethod()); 114 } 115 116 public static class ConstructorTakesArguments { 117 private final String argument; 118 ConstructorTakesArguments(String arg)119 public ConstructorTakesArguments(String arg) { 120 argument = arg; 121 } 122 method()123 public String method() { 124 throw new AssertionFailedError(); 125 } 126 } 127 128 @Test testConstruction_SucceedsIfCorrectArgumentsProvided()129 public void testConstruction_SucceedsIfCorrectArgumentsProvided() throws Throwable { 130 ConstructorTakesArguments proxy = proxyFor(ConstructorTakesArguments.class) 131 .constructorArgTypes(String.class) 132 .constructorArgValues("hello") 133 .build(); 134 assertEquals("hello", proxy.argument); 135 proxy.method(); 136 } 137 138 @Test(expected = IllegalArgumentException.class) testConstruction_FailsWithWrongNumberOfArguments()139 public void testConstruction_FailsWithWrongNumberOfArguments() throws Throwable { 140 proxyFor(ConstructorTakesArguments.class).build(); 141 } 142 143 @Test(expected = UnsupportedOperationException.class) testClassIsNotAccessbile_FailsWithUnsupportedOperationException()144 public void testClassIsNotAccessbile_FailsWithUnsupportedOperationException() throws Exception { 145 class MethodVisibilityClass {} 146 proxyFor(MethodVisibilityClass.class).build(); 147 } 148 149 private static class PrivateVisibilityClass {} 150 151 @Test(expected = UnsupportedOperationException.class) testPrivateClass_FailsWithUnsupportedOperationException()152 public void testPrivateClass_FailsWithUnsupportedOperationException() throws Exception { 153 proxyFor(PrivateVisibilityClass.class).build(); 154 } 155 156 protected static class ProtectedVisibilityClass { foo()157 public String foo() { 158 throw new AssertionFailedError(); 159 } 160 } 161 162 @Test testProtectedVisibility_WorksFine()163 public void testProtectedVisibility_WorksFine() throws Exception { 164 assertEquals("fake result", proxyFor(ProtectedVisibilityClass.class).build().foo()); 165 } 166 167 public static class HasFinalMethod { nonFinalMethod()168 public String nonFinalMethod() { 169 return "non-final method"; 170 } 171 finalMethod()172 public final String finalMethod() { 173 return "final method"; 174 } 175 } 176 177 @Test testCanProxyClassesWithFinalMethods_WillNotCallTheFinalMethod()178 public void testCanProxyClassesWithFinalMethods_WillNotCallTheFinalMethod() throws Throwable { 179 HasFinalMethod proxy = proxyFor(HasFinalMethod.class).build(); 180 assertEquals("final method", proxy.finalMethod()); 181 assertEquals("fake result", proxy.nonFinalMethod()); 182 } 183 184 public static class HasPrivateMethod { result()185 private String result() { 186 return "expected"; 187 } 188 } 189 190 @Test testProxyingPrivateMethods_NotIntercepted()191 public void testProxyingPrivateMethods_NotIntercepted() throws Throwable { 192 HasPrivateMethod proxy = proxyFor(HasPrivateMethod.class).build(); 193 try { 194 proxy.getClass().getDeclaredMethod("result"); 195 fail(); 196 } catch (NoSuchMethodException expected) { 197 198 } 199 200 assertEquals("expected", proxy.result()); 201 } 202 203 public static class HasPackagePrivateMethod { result()204 String result() { 205 throw new AssertionFailedError(); 206 } 207 } 208 209 @Test testProxyingPackagePrivateMethods_NotIntercepted()210 public void testProxyingPackagePrivateMethods_NotIntercepted() 211 throws Throwable { 212 HasPackagePrivateMethod proxy = proxyFor(HasPackagePrivateMethod.class) 213 .build(); 214 try { 215 proxy.getClass().getDeclaredMethod("result"); 216 fail(); 217 } catch (NoSuchMethodException expected) { 218 219 } 220 221 try { 222 proxy.result(); 223 } catch (AssertionFailedError expected) { 224 return; 225 } 226 fail(); 227 } 228 229 public static class HasPackagePrivateMethodSharedClassLoader { result()230 String result() { 231 throw new AssertionFailedError(); 232 } 233 } 234 235 @Test testProxyingPackagePrivateMethodsWithSharedClassLoader_AreIntercepted()236 public void testProxyingPackagePrivateMethodsWithSharedClassLoader_AreIntercepted() 237 throws Throwable { 238 assumeTrue(Build.VERSION.SDK_INT >= 24); 239 240 assertEquals("fake result", proxyFor(HasPackagePrivateMethodSharedClassLoader.class) 241 .withSharedClassLoader().build().result()); 242 } 243 244 245 public static class HasProtectedMethod { result()246 protected String result() { 247 throw new AssertionFailedError(); 248 } 249 } 250 251 @Test testProxyingProtectedMethods_AreIntercepted()252 public void testProxyingProtectedMethods_AreIntercepted() throws Throwable { 253 assertEquals("fake result", proxyFor(HasProtectedMethod.class).build().result()); 254 } 255 256 public static class MyParentClass { someMethod()257 String someMethod() { 258 return "package"; 259 } 260 } 261 262 public static class MyChildClassWithProtectedMethod extends MyParentClass { 263 @Override someMethod()264 protected String someMethod() { 265 return "protected"; 266 } 267 } 268 269 public static class MyChildClassWithPublicMethod extends MyParentClass { 270 @Override someMethod()271 public String someMethod() { 272 return "public"; 273 } 274 } 275 276 @Test testProxying_ClassHierarchy()277 public void testProxying_ClassHierarchy() throws Throwable { 278 assertEquals("package", proxyFor(MyParentClass.class).build().someMethod()); 279 assertEquals("fake result", proxyFor(MyChildClassWithProtectedMethod.class).build().someMethod()); 280 assertEquals("fake result", proxyFor(MyChildClassWithPublicMethod.class).build().someMethod()); 281 } 282 283 public static class HasVoidMethod { dangerousMethod()284 public void dangerousMethod() { 285 fail(); 286 } 287 } 288 289 @Test testVoidMethod_ShouldNotThrowRuntimeException()290 public void testVoidMethod_ShouldNotThrowRuntimeException() throws Throwable { 291 proxyFor(HasVoidMethod.class).build().dangerousMethod(); 292 } 293 294 @Test 295 @SuppressWarnings({"EqualsWithItself", "SelfEquals"}) testObjectMethodsAreAlsoProxied()296 public void testObjectMethodsAreAlsoProxied() throws Throwable { 297 Object proxy = proxyFor(Object.class).build(); 298 fakeHandler.setFakeResult("mystring"); 299 assertEquals("mystring", proxy.toString()); 300 fakeHandler.setFakeResult(-1); 301 assertEquals(-1, proxy.hashCode()); 302 fakeHandler.setFakeResult(false); 303 assertEquals(false, proxy.equals(proxy)); 304 } 305 306 public static class AllReturnTypes { getBoolean()307 public boolean getBoolean() { return true; } getInt()308 public int getInt() { return 1; } getByte()309 public byte getByte() { return 2; } getLong()310 public long getLong() { return 3L; } getShort()311 public short getShort() { return 4; } getFloat()312 public float getFloat() { return 5f; } getDouble()313 public double getDouble() { return 6.0; } getChar()314 public char getChar() { return 'c'; } getIntArray()315 public int[] getIntArray() { return new int[] { 8, 9 }; } getStringArray()316 public String[] getStringArray() { return new String[] { "d", "e" }; } 317 } 318 319 @Test testAllReturnTypes()320 public void testAllReturnTypes() throws Throwable { 321 AllReturnTypes proxy = proxyFor(AllReturnTypes.class).build(); 322 fakeHandler.setFakeResult(false); 323 assertEquals(false, proxy.getBoolean()); 324 fakeHandler.setFakeResult(8); 325 assertEquals(8, proxy.getInt()); 326 fakeHandler.setFakeResult((byte) 9); 327 assertEquals(9, proxy.getByte()); 328 fakeHandler.setFakeResult(10L); 329 assertEquals(10, proxy.getLong()); 330 fakeHandler.setFakeResult((short) 11); 331 assertEquals(11, proxy.getShort()); 332 fakeHandler.setFakeResult(12f); 333 assertEquals(12f, proxy.getFloat(), DELTA_FLOAT); 334 fakeHandler.setFakeResult(13.0); 335 assertEquals(13.0, proxy.getDouble(), DELTA_FLOAT); 336 fakeHandler.setFakeResult('z'); 337 assertEquals('z', proxy.getChar()); 338 fakeHandler.setFakeResult(new int[] { -1, -2 }); 339 assertEquals("[-1, -2]", Arrays.toString(proxy.getIntArray())); 340 fakeHandler.setFakeResult(new String[] { "x", "y" }); 341 assertEquals("[x, y]", Arrays.toString(proxy.getStringArray())); 342 } 343 344 public static class PassThroughAllTypes { getBoolean(boolean input)345 public boolean getBoolean(boolean input) { return input; } getInt(int input)346 public int getInt(int input) { return input; } getByte(byte input)347 public byte getByte(byte input) { return input; } getLong(long input)348 public long getLong(long input) { return input; } getShort(short input)349 public short getShort(short input) { return input; } getFloat(float input)350 public float getFloat(float input) { return input; } getDouble(double input)351 public double getDouble(double input) { return input; } getChar(char input)352 public char getChar(char input) { return input; } getString(String input)353 public String getString(String input) { return input; } getObject(Object input)354 public Object getObject(Object input) { return input; } getNothing()355 public void getNothing() {} 356 } 357 358 public static class InvokeSuperHandler implements InvocationHandler { 359 @Override invoke(Object proxy, Method method, Object[] args)360 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 361 return ProxyBuilder.callSuper(proxy, method, args); 362 } 363 } 364 365 @Test testPassThroughWorksForAllTypes()366 public void testPassThroughWorksForAllTypes() throws Exception { 367 PassThroughAllTypes proxy = proxyFor(PassThroughAllTypes.class) 368 .handler(new InvokeSuperHandler()) 369 .build(); 370 assertEquals(false, proxy.getBoolean(false)); 371 assertEquals(true, proxy.getBoolean(true)); 372 assertEquals(0, proxy.getInt(0)); 373 assertEquals(1, proxy.getInt(1)); 374 assertEquals((byte) 2, proxy.getByte((byte) 2)); 375 assertEquals((byte) 3, proxy.getByte((byte) 3)); 376 assertEquals(4L, proxy.getLong(4L)); 377 assertEquals(5L, proxy.getLong(5L)); 378 assertEquals((short) 6, proxy.getShort((short) 6)); 379 assertEquals((short) 7, proxy.getShort((short) 7)); 380 assertEquals(8f, proxy.getFloat(8f), DELTA_FLOAT); 381 assertEquals(9f, proxy.getFloat(9f), DELTA_FLOAT); 382 assertEquals(10.0, proxy.getDouble(10.0), DELTA_DOUBLE); 383 assertEquals(11.0, proxy.getDouble(11.0), DELTA_DOUBLE); 384 assertEquals('a', proxy.getChar('a')); 385 assertEquals('b', proxy.getChar('b')); 386 assertEquals("asdf", proxy.getString("asdf")); 387 assertEquals("qwer", proxy.getString("qwer")); 388 assertEquals(null, proxy.getString(null)); 389 Object a = new Object(); 390 assertEquals(a, proxy.getObject(a)); 391 assertEquals(null, proxy.getObject(null)); 392 proxy.getNothing(); 393 } 394 395 public static class ExtendsAllReturnTypes extends AllReturnTypes { example()396 public int example() { return 0; } 397 } 398 399 @Test testProxyWorksForSuperclassMethodsAlso()400 public void testProxyWorksForSuperclassMethodsAlso() throws Throwable { 401 ExtendsAllReturnTypes proxy = proxyFor(ExtendsAllReturnTypes.class).build(); 402 fakeHandler.setFakeResult(99); 403 assertEquals(99, proxy.example()); 404 assertEquals(99, proxy.getInt()); 405 assertEquals(99, proxy.hashCode()); 406 } 407 408 public static class HasOddParams { method(int first, Integer second)409 public long method(int first, Integer second) { 410 throw new AssertionFailedError(); 411 } 412 } 413 414 @Test testMixingBoxedAndUnboxedParams()415 public void testMixingBoxedAndUnboxedParams() throws Throwable { 416 HasOddParams proxy = proxyFor(HasOddParams.class).build(); 417 fakeHandler.setFakeResult(99L); 418 assertEquals(99L, proxy.method(1, Integer.valueOf(2))); 419 } 420 421 public static class SingleInt { getString(int value)422 public String getString(int value) { 423 throw new AssertionFailedError(); 424 } 425 } 426 427 @Test testSinglePrimitiveParameter()428 public void testSinglePrimitiveParameter() throws Throwable { 429 InvocationHandler handler = new InvocationHandler() { 430 @Override 431 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 432 return "asdf" + ((Integer) args[0]).intValue(); 433 } 434 }; 435 assertEquals("asdf1", proxyFor(SingleInt.class).handler(handler).build().getString(1)); 436 } 437 438 public static class TwoConstructors { 439 private final String string; 440 TwoConstructors()441 public TwoConstructors() { 442 string = "no-arg"; 443 } 444 TwoConstructors(boolean unused)445 public TwoConstructors(boolean unused) { 446 string = "one-arg"; 447 } 448 } 449 450 @Test testNoConstructorArguments_CallsNoArgConstructor()451 public void testNoConstructorArguments_CallsNoArgConstructor() throws Throwable { 452 TwoConstructors twoConstructors = proxyFor(TwoConstructors.class).build(); 453 assertEquals("no-arg", twoConstructors.string); 454 } 455 456 @Test testWithoutInvocationHandler_ThrowsIllegalArgumentException()457 public void testWithoutInvocationHandler_ThrowsIllegalArgumentException() throws Throwable { 458 try { 459 ProxyBuilder.forClass(TwoConstructors.class) 460 .dexCache(DexMakerTest.getDataDirectory()) 461 .build(); 462 fail(); 463 } catch (IllegalArgumentException expected) {} 464 } 465 466 public static class HardToConstructCorrectly { HardToConstructCorrectly()467 public HardToConstructCorrectly() { fail(); } HardToConstructCorrectly(Runnable ignored)468 public HardToConstructCorrectly(Runnable ignored) { fail(); } HardToConstructCorrectly(Exception ignored)469 public HardToConstructCorrectly(Exception ignored) { fail(); } HardToConstructCorrectly(Boolean ignored)470 public HardToConstructCorrectly(Boolean ignored) { /* safe */ } HardToConstructCorrectly(Integer ignored)471 public HardToConstructCorrectly(Integer ignored) { fail(); } 472 } 473 474 @Test testHardToConstruct_WorksIfYouSpecifyTheConstructorCorrectly()475 public void testHardToConstruct_WorksIfYouSpecifyTheConstructorCorrectly() throws Throwable { 476 proxyFor(HardToConstructCorrectly.class) 477 .constructorArgTypes(Boolean.class) 478 .constructorArgValues(true) 479 .build(); 480 } 481 482 @Test testHardToConstruct_EvenWorksWhenArgsAreAmbiguous()483 public void testHardToConstruct_EvenWorksWhenArgsAreAmbiguous() throws Throwable { 484 proxyFor(HardToConstructCorrectly.class) 485 .constructorArgTypes(Boolean.class) 486 .constructorArgValues(new Object[] { null }) 487 .build(); 488 } 489 490 @Test(expected = IllegalArgumentException.class) testHardToConstruct_DoesNotInferTypesFromValues()491 public void testHardToConstruct_DoesNotInferTypesFromValues() throws Throwable { 492 proxyFor(HardToConstructCorrectly.class) 493 .constructorArgValues(true) 494 .build(); 495 } 496 497 @Test testDefaultProxyHasSuperMethodToAccessOriginal()498 public void testDefaultProxyHasSuperMethodToAccessOriginal() throws Exception { 499 Object objectProxy = proxyFor(Object.class).build(); 500 assertNotNull(objectProxy.getClass().getMethod("super$hashCode$int")); 501 } 502 503 public static class PrintsOddAndValue { method(int value)504 public String method(int value) { 505 return "odd " + value; 506 } 507 } 508 509 @Test testSometimesDelegateToSuper()510 public void testSometimesDelegateToSuper() throws Exception { 511 InvocationHandler delegatesOddValues = new InvocationHandler() { 512 @Override 513 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 514 if (method.getName().equals("method")) { 515 int intValue = ((Integer) args[0]).intValue(); 516 if (intValue % 2 == 0) { 517 return "even " + intValue; 518 } 519 } 520 return ProxyBuilder.callSuper(proxy, method, args); 521 } 522 }; 523 PrintsOddAndValue proxy = proxyFor(PrintsOddAndValue.class) 524 .handler(delegatesOddValues) 525 .build(); 526 assertEquals("even 0", proxy.method(0)); 527 assertEquals("odd 1", proxy.method(1)); 528 assertEquals("even 2", proxy.method(2)); 529 assertEquals("odd 3", proxy.method(3)); 530 } 531 532 @Test testCallSuperThrows()533 public void testCallSuperThrows() throws Exception { 534 InvocationHandler handler = new InvocationHandler() { 535 @Override 536 public Object invoke(Object o, Method method, Object[] objects) throws Throwable { 537 return ProxyBuilder.callSuper(o, method, objects); 538 } 539 }; 540 541 FooThrows fooThrows = proxyFor(FooThrows.class) 542 .handler(handler) 543 .build(); 544 545 try { 546 fooThrows.foo(); 547 fail(); 548 } catch (IllegalStateException expected) { 549 assertEquals("boom!", expected.getMessage()); 550 } 551 } 552 553 public static class FooThrows { foo()554 public void foo() { 555 throw new IllegalStateException("boom!"); 556 } 557 } 558 559 public static class DoubleReturn { getValue()560 public double getValue() { 561 return 2.0; 562 } 563 } 564 565 @Test testUnboxedResult()566 public void testUnboxedResult() throws Exception { 567 fakeHandler.fakeResult = 2.0; 568 assertEquals(2.0, proxyFor(DoubleReturn.class).build().getValue(), DELTA_DOUBLE); 569 } 570 staticMethod()571 public static void staticMethod() {} 572 573 @Test testDoesNotOverrideStaticMethods()574 public void testDoesNotOverrideStaticMethods() throws Exception { 575 // Method should exist on this test class itself. 576 ProxyBuilderTest.class.getDeclaredMethod("staticMethod"); 577 // Method should not exist on the subclass. 578 try { 579 proxyFor(ProxyBuilderTest.class).build().getClass().getDeclaredMethod("staticMethod"); 580 fail(); 581 } catch (NoSuchMethodException expected) {} 582 } 583 584 @Test(expected = IOException.class) testIllegalCacheDirectory()585 public void testIllegalCacheDirectory() throws Exception { 586 proxyFor(ProxyForIllegalCacheDirectory.class) 587 .dexCache(new File("/poop/")) 588 .build(); 589 } 590 591 public static class ProxyForIllegalCacheDirectory {} 592 593 @Test(expected = IllegalArgumentException.class) testInvalidConstructorSpecification()594 public void testInvalidConstructorSpecification() throws Exception { 595 proxyFor(Object.class) 596 .constructorArgTypes(String.class, Boolean.class) 597 .constructorArgValues("asdf", true) 598 .build(); 599 } 600 601 public static abstract class AbstractClass { getValue()602 public abstract Object getValue(); 603 } 604 605 @Test testAbstractClassBehaviour()606 public void testAbstractClassBehaviour() throws Exception { 607 assertEquals("fake result", proxyFor(AbstractClass.class).build().getValue()); 608 } 609 610 @Test testCallAbstractSuperMethod()611 public void testCallAbstractSuperMethod() throws Exception { 612 AbstractClass a = proxyFor(AbstractClass.class).build(); 613 614 // Setting the handler to null routes all calls to the real methods. In this case the real 615 // method is abstract and cannot be called 616 ProxyBuilder.setInvocationHandler(a, null); 617 618 try { 619 a.getValue(); 620 fail(); 621 } catch (AbstractMethodError expected) { 622 } 623 } 624 625 public static class CtorHasDeclaredException { CtorHasDeclaredException()626 public CtorHasDeclaredException() throws IOException { 627 throw new IOException(); 628 } 629 } 630 631 public static class CtorHasRuntimeException { CtorHasRuntimeException()632 public CtorHasRuntimeException() { 633 throw new RuntimeException("my message"); 634 } 635 } 636 637 public static class CtorHasError { CtorHasError()638 public CtorHasError() { 639 throw new Error("my message again"); 640 } 641 } 642 643 @Test testParentConstructorThrowsDeclaredException()644 public void testParentConstructorThrowsDeclaredException() throws Exception { 645 try { 646 proxyFor(CtorHasDeclaredException.class).build(); 647 fail(); 648 } catch (UndeclaredThrowableException expected) { 649 assertTrue(expected.getCause() instanceof IOException); 650 } 651 try { 652 proxyFor(CtorHasRuntimeException.class).build(); 653 fail(); 654 } catch (RuntimeException expected) { 655 assertEquals("my message", expected.getMessage()); 656 } 657 try { 658 proxyFor(CtorHasError.class).build(); 659 } catch (Error expected) { 660 assertEquals("my message again", expected.getMessage()); 661 return; 662 } 663 fail(); 664 } 665 666 @Test testGetInvocationHandler_NormalOperation()667 public void testGetInvocationHandler_NormalOperation() throws Exception { 668 Object proxy = proxyFor(Object.class).build(); 669 assertSame(fakeHandler, ProxyBuilder.getInvocationHandler(proxy)); 670 } 671 672 @Test(expected = IllegalArgumentException.class) testGetInvocationHandler_NotAProxy()673 public void testGetInvocationHandler_NotAProxy() { 674 ProxyBuilder.getInvocationHandler(new Object()); 675 } 676 677 public static class ReturnsObject { getValue()678 public Object getValue() { 679 return new Object(); 680 } 681 } 682 683 public static class ReturnsString extends ReturnsObject { 684 @Override getValue()685 public String getValue() { 686 return "a string"; 687 } 688 } 689 690 @Test testCovariantReturnTypes_NormalBehaviour()691 public void testCovariantReturnTypes_NormalBehaviour() throws Exception { 692 String expected = "some string"; 693 fakeHandler.setFakeResult(expected); 694 assertSame(expected, proxyFor(ReturnsObject.class).build().getValue()); 695 assertSame(expected, proxyFor(ReturnsString.class).build().getValue()); 696 } 697 698 @Test testCovariantReturnTypes_WrongReturnType()699 public void testCovariantReturnTypes_WrongReturnType() throws Exception { 700 try { 701 fakeHandler.setFakeResult(new Object()); 702 proxyFor(ReturnsString.class).build().getValue(); 703 fail(); 704 } catch (ClassCastException expected) {} 705 } 706 707 @Test testCaching()708 public void testCaching() throws Exception { 709 SimpleClass a = proxyFor(SimpleClass.class).build(); 710 SimpleClass b = proxyFor(SimpleClass.class).build(); 711 assertSame(a.getClass(), b.getClass()); 712 } 713 714 @Test testCachingWithMultipleConstructors()715 public void testCachingWithMultipleConstructors() throws Exception { 716 HasMultipleConstructors a = ProxyBuilder.forClass(HasMultipleConstructors.class) 717 .constructorArgTypes() 718 .constructorArgValues() 719 .handler(fakeHandler) 720 .dexCache(DexMakerTest.getDataDirectory()).build(); 721 assertEquals("no args", a.calledConstructor); 722 HasMultipleConstructors b = ProxyBuilder.forClass(HasMultipleConstructors.class) 723 .constructorArgTypes(int.class) 724 .constructorArgValues(2) 725 .handler(fakeHandler) 726 .dexCache(DexMakerTest.getDataDirectory()).build(); 727 assertEquals("int 2", b.calledConstructor); 728 assertEquals(a.getClass(), b.getClass()); 729 730 HasMultipleConstructors c = ProxyBuilder.forClass(HasMultipleConstructors.class) 731 .constructorArgTypes(Integer.class) 732 .constructorArgValues(3) 733 .handler(fakeHandler) 734 .dexCache(DexMakerTest.getDataDirectory()).build(); 735 assertEquals("Integer 3", c.calledConstructor); 736 assertEquals(a.getClass(), c.getClass()); 737 } 738 739 public static class HasMultipleConstructors { 740 private final String calledConstructor; HasMultipleConstructors()741 public HasMultipleConstructors() { 742 calledConstructor = "no args"; 743 } HasMultipleConstructors(int b)744 public HasMultipleConstructors(int b) { 745 calledConstructor = "int " + b; 746 } HasMultipleConstructors(Integer c)747 public HasMultipleConstructors(Integer c) { 748 calledConstructor = "Integer " + c; 749 } 750 } 751 752 @Test testClassNotCachedWithDifferentParentClassLoaders()753 public void testClassNotCachedWithDifferentParentClassLoaders() throws Exception { 754 ClassLoader classLoaderA = newPathClassLoader(); 755 SimpleClass a = proxyFor(SimpleClass.class) 756 .parentClassLoader(classLoaderA) 757 .build(); 758 assertEquals(classLoaderA, a.getClass().getClassLoader().getParent()); 759 760 ClassLoader classLoaderB = newPathClassLoader(); 761 SimpleClass b = proxyFor(SimpleClass.class) 762 .parentClassLoader(classLoaderB) 763 .build(); 764 assertEquals(classLoaderB, b.getClass().getClassLoader().getParent()); 765 766 assertTrue(a.getClass() != b.getClass()); 767 } 768 769 @Test testAbstractClassWithUndeclaredInterfaceMethod()770 public void testAbstractClassWithUndeclaredInterfaceMethod() throws Throwable { 771 DeclaresInterface declaresInterface = proxyFor(DeclaresInterface.class) 772 .build(); 773 assertEquals("fake result", declaresInterface.call()); 774 try { 775 ProxyBuilder.callSuper(declaresInterface, Callable.class.getMethod("call")); 776 fail(); 777 } catch (IncompatibleClassChangeError expected) { 778 } 779 } 780 781 public static abstract class DeclaresInterface implements Callable<String> {} 782 783 @Test testImplementingInterfaces()784 public void testImplementingInterfaces() throws Throwable { 785 SimpleClass simpleClass = proxyFor(SimpleClass.class) 786 .implementing(Callable.class) 787 .implementing(Comparable.class) 788 .build(); 789 assertEquals("fake result", simpleClass.simpleMethod()); 790 791 Callable<?> asCallable = (Callable<?>) simpleClass; 792 assertEquals("fake result", asCallable.call()); 793 794 Comparable<?> asComparable = (Comparable<?>) simpleClass; 795 fakeHandler.fakeResult = 3; 796 assertEquals(3, asComparable.compareTo(null)); 797 } 798 799 @Test testCallSuperWithInterfaceMethod()800 public void testCallSuperWithInterfaceMethod() throws Throwable { 801 SimpleClass simpleClass = proxyFor(SimpleClass.class) 802 .implementing(Callable.class) 803 .build(); 804 try { 805 ProxyBuilder.callSuper(simpleClass, Callable.class.getMethod("call")); 806 fail(); 807 } catch (AbstractMethodError expected) { 808 } catch (NoSuchMethodError expected) { 809 } 810 } 811 812 @Test testImplementInterfaceCallingThroughConcreteClass()813 public void testImplementInterfaceCallingThroughConcreteClass() throws Throwable { 814 InvocationHandler invocationHandler = new InvocationHandler() { 815 @Override 816 public Object invoke(Object o, Method method, Object[] objects) throws Throwable { 817 assertEquals("a", ProxyBuilder.callSuper(o, method, objects)); 818 return "b"; 819 } 820 }; 821 ImplementsCallable proxy = proxyFor(ImplementsCallable.class) 822 .implementing(Callable.class) 823 .handler(invocationHandler) 824 .build(); 825 assertEquals("b", proxy.call()); 826 assertEquals("a", ProxyBuilder.callSuper( 827 proxy, ImplementsCallable.class.getMethod("call"))); 828 } 829 830 /** 831 * This test is a bit unintuitive because it exercises the synthetic methods 832 * that support covariant return types. Calling 'Object call()' on the 833 * interface bridges to 'String call()', and so the super method appears to 834 * also be proxied. 835 */ 836 @Test testImplementInterfaceCallingThroughInterface()837 public void testImplementInterfaceCallingThroughInterface() throws Throwable { 838 final AtomicInteger count = new AtomicInteger(); 839 840 InvocationHandler invocationHandler = new InvocationHandler() { 841 @Override 842 public Object invoke(Object o, Method method, Object[] objects) throws Throwable { 843 count.incrementAndGet(); 844 return ProxyBuilder.callSuper(o, method, objects); 845 } 846 }; 847 848 Callable<?> proxy = proxyFor(ImplementsCallable.class) 849 .implementing(Callable.class) 850 .handler(invocationHandler) 851 .build(); 852 853 // the invocation handler is called twice! 854 assertEquals("a", proxy.call()); 855 assertEquals(2, count.get()); 856 857 // the invocation handler is called, even though this is a callSuper() call! 858 assertEquals("a", ProxyBuilder.callSuper(proxy, Callable.class.getMethod("call"))); 859 assertEquals(3, count.get()); 860 } 861 862 public static class ImplementsCallable implements Callable<String> { 863 @Override call()864 public String call() throws Exception { 865 return "a"; 866 } 867 } 868 869 /** 870 * This test shows that our generated proxies follow the bytecode convention 871 * where methods can have the same name but unrelated return types. This is 872 * different from javac's convention where return types must be assignable 873 * in one direction or the other. 874 */ 875 @Test testInterfacesSameNamesDifferentReturnTypes()876 public void testInterfacesSameNamesDifferentReturnTypes() throws Throwable { 877 InvocationHandler handler = new InvocationHandler() { 878 @Override 879 public Object invoke(Object o, Method method, Object[] objects) throws Throwable { 880 if (method.getReturnType() == void.class) { 881 return null; 882 } else if (method.getReturnType() == String.class) { 883 return "X"; 884 } else if (method.getReturnType() == int.class) { 885 return 3; 886 } else { 887 throw new AssertionFailedError(); 888 } 889 } 890 }; 891 892 Object o = proxyFor(Object.class) 893 .implementing(FooReturnsVoid.class, FooReturnsString.class, FooReturnsInt.class) 894 .handler(handler) 895 .build(); 896 897 FooReturnsVoid a = (FooReturnsVoid) o; 898 a.foo(); 899 900 FooReturnsString b = (FooReturnsString) o; 901 assertEquals("X", b.foo()); 902 903 FooReturnsInt c = (FooReturnsInt) o; 904 assertEquals(3, c.foo()); 905 } 906 907 908 @Test testCallInterfaceSuperMethod()909 public void testCallInterfaceSuperMethod() throws Exception { 910 FooReturnsVoid f = (FooReturnsVoid)proxyFor(Object.class).implementing(FooReturnsVoid.class) 911 .build(); 912 913 // Setting the handler to null routes all calls to the real methods. In this case the real 914 // method is a method of an interface and cannot be called 915 ProxyBuilder.setInvocationHandler(f, null); 916 917 try { 918 f.foo(); 919 fail(); 920 } catch (AbstractMethodError expected) { 921 } 922 } 923 924 @Test testInterfacesSameNamesSameReturnType()925 public void testInterfacesSameNamesSameReturnType() throws Throwable { 926 Object o = proxyFor(Object.class) 927 .implementing(FooReturnsInt.class, FooReturnsInt2.class) 928 .build(); 929 930 fakeHandler.setFakeResult(3); 931 932 FooReturnsInt a = (FooReturnsInt) o; 933 assertEquals(3, a.foo()); 934 935 FooReturnsInt2 b = (FooReturnsInt2) o; 936 assertEquals(3, b.foo()); 937 } 938 939 public interface FooReturnsVoid { foo()940 void foo(); 941 } 942 943 public interface FooReturnsString { foo()944 String foo(); 945 } 946 947 public interface FooReturnsInt { foo()948 int foo(); 949 } 950 951 public interface FooReturnsInt2 { foo()952 int foo(); 953 } 954 newPathClassLoader()955 private ClassLoader newPathClassLoader() throws Exception { 956 return (ClassLoader) Class.forName("dalvik.system.PathClassLoader") 957 .getConstructor(String.class, ClassLoader.class) 958 .newInstance("", getClass().getClassLoader()); 959 960 } 961 962 @Test testSubclassOfRandom()963 public void testSubclassOfRandom() throws Exception { 964 proxyFor(Random.class) 965 .handler(new InvokeSuperHandler()) 966 .build(); 967 } 968 969 public static class FinalToString { 970 @Override toString()971 public final String toString() { 972 return "no proxy"; 973 } 974 } 975 976 // https://code.google.com/p/dexmaker/issues/detail?id=12 977 @Test testFinalToString()978 public void testFinalToString() throws Throwable { 979 assertEquals("no proxy", proxyFor(FinalToString.class).build().toString()); 980 } 981 982 public static class FinalInterfaceImpl implements FooReturnsString { 983 @Override foo()984 public final String foo() { 985 return "no proxy"; 986 } 987 } 988 989 public static class ExtenstionOfFinalInterfaceImpl extends FinalInterfaceImpl 990 implements FooReturnsString { 991 } 992 993 @Test testFinalInterfaceImpl()994 public void testFinalInterfaceImpl() throws Throwable { 995 assertEquals("no proxy", proxyFor(ExtenstionOfFinalInterfaceImpl.class).build().foo()); 996 } 997 998 // https://code.google.com/p/dexmaker/issues/detail?id=9 999 public interface DeclaresMethodLate { thisIsTheMethod()1000 void thisIsTheMethod(); 1001 } 1002 1003 public static class MakesMethodFinalEarly { thisIsTheMethod()1004 public final void thisIsTheMethod() {} 1005 } 1006 1007 public static class YouDoNotChooseYourFamily 1008 extends MakesMethodFinalEarly implements DeclaresMethodLate {} 1009 1010 @Test testInterfaceMethodMadeFinalBeforeActualInheritance()1011 public void testInterfaceMethodMadeFinalBeforeActualInheritance() throws Exception { 1012 proxyFor(YouDoNotChooseYourFamily.class).build(); 1013 } 1014 1015 public interface ExtendsAnotherInterface extends FooReturnsString { 1016 1017 } 1018 1019 @Test testExtraInterfaceExtendsInterface()1020 public void testExtraInterfaceExtendsInterface() throws Exception { 1021 ExtendsAnotherInterface proxy = (ExtendsAnotherInterface) 1022 proxyFor(SimpleClass.class) 1023 .implementing(ExtendsAnotherInterface.class) 1024 .build(); 1025 fakeHandler.setFakeResult(ExtendsAnotherInterface.class.getName()); 1026 assertEquals(ExtendsAnotherInterface.class.getName(), proxy.foo()); 1027 } 1028 1029 /** Simple helper to add the most common args for this test to the proxy builder. */ proxyFor(Class<T> clazz)1030 private <T> ProxyBuilder<T> proxyFor(Class<T> clazz) throws Exception { 1031 return ProxyBuilder.forClass(clazz) 1032 .handler(fakeHandler) 1033 .dexCache(DexMakerTest.getDataDirectory()); 1034 } 1035 1036 private static class FakeInvocationHandler implements InvocationHandler { 1037 private Object fakeResult = "fake result"; 1038 1039 @Override invoke(Object proxy, Method method, Object[] args)1040 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 1041 return fakeResult; 1042 } 1043 setFakeResult(Object result)1044 public void setFakeResult(Object result) { 1045 fakeResult = result; 1046 } 1047 } 1048 1049 public static class TestOrderingClass { returnsInt()1050 public int returnsInt() { 1051 return 0; 1052 } 1053 returnsInt(int param1, int param2)1054 public int returnsInt(int param1, int param2) { 1055 return 1; 1056 } 1057 returnsString()1058 public String returnsString() { 1059 return "string"; 1060 } 1061 returnsBoolean()1062 public boolean returnsBoolean() { 1063 return false; 1064 } 1065 returnsDouble()1066 public double returnsDouble() { 1067 return 1.0; 1068 } 1069 returnsObject()1070 public Object returnsObject() { 1071 return new Object(); 1072 } 1073 } 1074 1075 @Test 1076 @SuppressWarnings("unchecked") testMethodsGeneratedInDeterministicOrder()1077 public void testMethodsGeneratedInDeterministicOrder() throws Exception { 1078 // Grab the static methods array from the original class. 1079 Method[] methods1 = getMethodsForProxyClass(TestOrderingClass.class); 1080 assertNotNull(methods1); 1081 1082 // Clear ProxyBuilder's in-memory cache of classes. This will force 1083 // it to rebuild the class and reset the static methods field. 1084 Map<Class<?>, Class<?>> map = getGeneratedProxyClasses(); 1085 assertNotNull(map); 1086 map.clear(); 1087 1088 // Grab the static methods array from the rebuilt class. 1089 Method[] methods2 = getMethodsForProxyClass(TestOrderingClass.class); 1090 assertNotNull(methods2); 1091 1092 // Ensure that the two method arrays are equal. 1093 assertTrue(Arrays.equals(methods1, methods2)); 1094 } 1095 1096 @Test testOrderingClassWithDexMakerCaching()1097 public void testOrderingClassWithDexMakerCaching() throws Exception { 1098 doTestOrderClassWithDexMakerCaching(); 1099 1100 // Force ProxyBuilder to call DexMaker.generateAndLoad() 1101 getGeneratedProxyClasses().clear(); 1102 1103 doTestOrderClassWithDexMakerCaching(); 1104 } 1105 doTestOrderClassWithDexMakerCaching()1106 private void doTestOrderClassWithDexMakerCaching() throws Exception { 1107 TestOrderingClass proxy = ProxyBuilder.forClass(TestOrderingClass.class) 1108 .handler(new InvokeSuperHandler()) 1109 .dexCache(DexMakerTest.getDataDirectory()) 1110 .build(); 1111 assertEquals(0, proxy.returnsInt()); 1112 assertEquals(1, proxy.returnsInt(1, 1)); 1113 assertEquals("string", proxy.returnsString()); 1114 assertFalse(proxy.returnsBoolean()); 1115 assertEquals(1.0, proxy.returnsDouble(), DELTA_DOUBLE); 1116 assertNotNull(proxy.returnsObject()); 1117 assertTrue(versionedDxDir.listFiles().length != 0); 1118 } 1119 1120 // Returns static methods array from a proxy class. getMethodsForProxyClass(Class<?> parentClass)1121 private Method[] getMethodsForProxyClass(Class<?> parentClass) throws Exception { 1122 Class<?> proxyClass = proxyFor(parentClass).buildProxyClass(); 1123 Method[] methods = null; 1124 for (Field f : proxyClass.getDeclaredFields()) { 1125 if (Method[].class.isAssignableFrom(f.getType())) { 1126 f.setAccessible(true); 1127 methods = (Method[]) f.get(null); 1128 break; 1129 } 1130 } 1131 1132 return methods; 1133 } 1134 getGeneratedProxyClasses()1135 private Map<Class<?>, Class<?>> getGeneratedProxyClasses() throws Exception { 1136 Field mapField = ProxyBuilder.class 1137 .getDeclaredField("generatedProxyClasses"); 1138 mapField.setAccessible(true); 1139 return (Map<Class<?>, Class<?>>) mapField.get(null); 1140 } 1141 1142 public static class ConcreteClassA implements FooReturnsInt { 1143 @Override 1144 // from FooReturnsInt foo()1145 public int foo() { 1146 return 1; 1147 } 1148 1149 // not from FooReturnsInt bar()1150 public String bar() { 1151 return "bar"; 1152 } 1153 } 1154 1155 public static class ConcreteClassB implements FooReturnsInt { 1156 @Override 1157 // from FooReturnsInt foo()1158 public int foo() { 1159 return 0; 1160 } 1161 1162 // not from FooReturnsInt bar()1163 public String bar() { 1164 return "bahhr"; 1165 } 1166 } 1167 1168 @Test testTwoClassesWithIdenticalMethodSignatures_DexMakerCaching()1169 public void testTwoClassesWithIdenticalMethodSignatures_DexMakerCaching() throws Exception { 1170 ConcreteClassA proxyA = ProxyBuilder.forClass(ConcreteClassA.class) 1171 .handler(new InvokeSuperHandler()) 1172 .dexCache(DexMakerTest.getDataDirectory()) 1173 .build(); 1174 assertEquals(1, proxyA.foo()); 1175 assertEquals("bar", proxyA.bar()); 1176 int numFiles = versionedDxDir.listFiles().length; 1177 assertTrue(numFiles > 0); 1178 1179 ConcreteClassB proxyB = ProxyBuilder.forClass(ConcreteClassB.class) 1180 .handler(new InvokeSuperHandler()) 1181 .dexCache(DexMakerTest.getDataDirectory()) 1182 .build(); 1183 assertEquals(0, proxyB.foo()); 1184 assertEquals("bahhr", proxyB.bar()); 1185 assertTrue(numFiles < versionedDxDir.listFiles().length); 1186 } 1187 1188 public static abstract class PartiallyFinalClass { 1189 public String returnA() { 1190 return "A"; 1191 } 1192 1193 public String returnB() { 1194 return "B"; 1195 } 1196 1197 public String returnC() { 1198 return "C"; 1199 } 1200 1201 public final String returnD() { 1202 return "D"; 1203 } 1204 1205 public abstract String returnE(); 1206 } 1207 1208 @Test 1209 public void testProxyingSomeMethods() throws Throwable { 1210 ArrayList<Method> methodsToOverride = new ArrayList<>(); 1211 for (Method method : PartiallyFinalClass.class.getDeclaredMethods()) { 1212 if (!Modifier.isFinal(method.getModifiers()) && !method.getName().equals("returnC")) { 1213 methodsToOverride.add(method); 1214 } 1215 } 1216 1217 InvocationHandler handler = new InvokeSuperHandler() { 1218 @Override 1219 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 1220 if (method.getName().equals("returnA")) { 1221 return "fake A"; 1222 } else if (method.getName().equals("returnC")) { 1223 // This will never trigger as "returnC" is not overridden 1224 return "fake C"; 1225 } else if (method.getName().equals("returnE")) { 1226 return "fake E"; 1227 } else { 1228 return super.invoke(proxy, method, args); 1229 } 1230 } 1231 }; 1232 1233 PartiallyFinalClass proxy = ProxyBuilder.forClass(PartiallyFinalClass.class) 1234 .handler(handler).onlyMethods(methodsToOverride.toArray(new Method[]{})).build(); 1235 1236 assertEquals("fake A", proxy.returnA()); 1237 assertEquals("B", proxy.returnB()); 1238 assertEquals("C", proxy.returnC()); 1239 assertEquals("D", proxy.returnD()); 1240 assertEquals("fake E", proxy.returnE()); 1241 1242 } 1243 1244 @Test 1245 public void testImplementingDifferentInterfacesWithSharedClassLoader() throws IOException { 1246 assumeTrue(Build.VERSION.SDK_INT >= 24); 1247 1248 Class<?> c1 = ProxyBuilder.forClass(SimpleClass.class) 1249 .implementing(Runnable.class).withSharedClassLoader().buildProxyClass(); 1250 Class<?> c2 = ProxyBuilder.forClass(SimpleClass.class) 1251 .implementing(Callable.class).withSharedClassLoader().buildProxyClass(); 1252 assertNotSame(c1, c2); 1253 } 1254 1255 @Test testInterfaceOrder()1256 public void testInterfaceOrder() throws Exception { 1257 Class<?> c1 = proxyFor(SimpleClass.class) 1258 .implementing(Runnable.class, Callable.class).buildProxyClass(); 1259 assertEquals(new Class[]{Runnable.class, Callable.class}, c1.getInterfaces()); 1260 1261 Class<?> c2 = proxyFor(SimpleClass.class) 1262 .implementing(Callable.class, Runnable.class).buildProxyClass(); 1263 assertEquals(new Class[]{Callable.class, Runnable.class}, c2.getInterfaces()); 1264 } 1265 } 1266