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 */
21package proguard.classfile.editor;
22
23import proguard.classfile.*;
24import proguard.classfile.attribute.*;
25import proguard.classfile.attribute.preverification.*;
26import proguard.classfile.attribute.preverification.visitor.*;
27import proguard.classfile.attribute.visitor.*;
28import proguard.classfile.instruction.*;
29import proguard.classfile.instruction.visitor.InstructionVisitor;
30import proguard.classfile.util.SimplifiedVisitor;
31import proguard.util.ArrayUtil;
32
33import 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 */
41public class CodeAttributeEditor
42extends      SimplifiedVisitor
43implements   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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
387    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
388
389
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
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
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
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
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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
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
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
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
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
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
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
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
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
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
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
967    public void visitAnyVerificationType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationType verificationType) {}
968
969
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
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
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
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     */
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     */
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     */
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     */
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     */
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
1110        private CompositeInstruction(Instruction[] instructions)
1111        {
1112            this.instructions = instructions;
1113        }
1114
1115
1116        // Implementations for Instruction.
1117
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
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
1142        protected void readInfo(byte[] code, int offset)
1143        {
1144            throw new UnsupportedOperationException("Can't read composite instruction");
1145        }
1146
1147
1148        protected void writeInfo(byte[] code, int offset)
1149        {
1150            throw new UnsupportedOperationException("Can't write composite instruction");
1151        }
1152
1153
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
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
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