• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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