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