CodeAttributeComposer.java revision cfead78069f3dc32998dc118ee08cab3867acea2
1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2011 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;
31
32import java.util.Arrays;
33
34/**
35 * This AttributeVisitor accumulates instructions and exceptions, and then
36 * copies them into code attributes that it visits.
37 *
38 * @author Eric Lafortune
39 */
40public class CodeAttributeComposer
41extends      SimplifiedVisitor
42implements   AttributeVisitor,
43             InstructionVisitor,
44             ExceptionInfoVisitor,
45             StackMapFrameVisitor,
46             VerificationTypeVisitor,
47             LineNumberInfoVisitor,
48             LocalVariableInfoVisitor,
49             LocalVariableTypeInfoVisitor
50{
51    //*
52    private static final boolean DEBUG = false;
53    /*/
54    public  static       boolean DEBUG = false;
55    //*/
56
57
58    private static final int MAXIMUM_LEVELS = 32;
59    private static final int INVALID        = -1;
60
61
62    private boolean allowExternalExceptionHandlers;
63
64    private int maximumCodeLength;
65    private int codeLength;
66    private int exceptionTableLength;
67    private int level = -1;
68
69    private byte[]  code                  = new byte[ClassConstants.TYPICAL_CODE_LENGTH];
70    private int[]   oldInstructionOffsets = new int[ClassConstants.TYPICAL_CODE_LENGTH];
71
72    private final int[]   codeFragmentOffsets  = new int[MAXIMUM_LEVELS];
73    private final int[]   codeFragmentLengths  = new int[MAXIMUM_LEVELS];
74    private final int[][] instructionOffsetMap = new int[MAXIMUM_LEVELS][ClassConstants.TYPICAL_CODE_LENGTH + 1];
75
76    private ExceptionInfo[] exceptionTable = new ExceptionInfo[ClassConstants.TYPICAL_EXCEPTION_TABLE_LENGTH];
77
78    private int expectedStackMapFrameOffset;
79
80    private final StackSizeUpdater    stackSizeUpdater    = new StackSizeUpdater();
81    private final VariableSizeUpdater variableSizeUpdater = new VariableSizeUpdater();
82//    private final InstructionWriter   instructionWriter   = new InstructionWriter();
83
84
85    /**
86     * Creates a new CodeAttributeComposer that doesn't allow external exception
87     * handlers.
88     */
89    public CodeAttributeComposer()
90    {
91        this(false);
92    }
93
94
95    /**
96     * Creates a new CodeAttributeComposer that optionally allows external
97     * exception handlers.
98     */
99    public CodeAttributeComposer(boolean allowExternalExceptionHandlers)
100    {
101        this.allowExternalExceptionHandlers = allowExternalExceptionHandlers;
102    }
103
104
105    /**
106     * Starts a new code definition.
107     */
108    public void reset()
109    {
110        maximumCodeLength    = 0;
111        codeLength           = 0;
112        exceptionTableLength = 0;
113        level                = -1;
114    }
115
116
117    /**
118     * Starts a new code fragment. Branch instructions that are added are
119     * assumed to be relative within such code fragments.
120     * @param maximumCodeFragmentLength the maximum length of the code that will
121     *                                  be added as part of this fragment.
122     */
123    public void beginCodeFragment(int maximumCodeFragmentLength)
124    {
125        level++;
126
127        if (level >= MAXIMUM_LEVELS)
128        {
129            throw new IllegalArgumentException("Maximum number of code fragment levels exceeded ["+level+"]");
130        }
131
132//        // TODO: Figure out some length.
133//        if (level == 0)
134//        {
135//            // Prepare for possible widening of instructions.
136//            instructionWriter.reset(2 * maximumCodeFragmentLength);
137//        }
138
139        // Make sure there is sufficient space for adding the code fragment.
140        maximumCodeLength += maximumCodeFragmentLength;
141
142        ensureCodeLength(maximumCodeLength);
143
144        // Try to reuse the previous array for this code fragment.
145        if (instructionOffsetMap[level].length <= maximumCodeFragmentLength)
146        {
147            instructionOffsetMap[level] = new int[maximumCodeFragmentLength + 1];
148        }
149
150        // Initialize the offset map.
151        for (int index = 0; index <= maximumCodeFragmentLength; index++)
152        {
153            instructionOffsetMap[level][index] = INVALID;
154        }
155
156        // Remember the location of the code fragment.
157        codeFragmentOffsets[level] = codeLength;
158        codeFragmentLengths[level] = maximumCodeFragmentLength;
159    }
160
161
162    /**
163     * Appends the given instruction with the given old offset.
164     * @param oldInstructionOffset the old offset of the instruction, to which
165     *                             branches and other references in the current
166     *                             code fragment are pointing.
167     * @param instruction          the instruction to be appended.
168     */
169    public void appendInstruction(int         oldInstructionOffset,
170                                  Instruction instruction)
171    {
172        if (DEBUG)
173        {
174            println("["+codeLength+"] <- ", instruction.toString(oldInstructionOffset));
175        }
176
177        // Make sure the code array is large enough.
178        int newCodeLength = codeLength + instruction.length(codeLength);
179
180        ensureCodeLength(newCodeLength);
181
182        // Remember the old offset of the appended instruction.
183        oldInstructionOffsets[codeLength] = oldInstructionOffset;
184
185        // Write the instruction.
186//        instruction.accept(null,
187//                           null,
188//                           new CodeAttribute(0, 0, 0, 0, code, 0, null, 0, null),
189//                           codeLength,
190//                           instructionWriter);
191        instruction.write(code, codeLength);
192
193        // Fill out the new offset of the appended instruction.
194        instructionOffsetMap[level][oldInstructionOffset] = codeLength;
195
196        // Continue appending at the next instruction offset.
197        codeLength = newCodeLength;
198    }
199
200
201    /**
202     * Appends the given label with the given old offset.
203     * @param oldInstructionOffset the old offset of the label, to which
204     *                             branches and other references in the current
205     *                             code fragment are pointing.
206     */
207    public void appendLabel(int oldInstructionOffset)
208    {
209        if (DEBUG)
210        {
211            println("["+codeLength+"] <- ", "[" + oldInstructionOffset + "] (label)");
212        }
213
214        // Fill out the new offset of the appended instruction.
215        instructionOffsetMap[level][oldInstructionOffset] = codeLength;
216    }
217
218
219    /**
220     * Appends the given exception to the exception table.
221     * @param exceptionInfo the exception to be appended.
222     */
223    public void appendException(ExceptionInfo exceptionInfo)
224    {
225        if (DEBUG)
226        {
227            print("         ", "Exception ["+exceptionInfo.u2startPC+" -> "+exceptionInfo.u2endPC+": "+exceptionInfo.u2handlerPC+"]");
228        }
229
230        // Remap the exception right away.
231        visitExceptionInfo(null, null, null, exceptionInfo);
232
233        if (DEBUG)
234        {
235            System.out.println(" -> ["+exceptionInfo.u2startPC+" -> "+exceptionInfo.u2endPC+": "+exceptionInfo.u2handlerPC+"]");
236        }
237
238        // Don't add the exception if its instruction range is empty.
239        if (exceptionInfo.u2startPC == exceptionInfo.u2endPC)
240        {
241            if (DEBUG)
242            {
243                println("         ", "  (not added because of empty instruction range)");
244            }
245
246            return;
247        }
248
249        // Make sure there is sufficient space in the exception table.
250        if (exceptionTable.length <= exceptionTableLength)
251        {
252            ExceptionInfo[] newExceptionTable = new ExceptionInfo[exceptionTableLength+1];
253            System.arraycopy(exceptionTable, 0, newExceptionTable, 0, exceptionTableLength);
254            exceptionTable = newExceptionTable;
255        }
256
257        // Add the exception.
258        exceptionTable[exceptionTableLength++] = exceptionInfo;
259    }
260
261
262    /**
263     * Wraps up the current code fragment, continuing with the previous one on
264     * the stack.
265     */
266    public void endCodeFragment()
267    {
268        if (level < 0)
269        {
270            throw new IllegalArgumentException("Code fragment not begun ["+level+"]");
271        }
272
273        // Remap the instructions of the code fragment.
274        int instructionOffset = codeFragmentOffsets[level];
275        while (instructionOffset < codeLength)
276        {
277            // Get the next instruction.
278            Instruction instruction = InstructionFactory.create(code, instructionOffset);
279
280            // Does this instruction still have to be remapped?
281            if (oldInstructionOffsets[instructionOffset] >= 0)
282            {
283                // Adapt the instruction for its new offset.
284                instruction.accept(null, null, null, instructionOffset, this);
285
286                // Write the instruction back.
287//                instruction.accept(null,
288//                                   null,
289//                                   new CodeAttribute(0, 0, 0, 0, code, 0, null, 0, null),
290//                                   instructionOffset,
291//                                   instructionWriter);
292                instruction.write(code, instructionOffset);
293
294                // Don't remap this instruction again.
295                oldInstructionOffsets[instructionOffset] = -1;
296            }
297
298            // Continue remapping at the next instruction offset.
299            instructionOffset += instruction.length(instructionOffset);
300        }
301
302        // Correct the estimated maximum code length, now that we know the
303        // actual length of this code fragment.
304        maximumCodeLength += codeLength - codeFragmentOffsets[level] -
305                             codeFragmentLengths[level];
306
307        // Try to remap the exception handlers that couldn't be remapped before.
308        if (allowExternalExceptionHandlers)
309        {
310            for (int index = 0; index < exceptionTableLength; index++)
311            {
312                ExceptionInfo exceptionInfo = exceptionTable[index];
313
314                // Unmapped exception handlers are still negated.
315                int handlerPC = -exceptionInfo.u2handlerPC;
316                if (handlerPC > 0)
317                {
318                    if (remappableExceptionHandler(handlerPC))
319                    {
320                        exceptionInfo.u2handlerPC = remapInstructionOffset(handlerPC);
321                    }
322                    else if (level == 0)
323                    {
324                        throw new IllegalStateException("Couldn't remap exception handler offset ["+handlerPC+"]");
325                    }
326                }
327            }
328        }
329
330        level--;
331    }
332
333
334    // Implementations for AttributeVisitor.
335
336    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
337
338
339    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
340    {
341        if (DEBUG)
342        {
343            System.out.println("CodeAttributeComposer: putting results in ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"]");
344        }
345
346        if (level != -1)
347        {
348            throw new IllegalArgumentException("Code fragment not ended ["+level+"]");
349        }
350
351        level++;
352
353        // Make sure the code attribute has sufficient space for the composed
354        // code.
355        if (codeAttribute.u4codeLength < codeLength)
356        {
357            codeAttribute.code = new byte[codeLength];
358        }
359
360        // Copy the composed code over into the code attribute.
361        codeAttribute.u4codeLength = codeLength;
362        System.arraycopy(code, 0, codeAttribute.code, 0, codeLength);
363
364        // Remove exceptions with empty code blocks (done before).
365        //exceptionTableLength =
366        //    removeEmptyExceptions(exceptionTable, exceptionTableLength);
367
368        // Make sure the exception table has sufficient space for the composed
369        // exceptions.
370        if (codeAttribute.exceptionTable.length < exceptionTableLength)
371        {
372            codeAttribute.exceptionTable = new ExceptionInfo[exceptionTableLength];
373        }
374
375        // Copy the exception table.
376        codeAttribute.u2exceptionTableLength = exceptionTableLength;
377        System.arraycopy(exceptionTable, 0, codeAttribute.exceptionTable, 0, exceptionTableLength);
378
379        // Update the maximum stack size and local variable frame size.
380        stackSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute);
381        variableSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute);
382
383        // Remap  the line number table and the local variable table.
384        codeAttribute.attributesAccept(clazz, method, this);
385
386        // Remap the exception table.
387        //codeAttribute.exceptionsAccept(clazz, method, this);
388
389        // Remove exceptions with empty code blocks (done before).
390        //codeAttribute.u2exceptionTableLength =
391        //    removeEmptyExceptions(codeAttribute.exceptionTable,
392        //                          codeAttribute.u2exceptionTableLength);
393
394//        // Make sure instructions are widened if necessary.
395//        instructionWriter.visitCodeAttribute(clazz, method, codeAttribute);
396
397        level--;
398    }
399
400
401    public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute)
402    {
403        // Remap all stack map entries.
404        expectedStackMapFrameOffset = -1;
405        stackMapAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this);
406    }
407
408
409    public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute)
410    {
411        // Remap all stack map table entries.
412        expectedStackMapFrameOffset = 0;
413        stackMapTableAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this);
414    }
415
416
417    public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
418    {
419        // Remap all line number table entries.
420        lineNumberTableAttribute.lineNumbersAccept(clazz, method, codeAttribute, this);
421
422        // Remove line numbers with empty code blocks.
423        lineNumberTableAttribute.u2lineNumberTableLength =
424           removeEmptyLineNumbers(lineNumberTableAttribute.lineNumberTable,
425                                  lineNumberTableAttribute.u2lineNumberTableLength,
426                                  codeAttribute.u4codeLength);
427    }
428
429
430    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
431    {
432        // Remap all local variable table entries.
433        localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
434
435        // Remove local variables with empty code blocks.
436        localVariableTableAttribute.u2localVariableTableLength =
437            removeEmptyLocalVariables(localVariableTableAttribute.localVariableTable,
438                                      localVariableTableAttribute.u2localVariableTableLength,
439                                      codeAttribute.u2maxLocals);
440    }
441
442
443    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
444    {
445        // Remap all local variable table entries.
446        localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
447
448        // Remove local variables with empty code blocks.
449        localVariableTypeTableAttribute.u2localVariableTypeTableLength =
450            removeEmptyLocalVariableTypes(localVariableTypeTableAttribute.localVariableTypeTable,
451                                          localVariableTypeTableAttribute.u2localVariableTypeTableLength,
452                                          codeAttribute.u2maxLocals);
453    }
454
455
456    // Implementations for InstructionVisitor.
457
458    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
459
460
461    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
462    {
463        // Adjust the branch offset.
464        branchInstruction.branchOffset = remapBranchOffset(offset,
465                                                           branchInstruction.branchOffset);
466    }
467
468
469    public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction)
470    {
471        // Adjust the default jump offset.
472        switchInstruction.defaultOffset = remapBranchOffset(offset,
473                                                            switchInstruction.defaultOffset);
474
475        // Adjust the jump offsets.
476        remapJumpOffsets(offset,
477                         switchInstruction.jumpOffsets);
478    }
479
480
481    // Implementations for ExceptionInfoVisitor.
482
483    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
484    {
485        // Remap the code offsets. Note that the instruction offset map also has
486        // an entry for the first offset after the code, for u2endPC.
487        exceptionInfo.u2startPC = remapInstructionOffset(exceptionInfo.u2startPC);
488        exceptionInfo.u2endPC   = remapInstructionOffset(exceptionInfo.u2endPC);
489
490        // See if we can remap the handler right away. Unmapped exception
491        // handlers are negated, in order to mark them as external.
492        int handlerPC = exceptionInfo.u2handlerPC;
493        exceptionInfo.u2handlerPC =
494            !allowExternalExceptionHandlers ||
495            remappableExceptionHandler(handlerPC) ?
496                remapInstructionOffset(handlerPC) :
497                -handlerPC;
498    }
499
500
501    // Implementations for StackMapFrameVisitor.
502
503    public void visitAnyStackMapFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrame stackMapFrame)
504    {
505        // Remap the stack map frame offset.
506        int stackMapFrameOffset = remapInstructionOffset(offset);
507
508        int offsetDelta = stackMapFrameOffset;
509
510        // Compute the offset delta if the frame is part of a stack map frame
511        // table (for JDK 6.0) instead of a stack map (for Java Micro Edition).
512        if (expectedStackMapFrameOffset >= 0)
513        {
514            offsetDelta -= expectedStackMapFrameOffset;
515
516            expectedStackMapFrameOffset = stackMapFrameOffset + 1;
517        }
518
519        stackMapFrame.u2offsetDelta = offsetDelta;
520    }
521
522
523    public void visitSameOneFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameOneFrame sameOneFrame)
524    {
525        // Remap the stack map frame offset.
526        visitAnyStackMapFrame(clazz, method, codeAttribute, offset, sameOneFrame);
527
528        // Remap the verification type offset.
529        sameOneFrame.stackItemAccept(clazz, method, codeAttribute, offset, this);
530    }
531
532
533    public void visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame)
534    {
535        // Remap the stack map frame offset.
536        visitAnyStackMapFrame(clazz, method, codeAttribute, offset, moreZeroFrame);
537
538        // Remap the verification type offsets.
539        moreZeroFrame.additionalVariablesAccept(clazz, method, codeAttribute, offset, this);
540    }
541
542
543    public void visitFullFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame fullFrame)
544    {
545        // Remap the stack map frame offset.
546        visitAnyStackMapFrame(clazz, method, codeAttribute, offset, fullFrame);
547
548        // Remap the verification type offsets.
549        fullFrame.variablesAccept(clazz, method, codeAttribute, offset, this);
550        fullFrame.stackAccept(clazz, method, codeAttribute, offset, this);
551    }
552
553
554    // Implementations for VerificationTypeVisitor.
555
556    public void visitAnyVerificationType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationType verificationType) {}
557
558
559    public void visitUninitializedType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, UninitializedType uninitializedType)
560    {
561        // Remap the offset of the 'new' instruction.
562        uninitializedType.u2newInstructionOffset = remapInstructionOffset(uninitializedType.u2newInstructionOffset);
563    }
564
565
566    // Implementations for LineNumberInfoVisitor.
567
568    public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo)
569    {
570        // Remap the code offset.
571        lineNumberInfo.u2startPC = remapInstructionOffset(lineNumberInfo.u2startPC);
572    }
573
574
575    // Implementations for LocalVariableInfoVisitor.
576
577    public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
578    {
579        // Remap the code offset and length.
580        // TODO: The local variable frame might not be strictly preserved.
581        int startPC = remapInstructionOffset(localVariableInfo.u2startPC);
582        int endPC   = remapInstructionOffset(localVariableInfo.u2startPC + localVariableInfo.u2length);
583
584        localVariableInfo.u2startPC = startPC;
585        localVariableInfo.u2length  = endPC - startPC;
586    }
587
588    // Implementations for LocalVariableTypeInfoVisitor.
589
590    public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
591    {
592        // Remap the code offset and length.
593        // TODO: The local variable frame might not be strictly preserved.
594        int startPC = remapInstructionOffset(localVariableTypeInfo.u2startPC);
595        int endPC   = remapInstructionOffset(localVariableTypeInfo.u2startPC + localVariableTypeInfo.u2length);
596
597        localVariableTypeInfo.u2startPC = startPC;
598        localVariableTypeInfo.u2length  = endPC - startPC;
599    }
600
601
602    // Small utility methods.
603
604    /**
605     * Make sure the code arrays have at least the given size.
606     */
607    private void ensureCodeLength(int newCodeLength)
608    {
609        if (code.length < newCodeLength)
610        {
611            // Add 20% to avoid extending the arrays too often.
612            newCodeLength = newCodeLength * 6 / 5;
613
614            byte[] newCode = new byte[newCodeLength];
615            System.arraycopy(code, 0, newCode, 0, codeLength);
616            code = newCode;
617
618            int[] newOldInstructionOffsets = new int[newCodeLength];
619            System.arraycopy(oldInstructionOffsets, 0, newOldInstructionOffsets, 0, codeLength);
620            oldInstructionOffsets = newOldInstructionOffsets;
621        }
622    }
623
624
625    /**
626     * Adjusts the given jump offsets for the instruction at the given offset.
627     */
628    private void remapJumpOffsets(int offset, int[] jumpOffsets)
629    {
630        for (int index = 0; index < jumpOffsets.length; index++)
631        {
632            jumpOffsets[index] = remapBranchOffset(offset, jumpOffsets[index]);
633        }
634    }
635
636
637    /**
638     * Computes the new branch offset for the instruction at the given new offset
639     * with the given old branch offset.
640     */
641    private int remapBranchOffset(int newInstructionOffset, int branchOffset)
642    {
643        if (newInstructionOffset < 0 ||
644            newInstructionOffset > codeLength)
645        {
646            throw new IllegalArgumentException("Invalid instruction offset ["+newInstructionOffset +"] in code with length ["+codeLength+"]");
647        }
648
649        int oldInstructionOffset = oldInstructionOffsets[newInstructionOffset];
650
651        return remapInstructionOffset(oldInstructionOffset + branchOffset) -
652               remapInstructionOffset(oldInstructionOffset);
653    }
654
655
656    /**
657     * Computes the new instruction offset for the instruction at the given old
658     * offset.
659     */
660    private int remapInstructionOffset(int oldInstructionOffset)
661    {
662        if (oldInstructionOffset < 0 ||
663            oldInstructionOffset > codeFragmentLengths[level])
664        {
665            throw new IllegalArgumentException("Instruction offset ["+oldInstructionOffset +"] out of range in code fragment with length ["+codeFragmentLengths[level]+"] at level "+level);
666        }
667
668        int newInstructionOffset = instructionOffsetMap[level][oldInstructionOffset];
669        if (newInstructionOffset == INVALID)
670        {
671            throw new IllegalArgumentException("Invalid instruction offset ["+oldInstructionOffset +"] in code fragment at level "+level);
672        }
673
674        return newInstructionOffset;
675    }
676
677
678    /**
679     * Returns whether the given old exception handler can be remapped in the
680     * current code fragment.
681     */
682    private boolean remappableExceptionHandler(int oldInstructionOffset)
683    {
684        if (oldInstructionOffset > codeFragmentLengths[level])
685        {
686            return false;
687        }
688
689        int newInstructionOffset =
690            instructionOffsetMap[level][oldInstructionOffset];
691
692        return newInstructionOffset > INVALID &&
693               newInstructionOffset < codeLength;
694    }
695
696
697    /**
698     * Returns the given list of exceptions, without the ones that have empty
699     * code blocks.
700     */
701    private int removeEmptyExceptions(ExceptionInfo[] exceptionInfos,
702                                      int             exceptionInfoCount)
703    {
704        // Overwrite all empty exceptions.
705        int newIndex = 0;
706        for (int index = 0; index < exceptionInfoCount; index++)
707        {
708            ExceptionInfo exceptionInfo = exceptionInfos[index];
709            if (exceptionInfo.u2startPC < exceptionInfo.u2endPC)
710            {
711                exceptionInfos[newIndex++] = exceptionInfo;
712            }
713        }
714
715        // Clear the unused array entries.
716        Arrays.fill(exceptionInfos, newIndex, exceptionInfoCount, null);
717
718        return newIndex;
719    }
720
721
722    /**
723     * Returns the given list of line numbers, without the ones that have empty
724     * code blocks or that exceed the code size.
725     */
726    private int removeEmptyLineNumbers(LineNumberInfo[] lineNumberInfos,
727                                       int              lineNumberInfoCount,
728                                       int              codeLength)
729    {
730        // Overwrite all empty line number entries.
731        int newIndex = 0;
732        for (int index = 0; index < lineNumberInfoCount; index++)
733        {
734            LineNumberInfo lineNumberInfo = lineNumberInfos[index];
735            int startPC = lineNumberInfo.u2startPC;
736            if (startPC < codeLength &&
737                (index == 0 || startPC > lineNumberInfos[index-1].u2startPC))
738            {
739                lineNumberInfos[newIndex++] = lineNumberInfo;
740            }
741        }
742
743        // Clear the unused array entries.
744        Arrays.fill(lineNumberInfos, newIndex, lineNumberInfoCount, null);
745
746        return newIndex;
747    }
748
749
750    /**
751     * Returns the given list of local variables, without the ones that have empty
752     * code blocks or that exceed the actual number of local variables.
753     */
754    private int removeEmptyLocalVariables(LocalVariableInfo[] localVariableInfos,
755                                          int                 localVariableInfoCount,
756                                          int                 maxLocals)
757    {
758        // Overwrite all empty local variable entries.
759        int newIndex = 0;
760        for (int index = 0; index < localVariableInfoCount; index++)
761        {
762            LocalVariableInfo localVariableInfo = localVariableInfos[index];
763            if (localVariableInfo.u2length > 0 &&
764                localVariableInfo.u2index < maxLocals)
765            {
766                localVariableInfos[newIndex++] = localVariableInfo;
767            }
768        }
769
770        // Clear the unused array entries.
771        Arrays.fill(localVariableInfos, newIndex, localVariableInfoCount, null);
772
773        return newIndex;
774    }
775
776
777    /**
778     * Returns the given list of local variable types, without the ones that
779     * have empty code blocks or that exceed the actual number of local variables.
780     */
781    private int removeEmptyLocalVariableTypes(LocalVariableTypeInfo[] localVariableTypeInfos,
782                                              int                     localVariableTypeInfoCount,
783                                              int                     maxLocals)
784    {
785        // Overwrite all empty local variable type entries.
786        int newIndex = 0;
787        for (int index = 0; index < localVariableTypeInfoCount; index++)
788        {
789            LocalVariableTypeInfo localVariableTypeInfo = localVariableTypeInfos[index];
790            if (localVariableTypeInfo.u2length > 0 &&
791                localVariableTypeInfo.u2index < maxLocals)
792            {
793                localVariableTypeInfos[newIndex++] = localVariableTypeInfo;
794            }
795        }
796
797        // Clear the unused array entries.
798        Arrays.fill(localVariableTypeInfos, newIndex, localVariableTypeInfoCount, null);
799
800        return newIndex;
801    }
802
803
804    private void println(String string1, String string2)
805    {
806        print(string1, string2);
807
808        System.out.println();
809    }
810
811    private void print(String string1, String string2)
812    {
813        System.out.print(string1);
814
815        for (int index = 0; index < level; index++)
816        {
817            System.out.print("  ");
818        }
819
820        System.out.print(string2);
821    }
822
823
824    public static void main(String[] args)
825    {
826        CodeAttributeComposer composer = new CodeAttributeComposer();
827
828        composer.beginCodeFragment(4);
829        composer.appendInstruction(0, new SimpleInstruction(InstructionConstants.OP_ICONST_0));
830        composer.appendInstruction(1, new VariableInstruction(InstructionConstants.OP_ISTORE, 0));
831        composer.appendInstruction(2, new BranchInstruction(InstructionConstants.OP_GOTO, 1));
832
833        composer.beginCodeFragment(4);
834        composer.appendInstruction(0, new VariableInstruction(InstructionConstants.OP_IINC, 0, 1));
835        composer.appendInstruction(1, new VariableInstruction(InstructionConstants.OP_ILOAD, 0));
836        composer.appendInstruction(2, new SimpleInstruction(InstructionConstants.OP_ICONST_5));
837        composer.appendInstruction(3, new BranchInstruction(InstructionConstants.OP_IFICMPLT, -3));
838        composer.endCodeFragment();
839
840        composer.appendInstruction(3, new SimpleInstruction(InstructionConstants.OP_RETURN));
841        composer.endCodeFragment();
842    }
843}
844