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