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