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