• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
21 package proguard.optimize.peephole;
22 
23 import proguard.classfile.*;
24 import proguard.classfile.attribute.*;
25 import proguard.classfile.attribute.visitor.*;
26 import proguard.classfile.constant.*;
27 import proguard.classfile.constant.visitor.ConstantVisitor;
28 import proguard.classfile.instruction.*;
29 import proguard.classfile.instruction.visitor.InstructionVisitor;
30 import proguard.classfile.util.SimplifiedVisitor;
31 
32 /**
33  * This AttributeVisitor finds all instruction offsets, branch targets, and
34  * exception targets in the CodeAttribute objects that it visits.
35  *
36  * @author Eric Lafortune
37  */
38 public class BranchTargetFinder
39 extends      SimplifiedVisitor
40 implements   AttributeVisitor,
41              InstructionVisitor,
42              ExceptionInfoVisitor,
43              ConstantVisitor
44 {
45     //*
46     private static final boolean DEBUG = false;
47     /*/
48     private static       boolean DEBUG = true;
49     //*/
50 
51     public static final int NONE            = -2;
52     public static final int AT_METHOD_ENTRY = -1;
53 
54     private static final short INSTRUCTION           = 1 << 0;
55     private static final short BRANCH_ORIGIN         = 1 << 1;
56     private static final short BRANCH_TARGET         = 1 << 2;
57     private static final short AFTER_BRANCH          = 1 << 3;
58     private static final short EXCEPTION_START       = 1 << 4;
59     private static final short EXCEPTION_END         = 1 << 5;
60     private static final short EXCEPTION_HANDLER     = 1 << 6;
61     private static final short SUBROUTINE_INVOCATION = 1 << 7;
62     private static final short SUBROUTINE_RETURNING  = 1 << 8;
63 
64     private static final int MAXIMUM_CREATION_OFFSETS = 32;
65 
66 
67     private short[] instructionMarks      = new short[ClassConstants.TYPICAL_CODE_LENGTH + 1];
68     private int[]   subroutineStarts      = new int[ClassConstants.TYPICAL_CODE_LENGTH];
69     private int[]   subroutineEnds        = new int[ClassConstants.TYPICAL_CODE_LENGTH];
70     private int[]   creationOffsets       = new int[ClassConstants.TYPICAL_CODE_LENGTH];
71     private int[]   initializationOffsets = new int[ClassConstants.TYPICAL_CODE_LENGTH];
72     private int     superInitializationOffset;
73 
74     private int     currentSubroutineStart;
75     private int     currentSubroutineEnd;
76     private int[]   recentCreationOffsets = new int[MAXIMUM_CREATION_OFFSETS];
77     private int     recentCreationOffsetIndex;
78     private boolean isInitializer;
79 
80 
81     /**
82      * Returns whether there is an instruction at the given offset in the
83      * CodeAttribute that was visited most recently.
84      */
isInstruction(int offset)85     public boolean isInstruction(int offset)
86     {
87         return (instructionMarks[offset] & INSTRUCTION) != 0;
88     }
89 
90 
91     /**
92      * Returns whether the instruction at the given offset is the target of
93      * any kind in the CodeAttribute that was visited most recently.
94      */
isTarget(int offset)95     public boolean isTarget(int offset)
96     {
97         return offset == 0 ||
98                (instructionMarks[offset] & (BRANCH_TARGET   |
99                                             EXCEPTION_START |
100                                             EXCEPTION_END   |
101                                             EXCEPTION_HANDLER)) != 0;
102     }
103 
104 
105     /**
106      * Returns whether the instruction at the given offset is the origin of a
107      * branch instruction in the CodeAttribute that was visited most recently.
108      */
isBranchOrigin(int offset)109     public boolean isBranchOrigin(int offset)
110     {
111         return (instructionMarks[offset] & BRANCH_ORIGIN) != 0;
112     }
113 
114 
115     /**
116      * Returns whether the instruction at the given offset is the target of a
117      * branch instruction in the CodeAttribute that was visited most recently.
118      */
isBranchTarget(int offset)119     public boolean isBranchTarget(int offset)
120     {
121         return (instructionMarks[offset] & BRANCH_TARGET) != 0;
122     }
123 
124 
125     /**
126      * Returns whether the instruction at the given offset comes right after a
127      * definite branch instruction in the CodeAttribute that was visited most
128      * recently.
129      */
isAfterBranch(int offset)130     public boolean isAfterBranch(int offset)
131     {
132         return (instructionMarks[offset] & AFTER_BRANCH) != 0;
133     }
134 
135 
136     /**
137      * Returns whether the instruction at the given offset is the start of an
138      * exception try block in the CodeAttribute that was visited most recently.
139      */
isExceptionStart(int offset)140     public boolean isExceptionStart(int offset)
141     {
142         return (instructionMarks[offset] & EXCEPTION_START) != 0;
143     }
144 
145 
146     /**
147      * Returns whether the instruction at the given offset is the end of an
148      * exception try block in the CodeAttribute that was visited most recently.
149      */
isExceptionEnd(int offset)150     public boolean isExceptionEnd(int offset)
151     {
152         return (instructionMarks[offset] & EXCEPTION_END) != 0;
153     }
154 
155 
156     /**
157      * Returns whether the instruction at the given offset is the start of an
158      * exception catch block in the CodeAttribute that was visited most recently.
159      */
isExceptionHandler(int offset)160     public boolean isExceptionHandler(int offset)
161     {
162         return (instructionMarks[offset] & EXCEPTION_HANDLER) != 0;
163     }
164 
165 
166     /**
167      * Returns whether the instruction at the given offset is a subroutine
168      * invocation in the CodeAttribute that was visited most recently.
169      */
isSubroutineInvocation(int offset)170     public boolean isSubroutineInvocation(int offset)
171     {
172         return (instructionMarks[offset] & SUBROUTINE_INVOCATION) != 0;
173     }
174 
175 
176     /**
177      * Returns whether the instruction at the given offset is the start of a
178      * subroutine in the CodeAttribute that was visited most recently.
179      */
isSubroutineStart(int offset)180     public boolean isSubroutineStart(int offset)
181     {
182         return subroutineStarts[offset] == offset;
183     }
184 
185 
186     /**
187      * Returns whether the instruction at the given offset is part of a
188      * subroutine in the CodeAttribute that was visited most recently.
189      */
isSubroutine(int offset)190     public boolean isSubroutine(int offset)
191     {
192         return subroutineStarts[offset] != NONE;
193     }
194 
195 
196     /**
197      * Returns whether the subroutine at the given offset is ever returning
198      * by means of a regular 'ret' instruction.
199      */
isSubroutineReturning(int offset)200     public boolean isSubroutineReturning(int offset)
201     {
202         return (instructionMarks[offset] & SUBROUTINE_RETURNING) != 0;
203     }
204 
205 
206     /**
207      * Returns the start offset of the subroutine at the given offset, in the
208      * CodeAttribute that was visited most recently.
209      */
subroutineStart(int offset)210     public int subroutineStart(int offset)
211     {
212         return subroutineStarts[offset];
213     }
214 
215 
216     /**
217      * Returns the offset after the subroutine at the given offset, in the
218      * CodeAttribute that was visited most recently.
219      */
subroutineEnd(int offset)220     public int subroutineEnd(int offset)
221     {
222         return subroutineEnds[offset];
223     }
224 
225 
226     /**
227      * Returns whether the instruction at the given offset is a 'new'
228      * instruction, in the CodeAttribute that was visited most recently.
229      */
isNew(int offset)230     public boolean isNew(int offset)
231     {
232         return initializationOffsets[offset] != NONE;
233     }
234 
235 
236     /**
237      * Returns the instruction offset at which the object instance that is
238      * created at the given 'new' instruction offset is initialized, or
239      * <code>NONE</code> if it is not being created.
240      */
initializationOffset(int offset)241     public int initializationOffset(int offset)
242     {
243         return initializationOffsets[offset];
244     }
245 
246 
247     /**
248      * Returns whether the method is an instance initializer, in the
249      * CodeAttribute that was visited most recently.
250      */
isInitializer()251     public boolean isInitializer()
252     {
253         return superInitializationOffset != NONE;
254     }
255 
256 
257     /**
258      * Returns the instruction offset at which this initializer is calling
259      * the "super" or "this" initializer method, or <code>NONE</code> if it is
260      * not an initializer.
261      */
superInitializationOffset()262     public int superInitializationOffset()
263     {
264         return superInitializationOffset;
265     }
266 
267 
268     /**
269      * Returns whether the instruction at the given offset is the special
270      * invocation of an instance initializer, in the CodeAttribute that was
271      * visited most recently.
272      */
isInitializer(int offset)273     public boolean isInitializer(int offset)
274     {
275         return creationOffsets[offset] != NONE;
276     }
277 
278 
279     /**
280      * Returns the offset of the 'new' instruction that corresponds to the
281      * invocation of the instance initializer at the given offset, or
282      * <code>AT_METHOD_ENTRY</code> if the invocation is calling the "super" or
283      * "this" initializer method, , or <code>NONE</code> if it is not a 'new'
284      * instruction.
285      */
creationOffset(int offset)286     public int creationOffset(int offset)
287     {
288         return creationOffsets[offset];
289     }
290 
291 
292     // Implementations for AttributeVisitor.
293 
visitAnyAttribute(Clazz clazz, Attribute attribute)294     public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
295 
296 
visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)297     public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
298     {
299 //        DEBUG =
300 //            clazz.getName().equals("abc/Def") &&
301 //            method.getName(clazz).equals("abc");
302 
303         // Make sure there are sufficiently large arrays.
304         int codeLength = codeAttribute.u4codeLength;
305         if (subroutineStarts.length < codeLength)
306         {
307             // Create new arrays.
308             instructionMarks      = new short[codeLength + 1];
309             subroutineStarts      = new int[codeLength];
310             subroutineEnds        = new int[codeLength];
311             creationOffsets       = new int[codeLength];
312             initializationOffsets = new int[codeLength];
313 
314             // Reset the arrays.
315             for (int index = 0; index < codeLength; index++)
316             {
317                 subroutineStarts[index]      = NONE;
318                 subroutineEnds[index]        = NONE;
319                 creationOffsets[index]       = NONE;
320                 initializationOffsets[index] = NONE;
321             }
322         }
323         else
324         {
325             // Reset the arrays.
326             for (int index = 0; index < codeLength; index++)
327             {
328                 instructionMarks[index]      = 0;
329                 subroutineStarts[index]      = NONE;
330                 subroutineEnds[index]        = NONE;
331                 creationOffsets[index]       = NONE;
332                 initializationOffsets[index] = NONE;
333             }
334 
335             instructionMarks[codeLength] = 0;
336         }
337 
338         superInitializationOffset = NONE;
339 
340         // We're assuming all subroutines are contiguous blocks of code.
341         // We're not starting in a subroutine.
342         currentSubroutineStart = NONE;
343         currentSubroutineEnd   = NONE;
344 
345         recentCreationOffsetIndex = 0;
346 
347         // Initialize the stack of 'new' instruction offsets if this method is
348         // an instance initializer.
349         if (method.getName(clazz).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))
350         {
351             recentCreationOffsets[recentCreationOffsetIndex++] = AT_METHOD_ENTRY;
352         }
353 
354         // The end of the code is a branch target sentinel.
355         instructionMarks[codeLength] = BRANCH_TARGET;
356 
357         // Mark branch targets by going over all instructions.
358         codeAttribute.instructionsAccept(clazz, method, this);
359 
360         // Mark branch targets in the exception table.
361         codeAttribute.exceptionsAccept(clazz, method, this);
362 
363         // Fill out any gaps in the subroutine starts and the subroutine ends
364         // and subroutine returning flags, working backward.
365 
366         // We're not starting in a subroutine.
367         int     subroutineStart     = NONE;
368         int     subroutineEnd       = codeLength;
369         boolean subroutineReturning = false;
370 
371         for (int index = codeLength - 1; index >= 0; index--)
372         {
373             if (isInstruction(index))
374             {
375                 // Are we inside a previously marked subroutine?
376                 if (subroutineStarts[index] != NONE)
377                 {
378                     // Update the current subroutine start.
379                     subroutineStart = subroutineStarts[index];
380                 }
381                 else if (subroutineStart != NONE)
382                 {
383                     // Mark the subroutine start.
384                     subroutineStarts[index] = subroutineStart;
385                 }
386 
387                 // Did we reach the start of the subroutine.
388                 if (isSubroutineStart(index))
389                 {
390                     // Stop marking it.
391                     subroutineStart = NONE;
392                 }
393 
394                 // Are we inside a subroutine?
395                 if (isSubroutine(index))
396                 {
397                     // Mark the subroutine end.
398                     subroutineEnds[index] = subroutineEnd;
399 
400                     // Update or mark the subroutine returning flag.
401                     if (isSubroutineReturning(index))
402                     {
403                         subroutineReturning = true;
404                     }
405                     else if (subroutineReturning)
406                     {
407                         instructionMarks[index] |= SUBROUTINE_RETURNING;
408                     }
409                 }
410                 else
411                 {
412                     // Update the subroutine end and returning flag.
413                     subroutineEnd       = index;
414                     subroutineReturning = false;
415                 }
416             }
417         }
418 
419         if (DEBUG)
420         {
421             System.out.println();
422             System.out.println("Branch targets: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz));
423 
424             for (int index = 0; index < codeLength; index++)
425             {
426                 if (isInstruction(index))
427                 {
428                     System.out.println("" +
429                                        (isBranchOrigin(index)         ? 'B' : '-') +
430                                        (isAfterBranch(index)          ? 'b' : '-') +
431                                        (isBranchTarget(index)         ? 'T' : '-') +
432                                        (isExceptionStart(index)       ? 'E' : '-') +
433                                        (isExceptionEnd(index)         ? 'e' : '-') +
434                                        (isExceptionHandler(index)     ? 'H' : '-') +
435                                        (isSubroutineInvocation(index) ? 'J' : '-') +
436                                        (isSubroutineStart(index)      ? 'S' : '-') +
437                                        (isSubroutineReturning(index)  ? 'r' : '-') +
438                                        (isSubroutine(index)           ? " ["+subroutineStart(index)+" -> "+subroutineEnd(index)+"]" : "") +
439                                        (isNew(index)                  ? " ["+initializationOffset(index)+"] " : " ---- ") +
440                                        InstructionFactory.create(codeAttribute.code, index).toString(index));
441                 }
442             }
443         }
444     }
445 
446 
447     // Implementations for InstructionVisitor.
448 
visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)449     public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
450     {
451         // Mark the instruction.
452         instructionMarks[offset] |= INSTRUCTION;
453 
454         // Check if this is the first instruction of a subroutine.
455         checkSubroutine(offset);
456 
457         byte opcode = simpleInstruction.opcode;
458         if (opcode == InstructionConstants.OP_IRETURN ||
459             opcode == InstructionConstants.OP_LRETURN ||
460             opcode == InstructionConstants.OP_FRETURN ||
461             opcode == InstructionConstants.OP_DRETURN ||
462             opcode == InstructionConstants.OP_ARETURN ||
463             opcode == InstructionConstants.OP_ATHROW)
464         {
465             // Mark the branch origin.
466             markBranchOrigin(offset);
467 
468             // Mark the next instruction.
469             markAfterBranchOrigin(offset + simpleInstruction.length(offset));
470         }
471     }
472 
473 
visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)474     public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
475     {
476         // Mark the instruction.
477         instructionMarks[offset] |= INSTRUCTION;
478 
479         // Check if this is the first instruction of a subroutine.
480         checkSubroutine(offset);
481 
482         // Check if the instruction is a 'new' instruction.
483         if (constantInstruction.opcode == InstructionConstants.OP_NEW)
484         {
485             // Push the 'new' instruction offset on the stack.
486             recentCreationOffsets[recentCreationOffsetIndex++] = offset;
487         }
488         else
489         {
490             // Check if the instruction is an initializer invocation.
491             isInitializer = false;
492             clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
493             if (isInitializer)
494             {
495                 // Pop the 'new' instruction offset from the stack.
496                 int recentCreationOffset = recentCreationOffsets[--recentCreationOffsetIndex];
497 
498                 // Fill it out in the creation offsets.
499                 creationOffsets[offset] = recentCreationOffset;
500 
501                 // Fill out the initialization offsets.
502                 if (recentCreationOffset == AT_METHOD_ENTRY)
503                 {
504                     superInitializationOffset = offset;
505                 }
506                 else
507                 {
508                     initializationOffsets[recentCreationOffset] = offset;
509                 }
510             }
511         }
512     }
513 
514 
visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)515     public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
516     {
517         // Mark the instruction.
518         instructionMarks[offset] |= INSTRUCTION;
519 
520         // Check if this is the first instruction of a subroutine.
521         checkSubroutine(offset);
522 
523         if (variableInstruction.opcode == InstructionConstants.OP_RET)
524         {
525             // Mark the branch origin.
526             markBranchOrigin(offset);
527 
528             // Mark the regular subroutine return.
529             instructionMarks[offset] |= SUBROUTINE_RETURNING;
530 
531             // Mark the next instruction.
532             markAfterBranchOrigin(offset + variableInstruction.length(offset));
533         }
534     }
535 
536 
visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)537     public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
538     {
539         // Mark the branch origin.
540         markBranchOrigin(offset);
541 
542         // Check if this is the first instruction of a subroutine.
543         checkSubroutine(offset);
544 
545         // Mark the branch target.
546         markBranchTarget(offset, branchInstruction.branchOffset);
547 
548         byte opcode = branchInstruction.opcode;
549         if (opcode == InstructionConstants.OP_JSR ||
550             opcode == InstructionConstants.OP_JSR_W)
551         {
552             // Mark the subroutine invocation.
553             instructionMarks[offset] |= SUBROUTINE_INVOCATION;
554 
555             // Mark the subroutine start.
556             int targetOffset = offset + branchInstruction.branchOffset;
557             subroutineStarts[targetOffset] = targetOffset;
558         }
559         else if (opcode == InstructionConstants.OP_GOTO ||
560                  opcode == InstructionConstants.OP_GOTO_W)
561         {
562             // Mark the next instruction.
563             markAfterBranchOrigin(offset + branchInstruction.length(offset));
564         }
565     }
566 
567 
visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction)568     public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction)
569     {
570         // Mark the branch origin.
571         markBranchOrigin(offset);
572 
573         // Check if this is the first instruction of a subroutine.
574         checkSubroutine(offset);
575 
576         // Mark the branch targets of the default jump offset.
577         markBranchTarget(offset, switchInstruction.defaultOffset);
578 
579         // Mark the branch targets of the jump offsets.
580         markBranchTargets(offset,
581                           switchInstruction.jumpOffsets);
582 
583         // Mark the next instruction.
584         markAfterBranchOrigin(offset + switchInstruction.length(offset));
585     }
586 
587 
588     // Implementations for ConstantVisitor.
589 
visitAnyConstant(Clazz clazz, Constant constant)590     public void visitAnyConstant(Clazz clazz, Constant constant) {}
591 
592 
visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)593     public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)
594     {
595         isInitializer = methodrefConstant.getName(clazz).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT);
596     }
597 
598 
599     // Implementations for ExceptionInfoVisitor.
600 
visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)601     public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
602     {
603         // Mark the exception offsets.
604         instructionMarks[exceptionInfo.u2startPC]   |= EXCEPTION_START;
605         instructionMarks[exceptionInfo.u2endPC]     |= EXCEPTION_END;
606         instructionMarks[exceptionInfo.u2handlerPC] |= EXCEPTION_HANDLER;
607     }
608 
609 
610     // Small utility methods.
611 
612     /**
613      * Marks the branch targets of the given jump offsets for the instruction
614      * at the given offset.
615      */
markBranchTargets(int offset, int[] jumpOffsets)616     private void markBranchTargets(int offset, int[] jumpOffsets)
617     {
618         for (int index = 0; index < jumpOffsets.length; index++)
619         {
620             markBranchTarget(offset, jumpOffsets[index]);
621         }
622     }
623 
624 
625     /**
626      * Marks the branch origin at the given offset.
627      */
markBranchOrigin(int offset)628     private void markBranchOrigin(int offset)
629     {
630         instructionMarks[offset] |= INSTRUCTION | BRANCH_ORIGIN;
631     }
632 
633 
634     /**
635      * Marks the branch target at the given offset.
636      */
markBranchTarget(int offset, int jumpOffset)637     private void markBranchTarget(int offset, int jumpOffset)
638     {
639         int targetOffset = offset + jumpOffset;
640 
641         instructionMarks[targetOffset] |= BRANCH_TARGET;
642 
643         // Are we inside a previously marked subroutine?
644         if (isSubroutine(offset))
645         {
646             // Mark the subroutine start of the target.
647             subroutineStarts[targetOffset] = currentSubroutineStart;
648 
649             // Update the current subroutine end.
650             if (currentSubroutineEnd < targetOffset)
651             {
652                 currentSubroutineEnd = targetOffset;
653             }
654         }
655     }
656 
657 
658     /**
659      * Marks the instruction at the given offset, after a branch.
660      */
markAfterBranchOrigin(int nextOffset)661     private void markAfterBranchOrigin(int nextOffset)
662     {
663         instructionMarks[nextOffset] |= AFTER_BRANCH;
664 
665         // Are we at the end of the current subroutine?
666         if (currentSubroutineEnd <= nextOffset)
667         {
668             // Reset the subroutine start.
669             currentSubroutineStart = NONE;
670         }
671     }
672 
673 
674     /**
675      * Checks if the specified instruction is inside a subroutine.
676      */
checkSubroutine(int offset)677     private void checkSubroutine(int offset)
678     {
679         // Are we inside a previously marked subroutine?
680         if (isSubroutine(offset))
681         {
682             // Update the current subroutine start.
683             currentSubroutineStart = subroutineStarts[offset];
684         }
685         else
686         {
687             // Mark the subroutine start (or NONE).
688             subroutineStarts[offset] = currentSubroutineStart;
689         }
690     }
691 }
692