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