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