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