MethodDefinition.java revision 343df2f456f38c305ee7d6742f6601d9bde09715
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
29package org.jf.baksmali.Adaptors;
30
31import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory;
32import org.jf.dexlib.Code.Analysis.SyntheticAccessorResolver;
33import org.jf.dexlib.Code.InstructionWithReference;
34import org.jf.util.IndentingWriter;
35import org.jf.baksmali.baksmali;
36import org.jf.dexlib.*;
37import org.jf.dexlib.Code.Analysis.AnalyzedInstruction;
38import org.jf.dexlib.Code.Analysis.MethodAnalyzer;
39import org.jf.dexlib.Code.Analysis.ValidationException;
40import org.jf.dexlib.Code.Format.Format;
41import org.jf.dexlib.Code.Instruction;
42import org.jf.dexlib.Code.OffsetInstruction;
43import org.jf.dexlib.Code.Opcode;
44import org.jf.dexlib.Debug.DebugInstructionIterator;
45import org.jf.dexlib.Util.AccessFlags;
46import org.jf.dexlib.Util.ExceptionWithContext;
47import org.jf.dexlib.Util.SparseIntArray;
48
49import java.io.IOException;
50import java.util.*;
51
52public 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
464        for (CodeItem.TryItem tryItem: encodedMethod.codeItem.getTries()) {
465            int startAddress = tryItem.getStartCodeAddress();
466            int endAddress = tryItem.getStartCodeAddress() + tryItem.getTryLength();
467
468            /**
469             * The end address points to the address immediately after the end of the last
470             * instruction that the try block covers. We want the .catch directive and end_try
471             * label to be associated with the last covered instruction, so we need to get
472             * the address for that instruction
473             */
474
475            int index = instructionMap.get(endAddress, -1);
476            int lastInstructionAddress;
477
478            /**
479             * If we couldn't find the index, then the try block probably extends to the last instruction in the
480             * method, and so endAddress would be the address immediately after the end of the last instruction.
481             * Check to make sure this is the case, if not, throw an exception.
482             */
483            if (index == -1) {
484                Instruction lastInstruction = instructions[instructions.length - 1];
485                lastInstructionAddress = instructionMap.keyAt(instructionMap.size() - 1);
486
487                if (endAddress != lastInstructionAddress + lastInstruction.getSize(lastInstructionAddress)) {
488                    throw new RuntimeException("Invalid code offset " + endAddress + " for the try block end address");
489                }
490            } else {
491                if (index == 0) {
492                    throw new RuntimeException("Unexpected instruction index");
493                }
494                Instruction lastInstruction = instructions[index - 1];
495
496                if (lastInstruction.getFormat().variableSizeFormat) {
497                    throw new RuntimeException("This try block unexpectedly ends on a switch/array data block.");
498                }
499
500                //getSize for non-variable size formats should return the same size regardless of code address, so just
501                //use a dummy address of "0"
502                lastInstructionAddress = endAddress - lastInstruction.getSize(0);
503            }
504
505            //add the catch all handler if it exists
506            int catchAllAddress = tryItem.encodedCatchHandler.getCatchAllHandlerAddress();
507            if (catchAllAddress != -1) {
508                CatchMethodItem catchAllMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress, null,
509                        startAddress, endAddress, catchAllAddress);
510                methodItems.add(catchAllMethodItem);
511            }
512
513            //add the rest of the handlers
514            for (CodeItem.EncodedTypeAddrPair handler: tryItem.encodedCatchHandler.handlers) {
515                //use the address from the last covered instruction
516                CatchMethodItem catchMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress,
517                        handler.exceptionType, startAddress, endAddress, handler.getHandlerAddress());
518                methodItems.add(catchMethodItem);
519            }
520        }
521    }
522
523    private void addDebugInfo(final List<MethodItem> methodItems) {
524        if (encodedMethod.codeItem == null || encodedMethod.codeItem.getDebugInfo() == null) {
525            return;
526        }
527
528        final CodeItem codeItem = encodedMethod.codeItem;
529        DebugInfoItem debugInfoItem = codeItem.getDebugInfo();
530
531        DebugInstructionIterator.DecodeInstructions(debugInfoItem, codeItem.getRegisterCount(),
532                new DebugInstructionIterator.ProcessDecodedDebugInstructionDelegate() {
533                    @Override
534                    public void ProcessStartLocal(final int codeAddress, final int length, final int registerNum,
535                                                  final StringIdItem name, final TypeIdItem type) {
536                        methodItems.add(new DebugMethodItem(codeAddress, -1) {
537                            @Override
538                            public boolean writeTo(IndentingWriter writer) throws IOException {
539                                writeStartLocal(writer, codeItem, registerNum, name, type, null);
540                                return true;
541                            }
542                        });
543                    }
544
545                    @Override
546                    public void ProcessStartLocalExtended(final int codeAddress, final int length,
547                                                          final int registerNum, final StringIdItem name,
548                                                          final TypeIdItem type, final StringIdItem signature) {
549                        methodItems.add(new DebugMethodItem(codeAddress, -1) {
550                            @Override
551                            public boolean writeTo(IndentingWriter writer) throws IOException {
552                                writeStartLocal(writer, codeItem, registerNum, name, type, signature);
553                                return true;
554                            }
555                        });
556                    }
557
558                    @Override
559                    public void ProcessEndLocal(final int codeAddress, final int length, final int registerNum,
560                                                final StringIdItem name, final TypeIdItem type,
561                                                final StringIdItem signature) {
562                        methodItems.add(new DebugMethodItem(codeAddress, -1) {
563                            @Override
564                            public boolean writeTo(IndentingWriter writer) throws IOException {
565                                writeEndLocal(writer, codeItem, registerNum, name, type, signature);
566                                return true;
567                            }
568                        });
569                    }
570
571                    @Override
572                    public void ProcessRestartLocal(final int codeAddress, final int length, final int registerNum,
573                                                final StringIdItem name, final TypeIdItem type,
574                                                final StringIdItem signature) {
575                        methodItems.add(new DebugMethodItem(codeAddress, -1) {
576                            @Override
577                            public boolean writeTo(IndentingWriter writer) throws IOException {
578                                writeRestartLocal(writer, codeItem, registerNum, name, type, signature);
579                                return true;
580                            }
581                        });
582                    }
583
584                    @Override
585                    public void ProcessSetPrologueEnd(int codeAddress) {
586                        methodItems.add(new DebugMethodItem(codeAddress, -4) {
587                            @Override
588                            public boolean writeTo(IndentingWriter writer) throws IOException {
589                                writeEndPrologue(writer);
590                                return true;
591                            }
592                        });
593                    }
594
595                    @Override
596                    public void ProcessSetEpilogueBegin(int codeAddress) {
597                        methodItems.add(new DebugMethodItem(codeAddress, -4) {
598                            @Override
599                            public boolean writeTo(IndentingWriter writer) throws IOException {
600                                writeBeginEpilogue(writer);
601                                return true;
602                            }
603                        });
604                    }
605
606                    @Override
607                    public void ProcessSetFile(int codeAddress, int length, final StringIdItem name) {
608                        methodItems.add(new DebugMethodItem(codeAddress, -3) {
609                            @Override
610                            public boolean writeTo(IndentingWriter writer) throws IOException {
611                                writeSetFile(writer, name.getStringValue());
612                                return true;
613                            }
614                        });
615                    }
616
617                    @Override
618                    public void ProcessLineEmit(int codeAddress, final int line) {
619                        methodItems.add(new DebugMethodItem(codeAddress, -2) {
620                            @Override
621                            public boolean writeTo(IndentingWriter writer) throws IOException {
622                                writeLine(writer, line);
623                                return true;
624                            }
625                         });
626                    }
627                });
628    }
629
630    private void setLabelSequentialNumbers() {
631        HashMap<String, Integer> nextLabelSequenceByType = new HashMap<String, Integer>();
632        ArrayList<LabelMethodItem> sortedLabels = new ArrayList<LabelMethodItem>(labelCache.getLabels());
633
634        //sort the labels by their location in the method
635        Collections.sort(sortedLabels);
636
637        for (LabelMethodItem labelMethodItem: sortedLabels) {
638            Integer labelSequence = nextLabelSequenceByType.get(labelMethodItem.getLabelPrefix());
639            if (labelSequence == null) {
640                labelSequence = 0;
641            }
642            labelMethodItem.setLabelSequence(labelSequence);
643            nextLabelSequenceByType.put(labelMethodItem.getLabelPrefix(), labelSequence + 1);
644        }
645    }
646
647    public static class LabelCache {
648        protected HashMap<LabelMethodItem, LabelMethodItem> labels = new HashMap<LabelMethodItem, LabelMethodItem>();
649
650        public LabelCache() {
651        }
652
653        public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) {
654            LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem);
655            if (internedLabelMethodItem != null) {
656                return internedLabelMethodItem;
657            }
658            labels.put(labelMethodItem, labelMethodItem);
659            return labelMethodItem;
660        }
661
662
663        public Collection<LabelMethodItem> getLabels() {
664            return labels.values();
665        }
666    }
667}
668