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