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