1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2014 Eric Lafortune (eric@graphics.cornell.edu)
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21package proguard.optimize.peephole;
22
23import proguard.classfile.*;
24import proguard.classfile.attribute.*;
25import proguard.classfile.attribute.visitor.*;
26import proguard.classfile.constant.*;
27import proguard.classfile.constant.visitor.ConstantVisitor;
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 finds all instruction offsets, branch targets, and
36 * exception targets in the CodeAttribute objects that it visits.
37 *
38 * @author Eric Lafortune
39 */
40public class BranchTargetFinder
41extends      SimplifiedVisitor
42implements   AttributeVisitor,
43             InstructionVisitor,
44             ExceptionInfoVisitor,
45             ConstantVisitor
46{
47    //*
48    private static final boolean DEBUG = false;
49    /*/
50    private static       boolean DEBUG = System.getProperty("btf") != null;
51    //*/
52
53    public static final int NONE = -1;
54
55    // We'll explicitly mark instructions that are not part of a subroutine,
56    // with NO_SUBROUTINE. Subroutines may just branch back into normal code
57    // (e.g. due to a break instruction in Java code), and we want to avoid
58    // marking such normal code as subroutine. The first mark wins, so we're
59    // assuming that such code is marked as normal code before it is marked
60    // as subroutine.
61    public static final int UNKNOWN       = -1;
62    public static final int NO_SUBROUTINE = -2;
63
64    private static final short INSTRUCTION           = 1 << 0;
65    private static final short BRANCH_ORIGIN         = 1 << 1;
66    private static final short BRANCH_TARGET         = 1 << 2;
67    private static final short AFTER_BRANCH          = 1 << 3;
68    private static final short EXCEPTION_START       = 1 << 4;
69    private static final short EXCEPTION_END         = 1 << 5;
70    private static final short EXCEPTION_HANDLER     = 1 << 6;
71    private static final short SUBROUTINE_INVOCATION = 1 << 7;
72    private static final short SUBROUTINE_RETURNING  = 1 << 8;
73
74    private static final int MAXIMUM_CREATION_OFFSETS = 32;
75
76
77    private short[] instructionMarks      = new short[ClassConstants.TYPICAL_CODE_LENGTH + 1];
78    private int[]   subroutineStarts      = new int[ClassConstants.TYPICAL_CODE_LENGTH];
79    private int[]   subroutineEnds        = new int[ClassConstants.TYPICAL_CODE_LENGTH];
80    private int[]   creationOffsets       = new int[ClassConstants.TYPICAL_CODE_LENGTH];
81    private int[]   initializationOffsets = new int[ClassConstants.TYPICAL_CODE_LENGTH];
82    private int     superInitializationOffset;
83    private boolean containsSubroutines;
84
85    private boolean repeat;
86    private int     currentSubroutineStart;
87    private int[]   recentCreationOffsets = new int[MAXIMUM_CREATION_OFFSETS];
88    private int     recentCreationOffsetIndex;
89    private boolean isInitializer;
90
91
92    /**
93     * Returns whether there is an instruction at the given offset in the
94     * CodeAttribute that was visited most recently.
95     */
96    public boolean isInstruction(int offset)
97    {
98        return (instructionMarks[offset] & INSTRUCTION) != 0;
99    }
100
101
102    /**
103     * Returns whether the instruction at the given offset is the target of
104     * any kind in the CodeAttribute that was visited most recently.
105     */
106    public boolean isTarget(int offset)
107    {
108        return offset == 0 ||
109               (instructionMarks[offset] & (BRANCH_TARGET   |
110                                            EXCEPTION_START |
111                                            EXCEPTION_END   |
112                                            EXCEPTION_HANDLER)) != 0;
113    }
114
115
116    /**
117     * Returns whether the instruction at the given offset is the origin of a
118     * branch instruction in the CodeAttribute that was visited most recently.
119     */
120    public boolean isBranchOrigin(int offset)
121    {
122        return (instructionMarks[offset] & BRANCH_ORIGIN) != 0;
123    }
124
125
126    /**
127     * Returns whether the instruction at the given offset is the target of a
128     * branch instruction in the CodeAttribute that was visited most recently.
129     */
130    public boolean isBranchTarget(int offset)
131    {
132        return (instructionMarks[offset] & BRANCH_TARGET) != 0;
133    }
134
135
136    /**
137     * Returns whether the instruction at the given offset comes right after a
138     * definite branch instruction in the CodeAttribute that was visited most
139     * recently.
140     */
141    public boolean isAfterBranch(int offset)
142    {
143        return (instructionMarks[offset] & AFTER_BRANCH) != 0;
144    }
145
146
147    /**
148     * Returns whether the instruction at the given offset is the start of an
149     * exception try block in the CodeAttribute that was visited most recently.
150     */
151    public boolean isExceptionStart(int offset)
152    {
153        return (instructionMarks[offset] & EXCEPTION_START) != 0;
154    }
155
156
157    /**
158     * Returns whether the instruction at the given offset is the end of an
159     * exception try block in the CodeAttribute that was visited most recently.
160     */
161    public boolean isExceptionEnd(int offset)
162    {
163        return (instructionMarks[offset] & EXCEPTION_END) != 0;
164    }
165
166
167    /**
168     * Returns whether the instruction at the given offset is the start of an
169     * exception catch block in the CodeAttribute that was visited most recently.
170     */
171    public boolean isExceptionHandler(int offset)
172    {
173        return (instructionMarks[offset] & EXCEPTION_HANDLER) != 0;
174    }
175
176
177    /**
178     * Returns whether the instruction at the given offset is a subroutine
179     * invocation in the CodeAttribute that was visited most recently.
180     */
181    public boolean isSubroutineInvocation(int offset)
182    {
183        return (instructionMarks[offset] & SUBROUTINE_INVOCATION) != 0;
184    }
185
186
187    /**
188     * Returns whether the instruction at the given offset is the start of a
189     * subroutine in the CodeAttribute that was visited most recently.
190     */
191    public boolean isSubroutineStart(int offset)
192    {
193        return subroutineStarts[offset] == offset;
194    }
195
196
197    /**
198     * Returns whether the instruction at the given offset is part of a
199     * subroutine in the CodeAttribute that was visited most recently.
200     */
201    public boolean isSubroutine(int offset)
202    {
203        return subroutineStarts[offset] >= 0;
204    }
205
206
207    /**
208     * Returns whether the subroutine at the given offset is ever returning
209     * by means of a regular 'ret' instruction.
210     */
211    public boolean isSubroutineReturning(int offset)
212    {
213        return (instructionMarks[offset] & SUBROUTINE_RETURNING) != 0;
214    }
215
216
217    /**
218     * Returns the start offset of the subroutine at the given offset, in the
219     * CodeAttribute that was visited most recently.
220     */
221    public int subroutineStart(int offset)
222    {
223        return subroutineStarts[offset];
224    }
225
226
227    /**
228     * Returns the offset after the subroutine at the given offset, in the
229     * CodeAttribute that was visited most recently.
230     */
231    public int subroutineEnd(int offset)
232    {
233        return subroutineEnds[offset];
234    }
235
236
237    /**
238     * Returns whether the instruction at the given offset is a 'new'
239     * instruction, in the CodeAttribute that was visited most recently.
240     */
241    public boolean isNew(int offset)
242    {
243        return initializationOffsets[offset] != NONE;
244    }
245
246
247    /**
248     * Returns the instruction offset at which the object instance that is
249     * created at the given 'new' instruction offset is initialized, or
250     * <code>NONE</code> if it is not being created.
251     */
252    public int initializationOffset(int offset)
253    {
254        return initializationOffsets[offset];
255    }
256
257
258    /**
259     * Returns whether the method is an instance initializer, in the
260     * CodeAttribute that was visited most recently.
261     */
262    public boolean isInitializer()
263    {
264        return superInitializationOffset != NONE;
265    }
266
267
268    /**
269     * Returns the instruction offset at which this initializer is calling
270     * the "super" or "this" initializer method, or <code>NONE</code> if it is
271     * not an initializer.
272     */
273    public int superInitializationOffset()
274    {
275        return superInitializationOffset;
276    }
277
278
279    /**
280     * Returns whether the instruction at the given offset is the special
281     * invocation of an instance initializer, in the CodeAttribute that was
282     * visited most recently.
283     */
284    public boolean isInitializer(int offset)
285    {
286        return creationOffsets[offset] != NONE;
287    }
288
289
290    /**
291     * Returns the offset of the 'new' instruction that corresponds to the
292     * invocation of the instance initializer at the given offset, or
293     * <code>AT_METHOD_ENTRY</code> if the invocation is calling the "super" or
294     * "this" initializer method, , or <code>NONE</code> if it is not a 'new'
295     * instruction.
296     */
297    public int creationOffset(int offset)
298    {
299        return creationOffsets[offset];
300    }
301
302
303    /**
304     * Returns whether the method contains subroutines, in the CodeAttribute
305     * that was visited most recently.
306     */
307    public boolean containsSubroutines()
308    {
309        return containsSubroutines;
310    }
311
312
313    // Implementations for AttributeVisitor.
314
315    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
316
317
318    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
319    {
320//        DEBUG =
321//            clazz.getName().equals("abc/Def") &&
322//            method.getName(clazz).equals("abc");
323
324        // Make sure there are sufficiently large arrays.
325        int codeLength = codeAttribute.u4codeLength;
326        if (subroutineStarts.length < codeLength)
327        {
328            // Create new arrays.
329            instructionMarks      = new short[codeLength + 1];
330            subroutineStarts      = new int[codeLength];
331            subroutineEnds        = new int[codeLength];
332            creationOffsets       = new int[codeLength];
333            initializationOffsets = new int[codeLength];
334
335            // Reset the arrays.
336            Arrays.fill(subroutineStarts,      0, codeLength, UNKNOWN);
337            Arrays.fill(subroutineEnds,        0, codeLength, UNKNOWN);
338            Arrays.fill(creationOffsets,       0, codeLength, NONE);
339            Arrays.fill(initializationOffsets, 0, codeLength, NONE);
340        }
341        else
342        {
343            // Reset the arrays.
344            Arrays.fill(instructionMarks,      0, codeLength, (short)0);
345            Arrays.fill(subroutineStarts,      0, codeLength, UNKNOWN);
346            Arrays.fill(subroutineEnds,        0, codeLength, UNKNOWN);
347            Arrays.fill(creationOffsets,       0, codeLength, NONE);
348            Arrays.fill(initializationOffsets, 0, codeLength, NONE);
349
350            instructionMarks[codeLength] = 0;
351        }
352
353        superInitializationOffset = NONE;
354        containsSubroutines       = false;
355
356        // Iterate until all subroutines have been fully marked.
357        do
358        {
359            repeat                    = false;
360            currentSubroutineStart    = NO_SUBROUTINE;
361            recentCreationOffsetIndex = 0;
362
363            // Mark branch targets by going over all instructions.
364            codeAttribute.instructionsAccept(clazz, method, this);
365
366            // Mark branch targets in the exception table.
367            codeAttribute.exceptionsAccept(clazz, method, this);
368        }
369        while (repeat);
370
371        // The end of the code is a branch target sentinel.
372        instructionMarks[codeLength] = BRANCH_TARGET;
373
374        if (containsSubroutines)
375        {
376            // Set the subroutine returning flag and the subroutine end at each
377            // subroutine start.
378            int previousSubroutineStart = NO_SUBROUTINE;
379
380            for (int offset = 0; offset < codeLength; offset++)
381            {
382                if (isInstruction(offset))
383                {
384                    int subroutineStart = subroutineStarts[offset];
385
386                    if (subroutineStart >= 0 &&
387                        isSubroutineReturning(offset))
388                    {
389                        instructionMarks[subroutineStart] |= SUBROUTINE_RETURNING;
390                    }
391
392                    if (previousSubroutineStart >= 0)
393                    {
394                        subroutineEnds[previousSubroutineStart] = offset;
395                    }
396
397                    previousSubroutineStart = subroutineStart;
398                }
399            }
400
401            if (previousSubroutineStart >= 0)
402            {
403                subroutineEnds[previousSubroutineStart] = codeLength;
404            }
405
406            // Set the subroutine returning flag and the subroutine end at each
407            // subroutine instruction, based on the marks at the subroutine
408            // start.
409            for (int offset = 0; offset < codeLength; offset++)
410            {
411                if (isSubroutine(offset))
412                {
413                    int subroutineStart = subroutineStarts[offset];
414
415                    if (isSubroutineReturning(subroutineStart))
416                    {
417                        instructionMarks[offset] |= SUBROUTINE_RETURNING;
418                    }
419
420                    subroutineEnds[offset] = subroutineEnds[subroutineStart];
421                }
422            }
423        }
424
425        if (DEBUG)
426        {
427            System.out.println();
428            System.out.println("Branch targets: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz));
429
430            for (int index = 0; index < codeLength; index++)
431            {
432                if (isInstruction(index))
433                {
434                    System.out.println("" +
435                                       (isBranchOrigin(index)         ? 'B' : '-') +
436                                       (isAfterBranch(index)          ? 'b' : '-') +
437                                       (isBranchTarget(index)         ? 'T' : '-') +
438                                       (isExceptionStart(index)       ? 'E' : '-') +
439                                       (isExceptionEnd(index)         ? 'e' : '-') +
440                                       (isExceptionHandler(index)     ? 'H' : '-') +
441                                       (isSubroutineInvocation(index) ? 'J' : '-') +
442                                       (isSubroutineStart(index)      ? 'S' : '-') +
443                                       (isSubroutineReturning(index)  ? 'r' : '-') +
444                                       (isSubroutine(index)           ? " ["+subroutineStart(index)+" -> "+subroutineEnd(index)+"]" : "") +
445                                       (isNew(index)                  ? " ["+initializationOffset(index)+"] " : " ---- ") +
446                                       InstructionFactory.create(codeAttribute.code, index).toString(index));
447                }
448            }
449        }
450    }
451
452
453    // Implementations for InstructionVisitor.
454
455    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
456    {
457        // Mark the instruction.
458        instructionMarks[offset] |= INSTRUCTION;
459
460        // Check if this is an instruction of a subroutine.
461        checkSubroutine(offset);
462
463        byte opcode = simpleInstruction.opcode;
464        if (opcode == InstructionConstants.OP_IRETURN ||
465            opcode == InstructionConstants.OP_LRETURN ||
466            opcode == InstructionConstants.OP_FRETURN ||
467            opcode == InstructionConstants.OP_DRETURN ||
468            opcode == InstructionConstants.OP_ARETURN ||
469            opcode == InstructionConstants.OP_ATHROW)
470        {
471            // Mark the branch origin.
472            markBranchOrigin(offset);
473
474            // Mark the next instruction.
475            markAfterBranchOrigin(offset + simpleInstruction.length(offset));
476        }
477    }
478
479
480    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
481    {
482        // Mark the instruction.
483        instructionMarks[offset] |= INSTRUCTION;
484
485        // Check if this is an instruction of a subroutine.
486        checkSubroutine(offset);
487
488        byte opcode = constantInstruction.opcode;
489        if (opcode == InstructionConstants.OP_NEW)
490        {
491            // Push the 'new' instruction offset on the stack.
492            recentCreationOffsets[recentCreationOffsetIndex++] = offset;
493        }
494        else if (opcode == InstructionConstants.OP_INVOKESPECIAL)
495        {
496            // Is it calling an instance initializer?
497            isInitializer = false;
498            clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
499            if (isInitializer)
500            {
501                // Do we have any 'new' instruction offsets on the stack?
502                if (recentCreationOffsetIndex > 0)
503                {
504                    // Pop the 'new' instruction offset from the stack.
505                    int recentCreationOffset = recentCreationOffsets[--recentCreationOffsetIndex];
506
507                    // Link the creation offset and the initialization offset.
508                    // TODO: There could be multiple initialization offsets.
509                    creationOffsets[offset] = recentCreationOffset;
510
511                    initializationOffsets[recentCreationOffset] = offset;
512                }
513                else
514                {
515                    // Remember the super initialization offset.
516                    // TODO: There could be multiple initialization offsets.
517                    // For instance, in the constructor of the generated class
518                    // groovy.inspect.swingui.GeneratedBytecodeAwareGroovyClassLoader
519                    // in groovy-all-2.2.1.jar.
520                    superInitializationOffset = offset;
521                }
522            }
523        }
524    }
525
526
527    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
528    {
529        // Mark the instruction.
530        instructionMarks[offset] |= INSTRUCTION;
531
532        // Check if this is an instruction of a subroutine.
533        checkSubroutine(offset);
534
535        if (variableInstruction.opcode == InstructionConstants.OP_RET)
536        {
537            // Mark the method.
538            containsSubroutines = true;
539
540            // Mark the branch origin.
541            markBranchOrigin(offset);
542
543            // Mark the subroutine return at its return instruction.
544            instructionMarks[offset] |= SUBROUTINE_RETURNING;
545
546            // Mark the next instruction.
547            markAfterBranchOrigin(offset + variableInstruction.length(offset));
548        }
549    }
550
551
552    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
553    {
554        int branchOffset = branchInstruction.branchOffset;
555        int targetOffset = offset + branchOffset;
556
557        // Mark the branch origin.
558        markBranchOrigin(offset);
559
560        // Check if this is an instruction of a subroutine.
561        checkSubroutine(offset);
562
563        // Mark the branch target.
564        markBranchTarget(offset, branchOffset);
565
566        byte opcode = branchInstruction.opcode;
567        if (opcode == InstructionConstants.OP_JSR ||
568            opcode == InstructionConstants.OP_JSR_W)
569        {
570            // Mark the method.
571            containsSubroutines = true;
572
573            // Mark the subroutine invocation.
574            instructionMarks[offset] |= SUBROUTINE_INVOCATION;
575
576            // Mark the new subroutine start.
577            markBranchSubroutineStart(offset, branchOffset, targetOffset);
578        }
579        else if (currentSubroutineStart != UNKNOWN)
580        {
581            // Mark the continued subroutine start.
582            markBranchSubroutineStart(offset, branchOffset, currentSubroutineStart);
583        }
584
585        if (opcode == InstructionConstants.OP_GOTO ||
586            opcode == InstructionConstants.OP_GOTO_W)
587        {
588            // Mark the next instruction.
589            markAfterBranchOrigin(offset + branchInstruction.length(offset));
590        }
591    }
592
593
594    public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction)
595    {
596        // Mark the branch origin.
597        markBranchOrigin(offset);
598
599        // Check if this is an instruction of a subroutine.
600        checkSubroutine(offset);
601
602        // Mark the branch targets of the default jump offset.
603        markBranch(offset, switchInstruction.defaultOffset);
604
605        // Mark the branch targets of the jump offsets.
606        markBranches(offset, switchInstruction.jumpOffsets);
607
608        // Mark the next instruction.
609        markAfterBranchOrigin(offset + switchInstruction.length(offset));
610    }
611
612
613    // Implementations for ConstantVisitor.
614
615    public void visitAnyConstant(Clazz clazz, Constant constant) {}
616
617
618    public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)
619    {
620        // Remember whether the method is an initializer.
621        isInitializer = methodrefConstant.getName(clazz).equals(ClassConstants.METHOD_NAME_INIT);
622    }
623
624
625    // Implementations for ExceptionInfoVisitor.
626
627    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
628    {
629        int startPC   = exceptionInfo.u2startPC;
630        int endPC     = exceptionInfo.u2endPC;
631        int handlerPC = exceptionInfo.u2handlerPC;
632
633        // Mark the exception offsets.
634        instructionMarks[startPC]   |= EXCEPTION_START;
635        instructionMarks[endPC]     |= EXCEPTION_END;
636        instructionMarks[handlerPC] |= EXCEPTION_HANDLER;
637
638        // Mark the handler as part of a subroutine if necessary.
639        if (subroutineStarts[handlerPC] == UNKNOWN &&
640            subroutineStarts[startPC]   != UNKNOWN)
641        {
642            subroutineStarts[handlerPC] = subroutineStarts[startPC];
643
644            // We'll have to go over all instructions again.
645            repeat = true;
646        }
647    }
648
649
650    // Small utility methods.
651
652    /**
653     * Marks the branch targets and their subroutine starts at the given
654     * offsets.
655     */
656    private void markBranches(int offset, int[] jumpOffsets)
657    {
658        for (int index = 0; index < jumpOffsets.length; index++)
659        {
660            markBranch(offset, jumpOffsets[index]);
661        }
662    }
663
664
665    /**
666     * Marks the branch target and its subroutine start at the given offset.
667     */
668    private void markBranch(int offset, int jumpOffset)
669    {
670        markBranchTarget(offset, jumpOffset);
671
672        if (currentSubroutineStart != UNKNOWN)
673        {
674            markBranchSubroutineStart(offset, jumpOffset, currentSubroutineStart);
675        }
676    }
677
678    /**
679     * Marks the branch origin at the given offset.
680     */
681    private void markBranchOrigin(int offset)
682    {
683        instructionMarks[offset] |= INSTRUCTION | BRANCH_ORIGIN;
684    }
685
686
687    /**
688     * Marks the branch target at the given offset.
689     */
690    private void markBranchTarget(int offset, int jumpOffset)
691    {
692        int targetOffset = offset + jumpOffset;
693
694        instructionMarks[targetOffset] |= BRANCH_TARGET;
695    }
696
697
698    /**
699     * Marks the subroutine start at the given offset, if applicable.
700     */
701    private void markBranchSubroutineStart(int offset,
702                                           int jumpOffset,
703                                           int subroutineStart)
704    {
705        int targetOffset = offset + jumpOffset;
706
707        // Are we marking a subroutine and branching to an offset that hasn't
708        // been marked yet?
709        if (subroutineStarts[targetOffset] == UNKNOWN)
710        {
711            // Is it a backward branch?
712            if (jumpOffset < 0)
713            {
714                // Remember the smallest subroutine start.
715                if (subroutineStart > targetOffset)
716                {
717                    subroutineStart = targetOffset;
718                }
719
720                // We'll have to go over all instructions again.
721                repeat = true;
722            }
723
724            // Mark the subroutine start of the target.
725            subroutineStarts[targetOffset] = subroutineStart;
726        }
727    }
728
729
730    /**
731     * Marks the instruction at the given offset, after a branch.
732     */
733    private void markAfterBranchOrigin(int nextOffset)
734    {
735        instructionMarks[nextOffset] |= AFTER_BRANCH;
736
737        // Stop marking a subroutine.
738        currentSubroutineStart = UNKNOWN;
739    }
740
741
742    /**
743     * Checks if the specified instruction is inside a subroutine.
744     */
745    private void checkSubroutine(int offset)
746    {
747        // Are we inside a previously marked subroutine?
748        if (subroutineStarts[offset] != UNKNOWN)
749        {
750            // Start marking a subroutine.
751            currentSubroutineStart = subroutineStarts[offset];
752        }
753
754        // Are we marking a subroutine?
755        else if (currentSubroutineStart != UNKNOWN)
756        {
757            // Mark the subroutine start.
758            subroutineStarts[offset] = currentSubroutineStart;
759        }
760    }
761}
762