1 /* 2 * [The "BSD licence"] 3 * Copyright (c) 2010 Ben Gruver (JesusFreke) 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. The name of the author may not be used to endorse or promote products 15 * derived from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 package org.jf.baksmali.Adaptors; 30 31 import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory; 32 import org.jf.dexlib.Code.Analysis.SyntheticAccessorResolver; 33 import org.jf.dexlib.Code.InstructionWithReference; 34 import org.jf.util.IndentingWriter; 35 import org.jf.baksmali.baksmali; 36 import org.jf.dexlib.*; 37 import org.jf.dexlib.Code.Analysis.AnalyzedInstruction; 38 import org.jf.dexlib.Code.Analysis.MethodAnalyzer; 39 import org.jf.dexlib.Code.Analysis.ValidationException; 40 import org.jf.dexlib.Code.Format.Format; 41 import org.jf.dexlib.Code.Instruction; 42 import org.jf.dexlib.Code.OffsetInstruction; 43 import org.jf.dexlib.Code.Opcode; 44 import org.jf.dexlib.Debug.DebugInstructionIterator; 45 import org.jf.dexlib.Util.AccessFlags; 46 import org.jf.dexlib.Util.ExceptionWithContext; 47 import org.jf.dexlib.Util.SparseIntArray; 48 49 import java.io.IOException; 50 import java.util.*; 51 52 public class MethodDefinition { 53 private final ClassDataItem.EncodedMethod encodedMethod; 54 private MethodAnalyzer methodAnalyzer; 55 56 private final LabelCache labelCache = new LabelCache(); 57 58 private final SparseIntArray packedSwitchMap; 59 private final SparseIntArray sparseSwitchMap; 60 private final SparseIntArray instructionMap; 61 62 public MethodDefinition(ClassDataItem.EncodedMethod encodedMethod) { 63 64 65 try { 66 this.encodedMethod = encodedMethod; 67 68 //TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh. 69 70 if (encodedMethod.codeItem != null) { 71 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 72 73 packedSwitchMap = new SparseIntArray(1); 74 sparseSwitchMap = new SparseIntArray(1); 75 instructionMap = new SparseIntArray(instructions.length); 76 77 int currentCodeAddress = 0; 78 for (int i=0; i<instructions.length; i++) { 79 Instruction instruction = instructions[i]; 80 if (instruction.opcode == Opcode.PACKED_SWITCH) { 81 packedSwitchMap.append( 82 currentCodeAddress + 83 ((OffsetInstruction)instruction).getTargetAddressOffset(), 84 currentCodeAddress); 85 } else if (instruction.opcode == Opcode.SPARSE_SWITCH) { 86 sparseSwitchMap.append( 87 currentCodeAddress + 88 ((OffsetInstruction)instruction).getTargetAddressOffset(), 89 currentCodeAddress); 90 } 91 instructionMap.append(currentCodeAddress, i); 92 currentCodeAddress += instruction.getSize(currentCodeAddress); 93 } 94 } else { 95 packedSwitchMap = null; 96 sparseSwitchMap = null; 97 instructionMap = null; 98 methodAnalyzer = null; 99 } 100 }catch (Exception ex) { 101 throw ExceptionWithContext.withContext(ex, String.format("Error while processing method %s", 102 encodedMethod.method.getMethodString())); 103 } 104 } 105 106 public void writeTo(IndentingWriter writer, AnnotationSetItem annotationSet, 107 AnnotationSetRefList parameterAnnotations) throws IOException { 108 final CodeItem codeItem = encodedMethod.codeItem; 109 110 writer.write(".method "); 111 writeAccessFlags(writer, encodedMethod); 112 writer.write(encodedMethod.method.getMethodName().getStringValue()); 113 writer.write(encodedMethod.method.getPrototype().getPrototypeString()); 114 writer.write('\n'); 115 116 writer.indent(4); 117 if (codeItem != null) { 118 if (baksmali.useLocalsDirective) { 119 writer.write(".locals "); 120 } else { 121 writer.write(".registers "); 122 } 123 writer.printSignedIntAsDec(getRegisterCount(encodedMethod)); 124 writer.write('\n'); 125 writeParameters(writer, codeItem, parameterAnnotations); 126 if (annotationSet != null) { 127 AnnotationFormatter.writeTo(writer, annotationSet); 128 } 129 130 writer.write('\n'); 131 132 for (MethodItem methodItem: getMethodItems()) { 133 if (methodItem.writeTo(writer)) { 134 writer.write('\n'); 135 } 136 } 137 } else { 138 writeParameters(writer, codeItem, parameterAnnotations); 139 if (annotationSet != null) { 140 AnnotationFormatter.writeTo(writer, annotationSet); 141 } 142 } 143 writer.deindent(4); 144 writer.write(".end method\n"); 145 } 146 147 private static int getRegisterCount(ClassDataItem.EncodedMethod encodedMethod) 148 { 149 int totalRegisters = encodedMethod.codeItem.getRegisterCount(); 150 if (baksmali.useLocalsDirective) { 151 int parameterRegisters = encodedMethod.method.getPrototype().getParameterRegisterCount(); 152 if ((encodedMethod.accessFlags & AccessFlags.STATIC.getValue()) == 0) { 153 parameterRegisters++; 154 } 155 return totalRegisters - parameterRegisters; 156 } 157 return totalRegisters; 158 } 159 160 private static void writeAccessFlags(IndentingWriter writer, ClassDataItem.EncodedMethod encodedMethod) 161 throws IOException { 162 for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForMethod(encodedMethod.accessFlags)) { 163 writer.write(accessFlag.toString()); 164 writer.write(' '); 165 } 166 } 167 168 private static void writeParameters(IndentingWriter writer, CodeItem codeItem, 169 AnnotationSetRefList parameterAnnotations) throws IOException { 170 DebugInfoItem debugInfoItem = null; 171 if (baksmali.outputDebugInfo && codeItem != null) { 172 debugInfoItem = codeItem.getDebugInfo(); 173 } 174 175 int parameterCount = 0; 176 AnnotationSetItem[] annotations; 177 StringIdItem[] parameterNames = null; 178 179 if (parameterAnnotations != null) { 180 annotations = parameterAnnotations.getAnnotationSets(); 181 parameterCount = annotations.length; 182 } else { 183 annotations = new AnnotationSetItem[0]; 184 } 185 186 if (debugInfoItem != null) { 187 parameterNames = debugInfoItem.getParameterNames(); 188 } 189 if (parameterNames == null) { 190 parameterNames = new StringIdItem[0]; 191 } 192 193 if (parameterCount < parameterNames.length) { 194 parameterCount = parameterNames.length; 195 } 196 197 for (int i=0; i<parameterCount; i++) { 198 AnnotationSetItem annotationSet = null; 199 if (i < annotations.length) { 200 annotationSet = annotations[i]; 201 } 202 203 StringIdItem parameterName = null; 204 if (i < parameterNames.length) { 205 parameterName = parameterNames[i]; 206 } 207 208 writer.write(".parameter"); 209 210 if (parameterName != null) { 211 writer.write(" \""); 212 writer.write(parameterName.getStringValue()); 213 writer.write('"'); 214 } 215 216 writer.write('\n'); 217 if (annotationSet != null) { 218 writer.indent(4); 219 AnnotationFormatter.writeTo(writer, annotationSet); 220 writer.deindent(4); 221 222 writer.write(".end parameter\n"); 223 } 224 } 225 } 226 227 public LabelCache getLabelCache() { 228 return labelCache; 229 } 230 231 public ValidationException getValidationException() { 232 if (methodAnalyzer == null) { 233 return null; 234 } 235 236 return methodAnalyzer.getValidationException(); 237 } 238 239 public int getPackedSwitchBaseAddress(int packedSwitchDataAddress) { 240 int packedSwitchBaseAddress = this.packedSwitchMap.get(packedSwitchDataAddress, -1); 241 242 if (packedSwitchBaseAddress == -1) { 243 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 244 int index = instructionMap.get(packedSwitchDataAddress); 245 246 if (instructions[index].opcode == Opcode.NOP) { 247 packedSwitchBaseAddress = this.packedSwitchMap.get(packedSwitchDataAddress+2, -1); 248 } 249 } 250 251 return packedSwitchBaseAddress; 252 } 253 254 public int getSparseSwitchBaseAddress(int sparseSwitchDataAddress) { 255 int sparseSwitchBaseAddress = this.sparseSwitchMap.get(sparseSwitchDataAddress, -1); 256 257 if (sparseSwitchBaseAddress == -1) { 258 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 259 int index = instructionMap.get(sparseSwitchDataAddress); 260 261 if (instructions[index].opcode == Opcode.NOP) { 262 sparseSwitchBaseAddress = this.packedSwitchMap.get(sparseSwitchDataAddress+2, -1); 263 } 264 } 265 266 return sparseSwitchBaseAddress; 267 } 268 269 /** 270 * @param instructions The instructions array for this method 271 * @param instruction The instruction 272 * @return true if the specified instruction is a NOP, and the next instruction is one of the variable sized 273 * switch/array data structures 274 */ 275 private boolean isInstructionPaddingNop(List<AnalyzedInstruction> instructions, AnalyzedInstruction instruction) { 276 if (instruction.getInstruction().opcode != Opcode.NOP || 277 instruction.getInstruction().getFormat().variableSizeFormat) { 278 279 return false; 280 } 281 282 if (instruction.getInstructionIndex() == instructions.size()-1) { 283 return false; 284 } 285 286 AnalyzedInstruction nextInstruction = instructions.get(instruction.getInstructionIndex()+1); 287 if (nextInstruction.getInstruction().getFormat().variableSizeFormat) { 288 return true; 289 } 290 return false; 291 } 292 293 private boolean needsAnalyzed() { 294 for (Instruction instruction: encodedMethod.codeItem.getInstructions()) { 295 if (instruction.opcode.odexOnly()) { 296 return true; 297 } 298 } 299 return false; 300 } 301 302 private List<MethodItem> getMethodItems() { 303 ArrayList<MethodItem> methodItems = new ArrayList<MethodItem>(); 304 305 if (encodedMethod.codeItem == null) { 306 return methodItems; 307 } 308 309 if ((baksmali.registerInfo != 0) || baksmali.verify || 310 (baksmali.deodex && needsAnalyzed())) { 311 addAnalyzedInstructionMethodItems(methodItems); 312 } else { 313 addInstructionMethodItems(methodItems); 314 } 315 316 addTries(methodItems); 317 if (baksmali.outputDebugInfo) { 318 addDebugInfo(methodItems); 319 } 320 321 if (baksmali.useSequentialLabels) { 322 setLabelSequentialNumbers(); 323 } 324 325 for (LabelMethodItem labelMethodItem: labelCache.getLabels()) { 326 methodItems.add(labelMethodItem); 327 } 328 329 Collections.sort(methodItems); 330 331 return methodItems; 332 } 333 334 private void addInstructionMethodItems(List<MethodItem> methodItems) { 335 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 336 337 int currentCodeAddress = 0; 338 for (int i=0; i<instructions.length; i++) { 339 Instruction instruction = instructions[i]; 340 341 MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this, 342 encodedMethod.codeItem, currentCodeAddress, instruction); 343 344 methodItems.add(methodItem); 345 346 if (i != instructions.length - 1) { 347 methodItems.add(new BlankMethodItem(currentCodeAddress)); 348 } 349 350 if (baksmali.addCodeOffsets) { 351 methodItems.add(new MethodItem(currentCodeAddress) { 352 353 @Override 354 public double getSortOrder() { 355 return -1000; 356 } 357 358 @Override 359 public boolean writeTo(IndentingWriter writer) throws IOException { 360 writer.write("#@"); 361 writer.printUnsignedLongAsHex(codeAddress & 0xFFFFFFFF); 362 return true; 363 } 364 }); 365 } 366 367 if (!baksmali.noAccessorComments && (instruction instanceof InstructionWithReference)) { 368 if (instruction.opcode == Opcode.INVOKE_STATIC || instruction.opcode == Opcode.INVOKE_STATIC_RANGE) { 369 MethodIdItem methodIdItem = 370 (MethodIdItem)((InstructionWithReference) instruction).getReferencedItem(); 371 372 if (SyntheticAccessorResolver.looksLikeSyntheticAccessor(methodIdItem)) { 373 SyntheticAccessorResolver.AccessedMember accessedMember = 374 baksmali.syntheticAccessorResolver.getAccessedMember(methodIdItem); 375 if (accessedMember != null) { 376 methodItems.add(new SyntheticAccessCommentMethodItem(accessedMember, currentCodeAddress)); 377 } 378 } 379 } 380 } 381 382 currentCodeAddress += instruction.getSize(currentCodeAddress); 383 } 384 } 385 386 private void addAnalyzedInstructionMethodItems(List<MethodItem> methodItems) { 387 methodAnalyzer = new MethodAnalyzer(encodedMethod, baksmali.deodex, baksmali.inlineResolver); 388 389 methodAnalyzer.analyze(); 390 391 ValidationException validationException = methodAnalyzer.getValidationException(); 392 if (validationException != null) { 393 methodItems.add(new CommentMethodItem( 394 String.format("ValidationException: %s" ,validationException.getMessage()), 395 validationException.getCodeAddress(), Integer.MIN_VALUE)); 396 } else if (baksmali.verify) { 397 methodAnalyzer.verify(); 398 399 validationException = methodAnalyzer.getValidationException(); 400 if (validationException != null) { 401 methodItems.add(new CommentMethodItem( 402 String.format("ValidationException: %s" ,validationException.getMessage()), 403 validationException.getCodeAddress(), Integer.MIN_VALUE)); 404 } 405 } 406 407 List<AnalyzedInstruction> instructions = methodAnalyzer.getInstructions(); 408 409 int currentCodeAddress = 0; 410 for (int i=0; i<instructions.size(); i++) { 411 AnalyzedInstruction instruction = instructions.get(i); 412 413 MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this, 414 encodedMethod.codeItem, currentCodeAddress, instruction.getInstruction()); 415 416 methodItems.add(methodItem); 417 418 if (instruction.getInstruction().getFormat() == Format.UnresolvedOdexInstruction) { 419 methodItems.add(new CommentedOutMethodItem( 420 InstructionMethodItemFactory.makeInstructionFormatMethodItem(this, 421 encodedMethod.codeItem, currentCodeAddress, instruction.getOriginalInstruction()))); 422 } 423 424 if (i != instructions.size() - 1) { 425 methodItems.add(new BlankMethodItem(currentCodeAddress)); 426 } 427 428 if (baksmali.addCodeOffsets) { 429 methodItems.add(new MethodItem(currentCodeAddress) { 430 431 @Override 432 public double getSortOrder() { 433 return -1000; 434 } 435 436 @Override 437 public boolean writeTo(IndentingWriter writer) throws IOException { 438 writer.write("#@"); 439 writer.printUnsignedLongAsHex(codeAddress & 0xFFFFFFFF); 440 return true; 441 } 442 }); 443 } 444 445 if (baksmali.registerInfo != 0 && !instruction.getInstruction().getFormat().variableSizeFormat) { 446 methodItems.add( 447 new PreInstructionRegisterInfoMethodItem(instruction, methodAnalyzer, currentCodeAddress)); 448 449 methodItems.add( 450 new PostInstructionRegisterInfoMethodItem(instruction, methodAnalyzer, currentCodeAddress)); 451 } 452 453 currentCodeAddress += instruction.getInstruction().getSize(currentCodeAddress); 454 } 455 } 456 457 private void addTries(List<MethodItem> methodItems) { 458 if (encodedMethod.codeItem == null || encodedMethod.codeItem.getTries() == null) { 459 return; 460 } 461 462 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 463 int lastInstructionAddress = instructionMap.keyAt(instructionMap.size()-1); 464 int codeSize = lastInstructionAddress + instructions[instructions.length - 1].getSize(lastInstructionAddress); 465 466 for (CodeItem.TryItem tryItem: encodedMethod.codeItem.getTries()) { 467 int startAddress = tryItem.getStartCodeAddress(); 468 int endAddress = tryItem.getStartCodeAddress() + tryItem.getTryLength(); 469 470 if (startAddress >= codeSize) { 471 throw new RuntimeException(String.format("Try start offset %d is past the end of the code block.", 472 startAddress)); 473 } 474 // Note: not >=. endAddress == codeSize is valid, when the try covers the last instruction 475 if (endAddress > codeSize) { 476 throw new RuntimeException(String.format("Try end offset %d is past the end of the code block.", 477 endAddress)); 478 } 479 480 /** 481 * The end address points to the address immediately after the end of the last 482 * instruction that the try block covers. We want the .catch directive and end_try 483 * label to be associated with the last covered instruction, so we need to get 484 * the address for that instruction 485 */ 486 487 int lastCoveredIndex = instructionMap.getClosestSmaller(endAddress-1); 488 int lastCoveredAddress = instructionMap.keyAt(lastCoveredIndex); 489 490 //add the catch all handler if it exists 491 int catchAllAddress = tryItem.encodedCatchHandler.getCatchAllHandlerAddress(); 492 if (catchAllAddress != -1) { 493 if (catchAllAddress >= codeSize) { 494 throw new RuntimeException(String.format( 495 "Catch all handler offset %d is past the end of the code block.", catchAllAddress)); 496 } 497 498 CatchMethodItem catchAllMethodItem = new CatchMethodItem(labelCache, lastCoveredAddress, null, 499 startAddress, endAddress, catchAllAddress); 500 methodItems.add(catchAllMethodItem); 501 } 502 503 //add the rest of the handlers 504 for (CodeItem.EncodedTypeAddrPair handler: tryItem.encodedCatchHandler.handlers) { 505 if (handler.getHandlerAddress() >= codeSize) { 506 throw new RuntimeException(String.format( 507 "Exception handler offset %d is past the end of the code block.", catchAllAddress)); 508 } 509 510 //use the address from the last covered instruction 511 CatchMethodItem catchMethodItem = new CatchMethodItem(labelCache, lastCoveredAddress, 512 handler.exceptionType, startAddress, endAddress, handler.getHandlerAddress()); 513 methodItems.add(catchMethodItem); 514 } 515 } 516 } 517 518 private void addDebugInfo(final List<MethodItem> methodItems) { 519 if (encodedMethod.codeItem == null || encodedMethod.codeItem.getDebugInfo() == null) { 520 return; 521 } 522 523 final CodeItem codeItem = encodedMethod.codeItem; 524 DebugInfoItem debugInfoItem = codeItem.getDebugInfo(); 525 526 DebugInstructionIterator.DecodeInstructions(debugInfoItem, codeItem.getRegisterCount(), 527 new DebugInstructionIterator.ProcessDecodedDebugInstructionDelegate() { 528 @Override 529 public void ProcessStartLocal(final int codeAddress, final int length, final int registerNum, 530 final StringIdItem name, final TypeIdItem type) { 531 methodItems.add(new DebugMethodItem(codeAddress, -1) { 532 @Override 533 public boolean writeTo(IndentingWriter writer) throws IOException { 534 writeStartLocal(writer, codeItem, registerNum, name, type, null); 535 return true; 536 } 537 }); 538 } 539 540 @Override 541 public void ProcessStartLocalExtended(final int codeAddress, final int length, 542 final int registerNum, final StringIdItem name, 543 final TypeIdItem type, final StringIdItem signature) { 544 methodItems.add(new DebugMethodItem(codeAddress, -1) { 545 @Override 546 public boolean writeTo(IndentingWriter writer) throws IOException { 547 writeStartLocal(writer, codeItem, registerNum, name, type, signature); 548 return true; 549 } 550 }); 551 } 552 553 @Override 554 public void ProcessEndLocal(final int codeAddress, final int length, final int registerNum, 555 final StringIdItem name, final TypeIdItem type, 556 final StringIdItem signature) { 557 methodItems.add(new DebugMethodItem(codeAddress, -1) { 558 @Override 559 public boolean writeTo(IndentingWriter writer) throws IOException { 560 writeEndLocal(writer, codeItem, registerNum, name, type, signature); 561 return true; 562 } 563 }); 564 } 565 566 @Override 567 public void ProcessRestartLocal(final int codeAddress, final int length, final int registerNum, 568 final StringIdItem name, final TypeIdItem type, 569 final StringIdItem signature) { 570 methodItems.add(new DebugMethodItem(codeAddress, -1) { 571 @Override 572 public boolean writeTo(IndentingWriter writer) throws IOException { 573 writeRestartLocal(writer, codeItem, registerNum, name, type, signature); 574 return true; 575 } 576 }); 577 } 578 579 @Override 580 public void ProcessSetPrologueEnd(int codeAddress) { 581 methodItems.add(new DebugMethodItem(codeAddress, -4) { 582 @Override 583 public boolean writeTo(IndentingWriter writer) throws IOException { 584 writeEndPrologue(writer); 585 return true; 586 } 587 }); 588 } 589 590 @Override 591 public void ProcessSetEpilogueBegin(int codeAddress) { 592 methodItems.add(new DebugMethodItem(codeAddress, -4) { 593 @Override 594 public boolean writeTo(IndentingWriter writer) throws IOException { 595 writeBeginEpilogue(writer); 596 return true; 597 } 598 }); 599 } 600 601 @Override 602 public void ProcessSetFile(int codeAddress, int length, final StringIdItem name) { 603 methodItems.add(new DebugMethodItem(codeAddress, -3) { 604 @Override 605 public boolean writeTo(IndentingWriter writer) throws IOException { 606 writeSetFile(writer, name.getStringValue()); 607 return true; 608 } 609 }); 610 } 611 612 @Override 613 public void ProcessLineEmit(int codeAddress, final int line) { 614 methodItems.add(new DebugMethodItem(codeAddress, -2) { 615 @Override 616 public boolean writeTo(IndentingWriter writer) throws IOException { 617 writeLine(writer, line); 618 return true; 619 } 620 }); 621 } 622 }); 623 } 624 625 private void setLabelSequentialNumbers() { 626 HashMap<String, Integer> nextLabelSequenceByType = new HashMap<String, Integer>(); 627 ArrayList<LabelMethodItem> sortedLabels = new ArrayList<LabelMethodItem>(labelCache.getLabels()); 628 629 //sort the labels by their location in the method 630 Collections.sort(sortedLabels); 631 632 for (LabelMethodItem labelMethodItem: sortedLabels) { 633 Integer labelSequence = nextLabelSequenceByType.get(labelMethodItem.getLabelPrefix()); 634 if (labelSequence == null) { 635 labelSequence = 0; 636 } 637 labelMethodItem.setLabelSequence(labelSequence); 638 nextLabelSequenceByType.put(labelMethodItem.getLabelPrefix(), labelSequence + 1); 639 } 640 } 641 642 public static class LabelCache { 643 protected HashMap<LabelMethodItem, LabelMethodItem> labels = new HashMap<LabelMethodItem, LabelMethodItem>(); 644 645 public LabelCache() { 646 } 647 648 public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) { 649 LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem); 650 if (internedLabelMethodItem != null) { 651 return internedLabelMethodItem; 652 } 653 labels.put(labelMethodItem, labelMethodItem); 654 return labelMethodItem; 655 } 656 657 658 public Collection<LabelMethodItem> getLabels() { 659 return labels.values(); 660 } 661 } 662 } 663