Simulator.java revision 333201833d506a3accdeac6ceb7caba8d4b95797
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.dx.cf.code; 18 19import com.android.dx.rop.cst.Constant; 20import com.android.dx.rop.cst.CstFieldRef; 21import com.android.dx.rop.cst.CstInteger; 22import com.android.dx.rop.cst.CstInterfaceMethodRef; 23import com.android.dx.rop.cst.CstMethodRef; 24import com.android.dx.rop.cst.CstType; 25import com.android.dx.rop.type.Prototype; 26import com.android.dx.rop.type.Type; 27import com.android.dx.rop.code.LocalItem; 28import com.android.dx.util.Hex; 29 30import java.util.ArrayList; 31 32/** 33 * Class which knows how to simulate the effects of executing bytecode. 34 * 35 * <p><b>Note:</b> This class is not thread-safe. If multiple threads 36 * need to use a single instance, they must synchronize access explicitly 37 * between themselves.</p> 38 */ 39public class Simulator { 40 /** 41 * {@code non-null;} canned error message for local variable 42 * table mismatches 43 */ 44 private static final String LOCAL_MISMATCH_ERROR = 45 "This is symptomatic of .class transformation tools that ignore " + 46 "local variable information."; 47 48 /** {@code non-null;} machine to use when simulating */ 49 private final Machine machine; 50 51 /** {@code non-null;} array of bytecode */ 52 private final BytecodeArray code; 53 54 /** {@code non-null;} local variable information */ 55 private final LocalVariableList localVariables; 56 57 /** {@code non-null;} visitor instance to use */ 58 private final SimVisitor visitor; 59 60 /** 61 * Constructs an instance. 62 * 63 * @param machine {@code non-null;} machine to use when simulating 64 * @param method {@code non-null;} method data to use 65 */ 66 public Simulator(Machine machine, ConcreteMethod method) { 67 if (machine == null) { 68 throw new NullPointerException("machine == null"); 69 } 70 71 if (method == null) { 72 throw new NullPointerException("method == null"); 73 } 74 75 this.machine = machine; 76 this.code = method.getCode(); 77 this.localVariables = method.getLocalVariables(); 78 this.visitor = new SimVisitor(); 79 } 80 81 /** 82 * Simulates the effect of executing the given basic block. This modifies 83 * the passed-in frame to represent the end result. 84 * 85 * @param bb {@code non-null;} the basic block 86 * @param frame {@code non-null;} frame to operate on 87 */ 88 public void simulate(ByteBlock bb, Frame frame) { 89 int end = bb.getEnd(); 90 91 visitor.setFrame(frame); 92 93 try { 94 for (int off = bb.getStart(); off < end; /*off*/) { 95 int length = code.parseInstruction(off, visitor); 96 visitor.setPreviousOffset(off); 97 off += length; 98 } 99 } catch (SimException ex) { 100 frame.annotate(ex); 101 throw ex; 102 } 103 } 104 105 /** 106 * Simulates the effect of the instruction at the given offset, by 107 * making appropriate calls on the given frame. 108 * 109 * @param offset {@code >= 0;} offset of the instruction to simulate 110 * @param frame {@code non-null;} frame to operate on 111 * @return the length of the instruction, in bytes 112 */ 113 public int simulate(int offset, Frame frame) { 114 visitor.setFrame(frame); 115 return code.parseInstruction(offset, visitor); 116 } 117 118 /** 119 * Constructs an "illegal top-of-stack" exception, for the stack 120 * manipulation opcodes. 121 */ 122 private static SimException illegalTos() { 123 return new SimException("stack mismatch: illegal " + 124 "top-of-stack for opcode"); 125 } 126 127 /** 128 * Returns the required array type for an array load or store 129 * instruction, based on a given implied type and an observed 130 * actual array type. 131 * 132 * <p>The interesting cases here have to do with object arrays, 133 * <code>byte[]</code>s, <code>boolean[]</code>s, and 134 * known-nulls.</p> 135 * 136 * <p>In the case of arrays of objects, we want to narrow the type 137 * to the actual array present on the stack, as long as what is 138 * present is an object type. Similarly, due to a quirk of the 139 * original bytecode representation, the instructions for dealing 140 * with <code>byte[]</code> and <code>boolean[]</code> are 141 * undifferentiated, and we aim here to return whichever one was 142 * actually present on the stack.</p> 143 * 144 * <p>In the case where there is a known-null on the stack where 145 * an array is expected, we just fall back to the implied type of 146 * the instruction. Due to the quirk described above, this means 147 * that source code that uses <code>boolean[]</code> might get 148 * translated surprisingly -- but correctly -- into an instruction 149 * that specifies a <code>byte[]</code>. It will be correct, 150 * because should the code actually execute, it will necessarily 151 * throw a <code>NullPointerException</code>, and it won't matter 152 * what opcode variant is used to achieve that result.</p> 153 * 154 * @param impliedType {@code non-null;} type implied by the 155 * instruction; is <i>not</i> an array type 156 * @param foundArrayType {@code non-null;} type found on the 157 * stack; is either an array type or a known-null 158 * @return {@code non-null;} the array type that should be 159 * required in this context 160 */ 161 private static Type requiredArrayTypeFor(Type impliedType, 162 Type foundArrayType) { 163 if (foundArrayType == Type.KNOWN_NULL) { 164 return impliedType.getArrayType(); 165 } 166 167 if ((impliedType == Type.OBJECT) 168 && foundArrayType.isArray() 169 && foundArrayType.getComponentType().isReference()) { 170 return foundArrayType; 171 } 172 173 if ((impliedType == Type.BYTE) 174 && (foundArrayType == Type.BOOLEAN_ARRAY)) { 175 /* 176 * Per above, an instruction with implied byte[] is also 177 * allowed to be used on boolean[]. 178 */ 179 return Type.BOOLEAN_ARRAY; 180 } 181 182 return impliedType.getArrayType(); 183 } 184 185 /** 186 * Bytecode visitor used during simulation. 187 */ 188 private class SimVisitor implements BytecodeArray.Visitor { 189 /** 190 * {@code non-null;} machine instance to use (just to avoid excessive 191 * cross-object field access) 192 */ 193 private final Machine machine; 194 195 /** 196 * {@code null-ok;} frame to use; set with each call to 197 * {@link Simulator#simulate} 198 */ 199 private Frame frame; 200 201 /** offset of the previous bytecode */ 202 private int previousOffset; 203 204 /** 205 * Constructs an instance. 206 */ 207 public SimVisitor() { 208 this.machine = Simulator.this.machine; 209 this.frame = null; 210 } 211 212 /** 213 * Sets the frame to act on. 214 * 215 * @param frame {@code non-null;} the frame 216 */ 217 public void setFrame(Frame frame) { 218 if (frame == null) { 219 throw new NullPointerException("frame == null"); 220 } 221 222 this.frame = frame; 223 } 224 225 /** {@inheritDoc} */ 226 public void visitInvalid(int opcode, int offset, int length) { 227 throw new SimException("invalid opcode " + Hex.u1(opcode)); 228 } 229 230 /** {@inheritDoc} */ 231 public void visitNoArgs(int opcode, int offset, int length, 232 Type type) { 233 switch (opcode) { 234 case ByteOps.NOP: { 235 machine.clearArgs(); 236 break; 237 } 238 case ByteOps.INEG: { 239 machine.popArgs(frame, type); 240 break; 241 } 242 case ByteOps.I2L: 243 case ByteOps.I2F: 244 case ByteOps.I2D: 245 case ByteOps.I2B: 246 case ByteOps.I2C: 247 case ByteOps.I2S: { 248 machine.popArgs(frame, Type.INT); 249 break; 250 } 251 case ByteOps.L2I: 252 case ByteOps.L2F: 253 case ByteOps.L2D: { 254 machine.popArgs(frame, Type.LONG); 255 break; 256 } 257 case ByteOps.F2I: 258 case ByteOps.F2L: 259 case ByteOps.F2D: { 260 machine.popArgs(frame, Type.FLOAT); 261 break; 262 } 263 case ByteOps.D2I: 264 case ByteOps.D2L: 265 case ByteOps.D2F: { 266 machine.popArgs(frame, Type.DOUBLE); 267 break; 268 } 269 case ByteOps.RETURN: { 270 machine.clearArgs(); 271 checkReturnType(Type.VOID); 272 break; 273 } 274 case ByteOps.IRETURN: { 275 Type checkType = type; 276 if (type == Type.OBJECT) { 277 /* 278 * For an object return, use the best-known 279 * type of the popped value. 280 */ 281 checkType = frame.getStack().peekType(0); 282 } 283 machine.popArgs(frame, type); 284 checkReturnType(checkType); 285 break; 286 } 287 case ByteOps.POP: { 288 Type peekType = frame.getStack().peekType(0); 289 if (peekType.isCategory2()) { 290 throw illegalTos(); 291 } 292 machine.popArgs(frame, 1); 293 break; 294 } 295 case ByteOps.ARRAYLENGTH: { 296 Type arrayType = frame.getStack().peekType(0); 297 if (!arrayType.isArrayOrKnownNull()) { 298 throw new SimException("type mismatch: expected " + 299 "array type but encountered " + 300 arrayType.toHuman()); 301 } 302 machine.popArgs(frame, Type.OBJECT); 303 break; 304 } 305 case ByteOps.ATHROW: 306 case ByteOps.MONITORENTER: 307 case ByteOps.MONITOREXIT: { 308 machine.popArgs(frame, Type.OBJECT); 309 break; 310 } 311 case ByteOps.IALOAD: { 312 /* 313 * See comment on requiredArrayTypeFor() for explanation 314 * about what's going on here. 315 */ 316 Type foundArrayType = frame.getStack().peekType(1); 317 Type requiredArrayType = 318 requiredArrayTypeFor(type, foundArrayType); 319 320 // Make type agree with the discovered requiredArrayType. 321 type = requiredArrayType.getComponentType(); 322 323 machine.popArgs(frame, requiredArrayType, Type.INT); 324 break; 325 } 326 case ByteOps.IADD: 327 case ByteOps.ISUB: 328 case ByteOps.IMUL: 329 case ByteOps.IDIV: 330 case ByteOps.IREM: 331 case ByteOps.IAND: 332 case ByteOps.IOR: 333 case ByteOps.IXOR: { 334 machine.popArgs(frame, type, type); 335 break; 336 } 337 case ByteOps.ISHL: 338 case ByteOps.ISHR: 339 case ByteOps.IUSHR: { 340 machine.popArgs(frame, type, Type.INT); 341 break; 342 } 343 case ByteOps.LCMP: { 344 machine.popArgs(frame, Type.LONG, Type.LONG); 345 break; 346 } 347 case ByteOps.FCMPL: 348 case ByteOps.FCMPG: { 349 machine.popArgs(frame, Type.FLOAT, Type.FLOAT); 350 break; 351 } 352 case ByteOps.DCMPL: 353 case ByteOps.DCMPG: { 354 machine.popArgs(frame, Type.DOUBLE, Type.DOUBLE); 355 break; 356 } 357 case ByteOps.IASTORE: { 358 /* 359 * See comment on requiredArrayTypeFor() for 360 * explanation about what's going on here. In 361 * addition to that, the category 1 vs. 2 thing 362 * below is to deal with the fact that, if the 363 * element type is category 2, we have to skip 364 * over one extra stack slot to find the array. 365 */ 366 ExecutionStack stack = frame.getStack(); 367 int peekDepth = type.isCategory1() ? 2 : 3; 368 Type foundArrayType = stack.peekType(peekDepth); 369 boolean foundArrayLocal = stack.peekLocal(peekDepth); 370 371 Type requiredArrayType = 372 requiredArrayTypeFor(type, foundArrayType); 373 374 /* 375 * Make type agree with the discovered requiredArrayType 376 * if it has local info. 377 */ 378 if (foundArrayLocal) { 379 type = requiredArrayType.getComponentType(); 380 } 381 382 machine.popArgs(frame, requiredArrayType, Type.INT, type); 383 break; 384 } 385 case ByteOps.POP2: 386 case ByteOps.DUP2: { 387 ExecutionStack stack = frame.getStack(); 388 int pattern; 389 390 if (stack.peekType(0).isCategory2()) { 391 // "form 2" in vmspec-2 392 machine.popArgs(frame, 1); 393 pattern = 0x11; 394 } else if (stack.peekType(1).isCategory1()) { 395 // "form 1" 396 machine.popArgs(frame, 2); 397 pattern = 0x2121; 398 } else { 399 throw illegalTos(); 400 } 401 402 if (opcode == ByteOps.DUP2) { 403 machine.auxIntArg(pattern); 404 } 405 break; 406 } 407 case ByteOps.DUP: { 408 Type peekType = frame.getStack().peekType(0); 409 410 if (peekType.isCategory2()) { 411 throw illegalTos(); 412 } 413 414 machine.popArgs(frame, 1); 415 machine.auxIntArg(0x11); 416 break; 417 } 418 case ByteOps.DUP_X1: { 419 ExecutionStack stack = frame.getStack(); 420 421 if (!(stack.peekType(0).isCategory1() && 422 stack.peekType(1).isCategory1())) { 423 throw illegalTos(); 424 } 425 426 machine.popArgs(frame, 2); 427 machine.auxIntArg(0x212); 428 break; 429 } 430 case ByteOps.DUP_X2: { 431 ExecutionStack stack = frame.getStack(); 432 433 if (stack.peekType(0).isCategory2()) { 434 throw illegalTos(); 435 } 436 437 if (stack.peekType(1).isCategory2()) { 438 // "form 2" in vmspec-2 439 machine.popArgs(frame, 2); 440 machine.auxIntArg(0x212); 441 } else if (stack.peekType(2).isCategory1()) { 442 // "form 1" 443 machine.popArgs(frame, 3); 444 machine.auxIntArg(0x3213); 445 } else { 446 throw illegalTos(); 447 } 448 break; 449 } 450 case ByteOps.DUP2_X1: { 451 ExecutionStack stack = frame.getStack(); 452 453 if (stack.peekType(0).isCategory2()) { 454 // "form 2" in vmspec-2 455 if (stack.peekType(2).isCategory2()) { 456 throw illegalTos(); 457 } 458 machine.popArgs(frame, 2); 459 machine.auxIntArg(0x212); 460 } else { 461 // "form 1" 462 if (stack.peekType(1).isCategory2() || 463 stack.peekType(2).isCategory2()) { 464 throw illegalTos(); 465 } 466 machine.popArgs(frame, 3); 467 machine.auxIntArg(0x32132); 468 } 469 break; 470 } 471 case ByteOps.DUP2_X2: { 472 ExecutionStack stack = frame.getStack(); 473 474 if (stack.peekType(0).isCategory2()) { 475 if (stack.peekType(2).isCategory2()) { 476 // "form 4" in vmspec-2 477 machine.popArgs(frame, 2); 478 machine.auxIntArg(0x212); 479 } else if (stack.peekType(3).isCategory1()) { 480 // "form 2" 481 machine.popArgs(frame, 3); 482 machine.auxIntArg(0x3213); 483 } else { 484 throw illegalTos(); 485 } 486 } else if (stack.peekType(1).isCategory1()) { 487 if (stack.peekType(2).isCategory2()) { 488 // "form 3" 489 machine.popArgs(frame, 3); 490 machine.auxIntArg(0x32132); 491 } else if (stack.peekType(3).isCategory1()) { 492 // "form 1" 493 machine.popArgs(frame, 4); 494 machine.auxIntArg(0x432143); 495 } else { 496 throw illegalTos(); 497 } 498 } else { 499 throw illegalTos(); 500 } 501 break; 502 } 503 case ByteOps.SWAP: { 504 ExecutionStack stack = frame.getStack(); 505 506 if (!(stack.peekType(0).isCategory1() && 507 stack.peekType(1).isCategory1())) { 508 throw illegalTos(); 509 } 510 511 machine.popArgs(frame, 2); 512 machine.auxIntArg(0x12); 513 break; 514 } 515 default: { 516 visitInvalid(opcode, offset, length); 517 return; 518 } 519 } 520 521 machine.auxType(type); 522 machine.run(frame, offset, opcode); 523 } 524 525 /** 526 * Checks whether the prototype is compatible with returning the 527 * given type, and throws if not. 528 * 529 * @param encountered {@code non-null;} the encountered return type 530 */ 531 private void checkReturnType(Type encountered) { 532 Type returnType = machine.getPrototype().getReturnType(); 533 534 /* 535 * Check to see if the prototype's return type is 536 * possibly assignable from the type we encountered. This 537 * takes care of all the salient cases (types are the same, 538 * they're compatible primitive types, etc.). 539 */ 540 if (!Merger.isPossiblyAssignableFrom(returnType, encountered)) { 541 throw new SimException("return type mismatch: prototype " + 542 "indicates " + returnType.toHuman() + 543 ", but encountered type " + encountered.toHuman()); 544 } 545 } 546 547 /** {@inheritDoc} */ 548 public void visitLocal(int opcode, int offset, int length, 549 int idx, Type type, int value) { 550 /* 551 * Note that the "type" parameter is always the simplest 552 * type based on the original opcode, e.g., "int" for 553 * "iload" (per se) and "Object" for "aload". So, when 554 * possible, we replace the type with the one indicated in 555 * the local variable table, though we still need to check 556 * to make sure it's valid for the opcode. 557 * 558 * The reason we use (offset + length) for the localOffset 559 * for a store is because it is only after the store that 560 * the local type becomes valid. On the other hand, the 561 * type associated with a load is valid at the start of 562 * the instruction. 563 */ 564 int localOffset = 565 (opcode == ByteOps.ISTORE) ? (offset + length) : offset; 566 LocalVariableList.Item local = 567 localVariables.pcAndIndexToLocal(localOffset, idx); 568 Type localType; 569 570 if (local != null) { 571 localType = local.getType(); 572 if (localType.getBasicFrameType() != 573 type.getBasicFrameType()) { 574 BaseMachine.throwLocalMismatch(type, localType); 575 return; 576 } 577 } else { 578 localType = type; 579 } 580 581 switch (opcode) { 582 case ByteOps.ILOAD: 583 case ByteOps.RET: { 584 machine.localArg(frame, idx); 585 machine.localInfo(local != null); 586 machine.auxType(type); 587 break; 588 } 589 case ByteOps.ISTORE: { 590 LocalItem item 591 = (local == null) ? null : local.getLocalItem(); 592 machine.popArgs(frame, type); 593 machine.auxType(type); 594 machine.localTarget(idx, localType, item); 595 break; 596 } 597 case ByteOps.IINC: { 598 LocalItem item 599 = (local == null) ? null : local.getLocalItem(); 600 machine.localArg(frame, idx); 601 machine.localTarget(idx, localType, item); 602 machine.auxType(type); 603 machine.auxIntArg(value); 604 machine.auxCstArg(CstInteger.make(value)); 605 break; 606 } 607 default: { 608 visitInvalid(opcode, offset, length); 609 return; 610 } 611 } 612 613 machine.run(frame, offset, opcode); 614 } 615 616 /** {@inheritDoc} */ 617 public void visitConstant(int opcode, int offset, int length, 618 Constant cst, int value) { 619 switch (opcode) { 620 case ByteOps.ANEWARRAY: { 621 machine.popArgs(frame, Type.INT); 622 break; 623 } 624 case ByteOps.PUTSTATIC: { 625 Type fieldType = ((CstFieldRef) cst).getType(); 626 machine.popArgs(frame, fieldType); 627 break; 628 } 629 case ByteOps.GETFIELD: 630 case ByteOps.CHECKCAST: 631 case ByteOps.INSTANCEOF: { 632 machine.popArgs(frame, Type.OBJECT); 633 break; 634 } 635 case ByteOps.PUTFIELD: { 636 Type fieldType = ((CstFieldRef) cst).getType(); 637 machine.popArgs(frame, Type.OBJECT, fieldType); 638 break; 639 } 640 case ByteOps.INVOKEINTERFACE: { 641 /* 642 * Convert the interface method ref into a normal 643 * method ref. 644 */ 645 cst = ((CstInterfaceMethodRef) cst).toMethodRef(); 646 // and fall through... 647 } 648 case ByteOps.INVOKEVIRTUAL: 649 case ByteOps.INVOKESPECIAL: { 650 /* 651 * Get the instance prototype, and use it to direct 652 * the machine. 653 */ 654 Prototype prototype = 655 ((CstMethodRef) cst).getPrototype(false); 656 machine.popArgs(frame, prototype); 657 break; 658 } 659 case ByteOps.INVOKESTATIC: { 660 /* 661 * Get the static prototype, and use it to direct 662 * the machine. 663 */ 664 Prototype prototype = 665 ((CstMethodRef) cst).getPrototype(true); 666 machine.popArgs(frame, prototype); 667 break; 668 } 669 case ByteOps.MULTIANEWARRAY: { 670 /* 671 * The "value" here is the count of dimensions to 672 * create. Make a prototype of that many "int" 673 * types, and tell the machine to pop them. This 674 * isn't the most efficient way in the world to do 675 * this, but then again, multianewarray is pretty 676 * darn rare and so not worth much effort 677 * optimizing for. 678 */ 679 Prototype prototype = 680 Prototype.internInts(Type.VOID, value); 681 machine.popArgs(frame, prototype); 682 break; 683 } 684 default: { 685 machine.clearArgs(); 686 break; 687 } 688 } 689 690 machine.auxIntArg(value); 691 machine.auxCstArg(cst); 692 machine.run(frame, offset, opcode); 693 } 694 695 /** {@inheritDoc} */ 696 public void visitBranch(int opcode, int offset, int length, 697 int target) { 698 switch (opcode) { 699 case ByteOps.IFEQ: 700 case ByteOps.IFNE: 701 case ByteOps.IFLT: 702 case ByteOps.IFGE: 703 case ByteOps.IFGT: 704 case ByteOps.IFLE: { 705 machine.popArgs(frame, Type.INT); 706 break; 707 } 708 case ByteOps.IFNULL: 709 case ByteOps.IFNONNULL: { 710 machine.popArgs(frame, Type.OBJECT); 711 break; 712 } 713 case ByteOps.IF_ICMPEQ: 714 case ByteOps.IF_ICMPNE: 715 case ByteOps.IF_ICMPLT: 716 case ByteOps.IF_ICMPGE: 717 case ByteOps.IF_ICMPGT: 718 case ByteOps.IF_ICMPLE: { 719 machine.popArgs(frame, Type.INT, Type.INT); 720 break; 721 } 722 case ByteOps.IF_ACMPEQ: 723 case ByteOps.IF_ACMPNE: { 724 machine.popArgs(frame, Type.OBJECT, Type.OBJECT); 725 break; 726 } 727 case ByteOps.GOTO: 728 case ByteOps.JSR: 729 case ByteOps.GOTO_W: 730 case ByteOps.JSR_W: { 731 machine.clearArgs(); 732 break; 733 } 734 default: { 735 visitInvalid(opcode, offset, length); 736 return; 737 } 738 } 739 740 machine.auxTargetArg(target); 741 machine.run(frame, offset, opcode); 742 } 743 744 /** {@inheritDoc} */ 745 public void visitSwitch(int opcode, int offset, int length, 746 SwitchList cases, int padding) { 747 machine.popArgs(frame, Type.INT); 748 machine.auxIntArg(padding); 749 machine.auxSwitchArg(cases); 750 machine.run(frame, offset, opcode); 751 } 752 753 /** {@inheritDoc} */ 754 public void visitNewarray(int offset, int length, CstType type, 755 ArrayList<Constant> initValues) { 756 machine.popArgs(frame, Type.INT); 757 machine.auxInitValues(initValues); 758 machine.auxCstArg(type); 759 machine.run(frame, offset, ByteOps.NEWARRAY); 760 } 761 762 /** {@inheritDoc} */ 763 public void setPreviousOffset(int offset) { 764 previousOffset = offset; 765 } 766 767 /** {@inheritDoc} */ 768 public int getPreviousOffset() { 769 return previousOffset; 770 } 771 } 772} 773