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