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