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