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;
18 
19 import static com.android.dx.util.TestUtil.DELTA_DOUBLE;
20 import static com.android.dx.util.TestUtil.DELTA_FLOAT;
21 
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertFalse;
24 import static org.junit.Assert.assertNotNull;
25 import static org.junit.Assert.assertNotSame;
26 import static org.junit.Assert.assertSame;
27 import static org.junit.Assert.assertTrue;
28 import static org.junit.Assert.fail;
29 import static org.junit.Assume.assumeTrue;
30 
31 import android.os.Build;
32 
33 import androidx.test.InstrumentationRegistry;
34 
35 import dalvik.system.BaseDexClassLoader;
36 
37 import org.junit.Before;
38 import org.junit.Test;
39 
40 import static java.lang.reflect.Modifier.ABSTRACT;
41 import static java.lang.reflect.Modifier.FINAL;
42 import static java.lang.reflect.Modifier.NATIVE;
43 import static java.lang.reflect.Modifier.PRIVATE;
44 import static java.lang.reflect.Modifier.PROTECTED;
45 import static java.lang.reflect.Modifier.PUBLIC;
46 import static java.lang.reflect.Modifier.STATIC;
47 import static java.lang.reflect.Modifier.SYNCHRONIZED;
48 
49 import java.io.File;
50 import java.io.FilenameFilter;
51 import java.io.IOException;
52 import java.lang.reflect.Field;
53 import java.lang.reflect.InvocationTargetException;
54 import java.lang.reflect.Method;
55 import java.lang.reflect.Modifier;
56 import java.util.Arrays;
57 import java.util.List;
58 import java.util.concurrent.Callable;
59 
60 /**
61  * This generates a class named 'Generated' with one or more generated methods
62  * and fields. In loads the generated class into the current VM and uses
63  * reflection to invoke its methods.
64  *
65  * <p>This test must run on a Dalvik VM.
66  */
67 public final class DexMakerTest {
68     private DexMaker dexMaker;
69     private static TypeId<DexMakerTest> TEST_TYPE = TypeId.get(DexMakerTest.class);
70     private static TypeId<?> INT_ARRAY = TypeId.get(int[].class);
71     private static TypeId<boolean[]> BOOLEAN_ARRAY = TypeId.get(boolean[].class);
72     private static TypeId<long[]> LONG_ARRAY = TypeId.get(long[].class);
73     private static TypeId<Object[]> OBJECT_ARRAY = TypeId.get(Object[].class);
74     private static TypeId<long[][]> LONG_2D_ARRAY = TypeId.get(long[][].class);
75     private static TypeId<?> GENERATED = TypeId.get("LGenerated;");
76     private static TypeId<Callable> CALLABLE = TypeId.get(Callable.class);
77     private static MethodId<Callable, Object> CALL = CALLABLE.getMethod(TypeId.OBJECT, "call");
78 
79     @Before
setup()80     public void setup() {
81         reset();
82     }
83 
84     /**
85      * The generator is mutable. Calling reset creates a new empty generator.
86      * This is necessary to generate multiple classes in the same test method.
87      */
reset()88     private void reset() {
89         dexMaker = new DexMaker();
90         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT);
91         clearDataDirectory();
92     }
93 
clearDataDirectory()94     private void clearDataDirectory() {
95         for (File f : getDataDirectory().listFiles()) {
96             if (f.getName().endsWith(".jar") || f.getName().endsWith(".dex")) {
97                 f.delete();
98             }
99         }
100     }
101 
102     @Test
testNewInstance()103     public void testNewInstance() throws Exception {
104         /*
105          * public static Constructable call(long a, boolean b) {
106          *   Constructable result = new Constructable(a, b);
107          *   return result;
108          * }
109          */
110         TypeId<Constructable> constructable = TypeId.get(Constructable.class);
111         MethodId<?, Constructable> methodId = GENERATED.getMethod(
112                 constructable, "call", TypeId.LONG, TypeId.BOOLEAN);
113         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
114         Local<Long> localA = code.getParameter(0, TypeId.LONG);
115         Local<Boolean> localB = code.getParameter(1, TypeId.BOOLEAN);
116         MethodId<Constructable, Void> constructor
117                 = constructable.getConstructor(TypeId.LONG, TypeId.BOOLEAN);
118         Local<Constructable> localResult = code.newLocal(constructable);
119         code.newInstance(localResult, constructor, localA, localB);
120         code.returnValue(localResult);
121 
122         Constructable constructed = (Constructable) getMethod().invoke(null, 5L, false);
123         assertEquals(5L, constructed.a);
124         assertEquals(false, constructed.b);
125     }
126 
127     public static class Constructable {
128         private final long a;
129         private final boolean b;
Constructable(long a, boolean b)130         public Constructable(long a, boolean b) {
131             this.a = a;
132             this.b = b;
133         }
134     }
135 
136     @Test
testVoidNoArgMemberMethod()137     public void testVoidNoArgMemberMethod() throws Exception {
138         /*
139          * public void call() {
140          * }
141          */
142         MethodId<?, Void> methodId = GENERATED.getMethod(TypeId.VOID, "call");
143         Code code = dexMaker.declare(methodId, PUBLIC);
144         code.returnVoid();
145 
146         addDefaultConstructor();
147 
148         Class<?> generatedClass = generateAndLoad();
149         Object instance = generatedClass.getDeclaredConstructor().newInstance();
150         Method method = generatedClass.getMethod("call");
151         method.invoke(instance);
152     }
153 
154     @Test
testInvokeStatic()155     public void testInvokeStatic() throws Exception {
156         /*
157          * public static int call(int a) {
158          *   int result = DexMakerTest.staticMethod(a);
159          *   return result;
160          * }
161          */
162         MethodId<?, Integer> methodId = GENERATED.getMethod(TypeId.INT, "call", TypeId.INT);
163         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
164         Local<Integer> localA = code.getParameter(0, TypeId.INT);
165         Local<Integer> localResult = code.newLocal(TypeId.INT);
166         MethodId<?, Integer> staticMethod
167                 = TEST_TYPE.getMethod(TypeId.INT, "staticMethod", TypeId.INT);
168         code.invokeStatic(staticMethod, localResult, localA);
169         code.returnValue(localResult);
170 
171         assertEquals(10, getMethod().invoke(null, 4));
172     }
173 
174     @Test
testCreateLocalMethodAsNull()175     public void testCreateLocalMethodAsNull() throws Exception {
176         /*
177          * public void call(int value) {
178          *   Method method = null;
179          * }
180          */
181         MethodId<?, Void> methodId = GENERATED.getMethod(TypeId.VOID, "call", TypeId.INT);
182         TypeId<Method> methodType = TypeId.get(Method.class);
183         Code code = dexMaker.declare(methodId, PUBLIC);
184         Local<Method> localMethod = code.newLocal(methodType);
185         code.loadConstant(localMethod, null);
186         code.returnVoid();
187 
188         addDefaultConstructor();
189 
190         Class<?> generatedClass = generateAndLoad();
191         Object instance = generatedClass.getDeclaredConstructor().newInstance();
192         Method method = generatedClass.getMethod("call", int.class);
193         method.invoke(instance, 0);
194     }
195 
196     @SuppressWarnings("unused") // called by generated code
staticMethod(int a)197     public static int staticMethod(int a) {
198         return a + 6;
199     }
200 
201     @Test
testInvokeVirtual()202     public void testInvokeVirtual() throws Exception {
203         /*
204          * public static int call(DexMakerTest test, int a) {
205          *   int result = test.virtualMethod(a);
206          *   return result;
207          * }
208          */
209         MethodId<?, Integer> methodId = GENERATED.getMethod(TypeId.INT, "call", TEST_TYPE, TypeId.INT);
210         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
211         Local<DexMakerTest> localInstance = code.getParameter(0, TEST_TYPE);
212         Local<Integer> localA = code.getParameter(1, TypeId.INT);
213         Local<Integer> localResult = code.newLocal(TypeId.INT);
214         MethodId<DexMakerTest, Integer> virtualMethod
215                 = TEST_TYPE.getMethod(TypeId.INT, "virtualMethod", TypeId.INT);
216         code.invokeVirtual(virtualMethod, localResult, localInstance, localA);
217         code.returnValue(localResult);
218 
219         assertEquals(9, getMethod().invoke(null, this, 4));
220     }
221 
222     @SuppressWarnings("unused") // called by generated code
virtualMethod(int a)223     public int virtualMethod(int a) {
224         return a + 5;
225     }
226 
227     @Test
testInvokeDirect()228     public <G> void testInvokeDirect() throws Exception {
229         /*
230          * private int directMethod() {
231          *   int a = 5;
232          *   return a;
233          * }
234          *
235          * public static int call(Generated g) {
236          *   int b = g.directMethod();
237          *   return b;
238          * }
239          */
240         TypeId<G> generated = TypeId.get("LGenerated;");
241         MethodId<G, Integer> directMethodId = generated.getMethod(TypeId.INT, "directMethod");
242         Code directCode = dexMaker.declare(directMethodId, PRIVATE);
243         directCode.getThis(generated); // 'this' is unused
244         Local<Integer> localA = directCode.newLocal(TypeId.INT);
245         directCode.loadConstant(localA, 5);
246         directCode.returnValue(localA);
247 
248         MethodId<G, Integer> methodId = generated.getMethod(TypeId.INT, "call", generated);
249         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
250         Local<Integer> localB = code.newLocal(TypeId.INT);
251         Local<G> localG = code.getParameter(0, generated);
252         code.invokeDirect(directMethodId, localB, localG);
253         code.returnValue(localB);
254 
255         addDefaultConstructor();
256 
257         Class<?> generatedClass = generateAndLoad();
258         Object instance = generatedClass.getDeclaredConstructor().newInstance();
259         Method method = generatedClass.getMethod("call", generatedClass);
260         assertEquals(5, method.invoke(null, instance));
261     }
262 
263     @Test
testInvokeSuper()264     public <G> void testInvokeSuper() throws Exception {
265         /*
266          * public int superHashCode() {
267          *   int result = super.hashCode();
268          *   return result;
269          * }
270          * public int hashCode() {
271          *   return 0;
272          * }
273          */
274         TypeId<G> generated = TypeId.get("LGenerated;");
275         MethodId<Object, Integer> objectHashCode = TypeId.OBJECT.getMethod(TypeId.INT, "hashCode");
276         Code superHashCode = dexMaker.declare(
277                 GENERATED.getMethod(TypeId.INT, "superHashCode"), PUBLIC);
278         Local<Integer> localResult = superHashCode.newLocal(TypeId.INT);
279         Local<G> localThis = superHashCode.getThis(generated);
280         superHashCode.invokeSuper(objectHashCode, localResult, localThis);
281         superHashCode.returnValue(localResult);
282 
283         Code generatedHashCode = dexMaker.declare(
284                 GENERATED.getMethod(TypeId.INT, "hashCode"), PUBLIC);
285         Local<Integer> localZero = generatedHashCode.newLocal(TypeId.INT);
286         generatedHashCode.loadConstant(localZero, 0);
287         generatedHashCode.returnValue(localZero);
288 
289         addDefaultConstructor();
290 
291         Class<?> generatedClass = generateAndLoad();
292         Object instance = generatedClass.getDeclaredConstructor().newInstance();
293         Method method = generatedClass.getMethod("superHashCode");
294         assertEquals(System.identityHashCode(instance), method.invoke(instance));
295     }
296 
297     @Test
testInvokeInterface()298     public void testInvokeInterface() throws Exception {
299         /*
300          * public static Object call(Callable c) {
301          *   Object result = c.call();
302          *   return result;
303          * }
304          */
305         MethodId<?, Object> methodId = GENERATED.getMethod(TypeId.OBJECT, "call", CALLABLE);
306         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
307         Local<Callable> localC = code.getParameter(0, CALLABLE);
308         Local<Object> localResult = code.newLocal(TypeId.OBJECT);
309         code.invokeInterface(CALL, localResult, localC);
310         code.returnValue(localResult);
311 
312         Callable<Object> callable = new Callable<Object>() {
313             @Override
314             public Object call() throws Exception {
315                 return "abc";
316             }
317         };
318         assertEquals("abc", getMethod().invoke(null, callable));
319     }
320 
321     @Test
testInvokeVoidMethodIgnoresTargetLocal()322     public void testInvokeVoidMethodIgnoresTargetLocal() throws Exception {
323         /*
324          * public static int call() {
325          *   int result = 5;
326          *   DexMakerTest.voidMethod();
327          *   return result;
328          * }
329          */
330         MethodId<?, Integer> methodId = GENERATED.getMethod(TypeId.INT, "call");
331         MethodId<?, Void> voidMethod = TEST_TYPE.getMethod(TypeId.VOID, "voidMethod");
332         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
333         Local<Integer> result = code.newLocal(TypeId.INT);
334         code.loadConstant(result, 5);
335         code.invokeStatic(voidMethod, null);
336         code.returnValue(result);
337 
338         assertEquals(5, getMethod().invoke(null));
339     }
340 
341     @SuppressWarnings("unused") // called by generated code
voidMethod()342     public static void voidMethod() {}
343 
344     @Test
testParameterMismatch()345     public void testParameterMismatch() throws Exception {
346         TypeId<?>[] argTypes = {
347                 TypeId.get(Integer.class), // should fail because the code specifies int
348                 TypeId.OBJECT,
349         };
350         MethodId<?, Integer> methodId = GENERATED.getMethod(TypeId.INT, "call", argTypes);
351         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
352         try {
353             code.getParameter(0, TypeId.INT);
354         } catch (IllegalArgumentException e) {
355         }
356         try {
357             code.getParameter(2, TypeId.INT);
358         } catch (IndexOutOfBoundsException e) {
359         }
360     }
361 
362     @Test
testInvokeTypeSafety()363     public void testInvokeTypeSafety() throws Exception {
364         /*
365          * public static boolean call(DexMakerTest test) {
366          *   CharSequence cs = test.toString();
367          *   boolean result = cs.equals(test);
368          *   return result;
369          * }
370          */
371         MethodId<?, Boolean> methodId = GENERATED.getMethod(TypeId.BOOLEAN, "call", TEST_TYPE);
372         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
373         Local<DexMakerTest> localTest = code.getParameter(0, TEST_TYPE);
374         TypeId<CharSequence> charSequenceType = TypeId.get(CharSequence.class);
375         MethodId<Object, String> objectToString
376                 = TypeId.OBJECT.getMethod(TypeId.STRING, "toString");
377         MethodId<Object, Boolean> objectEquals
378                 = TypeId.OBJECT.getMethod(TypeId.BOOLEAN, "equals", TypeId.OBJECT);
379         Local<CharSequence> localCs = code.newLocal(charSequenceType);
380         Local<Boolean> localResult = code.newLocal(TypeId.BOOLEAN);
381         code.invokeVirtual(objectToString, localCs, localTest);
382         code.invokeVirtual(objectEquals, localResult, localCs, localTest);
383         code.returnValue(localResult);
384 
385         assertEquals(false, getMethod().invoke(null, this));
386     }
387 
388     @Test
testReturnTypeMismatch()389     public void testReturnTypeMismatch() {
390         MethodId<?, String> methodId = GENERATED.getMethod(TypeId.STRING, "call");
391         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
392         try {
393             code.returnValue(code.newLocal(TypeId.BOOLEAN));
394             fail();
395         } catch (IllegalArgumentException expected) {
396         }
397         try {
398             code.returnVoid();
399             fail();
400         } catch (IllegalArgumentException expected) {
401         }
402     }
403 
404     @Test
testDeclareStaticFields()405     public void testDeclareStaticFields() throws Exception {
406         /*
407          * class Generated {
408          *   public static int a;
409          *   protected static Object b;
410          * }
411          */
412         dexMaker.declare(GENERATED.getField(TypeId.INT, "a"), PUBLIC | STATIC, 3);
413         dexMaker.declare(GENERATED.getField(TypeId.OBJECT, "b"), PROTECTED | STATIC, null);
414         Class<?> generatedClass = generateAndLoad();
415 
416         Field a = generatedClass.getField("a");
417         assertEquals(int.class, a.getType());
418         assertEquals(3, a.get(null));
419 
420         Field b = generatedClass.getDeclaredField("b");
421         assertEquals(Object.class, b.getType());
422         b.setAccessible(true);
423         assertEquals(null, b.get(null));
424     }
425 
426     @Test
testDeclareInstanceFields()427     public void testDeclareInstanceFields() throws Exception {
428         /*
429          * class Generated {
430          *   public int a;
431          *   protected Object b;
432          * }
433          */
434         dexMaker.declare(GENERATED.getField(TypeId.INT, "a"), PUBLIC, null);
435         dexMaker.declare(GENERATED.getField(TypeId.OBJECT, "b"), PROTECTED, null);
436 
437         addDefaultConstructor();
438 
439         Class<?> generatedClass = generateAndLoad();
440         Object instance = generatedClass.getDeclaredConstructor().newInstance();
441 
442         Field a = generatedClass.getField("a");
443         assertEquals(int.class, a.getType());
444         assertEquals(0, a.get(instance));
445 
446         Field b = generatedClass.getDeclaredField("b");
447         assertEquals(Object.class, b.getType());
448         b.setAccessible(true);
449         assertEquals(null, b.get(instance));
450     }
451 
452     /**
453      * Declare a constructor that takes an int parameter and assigns it to a
454      * field.
455      */
456     @Test
testDeclareConstructor()457     public <G> void testDeclareConstructor() throws Exception {
458         /*
459          * class Generated {
460          *   public final int a;
461          *   public Generated(int a) {
462          *     this.a = a;
463          *   }
464          * }
465          */
466         TypeId<G> generated = TypeId.get("LGenerated;");
467         FieldId<G, Integer> fieldId = generated.getField(TypeId.INT, "a");
468         dexMaker.declare(fieldId, PUBLIC | FINAL, null);
469         MethodId<?, Void> constructor = GENERATED.getConstructor(TypeId.INT);
470         Code code = dexMaker.declare(constructor, PUBLIC);
471         Local<G> thisRef = code.getThis(generated);
472         Local<Integer> parameter = code.getParameter(0, TypeId.INT);
473         code.invokeDirect(TypeId.OBJECT.getConstructor(), null, thisRef);
474         code.iput(fieldId, thisRef, parameter);
475         code.returnVoid();
476 
477         Class<?> generatedClass = generateAndLoad();
478         Field a = generatedClass.getField("a");
479         Object instance = generatedClass.getConstructor(int.class).newInstance(0xabcd);
480         assertEquals(0xabcd, a.get(instance));
481     }
482 
483     @Test
testReturnType()484     public void testReturnType() throws Exception {
485         testReturnType(boolean.class, true);
486         testReturnType(byte.class, (byte) 5);
487         testReturnType(char.class, 'E');
488         testReturnType(double.class, 5.0);
489         testReturnType(float.class, 5.0f);
490         testReturnType(int.class, 5);
491         testReturnType(long.class, 5L);
492         testReturnType(short.class, (short) 5);
493         testReturnType(void.class, null);
494         testReturnType(String.class, "foo");
495         testReturnType(Class.class, List.class);
496     }
497 
testReturnType(Class<T> javaType, T value)498     private <T> void testReturnType(Class<T> javaType, T value) throws Exception {
499         /*
500          * public int call() {
501          *   int a = 5;
502          *   return a;
503          * }
504          */
505         reset();
506         TypeId<T> returnType = TypeId.get(javaType);
507         Code code = dexMaker.declare(GENERATED.getMethod(returnType, "call"), PUBLIC | STATIC);
508         if (value != null) {
509             Local<T> i = code.newLocal(returnType);
510             code.loadConstant(i, value);
511             code.returnValue(i);
512         } else {
513             code.returnVoid();
514         }
515 
516         Class<?> generatedClass = generateAndLoad();
517         Method method = generatedClass.getMethod("call");
518         assertEquals(javaType, method.getReturnType());
519         assertEquals(value, method.invoke(null));
520     }
521 
522     @Test
testBranching()523     public void testBranching() throws Exception {
524         Method lt = branchingMethod(Comparison.LT);
525         assertEquals(Boolean.TRUE, lt.invoke(null, 1, 2));
526         assertEquals(Boolean.FALSE, lt.invoke(null, 1, 1));
527         assertEquals(Boolean.FALSE, lt.invoke(null, 2, 1));
528 
529         Method le = branchingMethod(Comparison.LE);
530         assertEquals(Boolean.TRUE, le.invoke(null, 1, 2));
531         assertEquals(Boolean.TRUE, le.invoke(null, 1, 1));
532         assertEquals(Boolean.FALSE, le.invoke(null, 2, 1));
533 
534         Method eq = branchingMethod(Comparison.EQ);
535         assertEquals(Boolean.FALSE, eq.invoke(null, 1, 2));
536         assertEquals(Boolean.TRUE, eq.invoke(null, 1, 1));
537         assertEquals(Boolean.FALSE, eq.invoke(null, 2, 1));
538 
539         Method ge = branchingMethod(Comparison.GE);
540         assertEquals(Boolean.FALSE, ge.invoke(null, 1, 2));
541         assertEquals(Boolean.TRUE, ge.invoke(null, 1, 1));
542         assertEquals(Boolean.TRUE, ge.invoke(null, 2, 1));
543 
544         Method gt = branchingMethod(Comparison.GT);
545         assertEquals(Boolean.FALSE, gt.invoke(null, 1, 2));
546         assertEquals(Boolean.FALSE, gt.invoke(null, 1, 1));
547         assertEquals(Boolean.TRUE, gt.invoke(null, 2, 1));
548 
549         Method ne = branchingMethod(Comparison.NE);
550         assertEquals(Boolean.TRUE, ne.invoke(null, 1, 2));
551         assertEquals(Boolean.FALSE, ne.invoke(null, 1, 1));
552         assertEquals(Boolean.TRUE, ne.invoke(null, 2, 1));
553     }
554 
branchingMethod(Comparison comparison)555     private Method branchingMethod(Comparison comparison) throws Exception {
556         /*
557          * public static boolean call(int localA, int localB) {
558          *   if (a comparison b) {
559          *     return true;
560          *   }
561          *   return false;
562          * }
563          */
564         reset();
565         MethodId<?, Boolean> methodId = GENERATED.getMethod(
566                 TypeId.BOOLEAN, "call", TypeId.INT, TypeId.INT);
567         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
568         Local<Integer> localA = code.getParameter(0, TypeId.INT);
569         Local<Integer> localB = code.getParameter(1, TypeId.INT);
570         Local<Boolean> result = code.newLocal(TypeId.get(boolean.class));
571         Label afterIf = new Label();
572         Label ifBody = new Label();
573         code.compare(comparison, ifBody, localA, localB);
574         code.jump(afterIf);
575 
576         code.mark(ifBody);
577         code.loadConstant(result, true);
578         code.returnValue(result);
579 
580         code.mark(afterIf);
581         code.loadConstant(result, false);
582         code.returnValue(result);
583         return getMethod();
584     }
585 
586     @Test
testBranchingZ()587     public void testBranchingZ() throws Exception {
588         Method lt = branchingZMethod(Comparison.LT);
589         assertEquals(Boolean.TRUE, lt.invoke(null, -1));
590         assertEquals(Boolean.FALSE, lt.invoke(null, 0));
591         assertEquals(Boolean.FALSE, lt.invoke(null, 1));
592 
593         Method le = branchingZMethod(Comparison.LE);
594         assertEquals(Boolean.TRUE, le.invoke(null, -1));
595         assertEquals(Boolean.TRUE, le.invoke(null, 0));
596         assertEquals(Boolean.FALSE, le.invoke(null, 1));
597 
598         Method eq = branchingZMethod(Comparison.EQ);
599         assertEquals(Boolean.FALSE, eq.invoke(null, -1));
600         assertEquals(Boolean.TRUE, eq.invoke(null, 0));
601         assertEquals(Boolean.FALSE, eq.invoke(null, 1));
602 
603         Method ge = branchingZMethod(Comparison.GE);
604         assertEquals(Boolean.FALSE, ge.invoke(null, -1));
605         assertEquals(Boolean.TRUE, ge.invoke(null, 0));
606         assertEquals(Boolean.TRUE, ge.invoke(null, 1));
607 
608         Method gt = branchingZMethod(Comparison.GT);
609         assertEquals(Boolean.FALSE, gt.invoke(null, -1));
610         assertEquals(Boolean.FALSE, gt.invoke(null, 0));
611         assertEquals(Boolean.TRUE, gt.invoke(null, 1));
612 
613         Method ne = branchingZMethod(Comparison.NE);
614         assertEquals(Boolean.TRUE, ne.invoke(null, -1));
615         assertEquals(Boolean.FALSE, ne.invoke(null, 0));
616         assertEquals(Boolean.TRUE, ne.invoke(null, 1));
617     }
618 
branchingZMethod(Comparison comparison)619     private Method branchingZMethod(Comparison comparison) throws Exception {
620         /*
621          * public static boolean call(int localA) {
622          *   if (a comparison 0) {
623          *     return true;
624          *   }
625          *   return false;
626          * }
627          */
628         reset();
629         MethodId<?, Boolean> methodId = GENERATED.getMethod(
630             TypeId.BOOLEAN, "call", TypeId.INT);
631         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
632         Local<Integer> localA = code.getParameter(0, TypeId.INT);
633         Local<Boolean> result = code.newLocal(TypeId.get(boolean.class));
634         Label afterIf = new Label();
635         Label ifBody = new Label();
636         code.compareZ(comparison, ifBody, localA);
637         code.jump(afterIf);
638 
639         code.mark(ifBody);
640         code.loadConstant(result, true);
641         code.returnValue(result);
642 
643         code.mark(afterIf);
644         code.loadConstant(result, false);
645         code.returnValue(result);
646         return getMethod();
647     }
648 
649     @Test
testCastIntegerToInteger()650     public void testCastIntegerToInteger() throws Exception {
651         Method intToLong = numericCastingMethod(int.class, long.class);
652         assertEquals(0x0000000000000000L, intToLong.invoke(null, 0x00000000));
653         assertEquals(0x000000007fffffffL, intToLong.invoke(null, 0x7fffffff));
654         assertEquals(0xffffffff80000000L, intToLong.invoke(null, 0x80000000));
655         assertEquals(0xffffffffffffffffL, intToLong.invoke(null, 0xffffffff));
656 
657         Method longToInt = numericCastingMethod(long.class, int.class);
658         assertEquals(0x1234abcd, longToInt.invoke(null, 0x000000001234abcdL));
659         assertEquals(0x1234abcd, longToInt.invoke(null, 0x123456781234abcdL));
660         assertEquals(0x1234abcd, longToInt.invoke(null, 0xffffffff1234abcdL));
661 
662         Method intToShort = numericCastingMethod(int.class, short.class);
663         assertEquals((short) 0x1234, intToShort.invoke(null, 0x00001234));
664         assertEquals((short) 0x1234, intToShort.invoke(null, 0xabcd1234));
665         assertEquals((short) 0x1234, intToShort.invoke(null, 0xffff1234));
666 
667         Method intToChar = numericCastingMethod(int.class, char.class);
668         assertEquals((char) 0x1234, intToChar.invoke(null, 0x00001234));
669         assertEquals((char) 0x1234, intToChar.invoke(null, 0xabcd1234));
670         assertEquals((char) 0x1234, intToChar.invoke(null, 0xffff1234));
671 
672         Method intToByte = numericCastingMethod(int.class, byte.class);
673         assertEquals((byte) 0x34, intToByte.invoke(null, 0x00000034));
674         assertEquals((byte) 0x34, intToByte.invoke(null, 0xabcd1234));
675         assertEquals((byte) 0x34, intToByte.invoke(null, 0xffffff34));
676     }
677 
678     @Test
testCastIntegerToFloatingPoint()679     public void testCastIntegerToFloatingPoint() throws Exception {
680         Method intToFloat = numericCastingMethod(int.class, float.class);
681         assertEquals(0.0f, intToFloat.invoke(null, 0));
682         assertEquals(-1.0f, intToFloat.invoke(null, -1));
683         assertEquals(16777216f, intToFloat.invoke(null, 16777216));
684         assertEquals(16777216f, intToFloat.invoke(null, 16777217)); // precision
685 
686         Method intToDouble = numericCastingMethod(int.class, double.class);
687         assertEquals(0.0, intToDouble.invoke(null, 0));
688         assertEquals(-1.0, intToDouble.invoke(null, -1));
689         assertEquals(16777216.0, intToDouble.invoke(null, 16777216));
690         assertEquals(16777217.0, intToDouble.invoke(null, 16777217));
691 
692         Method longToFloat = numericCastingMethod(long.class, float.class);
693         assertEquals(0.0f, longToFloat.invoke(null, 0L));
694         assertEquals(-1.0f, longToFloat.invoke(null, -1L));
695         assertEquals(16777216f, longToFloat.invoke(null, 16777216L));
696         assertEquals(16777216f, longToFloat.invoke(null, 16777217L));
697 
698         Method longToDouble = numericCastingMethod(long.class, double.class);
699         assertEquals(0.0, longToDouble.invoke(null, 0L));
700         assertEquals(-1.0, longToDouble.invoke(null, -1L));
701         assertEquals(9007199254740992.0, longToDouble.invoke(null, 9007199254740992L));
702         assertEquals(9007199254740992.0, longToDouble.invoke(null, 9007199254740993L)); // precision
703     }
704 
705     @Test
706     @SuppressWarnings("FloatingPointLiteralPrecision")
testCastFloatingPointToInteger()707     public void testCastFloatingPointToInteger() throws Exception {
708         Method floatToInt = numericCastingMethod(float.class, int.class);
709         assertEquals(0, floatToInt.invoke(null, 0.0f));
710         assertEquals(-1, floatToInt.invoke(null, -1.0f));
711         assertEquals(Integer.MAX_VALUE, floatToInt.invoke(null, 10e15f));
712         assertEquals(0, floatToInt.invoke(null, 0.5f));
713         assertEquals(Integer.MIN_VALUE, floatToInt.invoke(null, Float.NEGATIVE_INFINITY));
714         assertEquals(0, floatToInt.invoke(null, Float.NaN));
715 
716         Method floatToLong = numericCastingMethod(float.class, long.class);
717         assertEquals(0L, floatToLong.invoke(null, 0.0f));
718         assertEquals(-1L, floatToLong.invoke(null, -1.0f));
719         assertEquals(10000000272564224L, floatToLong.invoke(null, 10e15f));
720         assertEquals(0L, floatToLong.invoke(null, 0.5f));
721         assertEquals(Long.MIN_VALUE, floatToLong.invoke(null, Float.NEGATIVE_INFINITY));
722         assertEquals(0L, floatToLong.invoke(null, Float.NaN));
723 
724         Method doubleToInt = numericCastingMethod(double.class, int.class);
725         assertEquals(0, doubleToInt.invoke(null, 0.0));
726         assertEquals(-1, doubleToInt.invoke(null, -1.0));
727         assertEquals(Integer.MAX_VALUE, doubleToInt.invoke(null, 10e15));
728         assertEquals(0, doubleToInt.invoke(null, 0.5));
729         assertEquals(Integer.MIN_VALUE, doubleToInt.invoke(null, Double.NEGATIVE_INFINITY));
730         assertEquals(0, doubleToInt.invoke(null, Double.NaN));
731 
732         Method doubleToLong = numericCastingMethod(double.class, long.class);
733         assertEquals(0L, doubleToLong.invoke(null, 0.0));
734         assertEquals(-1L, doubleToLong.invoke(null, -1.0));
735         assertEquals(10000000000000000L, doubleToLong.invoke(null, 10e15));
736         assertEquals(0L, doubleToLong.invoke(null, 0.5));
737         assertEquals(Long.MIN_VALUE, doubleToLong.invoke(null, Double.NEGATIVE_INFINITY));
738         assertEquals(0L, doubleToLong.invoke(null, Double.NaN));
739     }
740 
741     @Test
testCastFloatingPointToFloatingPoint()742     public void testCastFloatingPointToFloatingPoint() throws Exception {
743         Method floatToDouble = numericCastingMethod(float.class, double.class);
744         assertEquals(0.0, floatToDouble.invoke(null, 0.0f));
745         assertEquals(-1.0, floatToDouble.invoke(null, -1.0f));
746         assertEquals(0.5, floatToDouble.invoke(null, 0.5f));
747         assertEquals(Double.NEGATIVE_INFINITY, floatToDouble.invoke(null, Float.NEGATIVE_INFINITY));
748         assertEquals(Double.NaN, floatToDouble.invoke(null, Float.NaN));
749 
750         Method doubleToFloat = numericCastingMethod(double.class, float.class);
751         assertEquals(0.0f, doubleToFloat.invoke(null, 0.0));
752         assertEquals(-1.0f, doubleToFloat.invoke(null, -1.0));
753         assertEquals(0.5f, doubleToFloat.invoke(null, 0.5));
754         assertEquals(Float.NEGATIVE_INFINITY, doubleToFloat.invoke(null, Double.NEGATIVE_INFINITY));
755         assertEquals(Float.NaN, doubleToFloat.invoke(null, Double.NaN));
756     }
757 
numericCastingMethod(Class<?> source, Class<?> target)758     private Method numericCastingMethod(Class<?> source, Class<?> target)
759             throws Exception {
760         /*
761          * public static short call(int source) {
762          *   short casted = (short) source;
763          *   return casted;
764          * }
765          */
766         reset();
767         TypeId<?> sourceType = TypeId.get(source);
768         TypeId<?> targetType = TypeId.get(target);
769         MethodId<?, ?> methodId = GENERATED.getMethod(targetType, "call", sourceType);
770         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
771         Local<?> localSource = code.getParameter(0, sourceType);
772         Local<?> localCasted = code.newLocal(targetType);
773         code.cast(localCasted, localSource);
774         code.returnValue(localCasted);
775         return getMethod();
776     }
777 
778     @Test
testNot()779     public void testNot() throws Exception {
780         Method notInteger = notMethod(int.class);
781         assertEquals(0xffffffff, notInteger.invoke(null, 0x00000000));
782         assertEquals(0x00000000, notInteger.invoke(null, 0xffffffff));
783         assertEquals(0xedcba987, notInteger.invoke(null, 0x12345678));
784 
785         Method notLong = notMethod(long.class);
786         assertEquals(0xffffffffffffffffL, notLong.invoke(null, 0x0000000000000000L));
787         assertEquals(0x0000000000000000L, notLong.invoke(null, 0xffffffffffffffffL));
788         assertEquals(0x98765432edcba987L, notLong.invoke(null, 0x6789abcd12345678L));
789     }
790 
notMethod(Class<T> source)791     private <T> Method notMethod(Class<T> source) throws Exception {
792         /*
793          * public static short call(int source) {
794          *   source = ~source;
795          *   return not;
796          * }
797          */
798         reset();
799         TypeId<T> valueType = TypeId.get(source);
800         MethodId<?, T> methodId = GENERATED.getMethod(valueType, "call", valueType);
801         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
802         Local<T> localSource = code.getParameter(0, valueType);
803         code.op(UnaryOp.NOT, localSource, localSource);
804         code.returnValue(localSource);
805         return getMethod();
806     }
807 
808     @Test
testNegate()809     public void testNegate() throws Exception {
810         Method negateInteger = negateMethod(int.class);
811         assertEquals(0, negateInteger.invoke(null, 0));
812         assertEquals(-1, negateInteger.invoke(null, 1));
813         assertEquals(Integer.MIN_VALUE, negateInteger.invoke(null, Integer.MIN_VALUE));
814 
815         Method negateLong = negateMethod(long.class);
816         assertEquals(0L, negateLong.invoke(null, 0));
817         assertEquals(-1L, negateLong.invoke(null, 1));
818         assertEquals(Long.MIN_VALUE, negateLong.invoke(null, Long.MIN_VALUE));
819 
820         Method negateFloat = negateMethod(float.class);
821         assertEquals(-0.0f, negateFloat.invoke(null, 0.0f));
822         assertEquals(-1.0f, negateFloat.invoke(null, 1.0f));
823         assertEquals(Float.NaN, negateFloat.invoke(null, Float.NaN));
824         assertEquals(Float.POSITIVE_INFINITY, negateFloat.invoke(null, Float.NEGATIVE_INFINITY));
825 
826         Method negateDouble = negateMethod(double.class);
827         assertEquals(-0.0, negateDouble.invoke(null, 0.0));
828         assertEquals(-1.0, negateDouble.invoke(null, 1.0));
829         assertEquals(Double.NaN, negateDouble.invoke(null, Double.NaN));
830         assertEquals(Double.POSITIVE_INFINITY, negateDouble.invoke(null, Double.NEGATIVE_INFINITY));
831     }
832 
negateMethod(Class<T> source)833     private <T> Method negateMethod(Class<T> source) throws Exception {
834         /*
835          * public static short call(int source) {
836          *   source = -source;
837          *   return not;
838          * }
839          */
840         reset();
841         TypeId<T> valueType = TypeId.get(source);
842         MethodId<?, T> methodId = GENERATED.getMethod(valueType, "call", valueType);
843         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
844         Local<T> localSource = code.getParameter(0, valueType);
845         code.op(UnaryOp.NEGATE, localSource, localSource);
846         code.returnValue(localSource);
847         return getMethod();
848     }
849 
850     @Test
testIntBinaryOps()851     public void testIntBinaryOps() throws Exception {
852         Method add = binaryOpMethod(int.class, int.class, BinaryOp.ADD);
853         assertEquals(79, add.invoke(null, 75, 4));
854 
855         Method subtract = binaryOpMethod(int.class, int.class, BinaryOp.SUBTRACT);
856         assertEquals(71, subtract.invoke(null, 75, 4));
857 
858         Method multiply = binaryOpMethod(int.class, int.class, BinaryOp.MULTIPLY);
859         assertEquals(300, multiply.invoke(null, 75, 4));
860 
861         Method divide = binaryOpMethod(int.class, int.class, BinaryOp.DIVIDE);
862         assertEquals(18, divide.invoke(null, 75, 4));
863         try {
864             divide.invoke(null, 75, 0);
865             fail();
866         } catch (InvocationTargetException expected) {
867             assertEquals(ArithmeticException.class, expected.getCause().getClass());
868         }
869 
870         Method remainder = binaryOpMethod(int.class, int.class, BinaryOp.REMAINDER);
871         assertEquals(3, remainder.invoke(null, 75, 4));
872         try {
873             remainder.invoke(null, 75, 0);
874             fail();
875         } catch (InvocationTargetException expected) {
876             assertEquals(ArithmeticException.class, expected.getCause().getClass());
877         }
878 
879         Method and = binaryOpMethod(int.class, int.class, BinaryOp.AND);
880         assertEquals(0xff000000, and.invoke(null, 0xff00ff00, 0xffff0000));
881 
882         Method or = binaryOpMethod(int.class, int.class, BinaryOp.OR);
883         assertEquals(0xffffff00, or.invoke(null, 0xff00ff00, 0xffff0000));
884 
885         Method xor = binaryOpMethod(int.class, int.class, BinaryOp.XOR);
886         assertEquals(0x00ffff00, xor.invoke(null, 0xff00ff00, 0xffff0000));
887 
888         Method shiftLeft = binaryOpMethod(int.class, int.class, BinaryOp.SHIFT_LEFT);
889         assertEquals(0xcd123400, shiftLeft.invoke(null, 0xabcd1234, 8));
890 
891         Method shiftRight = binaryOpMethod(int.class, int.class, BinaryOp.SHIFT_RIGHT);
892         assertEquals(0xffabcd12, shiftRight.invoke(null, 0xabcd1234, 8));
893 
894         Method unsignedShiftRight = binaryOpMethod(int.class,
895                 int.class, BinaryOp.UNSIGNED_SHIFT_RIGHT);
896         assertEquals(0x00abcd12, unsignedShiftRight.invoke(null, 0xabcd1234, 8));
897     }
898 
899     @Test
testLongBinaryOps()900     public void testLongBinaryOps() throws Exception {
901         Method add = binaryOpMethod(long.class, long.class, BinaryOp.ADD);
902         assertEquals(30000000079L, add.invoke(null, 10000000075L, 20000000004L));
903 
904         Method subtract = binaryOpMethod(long.class, long.class, BinaryOp.SUBTRACT);
905         assertEquals(20000000071L, subtract.invoke(null, 30000000075L, 10000000004L));
906 
907         Method multiply = binaryOpMethod(long.class, long.class, BinaryOp.MULTIPLY);
908         assertEquals(-8742552812415203028L, multiply.invoke(null, 30000000075L, 20000000004L));
909 
910         Method divide = binaryOpMethod(long.class, long.class, BinaryOp.DIVIDE);
911         assertEquals(-2L, divide.invoke(null, -8742552812415203028L, 4142552812415203028L));
912         try {
913             divide.invoke(null, -8742552812415203028L, 0L);
914             fail();
915         } catch (InvocationTargetException expected) {
916             assertEquals(ArithmeticException.class, expected.getCause().getClass());
917         }
918 
919         Method remainder = binaryOpMethod(long.class, long.class, BinaryOp.REMAINDER);
920         assertEquals(10000000004L, remainder.invoke(null, 30000000079L, 20000000075L));
921         try {
922             remainder.invoke(null, 30000000079L, 0L);
923             fail();
924         } catch (InvocationTargetException expected) {
925             assertEquals(ArithmeticException.class, expected.getCause().getClass());
926         }
927 
928         Method and = binaryOpMethod(long.class, long.class, BinaryOp.AND);
929         assertEquals(0xff00ff0000000000L,
930                 and.invoke(null, 0xff00ff00ff00ff00L, 0xffffffff00000000L));
931 
932         Method or = binaryOpMethod(long.class, long.class, BinaryOp.OR);
933         assertEquals(0xffffffffff00ff00L,
934                 or.invoke(null, 0xff00ff00ff00ff00L, 0xffffffff00000000L));
935 
936         Method xor = binaryOpMethod(long.class, long.class, BinaryOp.XOR);
937         assertEquals(0x00ff00ffff00ff00L,
938                 xor.invoke(null, 0xff00ff00ff00ff00L, 0xffffffff00000000L));
939 
940         Method shiftLeft = binaryOpMethod(long.class, int.class, BinaryOp.SHIFT_LEFT);
941         assertEquals(0xcdef012345678900L, shiftLeft.invoke(null, 0xabcdef0123456789L, 8));
942 
943         Method shiftRight = binaryOpMethod(long.class, int.class, BinaryOp.SHIFT_RIGHT);
944         assertEquals(0xffabcdef01234567L, shiftRight.invoke(null, 0xabcdef0123456789L, 8));
945 
946         Method unsignedShiftRight = binaryOpMethod(
947                 long.class, int.class, BinaryOp.UNSIGNED_SHIFT_RIGHT);
948         assertEquals(0x00abcdef01234567L, unsignedShiftRight.invoke(null, 0xabcdef0123456789L, 8));
949     }
950 
951     @Test
testFloatBinaryOps()952     public void testFloatBinaryOps() throws Exception {
953         Method add = binaryOpMethod(float.class, float.class, BinaryOp.ADD);
954         assertEquals(6.75f, add.invoke(null, 5.5f, 1.25f));
955 
956         Method subtract = binaryOpMethod(float.class, float.class, BinaryOp.SUBTRACT);
957         assertEquals(4.25f, subtract.invoke(null, 5.5f, 1.25f));
958 
959         Method multiply = binaryOpMethod(float.class, float.class, BinaryOp.MULTIPLY);
960         assertEquals(6.875f, multiply.invoke(null, 5.5f, 1.25f));
961 
962         Method divide = binaryOpMethod(float.class, float.class, BinaryOp.DIVIDE);
963         assertEquals(4.4f, divide.invoke(null, 5.5f, 1.25f));
964         assertEquals(Float.POSITIVE_INFINITY, divide.invoke(null, 5.5f, 0.0f));
965 
966         Method remainder = binaryOpMethod(float.class, float.class, BinaryOp.REMAINDER);
967         assertEquals(0.5f, remainder.invoke(null, 5.5f, 1.25f));
968         assertEquals(Float.NaN, remainder.invoke(null, 5.5f, 0.0f));
969     }
970 
971     @Test
testDoubleBinaryOps()972     public void testDoubleBinaryOps() throws Exception {
973         Method add = binaryOpMethod(double.class, double.class, BinaryOp.ADD);
974         assertEquals(6.75, add.invoke(null, 5.5, 1.25));
975 
976         Method subtract = binaryOpMethod(double.class, double.class, BinaryOp.SUBTRACT);
977         assertEquals(4.25, subtract.invoke(null, 5.5, 1.25));
978 
979         Method multiply = binaryOpMethod(double.class, double.class, BinaryOp.MULTIPLY);
980         assertEquals(6.875, multiply.invoke(null, 5.5, 1.25));
981 
982         Method divide = binaryOpMethod(double.class, double.class, BinaryOp.DIVIDE);
983         assertEquals(4.4, divide.invoke(null, 5.5, 1.25));
984         assertEquals(Double.POSITIVE_INFINITY, divide.invoke(null, 5.5, 0.0));
985 
986         Method remainder = binaryOpMethod(double.class, double.class, BinaryOp.REMAINDER);
987         assertEquals(0.5, remainder.invoke(null, 5.5, 1.25));
988         assertEquals(Double.NaN, remainder.invoke(null, 5.5, 0.0));
989     }
990 
binaryOpMethod( Class<T1> valueAClass, Class<T2> valueBClass, BinaryOp op)991     private <T1, T2> Method binaryOpMethod(
992             Class<T1> valueAClass, Class<T2> valueBClass, BinaryOp op) throws Exception {
993         /*
994          * public static int binaryOp(int a, int b) {
995          *   int result = a + b;
996          *   return result;
997          * }
998          */
999         reset();
1000         TypeId<T1> valueAType = TypeId.get(valueAClass);
1001         TypeId<T2> valueBType = TypeId.get(valueBClass);
1002         MethodId<?, T1> methodId = GENERATED.getMethod(valueAType, "call", valueAType, valueBType);
1003         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1004         Local<T1> localA = code.getParameter(0, valueAType);
1005         Local<T2> localB = code.getParameter(1, valueBType);
1006         Local<T1> localResult = code.newLocal(valueAType);
1007         code.op(op, localResult, localA, localB);
1008         code.returnValue(localResult);
1009         return getMethod();
1010     }
1011 
1012     @Test
testReadAndWriteInstanceFields()1013     public void testReadAndWriteInstanceFields() throws Exception {
1014         Instance instance = new Instance();
1015 
1016         Method intSwap = instanceSwapMethod(int.class, "intValue");
1017         instance.intValue = 5;
1018         assertEquals(5, intSwap.invoke(null, instance, 10));
1019         assertEquals(10, instance.intValue);
1020 
1021         Method longSwap = instanceSwapMethod(long.class, "longValue");
1022         instance.longValue = 500L;
1023         assertEquals(500L, longSwap.invoke(null, instance, 1234L));
1024         assertEquals(1234L, instance.longValue);
1025 
1026         Method booleanSwap = instanceSwapMethod(boolean.class, "booleanValue");
1027         instance.booleanValue = false;
1028         assertEquals(false, booleanSwap.invoke(null, instance, true));
1029         assertEquals(true, instance.booleanValue);
1030 
1031         Method floatSwap = instanceSwapMethod(float.class, "floatValue");
1032         instance.floatValue = 1.5f;
1033         assertEquals(1.5f, floatSwap.invoke(null, instance, 0.5f));
1034         assertEquals(0.5f, instance.floatValue, DELTA_FLOAT);
1035 
1036         Method doubleSwap = instanceSwapMethod(double.class, "doubleValue");
1037         instance.doubleValue = 155.5;
1038         assertEquals(155.5, doubleSwap.invoke(null, instance, 266.6));
1039         assertEquals(266.6, instance.doubleValue, DELTA_DOUBLE);
1040 
1041         Method objectSwap = instanceSwapMethod(Object.class, "objectValue");
1042         instance.objectValue = "before";
1043         assertEquals("before", objectSwap.invoke(null, instance, "after"));
1044         assertEquals("after", instance.objectValue);
1045 
1046         Method byteSwap = instanceSwapMethod(byte.class, "byteValue");
1047         instance.byteValue = 0x35;
1048         assertEquals((byte) 0x35, byteSwap.invoke(null, instance, (byte) 0x64));
1049         assertEquals((byte) 0x64, instance.byteValue);
1050 
1051         Method charSwap = instanceSwapMethod(char.class, "charValue");
1052         instance.charValue = 'A';
1053         assertEquals('A', charSwap.invoke(null, instance, 'B'));
1054         assertEquals('B', instance.charValue);
1055 
1056         Method shortSwap = instanceSwapMethod(short.class, "shortValue");
1057         instance.shortValue = (short) 0xabcd;
1058         assertEquals((short) 0xabcd, shortSwap.invoke(null, instance, (short) 0x1234));
1059         assertEquals((short) 0x1234, instance.shortValue);
1060     }
1061 
1062     public static class Instance {
1063         public int intValue;
1064         public long longValue;
1065         public float floatValue;
1066         public double doubleValue;
1067         public Object objectValue;
1068         public boolean booleanValue;
1069         public byte byteValue;
1070         public char charValue;
1071         public short shortValue;
1072     }
1073 
instanceSwapMethod( Class<V> valueClass, String fieldName)1074     private <V> Method instanceSwapMethod(
1075             Class<V> valueClass, String fieldName) throws Exception {
1076         /*
1077          * public static int call(Instance instance, int newValue) {
1078          *   int oldValue = instance.intValue;
1079          *   instance.intValue = newValue;
1080          *   return oldValue;
1081          * }
1082          */
1083         reset();
1084         TypeId<V> valueType = TypeId.get(valueClass);
1085         TypeId<Instance> objectType = TypeId.get(Instance.class);
1086         FieldId<Instance, V> fieldId = objectType.getField(valueType, fieldName);
1087         MethodId<?, V> methodId = GENERATED.getMethod(valueType, "call", objectType, valueType);
1088         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1089         Local<Instance> localInstance = code.getParameter(0, objectType);
1090         Local<V> localNewValue = code.getParameter(1, valueType);
1091         Local<V> localOldValue = code.newLocal(valueType);
1092         code.iget(fieldId, localOldValue, localInstance);
1093         code.iput(fieldId, localInstance, localNewValue);
1094         code.returnValue(localOldValue);
1095         return getMethod();
1096     }
1097 
1098     @Test
testReadAndWriteStaticFields()1099     public void testReadAndWriteStaticFields() throws Exception {
1100         Method intSwap = staticSwapMethod(int.class, "intValue");
1101         Static.intValue = 5;
1102         assertEquals(5, intSwap.invoke(null, 10));
1103         assertEquals(10, Static.intValue);
1104 
1105         Method longSwap = staticSwapMethod(long.class, "longValue");
1106         Static.longValue = 500L;
1107         assertEquals(500L, longSwap.invoke(null, 1234L));
1108         assertEquals(1234L, Static.longValue);
1109 
1110         Method booleanSwap = staticSwapMethod(boolean.class, "booleanValue");
1111         Static.booleanValue = false;
1112         assertEquals(false, booleanSwap.invoke(null, true));
1113         assertEquals(true, Static.booleanValue);
1114 
1115         Method floatSwap = staticSwapMethod(float.class, "floatValue");
1116         Static.floatValue = 1.5f;
1117         assertEquals(1.5f, floatSwap.invoke(null, 0.5f));
1118         assertEquals(0.5f, Static.floatValue, DELTA_FLOAT);
1119 
1120         Method doubleSwap = staticSwapMethod(double.class, "doubleValue");
1121         Static.doubleValue = 155.5;
1122         assertEquals(155.5, doubleSwap.invoke(null, 266.6));
1123         assertEquals(266.6, Static.doubleValue, DELTA_DOUBLE);
1124 
1125         Method objectSwap = staticSwapMethod(Object.class, "objectValue");
1126         Static.objectValue = "before";
1127         assertEquals("before", objectSwap.invoke(null, "after"));
1128         assertEquals("after", Static.objectValue);
1129 
1130         Method byteSwap = staticSwapMethod(byte.class, "byteValue");
1131         Static.byteValue = 0x35;
1132         assertEquals((byte) 0x35, byteSwap.invoke(null, (byte) 0x64));
1133         assertEquals((byte) 0x64, Static.byteValue);
1134 
1135         Method charSwap = staticSwapMethod(char.class, "charValue");
1136         Static.charValue = 'A';
1137         assertEquals('A', charSwap.invoke(null, 'B'));
1138         assertEquals('B', Static.charValue);
1139 
1140         Method shortSwap = staticSwapMethod(short.class, "shortValue");
1141         Static.shortValue = (short) 0xabcd;
1142         assertEquals((short) 0xabcd, shortSwap.invoke(null, (short) 0x1234));
1143         assertEquals((short) 0x1234, Static.shortValue);
1144     }
1145 
1146     public static class Static {
1147         public static int intValue;
1148         public static long longValue;
1149         public static float floatValue;
1150         public static double doubleValue;
1151         public static Object objectValue;
1152         public static boolean booleanValue;
1153         public static byte byteValue;
1154         public static char charValue;
1155         public static short shortValue;
1156     }
1157 
staticSwapMethod(Class<V> valueClass, String fieldName)1158     private <V> Method staticSwapMethod(Class<V> valueClass, String fieldName)
1159             throws Exception {
1160         /*
1161          * public static int call(int newValue) {
1162          *   int oldValue = Static.intValue;
1163          *   Static.intValue = newValue;
1164          *   return oldValue;
1165          * }
1166          */
1167         reset();
1168         TypeId<V> valueType = TypeId.get(valueClass);
1169         TypeId<Static> objectType = TypeId.get(Static.class);
1170         FieldId<Static, V> fieldId = objectType.getField(valueType, fieldName);
1171         MethodId<?, V> methodId = GENERATED.getMethod(valueType, "call", valueType);
1172         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1173         Local<V> localNewValue = code.getParameter(0, valueType);
1174         Local<V> localOldValue = code.newLocal(valueType);
1175         code.sget(fieldId, localOldValue);
1176         code.sput(fieldId, localNewValue);
1177         code.returnValue(localOldValue);
1178         return getMethod();
1179     }
1180 
1181     @Test
testStaticInitializer()1182     public void testStaticInitializer() throws Exception {
1183         reset();
1184 
1185         StaticFieldSpec<?>[] fields = new StaticFieldSpec[] {
1186                 new StaticFieldSpec<>(boolean.class, "booleanValue", true),
1187                 new StaticFieldSpec<>(byte.class, "byteValue", Byte.MIN_VALUE),
1188                 new StaticFieldSpec<>(short.class, "shortValue", Short.MAX_VALUE),
1189                 new StaticFieldSpec<>(int.class, "intValue", Integer.MIN_VALUE),
1190                 new StaticFieldSpec<>(long.class, "longValue", Long.MAX_VALUE),
1191                 new StaticFieldSpec<>(float.class, "floatValue", Float.MIN_VALUE),
1192                 new StaticFieldSpec<>(double.class, "doubleValue", Double.MAX_VALUE),
1193                 new StaticFieldSpec<>(String.class, "stringValue", "qwerty"),
1194         };
1195 
1196         MethodId<?, Void> clinit = GENERATED.getStaticInitializer();
1197         assertTrue(clinit.isStaticInitializer());
1198 
1199         Code code = dexMaker.declare(clinit, Modifier.STATIC);
1200 
1201         for (StaticFieldSpec<?> field : fields) {
1202             field.createLocal(code);
1203         }
1204 
1205         for (StaticFieldSpec<?> field : fields) {
1206             field.initializeField(code);
1207         }
1208 
1209         code.returnVoid();
1210 
1211         Class<?> generated = generateAndLoad();
1212         for (StaticFieldSpec<?> fieldSpec : fields) {
1213             Field field = generated.getDeclaredField(fieldSpec.name);
1214             assertEquals(StaticFieldSpec.MODIFIERS, field.getModifiers());
1215             assertEquals(fieldSpec.value, field.get(null));
1216         }
1217     }
1218 
1219     private class StaticFieldSpec<T> {
1220         Class<T> type;
1221         TypeId<T> typeId;
1222         String name;
1223         T value;
1224         FieldId<?, T> fieldId;
1225         Local<T> local;
1226 
1227         static final int MODIFIERS = Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL;
1228 
StaticFieldSpec(Class<T> type, String name, T value)1229         public StaticFieldSpec(Class<T> type, String name, T value) {
1230             this.type = type;
1231             this.name = name;
1232             this.value = value;
1233 
1234             typeId = TypeId.get(type);
1235             fieldId = GENERATED.getField(typeId, name);
1236             dexMaker.declare(fieldId, MODIFIERS, null);
1237         }
1238 
createLocal(Code code)1239         public void createLocal(Code code) {
1240             local = code.newLocal(typeId);
1241         }
1242 
initializeField(Code code)1243         public void initializeField(Code code) {
1244             code.loadConstant(local, value);
1245             code.sput(fieldId, local);
1246         }
1247     }
1248 
1249     @Test
testTypeCast()1250     public void testTypeCast() throws Exception {
1251         /*
1252          * public static String call(Object o) {
1253          *   String s = (String) o;
1254          * }
1255          */
1256         MethodId<?, String> methodId = GENERATED.getMethod(TypeId.STRING, "call", TypeId.OBJECT);
1257         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1258         Local<Object> localObject = code.getParameter(0, TypeId.OBJECT);
1259         Local<String> localString = code.newLocal(TypeId.STRING);
1260         code.cast(localString, localObject);
1261         code.returnValue(localString);
1262 
1263         Method method = getMethod();
1264         assertEquals("s", method.invoke(null, "s"));
1265         assertEquals(null, method.invoke(null, (String) null));
1266         try {
1267             method.invoke(null, 5);
1268             fail();
1269         } catch (InvocationTargetException expected) {
1270             assertEquals(ClassCastException.class, expected.getCause().getClass());
1271         }
1272     }
1273 
1274     @Test
testInstanceOf()1275     public void testInstanceOf() throws Exception {
1276         /*
1277          * public static boolean call(Object o) {
1278          *   boolean result = o instanceof String;
1279          *   return result;
1280          * }
1281          */
1282         MethodId<?, Boolean> methodId = GENERATED.getMethod(TypeId.BOOLEAN, "call", TypeId.OBJECT);
1283         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1284         Local<Object> localObject = code.getParameter(0, TypeId.OBJECT);
1285         Local<Boolean> localResult = code.newLocal(TypeId.BOOLEAN);
1286         code.instanceOfType(localResult, localObject, TypeId.STRING);
1287         code.returnValue(localResult);
1288 
1289         Method method = getMethod();
1290         assertEquals(true, method.invoke(null, "s"));
1291         assertEquals(false, method.invoke(null, (String) null));
1292         assertEquals(false, method.invoke(null, 5));
1293     }
1294 
1295     /**
1296      * Tests that we can construct a for loop.
1297      */
1298     @Test
testForLoop()1299     public void testForLoop() throws Exception {
1300         /*
1301          * public static int call(int count) {
1302          *   int result = 1;
1303          *   for (int i = 0; i < count; i += 1) {
1304          *     result = result * 2;
1305          *   }
1306          *   return result;
1307          * }
1308          */
1309         MethodId<?, Integer> methodId = GENERATED.getMethod(TypeId.INT, "call", TypeId.INT);
1310         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1311         Local<Integer> localCount = code.getParameter(0, TypeId.INT);
1312         Local<Integer> localResult = code.newLocal(TypeId.INT);
1313         Local<Integer> localI = code.newLocal(TypeId.INT);
1314         Local<Integer> local1 = code.newLocal(TypeId.INT);
1315         Local<Integer> local2 = code.newLocal(TypeId.INT);
1316         code.loadConstant(local1, 1);
1317         code.loadConstant(local2, 2);
1318         code.loadConstant(localResult, 1);
1319         code.loadConstant(localI, 0);
1320         Label loopCondition = new Label();
1321         Label loopBody = new Label();
1322         Label afterLoop = new Label();
1323         code.mark(loopCondition);
1324         code.compare(Comparison.LT, loopBody, localI, localCount);
1325         code.jump(afterLoop);
1326         code.mark(loopBody);
1327         code.op(BinaryOp.MULTIPLY, localResult, localResult, local2);
1328         code.op(BinaryOp.ADD, localI, localI, local1);
1329         code.jump(loopCondition);
1330         code.mark(afterLoop);
1331         code.returnValue(localResult);
1332 
1333         Method pow2 = getMethod();
1334         assertEquals(1, pow2.invoke(null, 0));
1335         assertEquals(2, pow2.invoke(null, 1));
1336         assertEquals(4, pow2.invoke(null, 2));
1337         assertEquals(8, pow2.invoke(null, 3));
1338         assertEquals(16, pow2.invoke(null, 4));
1339     }
1340 
1341     /**
1342      * Tests that we can construct a while loop.
1343      */
1344     @Test
testWhileLoop()1345     public void testWhileLoop() throws Exception {
1346         /*
1347          * public static int call(int max) {
1348          *   int result = 1;
1349          *   while (result < max) {
1350          *     result = result * 2;
1351          *   }
1352          *   return result;
1353          * }
1354          */
1355         MethodId<?, Integer> methodId = GENERATED.getMethod(TypeId.INT, "call", TypeId.INT);
1356         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1357         Local<Integer> localMax = code.getParameter(0, TypeId.INT);
1358         Local<Integer> localResult = code.newLocal(TypeId.INT);
1359         Local<Integer> local2 = code.newLocal(TypeId.INT);
1360         code.loadConstant(localResult, 1);
1361         code.loadConstant(local2, 2);
1362         Label loopCondition = new Label();
1363         Label loopBody = new Label();
1364         Label afterLoop = new Label();
1365         code.mark(loopCondition);
1366         code.compare(Comparison.LT, loopBody, localResult, localMax);
1367         code.jump(afterLoop);
1368         code.mark(loopBody);
1369         code.op(BinaryOp.MULTIPLY, localResult, localResult, local2);
1370         code.jump(loopCondition);
1371         code.mark(afterLoop);
1372         code.returnValue(localResult);
1373 
1374         Method ceilPow2 = getMethod();
1375         assertEquals(1, ceilPow2.invoke(null, 1));
1376         assertEquals(2, ceilPow2.invoke(null, 2));
1377         assertEquals(4, ceilPow2.invoke(null, 3));
1378         assertEquals(16, ceilPow2.invoke(null, 10));
1379         assertEquals(128, ceilPow2.invoke(null, 100));
1380         assertEquals(1024, ceilPow2.invoke(null, 1000));
1381     }
1382 
1383     @Test
testIfElseBlock()1384     public void testIfElseBlock() throws Exception {
1385         /*
1386          * public static int call(int a, int b, int c) {
1387          *   if (a < b) {
1388          *     if (a < c) {
1389          *       return a;
1390          *     } else {
1391          *       return c;
1392          *     }
1393          *   } else if (b < c) {
1394          *     return b;
1395          *   } else {
1396          *     return c;
1397          *   }
1398          * }
1399          */
1400         MethodId<?, Integer> methodId = GENERATED.getMethod(
1401                 TypeId.INT, "call", TypeId.INT, TypeId.INT, TypeId.INT);
1402         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1403         Local<Integer> localA = code.getParameter(0, TypeId.INT);
1404         Local<Integer> localB = code.getParameter(1, TypeId.INT);
1405         Local<Integer> localC = code.getParameter(2, TypeId.INT);
1406         Label aLessThanB = new Label();
1407         Label aLessThanC = new Label();
1408         Label bLessThanC = new Label();
1409         code.compare(Comparison.LT, aLessThanB, localA, localB);
1410         code.compare(Comparison.LT, bLessThanC, localB, localC);
1411         code.returnValue(localC);
1412         // (a < b)
1413         code.mark(aLessThanB);
1414         code.compare(Comparison.LT, aLessThanC, localA, localC);
1415         code.returnValue(localC);
1416         // (a < c)
1417         code.mark(aLessThanC);
1418         code.returnValue(localA);
1419         // (b < c)
1420         code.mark(bLessThanC);
1421         code.returnValue(localB);
1422 
1423         Method min = getMethod();
1424         assertEquals(1, min.invoke(null, 1, 2, 3));
1425         assertEquals(1, min.invoke(null, 2, 3, 1));
1426         assertEquals(1, min.invoke(null, 2, 1, 3));
1427         assertEquals(1, min.invoke(null, 3, 2, 1));
1428     }
1429 
1430     @Test
testRecursion()1431     public void testRecursion() throws Exception {
1432         /*
1433          * public static int call(int a) {
1434          *   if (a < 2) {
1435          *     return a;
1436          *   }
1437          *   a -= 1;
1438          *   int x = call(a)
1439          *   a -= 1;
1440          *   int y = call(a);
1441          *   int result = x + y;
1442          *   return result;
1443          * }
1444          */
1445         MethodId<?, Integer> methodId = GENERATED.getMethod(TypeId.INT, "call", TypeId.INT);
1446         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1447         Local<Integer> localA = code.getParameter(0, TypeId.INT);
1448         Local<Integer> local1 = code.newLocal(TypeId.INT);
1449         Local<Integer> local2 = code.newLocal(TypeId.INT);
1450         Local<Integer> localX = code.newLocal(TypeId.INT);
1451         Local<Integer> localY = code.newLocal(TypeId.INT);
1452         Local<Integer> localResult = code.newLocal(TypeId.INT);
1453         Label baseCase = new Label();
1454         code.loadConstant(local1, 1);
1455         code.loadConstant(local2, 2);
1456         code.compare(Comparison.LT, baseCase, localA, local2);
1457         code.op(BinaryOp.SUBTRACT, localA, localA, local1);
1458         code.invokeStatic(methodId, localX, localA);
1459         code.op(BinaryOp.SUBTRACT, localA, localA, local1);
1460         code.invokeStatic(methodId, localY, localA);
1461         code.op(BinaryOp.ADD, localResult, localX, localY);
1462         code.returnValue(localResult);
1463         code.mark(baseCase);
1464         code.returnValue(localA);
1465 
1466         Method fib = getMethod();
1467         assertEquals(0, fib.invoke(null, 0));
1468         assertEquals(1, fib.invoke(null, 1));
1469         assertEquals(1, fib.invoke(null, 2));
1470         assertEquals(2, fib.invoke(null, 3));
1471         assertEquals(3, fib.invoke(null, 4));
1472         assertEquals(5, fib.invoke(null, 5));
1473         assertEquals(8, fib.invoke(null, 6));
1474     }
1475 
1476     @Test
testCatchExceptions()1477     public void testCatchExceptions() throws Exception {
1478         /*
1479          * public static String call(int i) {
1480          *   try {
1481          *     DexMakerTest.thrower(i);
1482          *     return "NONE";
1483          *   } catch (IllegalArgumentException e) {
1484          *     return "IAE";
1485          *   } catch (IllegalStateException e) {
1486          *     return "ISE";
1487          *   } catch (RuntimeException e) {
1488          *     return "RE";
1489          *   }
1490          */
1491         MethodId<?, String> methodId = GENERATED.getMethod(TypeId.STRING, "call", TypeId.INT);
1492         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1493         Local<Integer> localI = code.getParameter(0, TypeId.INT);
1494         Local<String> result = code.newLocal(TypeId.STRING);
1495         Label catchIae = new Label();
1496         Label catchIse = new Label();
1497         Label catchRe = new Label();
1498 
1499         code.addCatchClause(TypeId.get(IllegalArgumentException.class), catchIae);
1500         code.addCatchClause(TypeId.get(IllegalStateException.class), catchIse);
1501         code.addCatchClause(TypeId.get(RuntimeException.class), catchRe);
1502         MethodId<?, ?> thrower = TEST_TYPE.getMethod(TypeId.VOID, "thrower", TypeId.INT);
1503         code.invokeStatic(thrower, null, localI);
1504         code.loadConstant(result, "NONE");
1505         code.returnValue(result);
1506 
1507         code.mark(catchIae);
1508         code.loadConstant(result, "IAE");
1509         code.returnValue(result);
1510 
1511         code.mark(catchIse);
1512         code.loadConstant(result, "ISE");
1513         code.returnValue(result);
1514 
1515         code.mark(catchRe);
1516         code.loadConstant(result, "RE");
1517         code.returnValue(result);
1518 
1519         Method method = getMethod();
1520         assertEquals("NONE", method.invoke(null, 0));
1521         assertEquals("IAE", method.invoke(null, 1));
1522         assertEquals("ISE", method.invoke(null, 2));
1523         assertEquals("RE", method.invoke(null, 3));
1524         try {
1525             method.invoke(null, 4);
1526             fail();
1527         } catch (InvocationTargetException expected) {
1528             assertEquals(IOException.class, expected.getCause().getClass());
1529         }
1530     }
1531 
1532     @SuppressWarnings("unused") // called by generated code
thrower(int a)1533     public static void thrower(int a) throws Exception {
1534         switch (a) {
1535         case 0:
1536             return;
1537         case 1:
1538             throw new IllegalArgumentException();
1539         case 2:
1540             throw new IllegalStateException();
1541         case 3:
1542             throw new UnsupportedOperationException();
1543         case 4:
1544             throw new IOException();
1545         default:
1546             throw new AssertionError();
1547         }
1548     }
1549 
1550     @Test
testNestedCatchClauses()1551     public void testNestedCatchClauses() throws Exception {
1552         /*
1553          * public static String call(int a, int b, int c) {
1554          *   try {
1555          *     DexMakerTest.thrower(a);
1556          *     try {
1557          *       DexMakerTest.thrower(b);
1558          *     } catch (IllegalArgumentException) {
1559          *       return "INNER";
1560          *     }
1561          *     DexMakerTest.thrower(c);
1562          *     return "NONE";
1563          *   } catch (IllegalArgumentException e) {
1564          *     return "OUTER";
1565          *   }
1566          */
1567         MethodId<?, String> methodId = GENERATED.getMethod(
1568                 TypeId.STRING, "call", TypeId.INT, TypeId.INT, TypeId.INT);
1569         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1570         Local<Integer> localA = code.getParameter(0, TypeId.INT);
1571         Local<Integer> localB = code.getParameter(1, TypeId.INT);
1572         Local<Integer> localC = code.getParameter(2, TypeId.INT);
1573         Local<String> localResult = code.newLocal(TypeId.STRING);
1574         Label catchInner = new Label();
1575         Label catchOuter = new Label();
1576 
1577         TypeId<IllegalArgumentException> iaeType = TypeId.get(IllegalArgumentException.class);
1578         code.addCatchClause(iaeType, catchOuter);
1579 
1580         MethodId<?, ?> thrower = TEST_TYPE.getMethod(TypeId.VOID, "thrower", TypeId.INT);
1581         code.invokeStatic(thrower, null, localA);
1582 
1583         // for the inner catch clause, we stash the old label and put it back afterwards.
1584         Label previousLabel = code.removeCatchClause(iaeType);
1585         code.addCatchClause(iaeType, catchInner);
1586         code.invokeStatic(thrower, null, localB);
1587         code.removeCatchClause(iaeType);
1588         code.addCatchClause(iaeType, previousLabel);
1589         code.invokeStatic(thrower, null, localC);
1590         code.loadConstant(localResult, "NONE");
1591         code.returnValue(localResult);
1592 
1593         code.mark(catchInner);
1594         code.loadConstant(localResult, "INNER");
1595         code.returnValue(localResult);
1596 
1597         code.mark(catchOuter);
1598         code.loadConstant(localResult, "OUTER");
1599         code.returnValue(localResult);
1600 
1601         Method method = getMethod();
1602         assertEquals("OUTER", method.invoke(null, 1, 0, 0));
1603         assertEquals("INNER", method.invoke(null, 0, 1, 0));
1604         assertEquals("OUTER", method.invoke(null, 0, 0, 1));
1605         assertEquals("NONE", method.invoke(null, 0, 0, 0));
1606     }
1607 
1608     @Test
testThrow()1609     public void testThrow() throws Exception {
1610         /*
1611          * public static void call() {
1612          *   throw new IllegalStateException();
1613          * }
1614          */
1615         MethodId<?, Void> methodId = GENERATED.getMethod(TypeId.VOID, "call");
1616         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1617         TypeId<IllegalStateException> iseType = TypeId.get(IllegalStateException.class);
1618         MethodId<IllegalStateException, Void> iseConstructor = iseType.getConstructor();
1619         Local<IllegalStateException> localIse = code.newLocal(iseType);
1620         code.newInstance(localIse, iseConstructor);
1621         code.throwValue(localIse);
1622 
1623         try {
1624             getMethod().invoke(null);
1625             fail();
1626         } catch (InvocationTargetException expected) {
1627             assertEquals(IllegalStateException.class, expected.getCause().getClass());
1628         }
1629     }
1630 
1631     @Test
testUnusedParameters()1632     public void testUnusedParameters() throws Exception {
1633         /*
1634          * public static void call(int unused1, long unused2, long unused3) {}
1635          */
1636         MethodId<?, Void> methodId = GENERATED.getMethod(
1637                 TypeId.VOID, "call", TypeId.INT, TypeId.LONG, TypeId.LONG);
1638         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1639         code.returnVoid();
1640         getMethod().invoke(null, 1, 2, 3);
1641     }
1642 
1643     @Test
testFloatingPointCompare()1644     public void testFloatingPointCompare() throws Exception {
1645         Method floatG = floatingPointCompareMethod(TypeId.FLOAT, 1);
1646         assertEquals(-1, floatG.invoke(null, 1.0f, Float.POSITIVE_INFINITY));
1647         assertEquals(-1, floatG.invoke(null, 1.0f, 2.0f));
1648         assertEquals(0, floatG.invoke(null, 1.0f, 1.0f));
1649         assertEquals(1, floatG.invoke(null, 2.0f, 1.0f));
1650         assertEquals(1, floatG.invoke(null, 1.0f, Float.NaN));
1651         assertEquals(1, floatG.invoke(null, Float.NaN, 1.0f));
1652         assertEquals(1, floatG.invoke(null, Float.NaN, Float.NaN));
1653         assertEquals(1, floatG.invoke(null, Float.NaN, Float.POSITIVE_INFINITY));
1654 
1655         Method floatL = floatingPointCompareMethod(TypeId.FLOAT, -1);
1656         assertEquals(-1, floatG.invoke(null, 1.0f, Float.POSITIVE_INFINITY));
1657         assertEquals(-1, floatL.invoke(null, 1.0f, 2.0f));
1658         assertEquals(0, floatL.invoke(null, 1.0f, 1.0f));
1659         assertEquals(1, floatL.invoke(null, 2.0f, 1.0f));
1660         assertEquals(-1, floatL.invoke(null, 1.0f, Float.NaN));
1661         assertEquals(-1, floatL.invoke(null, Float.NaN, 1.0f));
1662         assertEquals(-1, floatL.invoke(null, Float.NaN, Float.NaN));
1663         assertEquals(-1, floatL.invoke(null, Float.NaN, Float.POSITIVE_INFINITY));
1664 
1665         Method doubleG = floatingPointCompareMethod(TypeId.DOUBLE, 1);
1666         assertEquals(-1, doubleG.invoke(null, 1.0, Double.POSITIVE_INFINITY));
1667         assertEquals(-1, doubleG.invoke(null, 1.0, 2.0));
1668         assertEquals(0, doubleG.invoke(null, 1.0, 1.0));
1669         assertEquals(1, doubleG.invoke(null, 2.0, 1.0));
1670         assertEquals(1, doubleG.invoke(null, 1.0, Double.NaN));
1671         assertEquals(1, doubleG.invoke(null, Double.NaN, 1.0));
1672         assertEquals(1, doubleG.invoke(null, Double.NaN, Double.NaN));
1673         assertEquals(1, doubleG.invoke(null, Double.NaN, Double.POSITIVE_INFINITY));
1674 
1675         Method doubleL = floatingPointCompareMethod(TypeId.DOUBLE, -1);
1676         assertEquals(-1, doubleL.invoke(null, 1.0, Double.POSITIVE_INFINITY));
1677         assertEquals(-1, doubleL.invoke(null, 1.0, 2.0));
1678         assertEquals(0, doubleL.invoke(null, 1.0, 1.0));
1679         assertEquals(1, doubleL.invoke(null, 2.0, 1.0));
1680         assertEquals(-1, doubleL.invoke(null, 1.0, Double.NaN));
1681         assertEquals(-1, doubleL.invoke(null, Double.NaN, 1.0));
1682         assertEquals(-1, doubleL.invoke(null, Double.NaN, Double.NaN));
1683         assertEquals(-1, doubleL.invoke(null, Double.NaN, Double.POSITIVE_INFINITY));
1684     }
1685 
floatingPointCompareMethod( TypeId<T> valueType, int nanValue)1686     private <T extends Number> Method floatingPointCompareMethod(
1687             TypeId<T> valueType, int nanValue) throws Exception {
1688         /*
1689          * public static int call(float a, float b) {
1690          *     int result = a <=> b;
1691          *     return result;
1692          * }
1693          */
1694         reset();
1695         MethodId<?, Integer> methodId = GENERATED.getMethod(
1696                 TypeId.INT, "call", valueType, valueType);
1697         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1698         Local<T> localA = code.getParameter(0, valueType);
1699         Local<T> localB = code.getParameter(1, valueType);
1700         Local<Integer> localResult = code.newLocal(TypeId.INT);
1701         code.compareFloatingPoint(localResult, localA, localB, nanValue);
1702         code.returnValue(localResult);
1703         return getMethod();
1704     }
1705 
1706     @Test
testLongCompare()1707     public void testLongCompare() throws Exception {
1708         /*
1709          * public static int call(long a, long b) {
1710          *   int result = a <=> b;
1711          *   return result;
1712          * }
1713          */
1714         MethodId<?, Integer> methodId = GENERATED.getMethod(TypeId.INT, "call", TypeId.LONG, TypeId.LONG);
1715         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1716         Local<Long> localA = code.getParameter(0, TypeId.LONG);
1717         Local<Long> localB = code.getParameter(1, TypeId.LONG);
1718         Local<Integer> localResult = code.newLocal(TypeId.INT);
1719         code.compareLongs(localResult, localA, localB);
1720         code.returnValue(localResult);
1721 
1722         Method method = getMethod();
1723         assertEquals(0, method.invoke(null, Long.MIN_VALUE, Long.MIN_VALUE));
1724         assertEquals(-1, method.invoke(null, Long.MIN_VALUE, 0));
1725         assertEquals(-1, method.invoke(null, Long.MIN_VALUE, Long.MAX_VALUE));
1726         assertEquals(1, method.invoke(null, 0, Long.MIN_VALUE));
1727         assertEquals(0, method.invoke(null, 0, 0));
1728         assertEquals(-1, method.invoke(null, 0, Long.MAX_VALUE));
1729         assertEquals(1, method.invoke(null, Long.MAX_VALUE, Long.MIN_VALUE));
1730         assertEquals(1, method.invoke(null, Long.MAX_VALUE, 0));
1731         assertEquals(0, method.invoke(null, Long.MAX_VALUE, Long.MAX_VALUE));
1732     }
1733 
1734     @Test
testArrayLength()1735     public void testArrayLength() throws Exception {
1736         Method booleanArrayLength = arrayLengthMethod(BOOLEAN_ARRAY);
1737         assertEquals(0, booleanArrayLength.invoke(null, new Object[] { new boolean[0] }));
1738         assertEquals(5, booleanArrayLength.invoke(null, new Object[] { new boolean[5] }));
1739 
1740         Method intArrayLength = arrayLengthMethod(INT_ARRAY);
1741         assertEquals(0, intArrayLength.invoke(null, new Object[] { new int[0] }));
1742         assertEquals(5, intArrayLength.invoke(null, new Object[] { new int[5] }));
1743 
1744         Method longArrayLength = arrayLengthMethod(LONG_ARRAY);
1745         assertEquals(0, longArrayLength.invoke(null, new Object[] { new long[0] }));
1746         assertEquals(5, longArrayLength.invoke(null, new Object[] { new long[5] }));
1747 
1748         Method objectArrayLength = arrayLengthMethod(OBJECT_ARRAY);
1749         assertEquals(0, objectArrayLength.invoke(null, new Object[] { new Object[0] }));
1750         assertEquals(5, objectArrayLength.invoke(null, new Object[] { new Object[5] }));
1751 
1752         Method long2dArrayLength = arrayLengthMethod(LONG_2D_ARRAY);
1753         assertEquals(0, long2dArrayLength.invoke(null, new Object[] { new long[0][0] }));
1754         assertEquals(5, long2dArrayLength.invoke(null, new Object[] { new long[5][10] }));
1755     }
1756 
arrayLengthMethod(TypeId<T> valueType)1757     private <T> Method arrayLengthMethod(TypeId<T> valueType) throws Exception {
1758         /*
1759          * public static int call(long[] array) {
1760          *   int result = array.length;
1761          *   return result;
1762          * }
1763          */
1764         reset();
1765         MethodId<?, Integer> methodId = GENERATED.getMethod(TypeId.INT, "call", valueType);
1766         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1767         Local<T> localArray = code.getParameter(0, valueType);
1768         Local<Integer> localResult = code.newLocal(TypeId.INT);
1769         code.arrayLength(localResult, localArray);
1770         code.returnValue(localResult);
1771         return getMethod();
1772     }
1773 
1774     @Test
testNewArray()1775     public void testNewArray() throws Exception {
1776         Method newBooleanArray = newArrayMethod(BOOLEAN_ARRAY);
1777         assertEquals("[]", Arrays.toString((boolean[]) newBooleanArray.invoke(null, 0)));
1778         assertEquals("[false, false, false]",
1779                 Arrays.toString((boolean[]) newBooleanArray.invoke(null, 3)));
1780 
1781         Method newIntArray = newArrayMethod(INT_ARRAY);
1782         assertEquals("[]", Arrays.toString((int[]) newIntArray.invoke(null, 0)));
1783         assertEquals("[0, 0, 0]", Arrays.toString((int[]) newIntArray.invoke(null, 3)));
1784 
1785         Method newLongArray = newArrayMethod(LONG_ARRAY);
1786         assertEquals("[]", Arrays.toString((long[]) newLongArray.invoke(null, 0)));
1787         assertEquals("[0, 0, 0]", Arrays.toString((long[]) newLongArray.invoke(null, 3)));
1788 
1789         Method newObjectArray = newArrayMethod(OBJECT_ARRAY);
1790         assertEquals("[]", Arrays.toString((Object[]) newObjectArray.invoke(null, 0)));
1791         assertEquals("[null, null, null]",
1792                 Arrays.toString((Object[]) newObjectArray.invoke(null, 3)));
1793 
1794         Method new2dLongArray = newArrayMethod(LONG_2D_ARRAY);
1795         assertEquals("[]", Arrays.deepToString((long[][]) new2dLongArray.invoke(null, 0)));
1796         assertEquals("[null, null, null]",
1797                 Arrays.deepToString((long[][]) new2dLongArray.invoke(null, 3)));
1798     }
1799 
newArrayMethod(TypeId<T> valueType)1800     private <T> Method newArrayMethod(TypeId<T> valueType) throws Exception {
1801         /*
1802          * public static long[] call(int length) {
1803          *   long[] result = new long[length];
1804          *   return result;
1805          * }
1806          */
1807         reset();
1808         MethodId<?, T> methodId = GENERATED.getMethod(valueType, "call", TypeId.INT);
1809         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1810         Local<Integer> localLength = code.getParameter(0, TypeId.INT);
1811         Local<T> localResult = code.newLocal(valueType);
1812         code.newArray(localResult, localLength);
1813         code.returnValue(localResult);
1814         return getMethod();
1815     }
1816 
1817     @Test
testReadAndWriteArray()1818     public void testReadAndWriteArray() throws Exception {
1819         Method swapBooleanArray = arraySwapMethod(BOOLEAN_ARRAY, TypeId.BOOLEAN);
1820         boolean[] booleans = new boolean[3];
1821         assertEquals(false, swapBooleanArray.invoke(null, booleans, 1, true));
1822         assertEquals("[false, true, false]", Arrays.toString(booleans));
1823 
1824         Method swapIntArray = arraySwapMethod(INT_ARRAY, TypeId.INT);
1825         int[] ints = new int[3];
1826         assertEquals(0, swapIntArray.invoke(null, ints, 1, 5));
1827         assertEquals("[0, 5, 0]", Arrays.toString(ints));
1828 
1829         Method swapLongArray = arraySwapMethod(LONG_ARRAY, TypeId.LONG);
1830         long[] longs = new long[3];
1831         assertEquals(0L, swapLongArray.invoke(null, longs, 1, 6L));
1832         assertEquals("[0, 6, 0]", Arrays.toString(longs));
1833 
1834         Method swapObjectArray = arraySwapMethod(OBJECT_ARRAY, TypeId.OBJECT);
1835         Object[] objects = new Object[3];
1836         assertEquals(null, swapObjectArray.invoke(null, objects, 1, "X"));
1837         assertEquals("[null, X, null]", Arrays.toString(objects));
1838 
1839         Method swapLong2dArray = arraySwapMethod(LONG_2D_ARRAY, LONG_ARRAY);
1840         long[][] longs2d = new long[3][];
1841         assertEquals(null, swapLong2dArray.invoke(null, longs2d, 1, new long[] { 7 }));
1842         assertEquals("[null, [7], null]", Arrays.deepToString(longs2d));
1843     }
1844 
arraySwapMethod(TypeId<A> arrayType, TypeId<T> singleType)1845     private <A, T> Method arraySwapMethod(TypeId<A> arrayType, TypeId<T> singleType)
1846             throws Exception {
1847         /*
1848          * public static long swap(long[] array, int index, long newValue) {
1849          *   long result = array[index];
1850          *   array[index] = newValue;
1851          *   return result;
1852          * }
1853          */
1854         reset();
1855         MethodId<?, T> methodId = GENERATED.getMethod(
1856                 singleType, "call", arrayType, TypeId.INT, singleType);
1857         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1858         Local<A> localArray = code.getParameter(0, arrayType);
1859         Local<Integer> localIndex = code.getParameter(1, TypeId.INT);
1860         Local<T> localNewValue = code.getParameter(2, singleType);
1861         Local<T> localResult = code.newLocal(singleType);
1862         code.aget(localResult, localArray, localIndex);
1863         code.aput(localArray, localIndex, localNewValue);
1864         code.returnValue(localResult);
1865         return getMethod();
1866     }
1867 
1868     @Test
testSynchronizedFlagImpactsDeclarationOnly()1869     public void testSynchronizedFlagImpactsDeclarationOnly() throws Exception {
1870         /*
1871          * public synchronized void call() {
1872          *   wait(100L);
1873          * }
1874          */
1875         MethodId<?, Void> methodId = GENERATED.getMethod(TypeId.VOID, "call");
1876         MethodId<Object, Void> wait = TypeId.OBJECT.getMethod(TypeId.VOID, "wait", TypeId.LONG);
1877         Code code = dexMaker.declare(methodId, PUBLIC | SYNCHRONIZED);
1878         Local<?> thisLocal = code.getThis(GENERATED);
1879         Local<Long> timeout = code.newLocal(TypeId.LONG);
1880         code.loadConstant(timeout, 100L);
1881         code.invokeVirtual(wait, null, thisLocal, timeout);
1882         code.returnVoid();
1883 
1884         addDefaultConstructor();
1885 
1886         Class<?> generatedClass = generateAndLoad();
1887         Object instance = generatedClass.getDeclaredConstructor().newInstance();
1888         Method method = generatedClass.getMethod("call");
1889         assertTrue(Modifier.isSynchronized(method.getModifiers()));
1890         try {
1891             method.invoke(instance);
1892             fail();
1893         } catch (InvocationTargetException expected) {
1894             assertTrue(expected.getCause() instanceof IllegalMonitorStateException);
1895         }
1896     }
1897 
1898     @Test
testMonitorEnterMonitorExit()1899     public void testMonitorEnterMonitorExit() throws Exception {
1900         /*
1901          * public synchronized void call() {
1902          *   synchronized (this) {
1903          *     wait(100L);
1904          *   }
1905          * }
1906          */
1907         MethodId<?, Void> methodId = GENERATED.getMethod(TypeId.VOID, "call");
1908         MethodId<Object, Void> wait = TypeId.OBJECT.getMethod(TypeId.VOID, "wait", TypeId.LONG);
1909         Code code = dexMaker.declare(methodId, PUBLIC);
1910         Local<?> thisLocal = code.getThis(GENERATED);
1911         Local<Long> timeout = code.newLocal(TypeId.LONG);
1912         code.monitorEnter(thisLocal);
1913         code.loadConstant(timeout, 100L);
1914         code.invokeVirtual(wait, null, thisLocal, timeout);
1915         code.monitorExit(thisLocal);
1916         code.returnVoid();
1917 
1918         addDefaultConstructor();
1919 
1920         Class<?> generatedClass = generateAndLoad();
1921         Object instance = generatedClass.getDeclaredConstructor().newInstance();
1922         Method method = generatedClass.getMethod("call");
1923         assertFalse(Modifier.isSynchronized(method.getModifiers()));
1924         method.invoke(instance); // will take 100ms
1925     }
1926 
1927     @Test
testMoveInt()1928     public void testMoveInt() throws Exception {
1929         /*
1930          * public static int call(int a) {
1931          *   int b = a;
1932          *   int c = a + b;
1933          *   return c;
1934          * }
1935          */
1936         MethodId<?, Integer> methodId = GENERATED.getMethod(TypeId.INT, "call", TypeId.INT);
1937         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
1938         Local<Integer> a = code.getParameter(0, TypeId.INT);
1939         Local<Integer> b = code.newLocal(TypeId.INT);
1940         Local<Integer> c = code.newLocal(TypeId.INT);
1941         code.move(b, a);
1942         code.op(BinaryOp.ADD, c, a, b);
1943         code.returnValue(c);
1944 
1945         assertEquals(6, getMethod().invoke(null, 3));
1946     }
1947 
1948     @Test
testPrivateClassesAreUnsupported()1949     public void testPrivateClassesAreUnsupported() {
1950         try {
1951             dexMaker.declare(TypeId.get("LPrivateClass;"), "PrivateClass.generated", PRIVATE,
1952                     TypeId.OBJECT);
1953             fail();
1954         } catch (IllegalArgumentException expected) {
1955         }
1956     }
1957 
1958     @Test
testAbstractMethodsAreUnsupported()1959     public void testAbstractMethodsAreUnsupported() {
1960         MethodId<?, Void> methodId = GENERATED.getMethod(TypeId.VOID, "call");
1961         try {
1962             dexMaker.declare(methodId, ABSTRACT);
1963             fail();
1964         } catch (IllegalArgumentException expected) {
1965         }
1966     }
1967 
1968     @Test
testNativeMethodsAreUnsupported()1969     public void testNativeMethodsAreUnsupported() {
1970         MethodId<?, Void> methodId = GENERATED.getMethod(TypeId.VOID, "call");
1971         try {
1972             dexMaker.declare(methodId, NATIVE);
1973             fail();
1974         } catch (IllegalArgumentException expected) {
1975         }
1976     }
1977 
1978     @Test
testSynchronizedFieldsAreUnsupported()1979     public void testSynchronizedFieldsAreUnsupported() {
1980         try {
1981             FieldId<?, ?> fieldId = GENERATED.getField(TypeId.OBJECT, "synchronizedField");
1982             dexMaker.declare(fieldId, SYNCHRONIZED, null);
1983             fail();
1984         } catch (IllegalArgumentException expected) {
1985         }
1986     }
1987 
1988     @Test
testInitialValueWithNonStaticField()1989     public void testInitialValueWithNonStaticField() {
1990         try {
1991             FieldId<?, ?> fieldId = GENERATED.getField(TypeId.OBJECT, "nonStaticField");
1992             dexMaker.declare(fieldId, 0, 1);
1993             fail();
1994         } catch (IllegalArgumentException expected) {
1995         }
1996     }
1997 
1998     // TODO: cast primitive to non-primitive
1999     // TODO: cast non-primitive to primitive
2000     // TODO: cast byte to integer
2001     // TODO: cast byte to long
2002     // TODO: cast long to byte
2003     // TODO: fail if a label is unreachable (never navigated to)
2004     // TODO: more strict type parameters: Integer on methods
2005     // TODO: don't generate multiple times (?)
2006     // TODO: test array types
2007     // TODO: test generating an interface
2008     // TODO: declare native method or abstract method
2009     // TODO: get a thrown exception 'e' into a local
2010     // TODO: move a primitive or reference
2011 
addDefaultConstructor()2012     private void addDefaultConstructor() {
2013         Code code = dexMaker.declare(GENERATED.getConstructor(), PUBLIC);
2014         Local<?> thisRef = code.getThis(GENERATED);
2015         code.invokeDirect(TypeId.OBJECT.getConstructor(), null, thisRef);
2016         code.returnVoid();
2017     }
2018 
2019     @Test
testCaching_Methods()2020     public void testCaching_Methods() throws Exception {
2021         int origSize = getDataDirectory().listFiles().length;
2022         final String defaultMethodName = "call";
2023 
2024         dexMaker = new DexMaker();
2025         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT);
2026         addMethodToDexMakerGenerator(TypeId.INT, defaultMethodName, TypeId.INT);
2027         generateAndLoad();
2028         int numFiles = getDataDirectory().listFiles().length;
2029         // DexMaker writes two files to disk at a time: Generated_XXXX.jar and Generated_XXXX.dex.
2030         assertTrue(origSize < numFiles);
2031 
2032         long lastModified  = getJarFiles()[0].lastModified();
2033 
2034         // Create new dexmaker generator with same method signature.
2035         dexMaker = new DexMaker();
2036         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT);
2037         addMethodToDexMakerGenerator(TypeId.INT, defaultMethodName, TypeId.INT);
2038         generateAndLoad();
2039         assertEquals(numFiles, getDataDirectory().listFiles().length);
2040         assertEquals(lastModified, getJarFiles()[0].lastModified());
2041 
2042         // Create new dexmaker generators with different params.
2043         dexMaker = new DexMaker();
2044         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT);
2045         addMethodToDexMakerGenerator(TypeId.INT, defaultMethodName, TypeId.DOUBLE);
2046         generateAndLoad();
2047         assertTrue(numFiles < getDataDirectory().listFiles().length);
2048         numFiles = getDataDirectory().listFiles().length;
2049 
2050         dexMaker = new DexMaker();
2051         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT);
2052         addMethodToDexMakerGenerator(TypeId.INT, defaultMethodName, TypeId.INT, TypeId.DOUBLE);
2053         generateAndLoad();
2054         assertTrue(numFiles < getDataDirectory().listFiles().length);
2055         numFiles = getDataDirectory().listFiles().length;
2056 
2057         // Create new dexmaker generator with different return types.
2058         dexMaker = new DexMaker();
2059         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT);
2060         addMethodToDexMakerGenerator(TypeId.DOUBLE, defaultMethodName, TypeId.INT);
2061         generateAndLoad();
2062         assertTrue(numFiles < getDataDirectory().listFiles().length);
2063         numFiles = getDataDirectory().listFiles().length;
2064 
2065         // Create new dexmaker generators with multiple methods.
2066         dexMaker = new DexMaker();
2067         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT);
2068         addMethodToDexMakerGenerator(TypeId.INT, defaultMethodName, TypeId.INT);
2069         addMethodToDexMakerGenerator(TypeId.INT, defaultMethodName, TypeId.INT, TypeId.BOOLEAN); // new method
2070         generateAndLoad();
2071         assertTrue(numFiles < getDataDirectory().listFiles().length);
2072         numFiles = getDataDirectory().listFiles().length;
2073 
2074         dexMaker = new DexMaker();
2075         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT);
2076         addMethodToDexMakerGenerator(TypeId.INT, defaultMethodName, TypeId.INT, TypeId.BOOLEAN);
2077         addMethodToDexMakerGenerator(TypeId.INT, defaultMethodName, TypeId.INT);
2078         generateAndLoad();
2079         assertEquals(numFiles, getDataDirectory().listFiles().length); // should already be cached.
2080 
2081         dexMaker = new DexMaker();
2082         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT);
2083         addMethodToDexMakerGenerator(TypeId.INT, defaultMethodName, TypeId.INT);
2084         addMethodToDexMakerGenerator(TypeId.INT, defaultMethodName, TypeId.INT, TypeId.INT, TypeId.BOOLEAN); // new method
2085         generateAndLoad();
2086         assertTrue(numFiles < getDataDirectory().listFiles().length);
2087         numFiles = getDataDirectory().listFiles().length;
2088 
2089         dexMaker = new DexMaker();
2090         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT);
2091         addMethodToDexMakerGenerator(TypeId.INT, defaultMethodName, TypeId.INT);
2092         addMethodToDexMakerGenerator(TypeId.INT, defaultMethodName, TypeId.INT, TypeId.INT); // new method
2093         generateAndLoad();
2094         assertTrue(numFiles < getDataDirectory().listFiles().length);
2095         numFiles = getDataDirectory().listFiles().length;
2096 
2097         dexMaker = new DexMaker();
2098         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT);
2099         addMethodToDexMakerGenerator(TypeId.INT, "differentName", TypeId.INT); // new method
2100         addMethodToDexMakerGenerator(TypeId.INT, defaultMethodName, TypeId.INT, TypeId.BOOLEAN);
2101         generateAndLoad();
2102         assertTrue(numFiles < getDataDirectory().listFiles().length);
2103     }
2104 
2105     public static class BlankClassA {}
2106 
2107     public static class BlankClassB {}
2108 
2109     @Test
2110     public void testCaching_DifferentParentClasses() throws Exception {
2111         int origSize = getDataDirectory().listFiles().length;
2112         final String defaultMethodName = "call";
2113 
2114         // Create new dexmaker generator with BlankClassA as supertype.
2115         dexMaker = new DexMaker();
2116         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.get(BlankClassA.class));
2117         addMethodToDexMakerGenerator(TypeId.INT, defaultMethodName, TypeId.INT);
2118         generateAndLoad();
2119         // DexMaker writes two files to disk at a time: Generated_XXXX.jar and Generated_XXXX.dex.
2120         int numFiles = getDataDirectory().listFiles().length;
2121         assertTrue(origSize < numFiles);
2122 
2123         // Create new dexmaker generator with BlankClassB as supertype.
2124         dexMaker = new DexMaker();
2125         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.get(BlankClassB.class));
2126         addMethodToDexMakerGenerator(TypeId.INT, defaultMethodName, TypeId.INT);
2127         generateAndLoad();
2128         assertTrue(numFiles < getDataDirectory().listFiles().length);
2129     }
2130 
2131     private void addMethodToDexMakerGenerator(TypeId<?> typeId, String methodName, TypeId<?>... params) throws Exception {
2132         MethodId<?, ?> methodId = GENERATED.getMethod(typeId, methodName, params);
2133         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
2134         TypeId<IllegalStateException> iseType = TypeId.get(IllegalStateException.class);
2135         Local<IllegalStateException> localIse = code.newLocal(iseType);
2136         if (params.length > 0) {
2137             if (params[0].equals(typeId)) {
2138                 Local<?> localResult = code.getParameter(0, TypeId.INT);
2139                 code.returnValue(localResult);
2140             } else {
2141                 code.throwValue(localIse);
2142             }
2143         } else {
2144             code.throwValue(localIse);
2145         }
2146     }
2147 
2148     public interface BlankInterfaceA {}
2149 
2150     public interface BlankInterfaceB {}
2151 
2152     @Test
2153     public void testCaching_DifferentInterfaces() throws Exception {
2154         int origSize = getDataDirectory().listFiles().length;
2155 
2156         // Create new dexmaker generator with BlankInterfaceA.
2157         dexMaker = new DexMaker();
2158         TypeId interfaceA = TypeId.get(BlankInterfaceA.class);
2159         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT, interfaceA);
2160         generateAndLoad();
2161         int numFiles = getDataDirectory().listFiles().length;
2162         assertTrue(origSize < numFiles);
2163 
2164         // Create new dexmaker generator with BlankInterfaceB.
2165         dexMaker = new DexMaker();
2166         TypeId interfaceB = TypeId.get(BlankInterfaceB.class);
2167         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT, interfaceB);
2168         generateAndLoad();
2169         assertTrue(numFiles < getDataDirectory().listFiles().length);
2170     }
2171 
2172     @Test
2173     public void testCaching_Constructors() throws Exception {
2174         int origSize = getDataDirectory().listFiles().length;
2175 
2176         // Create new dexmaker generator with Generated(int) constructor.
2177         dexMaker = new DexMaker();
2178         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT);
2179         addConstructorToDexMakerGenerator(TypeId.INT);
2180         generateAndLoad();
2181         // DexMaker writes two files to disk at a time: Generated_XXXX.jar and Generated_XXXX.dex.
2182         int numFiles = getDataDirectory().listFiles().length;
2183         assertTrue(origSize < numFiles);
2184 
2185         long lastModified  = getJarFiles()[0].lastModified();
2186 
2187         dexMaker = new DexMaker();
2188         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT);
2189         addConstructorToDexMakerGenerator(TypeId.INT);
2190         generateAndLoad();
2191         assertEquals(numFiles, getDataDirectory().listFiles().length);
2192         assertEquals(lastModified, getJarFiles()[0].lastModified());
2193 
2194         // Create new dexmaker generator with Generated(boolean) constructor.
2195         dexMaker = new DexMaker();
2196         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT);
2197         addConstructorToDexMakerGenerator(TypeId.BOOLEAN);
2198         generateAndLoad();
2199         assertTrue(numFiles < getDataDirectory().listFiles().length);
2200         numFiles = getDataDirectory().listFiles().length;
2201 
2202         // Create new dexmaker generator with multiple constructors.
2203         dexMaker = new DexMaker();
2204         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT);
2205         addConstructorToDexMakerGenerator(TypeId.INT);
2206         addConstructorToDexMakerGenerator(TypeId.BOOLEAN);
2207         generateAndLoad();
2208         assertTrue(numFiles < getDataDirectory().listFiles().length);
2209         numFiles = getDataDirectory().listFiles().length;
2210 
2211         // Ensure that order of constructors does not affect caching decision.
2212         dexMaker = new DexMaker();
2213         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT);
2214         addConstructorToDexMakerGenerator(TypeId.BOOLEAN);
2215         addConstructorToDexMakerGenerator(TypeId.INT);
2216         generateAndLoad();
2217         assertEquals(numFiles, getDataDirectory().listFiles().length);
2218     }
2219 
2220     private void addConstructorToDexMakerGenerator(TypeId<?>... params) throws Exception {
2221         MethodId<?, Void> constructor = GENERATED.getConstructor(params);
2222         Code code = dexMaker.declare(constructor, PUBLIC);
2223         code.returnVoid();
2224     }
2225 
2226     private File[] getJarFiles() {
2227         return getDataDirectory().listFiles(new FilenameFilter() {
2228             @Override
2229             public boolean accept(File dir, String name) {
2230                 return name.endsWith(".jar");
2231             }
2232         });
2233     }
2234 
2235     /**
2236      * Returns the generated method.
2237      */
2238     private Method getMethod() throws Exception {
2239         Class<?> generated = generateAndLoad();
2240         for (Method method : generated.getMethods()) {
2241             if (method.getName().equals("call")) {
2242                 return method;
2243             }
2244         }
2245         throw new IllegalStateException("no call() method");
2246     }
2247 
2248     public static File getDataDirectory() {
2249         String dataDir = InstrumentationRegistry.getTargetContext().getApplicationInfo().dataDir;
2250         return new File(dataDir);
2251     }
2252 
2253     private Class<?> generateAndLoad() throws Exception {
2254         return dexMaker.generateAndLoad(getClass().getClassLoader(), getDataDirectory())
2255                 .loadClass("Generated");
2256     }
2257 
2258     private final ClassLoader commonClassLoader = new BaseDexClassLoader(
2259             getDataDirectory().getPath(), getDataDirectory(), getDataDirectory().getPath(),
2260             DexMakerTest.class.getClassLoader());
2261 
2262     private final ClassLoader uncommonClassLoader = new ClassLoader() {
2263         @Override
2264         public Class<?> loadClass(String name) throws ClassNotFoundException {
2265             throw new IllegalStateException("Not used");
2266         }
2267     };
2268 
2269     private static void loadWithSharedClassLoader(ClassLoader cl, boolean markAsTrusted,
2270                                                   boolean shouldUseCL) throws Exception {
2271         DexMaker d = new DexMaker();
2272         d.setSharedClassLoader(cl);
2273 
2274         if (markAsTrusted) {
2275             d.markAsTrusted();
2276         }
2277 
2278         ClassLoader selectedCL = d.generateAndLoad(null, getDataDirectory());
2279 
2280         if (shouldUseCL) {
2281             assertSame(cl, selectedCL);
2282         } else {
2283             assertNotSame(cl, selectedCL);
2284 
2285             // An appropriate fallback should have been selected
2286             assertNotNull(selectedCL);
2287         }
2288     }
2289 
2290     @Test
2291     public void loadWithUncommonSharedClassLoader() throws Exception{
2292         assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N);
2293 
2294         loadWithSharedClassLoader(uncommonClassLoader, false, false);
2295     }
2296 
2297     @Test
2298     public void loadWithCommonSharedClassLoader() throws Exception{
2299         assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N);
2300 
2301         loadWithSharedClassLoader(commonClassLoader, false, true);
2302     }
2303 
2304     @Test
2305     public void loadAsTrustedWithUncommonSharedClassLoader() throws Exception{
2306         assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P);
2307 
2308         loadWithSharedClassLoader(uncommonClassLoader, true, false);
2309     }
2310 
2311     @Test
2312     public void loadAsTrustedWithCommonSharedClassLoader() throws Exception{
2313         assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P);
2314 
2315         loadWithSharedClassLoader(commonClassLoader, true, true);
2316     }
2317 }
2318