1 /* 2 * ProGuard -- shrinking, optimization, obfuscation, and preverification 3 * of Java bytecode. 4 * 5 * Copyright (c) 2002-2014 Eric Lafortune (eric@graphics.cornell.edu) 6 * 7 * This program is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU General Public License as published by the Free 9 * Software Foundation; either version 2 of the License, or (at your option) 10 * any later version. 11 * 12 * This program is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 15 * more details. 16 * 17 * You should have received a copy of the GNU General Public License along 18 * with this program; if not, write to the Free Software Foundation, Inc., 19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 */ 21 package proguard.classfile.editor; 22 23 import proguard.classfile.*; 24 import proguard.classfile.attribute.*; 25 import proguard.classfile.attribute.annotation.*; 26 import proguard.classfile.attribute.annotation.target.*; 27 import proguard.classfile.attribute.annotation.target.visitor.*; 28 import proguard.classfile.attribute.annotation.visitor.TypeAnnotationVisitor; 29 import proguard.classfile.attribute.preverification.*; 30 import proguard.classfile.attribute.preverification.visitor.*; 31 import proguard.classfile.attribute.visitor.*; 32 import proguard.classfile.instruction.*; 33 import proguard.classfile.instruction.visitor.InstructionVisitor; 34 import proguard.classfile.util.SimplifiedVisitor; 35 import proguard.util.ArrayUtil; 36 37 import java.util.Arrays; 38 39 /** 40 * This AttributeVisitor accumulates specified changes to code, and then applies 41 * these accumulated changes to the code attributes that it visits. 42 * 43 * @author Eric Lafortune 44 */ 45 public class CodeAttributeEditor 46 extends SimplifiedVisitor 47 implements AttributeVisitor, 48 InstructionVisitor, 49 ExceptionInfoVisitor, 50 StackMapFrameVisitor, 51 VerificationTypeVisitor, 52 LineNumberInfoVisitor, 53 LocalVariableInfoVisitor, 54 LocalVariableTypeInfoVisitor, 55 TypeAnnotationVisitor, 56 TargetInfoVisitor, 57 LocalVariableTargetElementVisitor 58 { 59 //* 60 private static final boolean DEBUG = false; 61 /*/ 62 public static boolean DEBUG = false; 63 //*/ 64 65 66 private final boolean updateFrameSizes; 67 private final boolean shrinkInstructions; 68 69 private int codeLength; 70 private boolean modified; 71 private boolean simple; 72 73 /*private*/public Instruction[] preInsertions = new Instruction[ClassConstants.TYPICAL_CODE_LENGTH]; 74 /*private*/public Instruction[] replacements = new Instruction[ClassConstants.TYPICAL_CODE_LENGTH]; 75 /*private*/public Instruction[] postInsertions = new Instruction[ClassConstants.TYPICAL_CODE_LENGTH]; 76 /*private*/public boolean[] deleted = new boolean[ClassConstants.TYPICAL_CODE_LENGTH]; 77 78 private int[] newInstructionOffsets = new int[ClassConstants.TYPICAL_CODE_LENGTH]; 79 private int newOffset; 80 private boolean lengthIncreased; 81 82 private int expectedStackMapFrameOffset; 83 84 private final StackSizeUpdater stackSizeUpdater = new StackSizeUpdater(); 85 private final VariableSizeUpdater variableSizeUpdater = new VariableSizeUpdater(); 86 private final InstructionWriter instructionWriter = new InstructionWriter(); 87 88 89 /** 90 * Creates a new CodeAttributeEditor that automatically updates frame 91 * sizes and shrinks instructions. 92 */ CodeAttributeEditor()93 public CodeAttributeEditor() 94 { 95 this(true, true); 96 } 97 98 99 /** 100 * Creates a new CodeAttributeEditor. 101 * @param updateFrameSizes specifies whether frame sizes of edited code 102 * should be updated. 103 * @param shrinkInstructions specifies whether added instructions should 104 * automatically be shrunk before being written. 105 */ CodeAttributeEditor(boolean updateFrameSizes, boolean shrinkInstructions)106 public CodeAttributeEditor(boolean updateFrameSizes, 107 boolean shrinkInstructions) 108 { 109 this.updateFrameSizes = updateFrameSizes; 110 this.shrinkInstructions = shrinkInstructions; 111 } 112 113 114 /** 115 * Resets the accumulated code changes. 116 * @param codeLength the length of the code that will be edited next. 117 */ reset(int codeLength)118 public void reset(int codeLength) 119 { 120 // Try to reuse the previous arrays. 121 if (preInsertions.length < codeLength) 122 { 123 preInsertions = new Instruction[codeLength]; 124 replacements = new Instruction[codeLength]; 125 postInsertions = new Instruction[codeLength]; 126 deleted = new boolean[codeLength]; 127 } 128 else 129 { 130 Arrays.fill(preInsertions, 0, codeLength, null); 131 Arrays.fill(replacements, 0, codeLength, null); 132 Arrays.fill(postInsertions, 0, codeLength, null); 133 Arrays.fill(deleted, 0, codeLength, false); 134 } 135 136 this.codeLength = codeLength; 137 138 modified = false; 139 simple = true; 140 } 141 142 143 /** 144 * Extends the size of the accumulated code changes. 145 * @param codeLength the length of the code that will be edited next. 146 */ extend(int codeLength)147 public void extend(int codeLength) 148 { 149 // Try to reuse the previous arrays. 150 if (preInsertions.length < codeLength) 151 { 152 preInsertions = (Instruction[])ArrayUtil.extendArray(preInsertions, codeLength); 153 replacements = (Instruction[])ArrayUtil.extendArray(replacements, codeLength); 154 postInsertions = (Instruction[])ArrayUtil.extendArray(postInsertions, codeLength); 155 deleted = ArrayUtil.extendArray(deleted, codeLength); 156 } 157 else 158 { 159 Arrays.fill(preInsertions, this.codeLength, codeLength, null); 160 Arrays.fill(replacements, this.codeLength, codeLength, null); 161 Arrays.fill(postInsertions, this.codeLength, codeLength, null); 162 Arrays.fill(deleted, this.codeLength, codeLength, false); 163 } 164 165 this.codeLength = codeLength; 166 } 167 168 169 /** 170 * Remembers to place the given instruction right before the instruction 171 * at the given offset. 172 * @param instructionOffset the offset of the instruction. 173 * @param instruction the new instruction. 174 */ insertBeforeInstruction(int instructionOffset, Instruction instruction)175 public void insertBeforeInstruction(int instructionOffset, Instruction instruction) 176 { 177 if (instructionOffset < 0 || 178 instructionOffset >= codeLength) 179 { 180 throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); 181 } 182 183 preInsertions[instructionOffset] = shrinkInstructions ? 184 instruction.shrink() : 185 instruction; 186 187 modified = true; 188 simple = false; 189 } 190 191 192 /** 193 * Remembers to place the given instructions right before the instruction 194 * at the given offset. 195 * @param instructionOffset the offset of the instruction. 196 * @param instructions the new instructions. 197 */ insertBeforeInstruction(int instructionOffset, Instruction[] instructions)198 public void insertBeforeInstruction(int instructionOffset, Instruction[] instructions) 199 { 200 if (instructionOffset < 0 || 201 instructionOffset >= codeLength) 202 { 203 throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); 204 } 205 206 CompositeInstruction instruction = 207 new CompositeInstruction(instructions); 208 209 preInsertions[instructionOffset] = shrinkInstructions ? 210 instruction.shrink() : 211 instruction; 212 213 modified = true; 214 simple = false; 215 } 216 217 218 /** 219 * Remembers to replace the instruction at the given offset by the given 220 * instruction. 221 * @param instructionOffset the offset of the instruction to be replaced. 222 * @param instruction the new instruction. 223 */ replaceInstruction(int instructionOffset, Instruction instruction)224 public void replaceInstruction(int instructionOffset, Instruction instruction) 225 { 226 if (instructionOffset < 0 || 227 instructionOffset >= codeLength) 228 { 229 throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); 230 } 231 232 replacements[instructionOffset] = shrinkInstructions ? 233 instruction.shrink() : 234 instruction; 235 236 modified = true; 237 } 238 239 240 /** 241 * Remembers to replace the instruction at the given offset by the given 242 * instructions. 243 * @param instructionOffset the offset of the instruction to be replaced. 244 * @param instructions the new instructions. 245 */ replaceInstruction(int instructionOffset, Instruction[] instructions)246 public void replaceInstruction(int instructionOffset, Instruction[] instructions) 247 { 248 if (instructionOffset < 0 || 249 instructionOffset >= codeLength) 250 { 251 throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); 252 } 253 254 CompositeInstruction instruction = 255 new CompositeInstruction(instructions); 256 257 replacements[instructionOffset] = shrinkInstructions ? 258 instruction.shrink() : 259 instruction; 260 261 modified = true; 262 } 263 264 265 /** 266 * Remembers to place the given instruction right after the instruction 267 * at the given offset. 268 * @param instructionOffset the offset of the instruction. 269 * @param instruction the new instruction. 270 */ insertAfterInstruction(int instructionOffset, Instruction instruction)271 public void insertAfterInstruction(int instructionOffset, Instruction instruction) 272 { 273 if (instructionOffset < 0 || 274 instructionOffset >= codeLength) 275 { 276 throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); 277 } 278 279 postInsertions[instructionOffset] = shrinkInstructions ? 280 instruction.shrink() : 281 instruction; 282 283 modified = true; 284 simple = false; 285 } 286 287 288 /** 289 * Remembers to place the given instructions right after the instruction 290 * at the given offset. 291 * @param instructionOffset the offset of the instruction. 292 * @param instructions the new instructions. 293 */ insertAfterInstruction(int instructionOffset, Instruction[] instructions)294 public void insertAfterInstruction(int instructionOffset, Instruction[] instructions) 295 { 296 if (instructionOffset < 0 || 297 instructionOffset >= codeLength) 298 { 299 throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); 300 } 301 302 CompositeInstruction instruction = 303 new CompositeInstruction(instructions); 304 305 postInsertions[instructionOffset] = shrinkInstructions ? 306 instruction.shrink() : 307 instruction; 308 309 modified = true; 310 simple = false; 311 } 312 313 314 /** 315 * Remembers to delete the instruction at the given offset. 316 * @param instructionOffset the offset of the instruction to be deleted. 317 */ deleteInstruction(int instructionOffset)318 public void deleteInstruction(int instructionOffset) 319 { 320 if (instructionOffset < 0 || 321 instructionOffset >= codeLength) 322 { 323 throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); 324 } 325 326 deleted[instructionOffset] = true; 327 328 modified = true; 329 simple = false; 330 } 331 332 333 /** 334 * Remembers not to delete the instruction at the given offset. 335 * @param instructionOffset the offset of the instruction not to be deleted. 336 */ undeleteInstruction(int instructionOffset)337 public void undeleteInstruction(int instructionOffset) 338 { 339 if (instructionOffset < 0 || 340 instructionOffset >= codeLength) 341 { 342 throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); 343 } 344 345 deleted[instructionOffset] = false; 346 } 347 348 349 /** 350 * Clears all modifications of the instruction at the given offset. 351 * @param instructionOffset the offset of the instruction to be deleted. 352 */ clearModifications(int instructionOffset)353 public void clearModifications(int instructionOffset) 354 { 355 if (instructionOffset < 0 || 356 instructionOffset >= codeLength) 357 { 358 throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); 359 } 360 361 preInsertions[instructionOffset] = null; 362 replacements[instructionOffset] = null; 363 postInsertions[instructionOffset] = null; 364 deleted[instructionOffset] = false; 365 } 366 367 368 /** 369 * Returns whether the code has been modified in any way. 370 */ isModified()371 public boolean isModified() 372 { 373 return modified; 374 } 375 376 377 /** 378 * Returns whether the instruction at the given offset has been modified 379 * in any way. 380 */ isModified(int instructionOffset)381 public boolean isModified(int instructionOffset) 382 { 383 return preInsertions[instructionOffset] != null || 384 replacements[instructionOffset] != null || 385 postInsertions[instructionOffset] != null || 386 deleted[instructionOffset]; 387 } 388 389 390 // Implementations for AttributeVisitor. 391 visitAnyAttribute(Clazz clazz, Attribute attribute)392 public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} 393 394 visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)395 public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) 396 { 397 // DEBUG = 398 // clazz.getName().equals("abc/Def") && 399 // method.getName(clazz).equals("abc"); 400 401 // TODO: Remove this when the code has stabilized. 402 // Catch any unexpected exceptions from the actual visiting method. 403 try 404 { 405 // Process the code. 406 visitCodeAttribute0(clazz, method, codeAttribute); 407 } 408 catch (RuntimeException ex) 409 { 410 System.err.println("Unexpected error while editing code:"); 411 System.err.println(" Class = ["+clazz.getName()+"]"); 412 System.err.println(" Method = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]"); 413 System.err.println(" Exception = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")"); 414 415 throw ex; 416 } 417 } 418 419 visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute)420 public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute) 421 { 422 // Do we have to update the code? 423 if (modified) 424 { 425 if (DEBUG) 426 { 427 System.out.println("CodeAttributeEditor: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)); 428 } 429 430 // Can we perform a faster simple replacement of instructions? 431 if (canPerformSimpleReplacements(codeAttribute)) 432 { 433 if (DEBUG) 434 { 435 System.out.println(" Simple editing"); 436 } 437 438 // Simply overwrite the instructions. 439 performSimpleReplacements(codeAttribute); 440 } 441 else 442 { 443 if (DEBUG) 444 { 445 System.out.println(" Full editing"); 446 } 447 448 // Move and remap the instructions. 449 codeAttribute.u4codeLength = 450 updateInstructions(clazz, method, codeAttribute); 451 452 // Update the exception table. 453 codeAttribute.exceptionsAccept(clazz, method, this); 454 455 // Remove exceptions with empty code blocks. 456 codeAttribute.u2exceptionTableLength = 457 removeEmptyExceptions(codeAttribute.exceptionTable, 458 codeAttribute.u2exceptionTableLength); 459 460 // Update the line number table and the local variable tables. 461 codeAttribute.attributesAccept(clazz, method, this); 462 } 463 464 // Make sure instructions are widened if necessary. 465 instructionWriter.visitCodeAttribute(clazz, method, codeAttribute); 466 } 467 468 // Update the maximum stack size and local variable frame size. 469 if (updateFrameSizes) 470 { 471 stackSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute); 472 variableSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute); 473 } 474 } 475 476 visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute)477 public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute) 478 { 479 // Update all stack map entries. 480 expectedStackMapFrameOffset = -1; 481 stackMapAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this); 482 } 483 484 visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute)485 public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute) 486 { 487 // Update all stack map table entries. 488 expectedStackMapFrameOffset = 0; 489 stackMapTableAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this); 490 } 491 492 visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)493 public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute) 494 { 495 // Update all line number table entries. 496 lineNumberTableAttribute.lineNumbersAccept(clazz, method, codeAttribute, this); 497 498 // Remove line numbers with empty code blocks. 499 lineNumberTableAttribute.u2lineNumberTableLength = 500 removeEmptyLineNumbers(lineNumberTableAttribute.lineNumberTable, 501 lineNumberTableAttribute.u2lineNumberTableLength, 502 codeAttribute.u4codeLength); 503 } 504 505 visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)506 public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) 507 { 508 // Update all local variable table entries. 509 localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); 510 } 511 512 visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)513 public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) 514 { 515 // Update all local variable table entries. 516 localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); 517 } 518 519 visitAnyTypeAnnotationsAttribute(Clazz clazz, TypeAnnotationsAttribute typeAnnotationsAttribute)520 public void visitAnyTypeAnnotationsAttribute(Clazz clazz, TypeAnnotationsAttribute typeAnnotationsAttribute) 521 { 522 typeAnnotationsAttribute.typeAnnotationsAccept(clazz, this); 523 } 524 525 526 /** 527 * Checks if it is possible to modifies the given code without having to 528 * update any offsets. 529 * @param codeAttribute the code to be changed. 530 * @return the new code length. 531 */ canPerformSimpleReplacements(CodeAttribute codeAttribute)532 private boolean canPerformSimpleReplacements(CodeAttribute codeAttribute) 533 { 534 if (!simple) 535 { 536 return false; 537 } 538 539 byte[] code = codeAttribute.code; 540 int codeLength = codeAttribute.u4codeLength; 541 542 // Go over all replacement instructions. 543 for (int offset = 0; offset < codeLength; offset++) 544 { 545 // Check if the replacement instruction, if any, has a different 546 // length than the original instruction. 547 Instruction replacementInstruction = replacements[offset]; 548 if (replacementInstruction != null && 549 replacementInstruction.length(offset) != 550 InstructionFactory.create(code, offset).length(offset)) 551 { 552 return false; 553 } 554 } 555 556 return true; 557 } 558 559 560 /** 561 * Modifies the given code without updating any offsets. 562 * @param codeAttribute the code to be changed. 563 */ performSimpleReplacements(CodeAttribute codeAttribute)564 private void performSimpleReplacements(CodeAttribute codeAttribute) 565 { 566 int codeLength = codeAttribute.u4codeLength; 567 568 // Go over all replacement instructions. 569 for (int offset = 0; offset < codeLength; offset++) 570 { 571 // Overwrite the original instruction with the replacement 572 // instruction if any. 573 Instruction replacementInstruction = replacements[offset]; 574 if (replacementInstruction != null) 575 { 576 replacementInstruction.write(codeAttribute, offset); 577 578 if (DEBUG) 579 { 580 System.out.println(" Replaced "+replacementInstruction.toString(offset)); 581 } 582 } 583 } 584 } 585 586 587 /** 588 * Modifies the given code based on the previously specified changes. 589 * @param clazz the class file of the code to be changed. 590 * @param method the method of the code to be changed. 591 * @param codeAttribute the code to be changed. 592 * @return the new code length. 593 */ updateInstructions(Clazz clazz, Method method, CodeAttribute codeAttribute)594 private int updateInstructions(Clazz clazz, 595 Method method, 596 CodeAttribute codeAttribute) 597 { 598 byte[] oldCode = codeAttribute.code; 599 int oldLength = codeAttribute.u4codeLength; 600 601 // Make sure there is a sufficiently large instruction offset map. 602 if (newInstructionOffsets.length < oldLength + 1) 603 { 604 newInstructionOffsets = new int[oldLength + 1]; 605 } 606 607 // Fill out the instruction offset map. 608 int newLength = mapInstructions(oldCode, 609 oldLength); 610 611 // Create a new code array if necessary. 612 if (lengthIncreased) 613 { 614 codeAttribute.code = new byte[newLength]; 615 } 616 617 // Prepare for possible widening of instructions. 618 instructionWriter.reset(newLength); 619 620 // Move the instructions into the new code array. 621 moveInstructions(clazz, 622 method, 623 codeAttribute, 624 oldCode, 625 oldLength); 626 627 // We can return the new length. 628 return newLength; 629 } 630 631 632 /** 633 * Fills out the instruction offset map for the given code block. 634 * @param oldCode the instructions to be moved. 635 * @param oldLength the code length. 636 * @return the new code length. 637 */ mapInstructions(byte[] oldCode, int oldLength)638 private int mapInstructions(byte[] oldCode, int oldLength) 639 { 640 // Start mapping instructions at the beginning. 641 newOffset = 0; 642 lengthIncreased = false; 643 644 int oldOffset = 0; 645 do 646 { 647 // Get the next instruction. 648 Instruction instruction = InstructionFactory.create(oldCode, oldOffset); 649 650 // Compute the mapping of the instruction. 651 mapInstruction(oldOffset, instruction); 652 653 oldOffset += instruction.length(oldOffset); 654 655 if (newOffset > oldOffset) 656 { 657 lengthIncreased = true; 658 } 659 } 660 while (oldOffset < oldLength); 661 662 // Also add an entry for the first offset after the code. 663 newInstructionOffsets[oldOffset] = newOffset; 664 665 return newOffset; 666 } 667 668 669 /** 670 * Fills out the instruction offset map for the given instruction. 671 * @param oldOffset the instruction's old offset. 672 * @param instruction the instruction to be moved. 673 */ mapInstruction(int oldOffset, Instruction instruction)674 private void mapInstruction(int oldOffset, 675 Instruction instruction) 676 { 677 newInstructionOffsets[oldOffset] = newOffset; 678 679 // Account for the pre-inserted instruction, if any. 680 Instruction preInstruction = preInsertions[oldOffset]; 681 if (preInstruction != null) 682 { 683 newOffset += preInstruction.length(newOffset); 684 } 685 686 // Account for the replacement instruction, or for the current 687 // instruction, if it shouldn't be deleted. 688 Instruction replacementInstruction = replacements[oldOffset]; 689 if (replacementInstruction != null) 690 { 691 newOffset += replacementInstruction.length(newOffset); 692 } 693 else if (!deleted[oldOffset]) 694 { 695 // Note that the instruction's length may change at its new offset, 696 // e.g. if it is a switch instruction. 697 newOffset += instruction.length(newOffset); 698 } 699 700 // Account for the post-inserted instruction, if any. 701 Instruction postInstruction = postInsertions[oldOffset]; 702 if (postInstruction != null) 703 { 704 newOffset += postInstruction.length(newOffset); 705 } 706 } 707 708 709 /** 710 * Moves the given code block to the new offsets. 711 * @param clazz the class file of the code to be changed. 712 * @param method the method of the code to be changed. 713 * @param codeAttribute the code to be changed. 714 * @param oldCode the original code to be moved. 715 * @param oldLength the original code length. 716 */ moveInstructions(Clazz clazz, Method method, CodeAttribute codeAttribute, byte[] oldCode, int oldLength)717 private void moveInstructions(Clazz clazz, 718 Method method, 719 CodeAttribute codeAttribute, 720 byte[] oldCode, 721 int oldLength) 722 { 723 // Start writing instructions at the beginning. 724 newOffset = 0; 725 726 int oldOffset = 0; 727 do 728 { 729 // Get the next instruction. 730 Instruction instruction = InstructionFactory.create(oldCode, oldOffset); 731 732 // Move the instruction to its new offset. 733 moveInstruction(clazz, 734 method, 735 codeAttribute, 736 oldOffset, 737 instruction); 738 739 oldOffset += instruction.length(oldOffset); 740 } 741 while (oldOffset < oldLength); 742 } 743 744 745 /** 746 * Moves the given instruction to its new offset. 747 * @param clazz the class file of the code to be changed. 748 * @param method the method of the code to be changed. 749 * @param codeAttribute the code to be changed. 750 * @param oldOffset the original instruction offset. 751 * @param instruction the original instruction. 752 */ moveInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int oldOffset, Instruction instruction)753 private void moveInstruction(Clazz clazz, 754 Method method, 755 CodeAttribute codeAttribute, 756 int oldOffset, 757 Instruction instruction) 758 { 759 // Update and insert the pre-inserted instruction, if any. 760 Instruction preInstruction = preInsertions[oldOffset]; 761 if (preInstruction != null) 762 { 763 if (DEBUG) 764 { 765 System.out.println(" Pre-inserted ["+oldOffset+"] -> "+preInstruction.toString(newOffset)); 766 } 767 768 // Update the instruction. 769 preInstruction.accept(clazz, method, codeAttribute, oldOffset, this); 770 } 771 772 // Update and insert the replacement instruction, or the current 773 // instruction, if it shouldn't be deleted. 774 Instruction replacementInstruction = replacements[oldOffset]; 775 if (replacementInstruction != null) 776 { 777 if (DEBUG) 778 { 779 System.out.println(" Replaced ["+oldOffset+"] -> "+replacementInstruction.toString(newOffset)); 780 } 781 782 // Update the instruction. 783 replacementInstruction.accept(clazz, method, codeAttribute, oldOffset, this); 784 } 785 else if (!deleted[oldOffset]) 786 { 787 if (DEBUG) 788 { 789 System.out.println(" Copied ["+oldOffset+"] -> "+instruction.toString(newOffset)); 790 } 791 792 // Update the instruction. 793 instruction.accept(clazz, method, codeAttribute, oldOffset, this); 794 } 795 796 // Update and insert the post-inserted instruction, if any. 797 Instruction postInstruction = postInsertions[oldOffset]; 798 if (postInstruction != null) 799 { 800 if (DEBUG) 801 { 802 System.out.println(" Post-inserted ["+oldOffset+"] -> "+postInstruction.toString(newOffset)); 803 } 804 805 // Update the instruction. 806 postInstruction.accept(clazz, method, codeAttribute, oldOffset, this); 807 } 808 } 809 810 811 // Implementations for InstructionVisitor. 812 visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)813 public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) 814 { 815 // Write out the instruction. 816 instructionWriter.visitSimpleInstruction(clazz, 817 method, 818 codeAttribute, 819 newOffset, 820 simpleInstruction); 821 822 newOffset += simpleInstruction.length(newOffset); 823 } 824 825 visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)826 public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) 827 { 828 // Write out the instruction. 829 instructionWriter.visitConstantInstruction(clazz, 830 method, 831 codeAttribute, 832 newOffset, 833 constantInstruction); 834 835 newOffset += constantInstruction.length(newOffset); 836 } 837 838 visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)839 public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) 840 { 841 // Write out the instruction. 842 instructionWriter.visitVariableInstruction(clazz, 843 method, 844 codeAttribute, 845 newOffset, 846 variableInstruction); 847 848 newOffset += variableInstruction.length(newOffset); 849 } 850 851 visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)852 public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) 853 { 854 // Update the branch offset, relative to the precise new offset. 855 branchInstruction.branchOffset = 856 newBranchOffset(offset, branchInstruction.branchOffset, newOffset); 857 858 // Write out the instruction. 859 instructionWriter.visitBranchInstruction(clazz, 860 method, 861 codeAttribute, 862 newOffset, 863 branchInstruction); 864 865 newOffset += branchInstruction.length(newOffset); 866 } 867 868 visitTableSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TableSwitchInstruction tableSwitchInstruction)869 public void visitTableSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TableSwitchInstruction tableSwitchInstruction) 870 { 871 // Update the default jump offset, relative to the precise new offset. 872 tableSwitchInstruction.defaultOffset = 873 newBranchOffset(offset, tableSwitchInstruction.defaultOffset, newOffset); 874 875 // Update the jump offsets, relative to the precise new offset. 876 newJumpOffsets(offset, 877 tableSwitchInstruction.jumpOffsets, 878 newOffset); 879 880 // Write out the instruction. 881 instructionWriter.visitTableSwitchInstruction(clazz, 882 method, 883 codeAttribute, 884 newOffset, 885 tableSwitchInstruction); 886 887 newOffset += tableSwitchInstruction.length(newOffset); 888 } 889 890 visitLookUpSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LookUpSwitchInstruction lookUpSwitchInstruction)891 public void visitLookUpSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LookUpSwitchInstruction lookUpSwitchInstruction) 892 { 893 // Update the default jump offset, relative to the precise new offset. 894 lookUpSwitchInstruction.defaultOffset = 895 newBranchOffset(offset, lookUpSwitchInstruction.defaultOffset, newOffset); 896 897 // Update the jump offsets, relative to the precise new offset. 898 newJumpOffsets(offset, 899 lookUpSwitchInstruction.jumpOffsets, 900 newOffset); 901 902 // Write out the instruction. 903 instructionWriter.visitLookUpSwitchInstruction(clazz, 904 method, 905 codeAttribute, 906 newOffset, 907 lookUpSwitchInstruction); 908 909 newOffset += lookUpSwitchInstruction.length(newOffset); 910 } 911 912 913 // Implementations for ExceptionInfoVisitor. 914 visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)915 public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) 916 { 917 // Update the code offsets. Note that the instruction offset map also 918 // has an entry for the first offset after the code, for u2endPC. 919 exceptionInfo.u2startPC = newInstructionOffset(exceptionInfo.u2startPC); 920 exceptionInfo.u2endPC = newInstructionOffset(exceptionInfo.u2endPC); 921 exceptionInfo.u2handlerPC = newInstructionOffset(exceptionInfo.u2handlerPC); 922 } 923 924 925 // Implementations for StackMapFrameVisitor. 926 visitAnyStackMapFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrame stackMapFrame)927 public void visitAnyStackMapFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrame stackMapFrame) 928 { 929 // Update the stack map frame offset. 930 int stackMapFrameOffset = newInstructionOffset(offset); 931 932 int offsetDelta = stackMapFrameOffset; 933 934 // Compute the offset delta if the frame is part of a stack map frame 935 // table (for JDK 6.0) instead of a stack map (for Java Micro Edition). 936 if (expectedStackMapFrameOffset >= 0) 937 { 938 offsetDelta -= expectedStackMapFrameOffset; 939 940 expectedStackMapFrameOffset = stackMapFrameOffset + 1; 941 } 942 943 stackMapFrame.u2offsetDelta = offsetDelta; 944 } 945 946 visitSameOneFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameOneFrame sameOneFrame)947 public void visitSameOneFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameOneFrame sameOneFrame) 948 { 949 // Update the stack map frame offset. 950 visitAnyStackMapFrame(clazz, method, codeAttribute, offset, sameOneFrame); 951 952 // Update the verification type offset. 953 sameOneFrame.stackItemAccept(clazz, method, codeAttribute, offset, this); 954 } 955 956 visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame)957 public void visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame) 958 { 959 // Update the stack map frame offset. 960 visitAnyStackMapFrame(clazz, method, codeAttribute, offset, moreZeroFrame); 961 962 // Update the verification type offsets. 963 moreZeroFrame.additionalVariablesAccept(clazz, method, codeAttribute, offset, this); 964 } 965 966 visitFullFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame fullFrame)967 public void visitFullFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame fullFrame) 968 { 969 // Update the stack map frame offset. 970 visitAnyStackMapFrame(clazz, method, codeAttribute, offset, fullFrame); 971 972 // Update the verification type offsets. 973 fullFrame.variablesAccept(clazz, method, codeAttribute, offset, this); 974 fullFrame.stackAccept(clazz, method, codeAttribute, offset, this); 975 } 976 977 978 // Implementations for VerificationTypeVisitor. 979 visitAnyVerificationType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationType verificationType)980 public void visitAnyVerificationType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationType verificationType) {} 981 982 visitUninitializedType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, UninitializedType uninitializedType)983 public void visitUninitializedType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, UninitializedType uninitializedType) 984 { 985 // Update the offset of the 'new' instruction. 986 uninitializedType.u2newInstructionOffset = newInstructionOffset(uninitializedType.u2newInstructionOffset); 987 } 988 989 990 // Implementations for LineNumberInfoVisitor. 991 visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo)992 public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo) 993 { 994 // Update the code offset. 995 lineNumberInfo.u2startPC = newInstructionOffset(lineNumberInfo.u2startPC); 996 } 997 998 999 // Implementations for LocalVariableInfoVisitor. 1000 visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)1001 public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) 1002 { 1003 // Update the code offset and length. 1004 // Be careful to update the length first. 1005 localVariableInfo.u2length = newBranchOffset(localVariableInfo.u2startPC, localVariableInfo.u2length); 1006 localVariableInfo.u2startPC = newInstructionOffset(localVariableInfo.u2startPC); 1007 } 1008 1009 1010 // Implementations for LocalVariableTypeInfoVisitor. 1011 visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)1012 public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo) 1013 { 1014 // Update the code offset and length. 1015 // Be careful to update the length first. 1016 localVariableTypeInfo.u2length = newBranchOffset(localVariableTypeInfo.u2startPC, localVariableTypeInfo.u2length); 1017 localVariableTypeInfo.u2startPC = newInstructionOffset(localVariableTypeInfo.u2startPC); 1018 } 1019 1020 1021 // Implementations for TypeAnnotationVisitor. 1022 visitTypeAnnotation(Clazz clazz, TypeAnnotation typeAnnotation)1023 public void visitTypeAnnotation(Clazz clazz, TypeAnnotation typeAnnotation) 1024 { 1025 // Update all local variable targets. 1026 typeAnnotation.targetInfoAccept(clazz, this); 1027 } 1028 1029 1030 // Implementations for TargetInfoVisitor. 1031 visitAnyTargetInfo(Clazz clazz, TypeAnnotation typeAnnotation, TargetInfo targetInfo)1032 public void visitAnyTargetInfo(Clazz clazz, TypeAnnotation typeAnnotation, TargetInfo targetInfo) {} 1033 1034 visitLocalVariableTargetInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, TypeAnnotation typeAnnotation, LocalVariableTargetInfo localVariableTargetInfo)1035 public void visitLocalVariableTargetInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, TypeAnnotation typeAnnotation, LocalVariableTargetInfo localVariableTargetInfo) 1036 { 1037 // Update the offsets of the variables. 1038 localVariableTargetInfo.targetElementsAccept(clazz, method, codeAttribute, typeAnnotation, this); 1039 } 1040 1041 visitOffsetTargetInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, TypeAnnotation typeAnnotation, OffsetTargetInfo offsetTargetInfo)1042 public void visitOffsetTargetInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, TypeAnnotation typeAnnotation, OffsetTargetInfo offsetTargetInfo) 1043 { 1044 // Update the offset. 1045 offsetTargetInfo.u2offset = newInstructionOffset(offsetTargetInfo.u2offset); 1046 } 1047 1048 1049 // Implementations for LocalVariableTargetElementVisitor. 1050 visitLocalVariableTargetElement(Clazz clazz, Method method, CodeAttribute codeAttribute, TypeAnnotation typeAnnotation, LocalVariableTargetInfo localVariableTargetInfo, LocalVariableTargetElement localVariableTargetElement)1051 public void visitLocalVariableTargetElement(Clazz clazz, Method method, CodeAttribute codeAttribute, TypeAnnotation typeAnnotation, LocalVariableTargetInfo localVariableTargetInfo, LocalVariableTargetElement localVariableTargetElement) 1052 { 1053 // Update the variable start offset and length. 1054 // Be careful to update the length first. 1055 localVariableTargetElement.u2length = newBranchOffset(localVariableTargetElement.u2startPC, localVariableTargetElement.u2length); 1056 localVariableTargetElement.u2startPC = newInstructionOffset(localVariableTargetElement.u2startPC); 1057 } 1058 1059 1060 // Small utility methods. 1061 1062 /** 1063 * Updates the given jump offsets for the instruction at the given offset, 1064 * relative to the given new offset. 1065 */ newJumpOffsets(int oldInstructionOffset, int[] oldJumpOffsets, int newInstructionOffset)1066 private void newJumpOffsets(int oldInstructionOffset, 1067 int[] oldJumpOffsets, 1068 int newInstructionOffset) 1069 { 1070 for (int index = 0; index < oldJumpOffsets.length; index++) 1071 { 1072 oldJumpOffsets[index] = newBranchOffset(oldInstructionOffset, 1073 oldJumpOffsets[index], 1074 newInstructionOffset); 1075 } 1076 } 1077 1078 1079 /** 1080 * Computes the new branch offset for the instruction at the given offset 1081 * with the given branch offset, relative to the new instruction (block) 1082 * offset. 1083 */ newBranchOffset(int oldInstructionOffset, int oldBranchOffset)1084 private int newBranchOffset(int oldInstructionOffset, 1085 int oldBranchOffset) 1086 { 1087 return newInstructionOffset(oldInstructionOffset + oldBranchOffset) - 1088 newInstructionOffset(oldInstructionOffset); 1089 } 1090 1091 1092 /** 1093 * Computes the new branch offset for the instruction at the given offset 1094 * with the given branch offset, relative to the given new offset. 1095 */ newBranchOffset(int oldInstructionOffset, int oldBranchOffset, int newInstructionOffset)1096 private int newBranchOffset(int oldInstructionOffset, 1097 int oldBranchOffset, 1098 int newInstructionOffset) 1099 { 1100 return newInstructionOffset(oldInstructionOffset + oldBranchOffset) - 1101 newInstructionOffset; 1102 } 1103 1104 1105 /** 1106 * Computes the new instruction offset for the instruction at the given 1107 * offset. 1108 */ newInstructionOffset(int oldInstructionOffset)1109 private int newInstructionOffset(int oldInstructionOffset) 1110 { 1111 if (oldInstructionOffset < 0 || 1112 oldInstructionOffset > codeLength) 1113 { 1114 throw new IllegalArgumentException("Invalid instruction offset ["+oldInstructionOffset+"] in code with length ["+codeLength+"]"); 1115 } 1116 1117 return newInstructionOffsets[oldInstructionOffset]; 1118 } 1119 1120 1121 /** 1122 * Returns the given list of exceptions, without the ones that have empty 1123 * code blocks. 1124 */ removeEmptyExceptions(ExceptionInfo[] exceptionInfos, int exceptionInfoCount)1125 private int removeEmptyExceptions(ExceptionInfo[] exceptionInfos, 1126 int exceptionInfoCount) 1127 { 1128 // Overwrite all empty exceptions. 1129 int newIndex = 0; 1130 for (int index = 0; index < exceptionInfoCount; index++) 1131 { 1132 ExceptionInfo exceptionInfo = exceptionInfos[index]; 1133 if (exceptionInfo.u2startPC < exceptionInfo.u2endPC) 1134 { 1135 exceptionInfos[newIndex++] = exceptionInfo; 1136 } 1137 } 1138 1139 return newIndex; 1140 } 1141 1142 1143 /** 1144 * Returns the given list of line numbers, without the ones that have empty 1145 * code blocks or that exceed the code size. 1146 */ removeEmptyLineNumbers(LineNumberInfo[] lineNumberInfos, int lineNumberInfoCount, int codeLength)1147 private int removeEmptyLineNumbers(LineNumberInfo[] lineNumberInfos, 1148 int lineNumberInfoCount, 1149 int codeLength) 1150 { 1151 // Overwrite all empty line number entries. 1152 int newIndex = 0; 1153 for (int index = 0; index < lineNumberInfoCount; index++) 1154 { 1155 LineNumberInfo lineNumberInfo = lineNumberInfos[index]; 1156 int startPC = lineNumberInfo.u2startPC; 1157 if (startPC < codeLength && 1158 (index == 0 || startPC > lineNumberInfos[index-1].u2startPC)) 1159 { 1160 lineNumberInfos[newIndex++] = lineNumberInfo; 1161 } 1162 } 1163 1164 return newIndex; 1165 } 1166 1167 1168 /** 1169 * This instruction is a composite of other instructions, for local use 1170 * inside the editor class only. 1171 */ 1172 private class CompositeInstruction 1173 extends Instruction 1174 { 1175 private Instruction[] instructions; 1176 1177 CompositeInstruction(Instruction[] instructions)1178 private CompositeInstruction(Instruction[] instructions) 1179 { 1180 this.instructions = instructions; 1181 } 1182 1183 1184 // Implementations for Instruction. 1185 shrink()1186 public Instruction shrink() 1187 { 1188 for (int index = 0; index < instructions.length; index++) 1189 { 1190 instructions[index] = instructions[index].shrink(); 1191 } 1192 1193 return this; 1194 } 1195 1196 write(byte[] code, int offset)1197 public void write(byte[] code, int offset) 1198 { 1199 for (int index = 0; index < instructions.length; index++) 1200 { 1201 Instruction instruction = instructions[index]; 1202 1203 instruction.write(code, offset); 1204 1205 offset += instruction.length(offset); 1206 } 1207 } 1208 1209 readInfo(byte[] code, int offset)1210 protected void readInfo(byte[] code, int offset) 1211 { 1212 throw new UnsupportedOperationException("Can't read composite instruction"); 1213 } 1214 1215 writeInfo(byte[] code, int offset)1216 protected void writeInfo(byte[] code, int offset) 1217 { 1218 throw new UnsupportedOperationException("Can't write composite instruction"); 1219 } 1220 1221 length(int offset)1222 public int length(int offset) 1223 { 1224 int newOffset = offset; 1225 1226 for (int index = 0; index < instructions.length; index++) 1227 { 1228 newOffset += instructions[index].length(newOffset); 1229 } 1230 1231 return newOffset - offset; 1232 } 1233 1234 accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, InstructionVisitor instructionVisitor)1235 public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, InstructionVisitor instructionVisitor) 1236 { 1237 if (instructionVisitor != CodeAttributeEditor.this) 1238 { 1239 throw new UnsupportedOperationException("Unexpected visitor ["+instructionVisitor+"]"); 1240 } 1241 1242 for (int index = 0; index < instructions.length; index++) 1243 { 1244 Instruction instruction = instructions[index]; 1245 1246 instruction.accept(clazz, method, codeAttribute, offset, CodeAttributeEditor.this); 1247 1248 offset += instruction.length(offset); 1249 } 1250 } 1251 1252 1253 // Implementations for Object. 1254 toString()1255 public String toString() 1256 { 1257 StringBuffer stringBuffer = new StringBuffer(); 1258 1259 for (int index = 0; index < instructions.length; index++) 1260 { 1261 stringBuffer.append(instructions[index].toString()).append("; "); 1262 } 1263 1264 return stringBuffer.toString(); 1265 } 1266 } 1267 } 1268