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