001 package edu.rice.cs.cunit.classFile.code; 002 003 import edu.rice.cs.cunit.classFile.attributes.CodeAttributeInfo; 004 import edu.rice.cs.cunit.classFile.code.instructions.AInstruction; 005 import edu.rice.cs.cunit.classFile.code.instructions.LineNumberTable; 006 import edu.rice.cs.cunit.classFile.constantPool.ConstantPool; 007 008 import java.io.ByteArrayOutputStream; 009 import java.io.IOException; 010 import java.util.LinkedList; 011 import java.util.ArrayList; 012 013 /** 014 * Represents a list of Java instructions. 015 * 016 * @author Mathias Ricken 017 */ 018 public class InstructionList { 019 LinkedList<AInstruction> _instructionList = new LinkedList<AInstruction>(); 020 protected int _index; 021 LineNumberTable _lnt; 022 023 /** 024 * Constructor. 025 * 026 * @param code bytecode 027 */ 028 public InstructionList(byte[] code) { 029 setCode(code); 030 _index = 0; 031 } 032 033 /** 034 * Copy constructor. 035 * 036 * @param original original 037 */ 038 public InstructionList(InstructionList original) { 039 _instructionList.addAll(original._instructionList); 040 _index = original._index; 041 _lnt = original._lnt; 042 } 043 044 /** 045 * Constructor. 046 * 047 * @param code bytecode 048 * @param index program index. 049 * 050 * @throws IndexOutOfBoundsException 051 */ 052 public InstructionList(byte[] code, int index) throws IndexOutOfBoundsException { 053 this(code); 054 setIndex(index); 055 } 056 057 /** 058 * Return a human-readable version of the code. 059 * 060 * @return mnemonics 061 */ 062 public String toString() { 063 return toString(true, true, null); 064 } 065 066 /** 067 * Return a human-readable version of the code. 068 * @param cp constant pool 069 * @return mnemonics 070 */ 071 public String toString(ConstantPool cp) { 072 return toString(true, true, cp); 073 } 074 075 /** 076 * Return a human-readable version of the code. 077 * 078 * @param lineNumbers print line numbers 079 * @param PCs print PC values 080 * @param cp constant pool, or null if not verbose 081 * @return mnemonics 082 */ 083 public String toString(boolean lineNumbers, boolean PCs, ConstantPool cp) { 084 StringBuilder x = new StringBuilder(); 085 086 int index = 0; 087 int pc = 0; 088 for(AInstruction instr : _instructionList) { 089 if (lineNumbers) { 090 x.append(String.format("%5d", new Object[]{new Integer(index)})); 091 if (PCs) { 092 x.append(' '); 093 } 094 else { 095 x.append(": "); 096 } 097 } 098 if (PCs) { 099 x.append(String.format("(pc %5d): ", new Object[]{new Integer(pc)})); 100 } 101 if ((!lineNumbers) && (!PCs)) { 102 x.append(" "); 103 } 104 if (cp!=null) { 105 x.append(instr.toStringVerbose(cp)); 106 } 107 else { 108 x.append(instr.toString()); 109 } 110 x.append("\n"); 111 pc += instr.getBytecodeLength(pc); 112 ++index; 113 } 114 115 return x.toString(); 116 } 117 118 /** 119 * Accessor for the bytecode. 120 * 121 * @return bytecode 122 * 123 * @throws IllegalStateException 124 */ 125 public byte[] getCode() throws IllegalStateException { 126 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 127 int pc = 0; 128 for(AInstruction instr : _instructionList) { 129 byte[] b = instr.getBytecode(pc, _lnt); 130 try { 131 baos.write(b); 132 } 133 catch(IOException e) { 134 throw new IllegalStateException("Could not generate bytecode", e); 135 } 136 137 pc += b.length; 138 } 139 140 return baos.toByteArray(); 141 } 142 143 /** 144 * Mutator for the bytecode. This also resets the program counter to 0. 145 * 146 * @param code bytecode 147 */ 148 public void setCode(byte[] code) { 149 _lnt = new LineNumberTable(code); 150 151 int pc = 0; 152 _instructionList = new LinkedList<AInstruction>(); 153 while(pc < code.length) { 154 // make instruction and add it 155 _instructionList.addLast(AInstruction.makeInstruction(code, pc, pc, _lnt)); 156 157 pc += Opcode.getInstrSize(code, pc); 158 } 159 if (pc != code.length) { 160 throw new IllegalArgumentException("Invalid bytecode length"); 161 } 162 _index = 0; 163 } 164 165 /** 166 * Accessor for the program index. 167 * 168 * @return program index 169 */ 170 public int getIndex() { 171 return _index; 172 } 173 174 /** 175 * Calculate the PC for an index. 176 * 177 * @param index index 178 * 179 * @return PC 180 */ 181 public int getPCFromIndex(int index) { 182 int pc = 0; 183 for(int i = 0; i < index; ++i) { 184 pc += _instructionList.get(i).getBytecodeLength(pc); 185 } 186 187 return pc; 188 } 189 190 /** 191 * Get the length of the instruction list. 192 * 193 * @return length 194 */ 195 public int getLength() { 196 return _instructionList.size(); 197 } 198 199 /** 200 * Mutator for the program index. 201 * 202 * @param index new program index 203 * 204 * @throws IndexOutOfBoundsException 205 */ 206 public void setIndex(int index) throws IndexOutOfBoundsException, IllegalArgumentException { 207 if ((index < 0) || (index >= _instructionList.size())) { 208 throw new IndexOutOfBoundsException( 209 "The program index (" + index + ") is not within the instruction list (length=" + _instructionList.size() + ")"); 210 } 211 212 _index = index; 213 } 214 215 /** 216 * Advance the program index by multiple instructions. 217 * @param count number of instructions 218 * @return true while the end of the instruction list has not been reached 219 */ 220 public boolean advanceIndex(int count) { 221 while(count>0) { 222 if (!advanceIndex()) { 223 return false; 224 } 225 --count; 226 } 227 return true; 228 } 229 230 /** 231 * Advance the program index to the next instruction. 232 * 233 * @return true while the end of the instruction list has not been reached 234 */ 235 public boolean advanceIndex() { 236 if (_index < 0) { 237 throw new IndexOutOfBoundsException( 238 "The program index (" + _index + ") is not within the instruction list (length=" + _instructionList.size() + ")"); 239 } 240 if (_index < _instructionList.size()) { 241 ++_index; 242 } 243 return (_index < _instructionList.size()); 244 } 245 246 /** 247 * Rewind the program index by multiple instructions. 248 * @param count number of instructions 249 * @return true while the beginning of the instruction list has not been reached 250 */ 251 public boolean rewindIndex(int count) { 252 while(count>0) { 253 if (!rewindIndex()) { 254 return false; 255 } 256 --count; 257 } 258 return true; 259 } 260 261 /** 262 * Rewind the program index to the previous instruction. 263 * 264 * @return true while the beginning of the instruction list has not been reached 265 */ 266 public boolean rewindIndex() { 267 if (_index < 0) { 268 throw new IndexOutOfBoundsException( 269 "The program index (" + _index + ") is not within the instruction list (length=" + _instructionList.size() + ")"); 270 } 271 if (_index > 0) { 272 --_index; 273 } 274 return (_index > 0); 275 } 276 277 /** 278 * Return the opcode at the program index. 279 * 280 * @return opcode at program index 281 */ 282 public byte getOpcode() { 283 if ((_index < 0) || (_index >= _instructionList.size())) { 284 throw new IndexOutOfBoundsException( 285 "The program index (" + _index + ") is not within the instruction list (length=" + _instructionList.size() + ")"); 286 } 287 return _instructionList.get(_index).getOpcode(); 288 } 289 290 /** 291 * Return the instruction at the program index. This includes the code and the operands. 292 * 293 * @return instruction at program index 294 */ 295 public AInstruction getInstr() { 296 if ((_index < 0) || (_index >= _instructionList.size())) { 297 throw new IndexOutOfBoundsException( 298 "The program index (" + _index + ") is not within the instruction list (length=" + _instructionList.size() + ")"); 299 } 300 301 return _instructionList.get(_index); 302 } 303 304 /** 305 * This method deletes the instruction at the program index. It also relocates all jumps. All jumps to places after 306 * the deletion point will be changed so that they still point to the same instruction. Jumps to the deletion point 307 * or to places before it are not changed. The index will remain unchanged. In case the program counter is past the 308 * end of the code block, false will be returned. If codeAttribute is non-null, the PCs in the CodeAttribute will be 309 * adjusted. 310 * 311 * @param codeAttribute CodeAttribute that contains this code block, or null if none 312 * 313 * @return true while the end of the code block has not been reached 314 */ 315 public boolean deleteInstr(CodeAttributeInfo codeAttribute) { 316 if ((_index < 0) || (_index >= _instructionList.size())) { 317 throw new IndexOutOfBoundsException( 318 "The program index (" + _index + ") is not within the instruction list (length=" + _instructionList.size() + ")"); 319 } 320 321 byte[] oldCode = getCode(); 322 int deletionPoint = _index; 323 324 // walk through all instructions and relocate jumps 325 InstructionList oldCodeBlock = new InstructionList(oldCode); 326 do { 327 if (Opcode.isBranch(oldCodeBlock.getOpcode())) { 328 // this is a branch, so we might have to relocate 329 int[] branchTargets = oldCodeBlock.getInstr().getBranchTargets(); 330 for(int i = 0; i < branchTargets.length; ++i) { 331 // Debug.out.println("insertInstr: DP = "+deletionPoint+", PC="+oldCodeBlock.getPC()+", BT="+branchTargets[i]); 332 if ((branchTargets[i] > deletionPoint) || (branchTargets[i] == oldCodeBlock.getLength() - 1)) { 333 // the target of this instruction was the insertion point or somewhere past it 334 // we need to relocate 335 --branchTargets[i]; 336 337 // Debug.out.println("\tnew BT="+branchTargets[i]); 338 } 339 } 340 oldCodeBlock.getInstr().setBranchTargets(branchTargets); 341 } 342 } while(oldCodeBlock.advanceIndex()); 343 344 oldCodeBlock._instructionList.remove(deletionPoint); 345 oldCodeBlock._lnt = new LineNumberTable(oldCodeBlock); 346 byte[] newCode = oldCodeBlock.getCode(); 347 348 if (codeAttribute != null) { 349 // adjust CodeAttribute PCs 350 codeAttribute.translatePC(deletionPoint, -1, _lnt, oldCodeBlock._lnt); 351 } 352 353 // transfer the code 354 setCode(newCode); 355 356 if (deletionPoint == getLength()) { 357 _index = getLength(); 358 return false; 359 } 360 else { 361 setIndex(deletionPoint); 362 return true; 363 } 364 } 365 366 367 /** 368 * This method inserts the instruction at the program counter. The instruction currently at the program counter will 369 * be pushed towards the end of the bytecode array. It also relocates all jumps. Jumps to the insertion point or to 370 * places before it are not changed. Jumps to all other places will be changed so that they still point to the same 371 * instruction. That means that jumps to the instruction previously at the insertion point will now jump to the 372 * newly inserted instruction. If codeAttribute is non-null, the PCs in the CodeAttribute will be adjusted. The 373 * program counter will remain unchanged. If the inserted instruction itself contains jump targets, these will be 374 * relocated as well. Notice, though, that if the instruction contains a jump exactly to the insertion point, this 375 * jump will be relocated so that it points to the old instruction (this is the behavior of insertBeforeInstr). 376 * <p/> 377 * Assume the following code: 378 * 1. a 379 * 2. b 380 * 3. c 381 * 4. goto 1 // jump to place before insertion point 382 * 5. goto 2 // jump to insertion point 383 * 6. goto 3 // junp to place after insertion point 384 * The program counter is currently at 2, and the instruction x is inserted using insertInstr. The result is: 385 * 1. a 386 * 2. x // inserted 387 * 3. b 388 * 4. c 389 * 5. goto 1 // not changed 390 * 6. goto 2 // not changed 391 * 7. goto 4 // changed 392 * <p/> 393 * Relocation of branches in the instruction inserted. 394 * Type : jump to place before insertion point 395 * Action : not changed 396 * Example; goto 1 --> goto 1 397 * 398 * Type : jump to insertion point 399 * Action : changed 400 * Example; goto 2 --> goto 3 401 402 * Type : jump to place after instruction point 403 * Action : changed 404 * Example; goto 3 --> goto 4 405 * 406 * @param codeAttribute CodeAttribute that contains this code block, or null if none 407 * @param instr instruction to be inserted 408 */ 409 public void insertInstr(AInstruction instr, CodeAttributeInfo codeAttribute) { 410 byte[] oldCode = getCode(); 411 int insertionPoint = _index; 412 413 // walk through all instructions and relocate jumps 414 InstructionList oldCodeBlock = new InstructionList(oldCode); 415 do { 416 if (Opcode.isBranch(oldCodeBlock.getOpcode())) { 417 // this is a branch, so we might have to relocate 418 AInstruction relocInstr = oldCodeBlock.getInstr(); 419 int ip = insertionPoint; 420 relocateBranches(relocInstr, ip); 421 } 422 } while(oldCodeBlock.advanceIndex()); 423 424 // relocate instruction to be inserted 425 if (Opcode.isBranch(instr.getOpcode())) { 426 // this is a branch, so we might have to relocate 427 relocateBranches(instr, insertionPoint-1); 428 } 429 430 oldCodeBlock._instructionList.add(insertionPoint, instr); 431 oldCodeBlock._lnt = new LineNumberTable(oldCodeBlock); 432 byte[] newCode = oldCodeBlock.getCode(); 433 434 if (codeAttribute != null) { 435 // adjust CodeAttribute PCs 436 codeAttribute.translatePC(insertionPoint, 1, _lnt, oldCodeBlock._lnt); 437 } 438 439 // transfer the code 440 setCode(newCode); 441 setIndex(insertionPoint); 442 } 443 444 /** 445 * Relocate branches in the instruction. If a branch target is to somewhere past the insertion point, it is 446 * increased by one. 447 * @param relocInstr instruction whose branches should be relocated 448 * @param ip insertion point 449 */ 450 protected void relocateBranches(AInstruction relocInstr, int ip) { 451 int[] branchTargets = relocInstr.getBranchTargets(); 452 for(int i = 0; i < branchTargets.length; ++i) { 453 // Debug.out.println("insertInstr: DP = "+deletionPoint+", PC="+oldCodeBlock.getPC()+", BT="+branchTargets[i]); 454 if (branchTargets[i] > ip) { 455 // the target of this instruction was somewhere past the insertion point 456 // we need to relocate 457 ++branchTargets[i]; 458 459 // Debug.out.println("\tnew BT="+branchTargets[i]); 460 } 461 } 462 relocInstr.setBranchTargets(branchTargets); 463 } 464 465 /** 466 * This method inserts the instruction before the program counter. The instruction currently at the program counter 467 * will be pushed towards the end of the bytecode array. It also relocates all jumps. Jumps to places before the 468 * insertion point are not changed. Jumps to all other places will be changed so that they still point to the same 469 * instruction. That means that jumps to the instruction previously at the insertion point will still jump to the 470 * old instruction. If codeAttribute is non-null, the PCs in the CodeAttribute will be adjusted. The 471 * program counter will remain unchanged. If the inserted instruction itself contains jump targets, these will be 472 * relocated as well. 473 * <p/> 474 * Assume the following code: 475 * 1. a 476 * 2. b 477 * 3. c 478 * 4. goto 1 // jump to place before insertion point 479 * 5. goto 2 // jump to insertion point 480 * 6. goto 3 // junp to place after insertion point 481 * The program counter is currently at 2, and the instruction x is inserted using insertBeforeInstr. The result is: 482 * 1. a 483 * 2. x // inserted 484 * 3. b 485 * 4. c 486 * 5. goto 1 // not changed 487 * 6. goto 3 // changed 488 * 7. goto 4 // changed 489 * <p/> 490 * Relocation of branches in the instruction inserted. 491 * Type : jump to place before insertion point 492 * Action : not changed 493 * Example; goto 1 --> goto 1 494 * 495 * Type : jump to insertion point 496 * Action : changed 497 * Example; goto 2 --> goto 3 498 499 * Type : jump to place after instruction point 500 * Action : changed 501 * Example; goto 3 --> goto 4 502 * 503 * @param codeAttribute CodeAttribute that contains this code block, or null if none 504 * @param instr instruction to be inserted 505 */ 506 public void insertBeforeInstr(AInstruction instr, CodeAttributeInfo codeAttribute) { 507 byte[] oldCode = getCode(); 508 int insertionPoint = _index; 509 510 // walk through all instructions and relocate jumps 511 InstructionList oldCodeBlock = new InstructionList(oldCode); 512 do { 513 if (Opcode.isBranch(oldCodeBlock.getOpcode())) { 514 // this is a branch, so we might have to relocate 515 AInstruction relocInstr = oldCodeBlock.getInstr(); 516 relocateBranches(relocInstr, insertionPoint-1); 517 } 518 } while(oldCodeBlock.advanceIndex()); 519 520 // relocate instruction to be inserted 521 if (Opcode.isBranch(instr.getOpcode())) { 522 // this is a branch, so we might have to relocate 523 relocateBranches(instr, insertionPoint-1); 524 } 525 526 oldCodeBlock._instructionList.add(insertionPoint, instr); 527 oldCodeBlock._lnt = new LineNumberTable(oldCodeBlock); 528 byte[] newCode = oldCodeBlock.getCode(); 529 530 if (codeAttribute != null) { 531 // adjust CodeAttribute PCs 532 codeAttribute.translatePC(insertionPoint-1, 1, _lnt, oldCodeBlock._lnt); 533 } 534 535 // transfer the code 536 setCode(newCode); 537 setIndex(insertionPoint); 538 } 539 540 /** 541 * Finds the next instruction with the specified opcode and moves the program counter there. If the current 542 * instruction matches the opcode, the program counter will not be changed. This means that the caller is 543 * responsible for advancing the program counter after a match has been found! 544 * 545 * @param opcode opcode to find 546 * 547 * @return true while the end of the code block has not been reached 548 */ 549 public boolean findOpcode(byte opcode) { 550 return findOpcode(new byte[] {opcode}); 551 } 552 553 /** 554 * Finds the next instruction with one of the specified opcodes and moves the program counter there. If the current 555 * instruction matches one of the opcodes, the program counter will not be changed. This means that the caller is 556 * responsible for advancing the program counter after a match has been found! 557 * 558 * @param opcodes array of opcodes to find 559 * 560 * @return true while the end of the code block has not been reached 561 */ 562 public boolean findOpcode(byte[] opcodes) { 563 if (_index < 0) { 564 throw new IndexOutOfBoundsException( 565 "The program index (" + _index + ") is not within the instruction list (length=" + _instructionList.size() + ")"); 566 } 567 if (_index == _instructionList.size()) { 568 // past end already 569 return false; 570 } 571 ArrayList<Byte> opcodeList = new ArrayList<Byte>(opcodes.length); 572 for(byte o: opcodes) { 573 opcodeList.add(o); 574 } 575 do { 576 if (opcodeList.contains(getOpcode())) { 577 return true; 578 } 579 } while(advanceIndex()); 580 return false; 581 } 582 583 /** 584 * Finds the next instruction that matches the specified instruction completely and moves the program counter there. 585 * A complete match requires that both the opcode and all the operands are identical. If the current instruction 586 * matches already, the program counter will not be changed. This means that the caller is responsible for advancing 587 * the program counter after a match has been found! 588 * 589 * @param instr instruction to find 590 * 591 * @return true while the end of the code block has not been reached 592 */ 593 public boolean findInstruction(AInstruction instr) { 594 return findInstruction(new AInstruction[] {instr}); 595 } 596 /** 597 * Finds the next instruction that matches one of the specified instructions completely and moves the program counter there. 598 * A complete match requires that both the opcode and all the operands are identical. If the current instruction 599 * matches already, the program counter will not be changed. This means that the caller is responsible for advancing 600 * the program counter after a match has been found! 601 * 602 * @param instrs instructions to find 603 * 604 * @return true while the end of the code block has not been reached 605 */ 606 public boolean findInstruction(AInstruction[] instrs) { 607 if (_index < 0) { 608 throw new IndexOutOfBoundsException( 609 "The program index (" + _index + ") is not within the instruction list (length=" + _instructionList.size() + ")"); 610 } 611 if (_index == _instructionList.size()) { 612 // past end already 613 return false; 614 } 615 ArrayList<AInstruction> instrList = new ArrayList<AInstruction>(instrs.length); 616 for(AInstruction o: instrs) { 617 instrList.add(o); 618 } 619 do { 620 if (instrList.contains(getInstr())) { 621 return true; 622 } 623 } while(advanceIndex()); 624 return false; 625 } 626 627 /** 628 * Return true if this instruction list and the other object are equal. 629 * 630 * @param o other object 631 * 632 * @return true if equal 633 */ 634 public boolean equals(Object o) { 635 if (this == o) { 636 return true; 637 } 638 if (!(o instanceof InstructionList)) { 639 return false; 640 } 641 642 final InstructionList instructionList = (InstructionList)o; 643 644 if (!_instructionList.equals(instructionList._instructionList)) { 645 return false; 646 } 647 648 return true; 649 } 650 651 /** 652 * Return hash code. 653 * 654 * @return hash code 655 */ 656 public int hashCode() { 657 return _instructionList.hashCode(); 658 } 659 }