Home | History | Annotate | Download | only in dexmaker
      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 
     17 package com.google.dexmaker;
     18 
     19 import java.io.File;
     20 import java.io.IOException;
     21 import java.lang.reflect.Field;
     22 import java.lang.reflect.InvocationTargetException;
     23 import java.lang.reflect.Method;
     24 import java.lang.reflect.Modifier;
     25 import static java.lang.reflect.Modifier.ABSTRACT;
     26 import static java.lang.reflect.Modifier.FINAL;
     27 import static java.lang.reflect.Modifier.NATIVE;
     28 import static java.lang.reflect.Modifier.PRIVATE;
     29 import static java.lang.reflect.Modifier.PROTECTED;
     30 import static java.lang.reflect.Modifier.PUBLIC;
     31 import static java.lang.reflect.Modifier.STATIC;
     32 import static java.lang.reflect.Modifier.SYNCHRONIZED;
     33 import java.util.Arrays;
     34 import java.util.List;
     35 import java.util.concurrent.Callable;
     36 import 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  */
     45 public 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, BinaryOp.ADD);
    734         assertEquals(79, add.invoke(null, 75, 4));
    735 
    736         Method subtract = binaryOpMethod(int.class, BinaryOp.SUBTRACT);
    737         assertEquals(71, subtract.invoke(null, 75, 4));
    738 
    739         Method multiply = binaryOpMethod(int.class, BinaryOp.MULTIPLY);
    740         assertEquals(300, multiply.invoke(null, 75, 4));
    741 
    742         Method divide = binaryOpMethod(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, 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, BinaryOp.AND);
    761         assertEquals(0xff000000, and.invoke(null, 0xff00ff00, 0xffff0000));
    762 
    763         Method or = binaryOpMethod(int.class, BinaryOp.OR);
    764         assertEquals(0xffffff00, or.invoke(null, 0xff00ff00, 0xffff0000));
    765 
    766         Method xor = binaryOpMethod(int.class, BinaryOp.XOR);
    767         assertEquals(0x00ffff00, xor.invoke(null, 0xff00ff00, 0xffff0000));
    768 
    769         Method shiftLeft = binaryOpMethod(int.class, BinaryOp.SHIFT_LEFT);
    770         assertEquals(0xcd123400, shiftLeft.invoke(null, 0xabcd1234, 8));
    771 
    772         Method shiftRight = binaryOpMethod(int.class, BinaryOp.SHIFT_RIGHT);
    773         assertEquals(0xffabcd12, shiftRight.invoke(null, 0xabcd1234, 8));
    774 
    775         Method unsignedShiftRight = binaryOpMethod(int.class,
    776                 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, BinaryOp.ADD);
    782         assertEquals(79L, add.invoke(null, 75L, 4L));
    783 
    784         Method subtract = binaryOpMethod(long.class, BinaryOp.SUBTRACT);
    785         assertEquals(71L, subtract.invoke(null, 75L, 4L));
    786 
    787         Method multiply = binaryOpMethod(long.class, BinaryOp.MULTIPLY);
    788         assertEquals(300L, multiply.invoke(null, 75L, 4L));
    789 
    790         Method divide = binaryOpMethod(long.class, BinaryOp.DIVIDE);
    791         assertEquals(18L, divide.invoke(null, 75L, 4L));
    792         try {
    793             divide.invoke(null, 75L, 0L);
    794             fail();
    795         } catch (InvocationTargetException expected) {
    796             assertEquals(ArithmeticException.class, expected.getCause().getClass());
    797         }
    798 
    799         Method remainder = binaryOpMethod(long.class, BinaryOp.REMAINDER);
    800         assertEquals(3L, remainder.invoke(null, 75L, 4L));
    801         try {
    802             remainder.invoke(null, 75L, 0L);
    803             fail();
    804         } catch (InvocationTargetException expected) {
    805             assertEquals(ArithmeticException.class, expected.getCause().getClass());
    806         }
    807 
    808         Method and = binaryOpMethod(long.class, BinaryOp.AND);
    809         assertEquals(0xff00ff0000000000L,
    810                 and.invoke(null, 0xff00ff00ff00ff00L, 0xffffffff00000000L));
    811 
    812         Method or = binaryOpMethod(long.class, BinaryOp.OR);
    813         assertEquals(0xffffffffff00ff00L,
    814                 or.invoke(null, 0xff00ff00ff00ff00L, 0xffffffff00000000L));
    815 
    816         Method xor = binaryOpMethod(long.class, BinaryOp.XOR);
    817         assertEquals(0x00ff00ffff00ff00L,
    818                 xor.invoke(null, 0xff00ff00ff00ff00L, 0xffffffff00000000L));
    819 
    820         Method shiftLeft = binaryOpMethod(long.class, BinaryOp.SHIFT_LEFT);
    821         assertEquals(0xcdef012345678900L, shiftLeft.invoke(null, 0xabcdef0123456789L, 8L));
    822 
    823         Method shiftRight = binaryOpMethod(long.class, BinaryOp.SHIFT_RIGHT);
    824         assertEquals(0xffabcdef01234567L, shiftRight.invoke(null, 0xabcdef0123456789L, 8L));
    825 
    826         Method unsignedShiftRight = binaryOpMethod(long.class,
    827                 BinaryOp.UNSIGNED_SHIFT_RIGHT);
    828         assertEquals(0x00abcdef01234567L, unsignedShiftRight.invoke(null, 0xabcdef0123456789L, 8L));
    829     }
    830 
    831     public void testFloatBinaryOps() throws Exception {
    832         Method add = binaryOpMethod(float.class, BinaryOp.ADD);
    833         assertEquals(6.75f, add.invoke(null, 5.5f, 1.25f));
    834 
    835         Method subtract = binaryOpMethod(float.class, BinaryOp.SUBTRACT);
    836         assertEquals(4.25f, subtract.invoke(null, 5.5f, 1.25f));
    837 
    838         Method multiply = binaryOpMethod(float.class, BinaryOp.MULTIPLY);
    839         assertEquals(6.875f, multiply.invoke(null, 5.5f, 1.25f));
    840 
    841         Method divide = binaryOpMethod(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, 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, BinaryOp.ADD);
    852         assertEquals(6.75, add.invoke(null, 5.5, 1.25));
    853 
    854         Method subtract = binaryOpMethod(double.class, BinaryOp.SUBTRACT);
    855         assertEquals(4.25, subtract.invoke(null, 5.5, 1.25));
    856 
    857         Method multiply = binaryOpMethod(double.class, BinaryOp.MULTIPLY);
    858         assertEquals(6.875, multiply.invoke(null, 5.5, 1.25));
    859 
    860         Method divide = binaryOpMethod(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, 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 <T> Method binaryOpMethod(Class<T> valueClass, BinaryOp op)
    870             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<T> valueType = TypeId.get(valueClass);
    879         MethodId<?, T> methodId = GENERATED.getMethod(valueType, "call", valueType, valueType);
    880         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
    881         Local<T> localA = code.getParameter(0, valueType);
    882         Local<T> localB = code.getParameter(1, valueType);
    883         Local<T> localResult = code.newLocal(valueType);
    884         code.op(op, localResult, localA, localB);
    885         code.returnValue(localResult);
    886         return getMethod();
    887     }
    888 
    889     public void testReadAndWriteInstanceFields() throws Exception {
    890         Instance instance = new Instance();
    891 
    892         Method intSwap = instanceSwapMethod(int.class, "intValue");
    893         instance.intValue = 5;
    894         assertEquals(5, intSwap.invoke(null, instance, 10));
    895         assertEquals(10, instance.intValue);
    896 
    897         Method longSwap = instanceSwapMethod(long.class, "longValue");
    898         instance.longValue = 500L;
    899         assertEquals(500L, longSwap.invoke(null, instance, 1234L));
    900         assertEquals(1234L, instance.longValue);
    901 
    902         Method booleanSwap = instanceSwapMethod(boolean.class, "booleanValue");
    903         instance.booleanValue = false;
    904         assertEquals(false, booleanSwap.invoke(null, instance, true));
    905         assertEquals(true, instance.booleanValue);
    906 
    907         Method floatSwap = instanceSwapMethod(float.class, "floatValue");
    908         instance.floatValue = 1.5f;
    909         assertEquals(1.5f, floatSwap.invoke(null, instance, 0.5f));
    910         assertEquals(0.5f, instance.floatValue);
    911 
    912         Method doubleSwap = instanceSwapMethod(double.class, "doubleValue");
    913         instance.doubleValue = 155.5;
    914         assertEquals(155.5, doubleSwap.invoke(null, instance, 266.6));
    915         assertEquals(266.6, instance.doubleValue);
    916 
    917         Method objectSwap = instanceSwapMethod(Object.class, "objectValue");
    918         instance.objectValue = "before";
    919         assertEquals("before", objectSwap.invoke(null, instance, "after"));
    920         assertEquals("after", instance.objectValue);
    921 
    922         Method byteSwap = instanceSwapMethod(byte.class, "byteValue");
    923         instance.byteValue = 0x35;
    924         assertEquals((byte) 0x35, byteSwap.invoke(null, instance, (byte) 0x64));
    925         assertEquals((byte) 0x64, instance.byteValue);
    926 
    927         Method charSwap = instanceSwapMethod(char.class, "charValue");
    928         instance.charValue = 'A';
    929         assertEquals('A', charSwap.invoke(null, instance, 'B'));
    930         assertEquals('B', instance.charValue);
    931 
    932         Method shortSwap = instanceSwapMethod(short.class, "shortValue");
    933         instance.shortValue = (short) 0xabcd;
    934         assertEquals((short) 0xabcd, shortSwap.invoke(null, instance, (short) 0x1234));
    935         assertEquals((short) 0x1234, instance.shortValue);
    936     }
    937 
    938     public class Instance {
    939         public int intValue;
    940         public long longValue;
    941         public float floatValue;
    942         public double doubleValue;
    943         public Object objectValue;
    944         public boolean booleanValue;
    945         public byte byteValue;
    946         public char charValue;
    947         public short shortValue;
    948     }
    949 
    950     private <V> Method instanceSwapMethod(
    951             Class<V> valueClass, String fieldName) throws Exception {
    952         /*
    953          * public static int call(Instance instance, int newValue) {
    954          *   int oldValue = instance.intValue;
    955          *   instance.intValue = newValue;
    956          *   return oldValue;
    957          * }
    958          */
    959         reset();
    960         TypeId<V> valueType = TypeId.get(valueClass);
    961         TypeId<Instance> objectType = TypeId.get(Instance.class);
    962         FieldId<Instance, V> fieldId = objectType.getField(valueType, fieldName);
    963         MethodId<?, V> methodId = GENERATED.getMethod(valueType, "call", objectType, valueType);
    964         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
    965         Local<Instance> localInstance = code.getParameter(0, objectType);
    966         Local<V> localNewValue = code.getParameter(1, valueType);
    967         Local<V> localOldValue = code.newLocal(valueType);
    968         code.iget(fieldId, localOldValue, localInstance);
    969         code.iput(fieldId, localInstance, localNewValue);
    970         code.returnValue(localOldValue);
    971         return getMethod();
    972     }
    973 
    974     public void testReadAndWriteStaticFields() throws Exception {
    975         Method intSwap = staticSwapMethod(int.class, "intValue");
    976         Static.intValue = 5;
    977         assertEquals(5, intSwap.invoke(null, 10));
    978         assertEquals(10, Static.intValue);
    979 
    980         Method longSwap = staticSwapMethod(long.class, "longValue");
    981         Static.longValue = 500L;
    982         assertEquals(500L, longSwap.invoke(null, 1234L));
    983         assertEquals(1234L, Static.longValue);
    984 
    985         Method booleanSwap = staticSwapMethod(boolean.class, "booleanValue");
    986         Static.booleanValue = false;
    987         assertEquals(false, booleanSwap.invoke(null, true));
    988         assertEquals(true, Static.booleanValue);
    989 
    990         Method floatSwap = staticSwapMethod(float.class, "floatValue");
    991         Static.floatValue = 1.5f;
    992         assertEquals(1.5f, floatSwap.invoke(null, 0.5f));
    993         assertEquals(0.5f, Static.floatValue);
    994 
    995         Method doubleSwap = staticSwapMethod(double.class, "doubleValue");
    996         Static.doubleValue = 155.5;
    997         assertEquals(155.5, doubleSwap.invoke(null, 266.6));
    998         assertEquals(266.6, Static.doubleValue);
    999 
   1000         Method objectSwap = staticSwapMethod(Object.class, "objectValue");
   1001         Static.objectValue = "before";
   1002         assertEquals("before", objectSwap.invoke(null, "after"));
   1003         assertEquals("after", Static.objectValue);
   1004 
   1005         Method byteSwap = staticSwapMethod(byte.class, "byteValue");
   1006         Static.byteValue = 0x35;
   1007         assertEquals((byte) 0x35, byteSwap.invoke(null, (byte) 0x64));
   1008         assertEquals((byte) 0x64, Static.byteValue);
   1009 
   1010         Method charSwap = staticSwapMethod(char.class, "charValue");
   1011         Static.charValue = 'A';
   1012         assertEquals('A', charSwap.invoke(null, 'B'));
   1013         assertEquals('B', Static.charValue);
   1014 
   1015         Method shortSwap = staticSwapMethod(short.class, "shortValue");
   1016         Static.shortValue = (short) 0xabcd;
   1017         assertEquals((short) 0xabcd, shortSwap.invoke(null, (short) 0x1234));
   1018         assertEquals((short) 0x1234, Static.shortValue);
   1019     }
   1020 
   1021     public static class Static {
   1022         public static int intValue;
   1023         public static long longValue;
   1024         public static float floatValue;
   1025         public static double doubleValue;
   1026         public static Object objectValue;
   1027         public static boolean booleanValue;
   1028         public static byte byteValue;
   1029         public static char charValue;
   1030         public static short shortValue;
   1031     }
   1032 
   1033     private <V> Method staticSwapMethod(Class<V> valueClass, String fieldName)
   1034             throws Exception {
   1035         /*
   1036          * public static int call(int newValue) {
   1037          *   int oldValue = Static.intValue;
   1038          *   Static.intValue = newValue;
   1039          *   return oldValue;
   1040          * }
   1041          */
   1042         reset();
   1043         TypeId<V> valueType = TypeId.get(valueClass);
   1044         TypeId<Static> objectType = TypeId.get(Static.class);
   1045         FieldId<Static, V> fieldId = objectType.getField(valueType, fieldName);
   1046         MethodId<?, V> methodId = GENERATED.getMethod(valueType, "call", valueType);
   1047         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
   1048         Local<V> localNewValue = code.getParameter(0, valueType);
   1049         Local<V> localOldValue = code.newLocal(valueType);
   1050         code.sget(fieldId, localOldValue);
   1051         code.sput(fieldId, localNewValue);
   1052         code.returnValue(localOldValue);
   1053         return getMethod();
   1054     }
   1055 
   1056     public void testTypeCast() throws Exception {
   1057         /*
   1058          * public static String call(Object o) {
   1059          *   String s = (String) o;
   1060          * }
   1061          */
   1062         MethodId<?, String> methodId = GENERATED.getMethod(TypeId.STRING, "call", TypeId.OBJECT);
   1063         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
   1064         Local<Object> localObject = code.getParameter(0, TypeId.OBJECT);
   1065         Local<String> localString = code.newLocal(TypeId.STRING);
   1066         code.cast(localString, localObject);
   1067         code.returnValue(localString);
   1068 
   1069         Method method = getMethod();
   1070         assertEquals("s", method.invoke(null, "s"));
   1071         assertEquals(null, method.invoke(null, (String) null));
   1072         try {
   1073             method.invoke(null, 5);
   1074             fail();
   1075         } catch (InvocationTargetException expected) {
   1076             assertEquals(ClassCastException.class, expected.getCause().getClass());
   1077         }
   1078     }
   1079 
   1080     public void testInstanceOf() throws Exception {
   1081         /*
   1082          * public static boolean call(Object o) {
   1083          *   boolean result = o instanceof String;
   1084          *   return result;
   1085          * }
   1086          */
   1087         MethodId<?, Boolean> methodId = GENERATED.getMethod(TypeId.BOOLEAN, "call", TypeId.OBJECT);
   1088         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
   1089         Local<Object> localObject = code.getParameter(0, TypeId.OBJECT);
   1090         Local<Boolean> localResult = code.newLocal(TypeId.BOOLEAN);
   1091         code.instanceOfType(localResult, localObject, TypeId.STRING);
   1092         code.returnValue(localResult);
   1093 
   1094         Method method = getMethod();
   1095         assertEquals(true, method.invoke(null, "s"));
   1096         assertEquals(false, method.invoke(null, (String) null));
   1097         assertEquals(false, method.invoke(null, 5));
   1098     }
   1099 
   1100     /**
   1101      * Tests that we can construct a for loop.
   1102      */
   1103     public void testForLoop() throws Exception {
   1104         /*
   1105          * public static int call(int count) {
   1106          *   int result = 1;
   1107          *   for (int i = 0; i < count; i += 1) {
   1108          *     result = result * 2;
   1109          *   }
   1110          *   return result;
   1111          * }
   1112          */
   1113         MethodId<?, Integer> methodId = GENERATED.getMethod(TypeId.INT, "call", TypeId.INT);
   1114         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
   1115         Local<Integer> localCount = code.getParameter(0, TypeId.INT);
   1116         Local<Integer> localResult = code.newLocal(TypeId.INT);
   1117         Local<Integer> localI = code.newLocal(TypeId.INT);
   1118         Local<Integer> local1 = code.newLocal(TypeId.INT);
   1119         Local<Integer> local2 = code.newLocal(TypeId.INT);
   1120         code.loadConstant(local1, 1);
   1121         code.loadConstant(local2, 2);
   1122         code.loadConstant(localResult, 1);
   1123         code.loadConstant(localI, 0);
   1124         Label loopCondition = new Label();
   1125         Label loopBody = new Label();
   1126         Label afterLoop = new Label();
   1127         code.mark(loopCondition);
   1128         code.compare(Comparison.LT, loopBody, localI, localCount);
   1129         code.jump(afterLoop);
   1130         code.mark(loopBody);
   1131         code.op(BinaryOp.MULTIPLY, localResult, localResult, local2);
   1132         code.op(BinaryOp.ADD, localI, localI, local1);
   1133         code.jump(loopCondition);
   1134         code.mark(afterLoop);
   1135         code.returnValue(localResult);
   1136 
   1137         Method pow2 = getMethod();
   1138         assertEquals(1, pow2.invoke(null, 0));
   1139         assertEquals(2, pow2.invoke(null, 1));
   1140         assertEquals(4, pow2.invoke(null, 2));
   1141         assertEquals(8, pow2.invoke(null, 3));
   1142         assertEquals(16, pow2.invoke(null, 4));
   1143     }
   1144 
   1145     /**
   1146      * Tests that we can construct a while loop.
   1147      */
   1148     public void testWhileLoop() throws Exception {
   1149         /*
   1150          * public static int call(int max) {
   1151          *   int result = 1;
   1152          *   while (result < max) {
   1153          *     result = result * 2;
   1154          *   }
   1155          *   return result;
   1156          * }
   1157          */
   1158         MethodId<?, Integer> methodId = GENERATED.getMethod(TypeId.INT, "call", TypeId.INT);
   1159         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
   1160         Local<Integer> localMax = code.getParameter(0, TypeId.INT);
   1161         Local<Integer> localResult = code.newLocal(TypeId.INT);
   1162         Local<Integer> local2 = code.newLocal(TypeId.INT);
   1163         code.loadConstant(localResult, 1);
   1164         code.loadConstant(local2, 2);
   1165         Label loopCondition = new Label();
   1166         Label loopBody = new Label();
   1167         Label afterLoop = new Label();
   1168         code.mark(loopCondition);
   1169         code.compare(Comparison.LT, loopBody, localResult, localMax);
   1170         code.jump(afterLoop);
   1171         code.mark(loopBody);
   1172         code.op(BinaryOp.MULTIPLY, localResult, localResult, local2);
   1173         code.jump(loopCondition);
   1174         code.mark(afterLoop);
   1175         code.returnValue(localResult);
   1176 
   1177         Method ceilPow2 = getMethod();
   1178         assertEquals(1, ceilPow2.invoke(null, 1));
   1179         assertEquals(2, ceilPow2.invoke(null, 2));
   1180         assertEquals(4, ceilPow2.invoke(null, 3));
   1181         assertEquals(16, ceilPow2.invoke(null, 10));
   1182         assertEquals(128, ceilPow2.invoke(null, 100));
   1183         assertEquals(1024, ceilPow2.invoke(null, 1000));
   1184     }
   1185 
   1186     public void testIfElseBlock() throws Exception {
   1187         /*
   1188          * public static int call(int a, int b, int c) {
   1189          *   if (a < b) {
   1190          *     if (a < c) {
   1191          *       return a;
   1192          *     } else {
   1193          *       return c;
   1194          *     }
   1195          *   } else if (b < c) {
   1196          *     return b;
   1197          *   } else {
   1198          *     return c;
   1199          *   }
   1200          * }
   1201          */
   1202         MethodId<?, Integer> methodId = GENERATED.getMethod(
   1203                 TypeId.INT, "call", TypeId.INT, TypeId.INT, TypeId.INT);
   1204         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
   1205         Local<Integer> localA = code.getParameter(0, TypeId.INT);
   1206         Local<Integer> localB = code.getParameter(1, TypeId.INT);
   1207         Local<Integer> localC = code.getParameter(2, TypeId.INT);
   1208         Label aLessThanB = new Label();
   1209         Label aLessThanC = new Label();
   1210         Label bLessThanC = new Label();
   1211         code.compare(Comparison.LT, aLessThanB, localA, localB);
   1212         code.compare(Comparison.LT, bLessThanC, localB, localC);
   1213         code.returnValue(localC);
   1214         // (a < b)
   1215         code.mark(aLessThanB);
   1216         code.compare(Comparison.LT, aLessThanC, localA, localC);
   1217         code.returnValue(localC);
   1218         // (a < c)
   1219         code.mark(aLessThanC);
   1220         code.returnValue(localA);
   1221         // (b < c)
   1222         code.mark(bLessThanC);
   1223         code.returnValue(localB);
   1224 
   1225         Method min = getMethod();
   1226         assertEquals(1, min.invoke(null, 1, 2, 3));
   1227         assertEquals(1, min.invoke(null, 2, 3, 1));
   1228         assertEquals(1, min.invoke(null, 2, 1, 3));
   1229         assertEquals(1, min.invoke(null, 3, 2, 1));
   1230     }
   1231 
   1232     public void testRecursion() throws Exception {
   1233         /*
   1234          * public static int call(int a) {
   1235          *   if (a < 2) {
   1236          *     return a;
   1237          *   }
   1238          *   a -= 1;
   1239          *   int x = call(a)
   1240          *   a -= 1;
   1241          *   int y = call(a);
   1242          *   int result = x + y;
   1243          *   return result;
   1244          * }
   1245          */
   1246         MethodId<?, Integer> methodId = GENERATED.getMethod(TypeId.INT, "call", TypeId.INT);
   1247         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
   1248         Local<Integer> localA = code.getParameter(0, TypeId.INT);
   1249         Local<Integer> local1 = code.newLocal(TypeId.INT);
   1250         Local<Integer> local2 = code.newLocal(TypeId.INT);
   1251         Local<Integer> localX = code.newLocal(TypeId.INT);
   1252         Local<Integer> localY = code.newLocal(TypeId.INT);
   1253         Local<Integer> localResult = code.newLocal(TypeId.INT);
   1254         Label baseCase = new Label();
   1255         code.loadConstant(local1, 1);
   1256         code.loadConstant(local2, 2);
   1257         code.compare(Comparison.LT, baseCase, localA, local2);
   1258         code.op(BinaryOp.SUBTRACT, localA, localA, local1);
   1259         code.invokeStatic(methodId, localX, localA);
   1260         code.op(BinaryOp.SUBTRACT, localA, localA, local1);
   1261         code.invokeStatic(methodId, localY, localA);
   1262         code.op(BinaryOp.ADD, localResult, localX, localY);
   1263         code.returnValue(localResult);
   1264         code.mark(baseCase);
   1265         code.returnValue(localA);
   1266 
   1267         Method fib = getMethod();
   1268         assertEquals(0, fib.invoke(null, 0));
   1269         assertEquals(1, fib.invoke(null, 1));
   1270         assertEquals(1, fib.invoke(null, 2));
   1271         assertEquals(2, fib.invoke(null, 3));
   1272         assertEquals(3, fib.invoke(null, 4));
   1273         assertEquals(5, fib.invoke(null, 5));
   1274         assertEquals(8, fib.invoke(null, 6));
   1275     }
   1276 
   1277     public void testCatchExceptions() throws Exception {
   1278         /*
   1279          * public static String call(int i) {
   1280          *   try {
   1281          *     DexMakerTest.thrower(i);
   1282          *     return "NONE";
   1283          *   } catch (IllegalArgumentException e) {
   1284          *     return "IAE";
   1285          *   } catch (IllegalStateException e) {
   1286          *     return "ISE";
   1287          *   } catch (RuntimeException e) {
   1288          *     return "RE";
   1289          *   }
   1290          */
   1291         MethodId<?, String> methodId = GENERATED.getMethod(TypeId.STRING, "call", TypeId.INT);
   1292         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
   1293         Local<Integer> localI = code.getParameter(0, TypeId.INT);
   1294         Local<String> result = code.newLocal(TypeId.STRING);
   1295         Label catchIae = new Label();
   1296         Label catchIse = new Label();
   1297         Label catchRe = new Label();
   1298 
   1299         code.addCatchClause(TypeId.get(IllegalArgumentException.class), catchIae);
   1300         code.addCatchClause(TypeId.get(IllegalStateException.class), catchIse);
   1301         code.addCatchClause(TypeId.get(RuntimeException.class), catchRe);
   1302         MethodId<?, ?> thrower = TEST_TYPE.getMethod(TypeId.VOID, "thrower", TypeId.INT);
   1303         code.invokeStatic(thrower, null, localI);
   1304         code.loadConstant(result, "NONE");
   1305         code.returnValue(result);
   1306 
   1307         code.mark(catchIae);
   1308         code.loadConstant(result, "IAE");
   1309         code.returnValue(result);
   1310 
   1311         code.mark(catchIse);
   1312         code.loadConstant(result, "ISE");
   1313         code.returnValue(result);
   1314 
   1315         code.mark(catchRe);
   1316         code.loadConstant(result, "RE");
   1317         code.returnValue(result);
   1318 
   1319         Method method = getMethod();
   1320         assertEquals("NONE", method.invoke(null, 0));
   1321         assertEquals("IAE", method.invoke(null, 1));
   1322         assertEquals("ISE", method.invoke(null, 2));
   1323         assertEquals("RE", method.invoke(null, 3));
   1324         try {
   1325             method.invoke(null, 4);
   1326             fail();
   1327         } catch (InvocationTargetException expected) {
   1328             assertEquals(IOException.class, expected.getCause().getClass());
   1329         }
   1330     }
   1331 
   1332     @SuppressWarnings("unused") // called by generated code
   1333     public static void thrower(int a) throws Exception {
   1334         switch (a) {
   1335         case 0:
   1336             return;
   1337         case 1:
   1338             throw new IllegalArgumentException();
   1339         case 2:
   1340             throw new IllegalStateException();
   1341         case 3:
   1342             throw new UnsupportedOperationException();
   1343         case 4:
   1344             throw new IOException();
   1345         default:
   1346             throw new AssertionError();
   1347         }
   1348     }
   1349 
   1350     public void testNestedCatchClauses() throws Exception {
   1351         /*
   1352          * public static String call(int a, int b, int c) {
   1353          *   try {
   1354          *     DexMakerTest.thrower(a);
   1355          *     try {
   1356          *       DexMakerTest.thrower(b);
   1357          *     } catch (IllegalArgumentException) {
   1358          *       return "INNER";
   1359          *     }
   1360          *     DexMakerTest.thrower(c);
   1361          *     return "NONE";
   1362          *   } catch (IllegalArgumentException e) {
   1363          *     return "OUTER";
   1364          *   }
   1365          */
   1366         MethodId<?, String> methodId = GENERATED.getMethod(
   1367                 TypeId.STRING, "call", TypeId.INT, TypeId.INT, TypeId.INT);
   1368         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
   1369         Local<Integer> localA = code.getParameter(0, TypeId.INT);
   1370         Local<Integer> localB = code.getParameter(1, TypeId.INT);
   1371         Local<Integer> localC = code.getParameter(2, TypeId.INT);
   1372         Local<String> localResult = code.newLocal(TypeId.STRING);
   1373         Label catchInner = new Label();
   1374         Label catchOuter = new Label();
   1375 
   1376         TypeId<IllegalArgumentException> iaeType = TypeId.get(IllegalArgumentException.class);
   1377         code.addCatchClause(iaeType, catchOuter);
   1378 
   1379         MethodId<?, ?> thrower = TEST_TYPE.getMethod(TypeId.VOID, "thrower", TypeId.INT);
   1380         code.invokeStatic(thrower, null, localA);
   1381 
   1382         // for the inner catch clause, we stash the old label and put it back afterwards.
   1383         Label previousLabel = code.removeCatchClause(iaeType);
   1384         code.addCatchClause(iaeType, catchInner);
   1385         code.invokeStatic(thrower, null, localB);
   1386         code.removeCatchClause(iaeType);
   1387         code.addCatchClause(iaeType, previousLabel);
   1388         code.invokeStatic(thrower, null, localC);
   1389         code.loadConstant(localResult, "NONE");
   1390         code.returnValue(localResult);
   1391 
   1392         code.mark(catchInner);
   1393         code.loadConstant(localResult, "INNER");
   1394         code.returnValue(localResult);
   1395 
   1396         code.mark(catchOuter);
   1397         code.loadConstant(localResult, "OUTER");
   1398         code.returnValue(localResult);
   1399 
   1400         Method method = getMethod();
   1401         assertEquals("OUTER", method.invoke(null, 1, 0, 0));
   1402         assertEquals("INNER", method.invoke(null, 0, 1, 0));
   1403         assertEquals("OUTER", method.invoke(null, 0, 0, 1));
   1404         assertEquals("NONE", method.invoke(null, 0, 0, 0));
   1405     }
   1406 
   1407     public void testThrow() throws Exception {
   1408         /*
   1409          * public static void call() {
   1410          *   throw new IllegalStateException();
   1411          * }
   1412          */
   1413         MethodId<?, Void> methodId = GENERATED.getMethod(TypeId.VOID, "call");
   1414         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
   1415         TypeId<IllegalStateException> iseType = TypeId.get(IllegalStateException.class);
   1416         MethodId<IllegalStateException, Void> iseConstructor = iseType.getConstructor();
   1417         Local<IllegalStateException> localIse = code.newLocal(iseType);
   1418         code.newInstance(localIse, iseConstructor);
   1419         code.throwValue(localIse);
   1420 
   1421         try {
   1422             getMethod().invoke(null);
   1423             fail();
   1424         } catch (InvocationTargetException expected) {
   1425             assertEquals(IllegalStateException.class, expected.getCause().getClass());
   1426         }
   1427     }
   1428 
   1429     public void testUnusedParameters() throws Exception {
   1430         /*
   1431          * public static void call(int unused1, long unused2, long unused3) {}
   1432          */
   1433         MethodId<?, Void> methodId = GENERATED.getMethod(
   1434                 TypeId.VOID, "call", TypeId.INT, TypeId.LONG, TypeId.LONG);
   1435         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
   1436         code.returnVoid();
   1437         getMethod().invoke(null, 1, 2, 3);
   1438     }
   1439 
   1440     public void testFloatingPointCompare() throws Exception {
   1441         Method floatG = floatingPointCompareMethod(TypeId.FLOAT, 1);
   1442         assertEquals(-1, floatG.invoke(null, 1.0f, Float.POSITIVE_INFINITY));
   1443         assertEquals(-1, floatG.invoke(null, 1.0f, 2.0f));
   1444         assertEquals(0, floatG.invoke(null, 1.0f, 1.0f));
   1445         assertEquals(1, floatG.invoke(null, 2.0f, 1.0f));
   1446         assertEquals(1, floatG.invoke(null, 1.0f, Float.NaN));
   1447         assertEquals(1, floatG.invoke(null, Float.NaN, 1.0f));
   1448         assertEquals(1, floatG.invoke(null, Float.NaN, Float.NaN));
   1449         assertEquals(1, floatG.invoke(null, Float.NaN, Float.POSITIVE_INFINITY));
   1450 
   1451         Method floatL = floatingPointCompareMethod(TypeId.FLOAT, -1);
   1452         assertEquals(-1, floatG.invoke(null, 1.0f, Float.POSITIVE_INFINITY));
   1453         assertEquals(-1, floatL.invoke(null, 1.0f, 2.0f));
   1454         assertEquals(0, floatL.invoke(null, 1.0f, 1.0f));
   1455         assertEquals(1, floatL.invoke(null, 2.0f, 1.0f));
   1456         assertEquals(-1, floatL.invoke(null, 1.0f, Float.NaN));
   1457         assertEquals(-1, floatL.invoke(null, Float.NaN, 1.0f));
   1458         assertEquals(-1, floatL.invoke(null, Float.NaN, Float.NaN));
   1459         assertEquals(-1, floatL.invoke(null, Float.NaN, Float.POSITIVE_INFINITY));
   1460 
   1461         Method doubleG = floatingPointCompareMethod(TypeId.DOUBLE, 1);
   1462         assertEquals(-1, doubleG.invoke(null, 1.0, Double.POSITIVE_INFINITY));
   1463         assertEquals(-1, doubleG.invoke(null, 1.0, 2.0));
   1464         assertEquals(0, doubleG.invoke(null, 1.0, 1.0));
   1465         assertEquals(1, doubleG.invoke(null, 2.0, 1.0));
   1466         assertEquals(1, doubleG.invoke(null, 1.0, Double.NaN));
   1467         assertEquals(1, doubleG.invoke(null, Double.NaN, 1.0));
   1468         assertEquals(1, doubleG.invoke(null, Double.NaN, Double.NaN));
   1469         assertEquals(1, doubleG.invoke(null, Double.NaN, Double.POSITIVE_INFINITY));
   1470 
   1471         Method doubleL = floatingPointCompareMethod(TypeId.DOUBLE, -1);
   1472         assertEquals(-1, doubleL.invoke(null, 1.0, Double.POSITIVE_INFINITY));
   1473         assertEquals(-1, doubleL.invoke(null, 1.0, 2.0));
   1474         assertEquals(0, doubleL.invoke(null, 1.0, 1.0));
   1475         assertEquals(1, doubleL.invoke(null, 2.0, 1.0));
   1476         assertEquals(-1, doubleL.invoke(null, 1.0, Double.NaN));
   1477         assertEquals(-1, doubleL.invoke(null, Double.NaN, 1.0));
   1478         assertEquals(-1, doubleL.invoke(null, Double.NaN, Double.NaN));
   1479         assertEquals(-1, doubleL.invoke(null, Double.NaN, Double.POSITIVE_INFINITY));
   1480     }
   1481 
   1482     private <T extends Number> Method floatingPointCompareMethod(
   1483             TypeId<T> valueType, int nanValue) throws Exception {
   1484         /*
   1485          * public static int call(float a, float b) {
   1486          *     int result = a <=> b;
   1487          *     return result;
   1488          * }
   1489          */
   1490         reset();
   1491         MethodId<?, Integer> methodId = GENERATED.getMethod(
   1492                 TypeId.INT, "call", valueType, valueType);
   1493         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
   1494         Local<T> localA = code.getParameter(0, valueType);
   1495         Local<T> localB = code.getParameter(1, valueType);
   1496         Local<Integer> localResult = code.newLocal(TypeId.INT);
   1497         code.compareFloatingPoint(localResult, localA, localB, nanValue);
   1498         code.returnValue(localResult);
   1499         return getMethod();
   1500     }
   1501 
   1502     public void testLongCompare() throws Exception {
   1503         /*
   1504          * public static int call(long a, long b) {
   1505          *   int result = a <=> b;
   1506          *   return result;
   1507          * }
   1508          */
   1509         MethodId<?, Integer> methodId = GENERATED.getMethod(TypeId.INT, "call", TypeId.LONG, TypeId.LONG);
   1510         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
   1511         Local<Long> localA = code.getParameter(0, TypeId.LONG);
   1512         Local<Long> localB = code.getParameter(1, TypeId.LONG);
   1513         Local<Integer> localResult = code.newLocal(TypeId.INT);
   1514         code.compareLongs(localResult, localA, localB);
   1515         code.returnValue(localResult);
   1516 
   1517         Method method = getMethod();
   1518         assertEquals(0, method.invoke(null, Long.MIN_VALUE, Long.MIN_VALUE));
   1519         assertEquals(-1, method.invoke(null, Long.MIN_VALUE, 0));
   1520         assertEquals(-1, method.invoke(null, Long.MIN_VALUE, Long.MAX_VALUE));
   1521         assertEquals(1, method.invoke(null, 0, Long.MIN_VALUE));
   1522         assertEquals(0, method.invoke(null, 0, 0));
   1523         assertEquals(-1, method.invoke(null, 0, Long.MAX_VALUE));
   1524         assertEquals(1, method.invoke(null, Long.MAX_VALUE, Long.MIN_VALUE));
   1525         assertEquals(1, method.invoke(null, Long.MAX_VALUE, 0));
   1526         assertEquals(0, method.invoke(null, Long.MAX_VALUE, Long.MAX_VALUE));
   1527     }
   1528 
   1529     public void testArrayLength() throws Exception {
   1530         Method booleanArrayLength = arrayLengthMethod(BOOLEAN_ARRAY);
   1531         assertEquals(0, booleanArrayLength.invoke(null, new Object[] { new boolean[0] }));
   1532         assertEquals(5, booleanArrayLength.invoke(null, new Object[] { new boolean[5] }));
   1533 
   1534         Method intArrayLength = arrayLengthMethod(INT_ARRAY);
   1535         assertEquals(0, intArrayLength.invoke(null, new Object[] { new int[0] }));
   1536         assertEquals(5, intArrayLength.invoke(null, new Object[] { new int[5] }));
   1537 
   1538         Method longArrayLength = arrayLengthMethod(LONG_ARRAY);
   1539         assertEquals(0, longArrayLength.invoke(null, new Object[] { new long[0] }));
   1540         assertEquals(5, longArrayLength.invoke(null, new Object[] { new long[5] }));
   1541 
   1542         Method objectArrayLength = arrayLengthMethod(OBJECT_ARRAY);
   1543         assertEquals(0, objectArrayLength.invoke(null, new Object[] { new Object[0] }));
   1544         assertEquals(5, objectArrayLength.invoke(null, new Object[] { new Object[5] }));
   1545 
   1546         Method long2dArrayLength = arrayLengthMethod(LONG_2D_ARRAY);
   1547         assertEquals(0, long2dArrayLength.invoke(null, new Object[] { new long[0][0] }));
   1548         assertEquals(5, long2dArrayLength.invoke(null, new Object[] { new long[5][10] }));
   1549     }
   1550 
   1551     private <T> Method arrayLengthMethod(TypeId<T> valueType) throws Exception {
   1552         /*
   1553          * public static int call(long[] array) {
   1554          *   int result = array.length;
   1555          *   return result;
   1556          * }
   1557          */
   1558         reset();
   1559         MethodId<?, Integer> methodId = GENERATED.getMethod(TypeId.INT, "call", valueType);
   1560         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
   1561         Local<T> localArray = code.getParameter(0, valueType);
   1562         Local<Integer> localResult = code.newLocal(TypeId.INT);
   1563         code.arrayLength(localResult, localArray);
   1564         code.returnValue(localResult);
   1565         return getMethod();
   1566     }
   1567 
   1568     public void testNewArray() throws Exception {
   1569         Method newBooleanArray = newArrayMethod(BOOLEAN_ARRAY);
   1570         assertEquals("[]", Arrays.toString((boolean[]) newBooleanArray.invoke(null, 0)));
   1571         assertEquals("[false, false, false]",
   1572                 Arrays.toString((boolean[]) newBooleanArray.invoke(null, 3)));
   1573 
   1574         Method newIntArray = newArrayMethod(INT_ARRAY);
   1575         assertEquals("[]", Arrays.toString((int[]) newIntArray.invoke(null, 0)));
   1576         assertEquals("[0, 0, 0]", Arrays.toString((int[]) newIntArray.invoke(null, 3)));
   1577 
   1578         Method newLongArray = newArrayMethod(LONG_ARRAY);
   1579         assertEquals("[]", Arrays.toString((long[]) newLongArray.invoke(null, 0)));
   1580         assertEquals("[0, 0, 0]", Arrays.toString((long[]) newLongArray.invoke(null, 3)));
   1581 
   1582         Method newObjectArray = newArrayMethod(OBJECT_ARRAY);
   1583         assertEquals("[]", Arrays.toString((Object[]) newObjectArray.invoke(null, 0)));
   1584         assertEquals("[null, null, null]",
   1585                 Arrays.toString((Object[]) newObjectArray.invoke(null, 3)));
   1586 
   1587         Method new2dLongArray = newArrayMethod(LONG_2D_ARRAY);
   1588         assertEquals("[]", Arrays.deepToString((long[][]) new2dLongArray.invoke(null, 0)));
   1589         assertEquals("[null, null, null]",
   1590                 Arrays.deepToString((long[][]) new2dLongArray.invoke(null, 3)));
   1591     }
   1592 
   1593     private <T> Method newArrayMethod(TypeId<T> valueType) throws Exception {
   1594         /*
   1595          * public static long[] call(int length) {
   1596          *   long[] result = new long[length];
   1597          *   return result;
   1598          * }
   1599          */
   1600         reset();
   1601         MethodId<?, T> methodId = GENERATED.getMethod(valueType, "call", TypeId.INT);
   1602         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
   1603         Local<Integer> localLength = code.getParameter(0, TypeId.INT);
   1604         Local<T> localResult = code.newLocal(valueType);
   1605         code.newArray(localResult, localLength);
   1606         code.returnValue(localResult);
   1607         return getMethod();
   1608     }
   1609 
   1610     public void testReadAndWriteArray() throws Exception {
   1611         Method swapBooleanArray = arraySwapMethod(BOOLEAN_ARRAY, TypeId.BOOLEAN);
   1612         boolean[] booleans = new boolean[3];
   1613         assertEquals(false, swapBooleanArray.invoke(null, booleans, 1, true));
   1614         assertEquals("[false, true, false]", Arrays.toString(booleans));
   1615 
   1616         Method swapIntArray = arraySwapMethod(INT_ARRAY, TypeId.INT);
   1617         int[] ints = new int[3];
   1618         assertEquals(0, swapIntArray.invoke(null, ints, 1, 5));
   1619         assertEquals("[0, 5, 0]", Arrays.toString(ints));
   1620 
   1621         Method swapLongArray = arraySwapMethod(LONG_ARRAY, TypeId.LONG);
   1622         long[] longs = new long[3];
   1623         assertEquals(0L, swapLongArray.invoke(null, longs, 1, 6L));
   1624         assertEquals("[0, 6, 0]", Arrays.toString(longs));
   1625 
   1626         Method swapObjectArray = arraySwapMethod(OBJECT_ARRAY, TypeId.OBJECT);
   1627         Object[] objects = new Object[3];
   1628         assertEquals(null, swapObjectArray.invoke(null, objects, 1, "X"));
   1629         assertEquals("[null, X, null]", Arrays.toString(objects));
   1630 
   1631         Method swapLong2dArray = arraySwapMethod(LONG_2D_ARRAY, LONG_ARRAY);
   1632         long[][] longs2d = new long[3][];
   1633         assertEquals(null, swapLong2dArray.invoke(null, longs2d, 1, new long[] { 7 }));
   1634         assertEquals("[null, [7], null]", Arrays.deepToString(longs2d));
   1635     }
   1636 
   1637     private <A, T> Method arraySwapMethod(TypeId<A> arrayType, TypeId<T> singleType)
   1638             throws Exception {
   1639         /*
   1640          * public static long swap(long[] array, int index, long newValue) {
   1641          *   long result = array[index];
   1642          *   array[index] = newValue;
   1643          *   return result;
   1644          * }
   1645          */
   1646         reset();
   1647         MethodId<?, T> methodId = GENERATED.getMethod(
   1648                 singleType, "call", arrayType, TypeId.INT, singleType);
   1649         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
   1650         Local<A> localArray = code.getParameter(0, arrayType);
   1651         Local<Integer> localIndex = code.getParameter(1, TypeId.INT);
   1652         Local<T> localNewValue = code.getParameter(2, singleType);
   1653         Local<T> localResult = code.newLocal(singleType);
   1654         code.aget(localResult, localArray, localIndex);
   1655         code.aput(localArray, localIndex, localNewValue);
   1656         code.returnValue(localResult);
   1657         return getMethod();
   1658     }
   1659 
   1660     public void testSynchronizedFlagImpactsDeclarationOnly() throws Exception {
   1661         /*
   1662          * public synchronized void call() {
   1663          *   wait(100L);
   1664          * }
   1665          */
   1666         MethodId<?, Void> methodId = GENERATED.getMethod(TypeId.VOID, "call");
   1667         MethodId<Object, Void> wait = TypeId.OBJECT.getMethod(TypeId.VOID, "wait", TypeId.LONG);
   1668         Code code = dexMaker.declare(methodId, PUBLIC | SYNCHRONIZED);
   1669         Local<?> thisLocal = code.getThis(GENERATED);
   1670         Local<Long> timeout = code.newLocal(TypeId.LONG);
   1671         code.loadConstant(timeout, 100L);
   1672         code.invokeVirtual(wait, null, thisLocal, timeout);
   1673         code.returnVoid();
   1674 
   1675         addDefaultConstructor();
   1676 
   1677         Class<?> generatedClass = generateAndLoad();
   1678         Object instance = generatedClass.newInstance();
   1679         Method method = generatedClass.getMethod("call");
   1680         assertTrue(Modifier.isSynchronized(method.getModifiers()));
   1681         try {
   1682             method.invoke(instance);
   1683             fail();
   1684         } catch (InvocationTargetException expected) {
   1685             assertTrue(expected.getCause() instanceof IllegalMonitorStateException);
   1686         }
   1687     }
   1688 
   1689     public void testMonitorEnterMonitorExit() throws Exception {
   1690         /*
   1691          * public synchronized void call() {
   1692          *   synchronized (this) {
   1693          *     wait(100L);
   1694          *   }
   1695          * }
   1696          */
   1697         MethodId<?, Void> methodId = GENERATED.getMethod(TypeId.VOID, "call");
   1698         MethodId<Object, Void> wait = TypeId.OBJECT.getMethod(TypeId.VOID, "wait", TypeId.LONG);
   1699         Code code = dexMaker.declare(methodId, PUBLIC);
   1700         Local<?> thisLocal = code.getThis(GENERATED);
   1701         Local<Long> timeout = code.newLocal(TypeId.LONG);
   1702         code.monitorEnter(thisLocal);
   1703         code.loadConstant(timeout, 100L);
   1704         code.invokeVirtual(wait, null, thisLocal, timeout);
   1705         code.monitorExit(thisLocal);
   1706         code.returnVoid();
   1707 
   1708         addDefaultConstructor();
   1709 
   1710         Class<?> generatedClass = generateAndLoad();
   1711         Object instance = generatedClass.newInstance();
   1712         Method method = generatedClass.getMethod("call");
   1713         assertFalse(Modifier.isSynchronized(method.getModifiers()));
   1714         method.invoke(instance); // will take 100ms
   1715     }
   1716 
   1717     public void testMoveInt() throws Exception {
   1718         /*
   1719          * public static int call(int a) {
   1720          *   int b = a;
   1721          *   int c = a + b;
   1722          *   return c;
   1723          * }
   1724          */
   1725         MethodId<?, Integer> methodId = GENERATED.getMethod(TypeId.INT, "call", TypeId.INT);
   1726         Code code = dexMaker.declare(methodId, PUBLIC | STATIC);
   1727         Local<Integer> a = code.getParameter(0, TypeId.INT);
   1728         Local<Integer> b = code.newLocal(TypeId.INT);
   1729         Local<Integer> c = code.newLocal(TypeId.INT);
   1730         code.move(b, a);
   1731         code.op(BinaryOp.ADD, c, a, b);
   1732         code.returnValue(c);
   1733 
   1734         assertEquals(6, getMethod().invoke(null, 3));
   1735     }
   1736 
   1737     public void testPrivateClassesAreUnsupported() {
   1738         try {
   1739             dexMaker.declare(TypeId.get("LPrivateClass;"), "PrivateClass.generated", PRIVATE,
   1740                     TypeId.OBJECT);
   1741             fail();
   1742         } catch (IllegalArgumentException expected) {
   1743         }
   1744     }
   1745 
   1746     public void testAbstractMethodsAreUnsupported() {
   1747         MethodId<?, Void> methodId = GENERATED.getMethod(TypeId.VOID, "call");
   1748         try {
   1749             dexMaker.declare(methodId, ABSTRACT);
   1750             fail();
   1751         } catch (IllegalArgumentException expected) {
   1752         }
   1753     }
   1754 
   1755     public void testNativeMethodsAreUnsupported() {
   1756         MethodId<?, Void> methodId = GENERATED.getMethod(TypeId.VOID, "call");
   1757         try {
   1758             dexMaker.declare(methodId, NATIVE);
   1759             fail();
   1760         } catch (IllegalArgumentException expected) {
   1761         }
   1762     }
   1763 
   1764     public void testSynchronizedFieldsAreUnsupported() {
   1765         try {
   1766             FieldId<?, ?> fieldId = GENERATED.getField(TypeId.OBJECT, "synchronizedField");
   1767             dexMaker.declare(fieldId, SYNCHRONIZED, null);
   1768             fail();
   1769         } catch (IllegalArgumentException expected) {
   1770         }
   1771     }
   1772 
   1773     public void testInitialValueWithNonStaticField() {
   1774         try {
   1775             FieldId<?, ?> fieldId = GENERATED.getField(TypeId.OBJECT, "nonStaticField");
   1776             dexMaker.declare(fieldId, 0, 1);
   1777             fail();
   1778         } catch (IllegalArgumentException expected) {
   1779         }
   1780     }
   1781 
   1782     // TODO: cast primitive to non-primitive
   1783     // TODO: cast non-primitive to primitive
   1784     // TODO: cast byte to integer
   1785     // TODO: cast byte to long
   1786     // TODO: cast long to byte
   1787     // TODO: fail if a label is unreachable (never navigated to)
   1788     // TODO: more strict type parameters: Integer on methods
   1789     // TODO: don't generate multiple times (?)
   1790     // TODO: test array types
   1791     // TODO: test generating an interface
   1792     // TODO: declare native method or abstract method
   1793     // TODO: get a thrown exception 'e' into a local
   1794     // TODO: move a primitive or reference
   1795 
   1796     private void addDefaultConstructor() {
   1797         Code code = dexMaker.declare(GENERATED.getConstructor(), PUBLIC);
   1798         Local<?> thisRef = code.getThis(GENERATED);
   1799         code.invokeDirect(TypeId.OBJECT.getConstructor(), null, thisRef);
   1800         code.returnVoid();
   1801     }
   1802 
   1803     /**
   1804      * Returns the generated method.
   1805      */
   1806     private Method getMethod() throws Exception {
   1807         Class<?> generated = generateAndLoad();
   1808         for (Method method : generated.getMethods()) {
   1809             if (method.getName().equals("call")) {
   1810                 return method;
   1811             }
   1812         }
   1813         throw new IllegalStateException("no call() method");
   1814     }
   1815 
   1816     public static File getDataDirectory() throws Exception {
   1817         Class<?> environmentClass = Class.forName("android.os.Environment");
   1818         Method method = environmentClass.getMethod("getDataDirectory");
   1819         Object dataDirectory = method.invoke(null);
   1820         return (File) dataDirectory;
   1821     }
   1822 
   1823     private Class<?> generateAndLoad() throws Exception {
   1824         return dexMaker.generateAndLoad(getClass().getClassLoader(), getDataDirectory())
   1825                 .loadClass("Generated");
   1826     }
   1827 }
   1828