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