• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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