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