| /* |
| * [The "BSD licence"] |
| * Copyright (c) 2010 Ben Gruver (JesusFreke) |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
| * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
| * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package org.jf.baksmali.Adaptors; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Lists; |
| import org.jf.baksmali.Adaptors.Debug.DebugMethodItem; |
| import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory; |
| import org.jf.baksmali.BaksmaliOptions; |
| import org.jf.dexlib2.AccessFlags; |
| import org.jf.dexlib2.Format; |
| import org.jf.dexlib2.Opcode; |
| import org.jf.dexlib2.ReferenceType; |
| import org.jf.dexlib2.analysis.AnalysisException; |
| import org.jf.dexlib2.analysis.AnalyzedInstruction; |
| import org.jf.dexlib2.analysis.MethodAnalyzer; |
| import org.jf.dexlib2.dexbacked.DexBackedDexFile.InvalidItemIndex; |
| import org.jf.dexlib2.iface.*; |
| import org.jf.dexlib2.iface.debug.DebugItem; |
| import org.jf.dexlib2.iface.instruction.Instruction; |
| import org.jf.dexlib2.iface.instruction.OffsetInstruction; |
| import org.jf.dexlib2.iface.instruction.ReferenceInstruction; |
| import org.jf.dexlib2.iface.instruction.formats.Instruction31t; |
| import org.jf.dexlib2.iface.reference.MethodReference; |
| import org.jf.dexlib2.immutable.instruction.ImmutableInstruction31t; |
| import org.jf.dexlib2.util.InstructionOffsetMap; |
| import org.jf.dexlib2.util.InstructionOffsetMap.InvalidInstructionOffset; |
| import org.jf.dexlib2.util.ReferenceUtil; |
| import org.jf.dexlib2.util.SyntheticAccessorResolver; |
| import org.jf.dexlib2.util.SyntheticAccessorResolver.AccessedMember; |
| import org.jf.dexlib2.util.TypeUtils; |
| import org.jf.util.ExceptionWithContext; |
| import org.jf.util.IndentingWriter; |
| import org.jf.util.SparseIntArray; |
| |
| import javax.annotation.Nonnull; |
| import javax.annotation.Nullable; |
| import java.io.IOException; |
| import java.util.*; |
| |
| public class MethodDefinition { |
| @Nonnull public final ClassDefinition classDef; |
| @Nonnull public final Method method; |
| @Nonnull public final MethodImplementation methodImpl; |
| @Nonnull public final ImmutableList<Instruction> instructions; |
| @Nonnull public final List<Instruction> effectiveInstructions; |
| |
| @Nonnull public final ImmutableList<MethodParameter> methodParameters; |
| public RegisterFormatter registerFormatter; |
| |
| @Nonnull private final LabelCache labelCache = new LabelCache(); |
| |
| @Nonnull private final SparseIntArray packedSwitchMap; |
| @Nonnull private final SparseIntArray sparseSwitchMap; |
| @Nonnull private final InstructionOffsetMap instructionOffsetMap; |
| |
| public MethodDefinition(@Nonnull ClassDefinition classDef, @Nonnull Method method, |
| @Nonnull MethodImplementation methodImpl) { |
| this.classDef = classDef; |
| this.method = method; |
| this.methodImpl = methodImpl; |
| |
| try { |
| //TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh. |
| |
| instructions = ImmutableList.copyOf(methodImpl.getInstructions()); |
| methodParameters = ImmutableList.copyOf(method.getParameters()); |
| |
| effectiveInstructions = Lists.newArrayList(instructions); |
| |
| packedSwitchMap = new SparseIntArray(0); |
| sparseSwitchMap = new SparseIntArray(0); |
| instructionOffsetMap = new InstructionOffsetMap(instructions); |
| |
| int endOffset = instructionOffsetMap.getInstructionCodeOffset(instructions.size()-1) + |
| instructions.get(instructions.size()-1).getCodeUnits(); |
| |
| for (int i=0; i<instructions.size(); i++) { |
| Instruction instruction = instructions.get(i); |
| |
| Opcode opcode = instruction.getOpcode(); |
| if (opcode == Opcode.PACKED_SWITCH) { |
| boolean valid = true; |
| int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i); |
| int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset(); |
| try { |
| targetOffset = findPayloadOffset(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD); |
| } catch (InvalidSwitchPayload ex) { |
| valid = false; |
| } |
| if (valid) { |
| if (packedSwitchMap.get(targetOffset, -1) != -1) { |
| Instruction payloadInstruction = |
| findSwitchPayload(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD); |
| targetOffset = endOffset; |
| effectiveInstructions.set(i, new ImmutableInstruction31t(opcode, |
| ((Instruction31t)instruction).getRegisterA(), targetOffset-codeOffset)); |
| effectiveInstructions.add(payloadInstruction); |
| endOffset += payloadInstruction.getCodeUnits(); |
| } |
| packedSwitchMap.append(targetOffset, codeOffset); |
| } |
| } else if (opcode == Opcode.SPARSE_SWITCH) { |
| boolean valid = true; |
| int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i); |
| int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset(); |
| try { |
| targetOffset = findPayloadOffset(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD); |
| } catch (InvalidSwitchPayload ex) { |
| valid = false; |
| // The offset to the payload instruction was invalid. Nothing to do, except that we won't |
| // add this instruction to the map. |
| } |
| if (valid) { |
| if (sparseSwitchMap.get(targetOffset, -1) != -1) { |
| Instruction payloadInstruction = |
| findSwitchPayload(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD); |
| targetOffset = endOffset; |
| effectiveInstructions.set(i, new ImmutableInstruction31t(opcode, |
| ((Instruction31t)instruction).getRegisterA(), targetOffset-codeOffset)); |
| effectiveInstructions.add(payloadInstruction); |
| endOffset += payloadInstruction.getCodeUnits(); |
| } |
| sparseSwitchMap.append(targetOffset, codeOffset); |
| } |
| } |
| } |
| } catch (Exception ex) { |
| String methodString; |
| try { |
| methodString = ReferenceUtil.getMethodDescriptor(method); |
| } catch (Exception ex2) { |
| throw ExceptionWithContext.withContext(ex, "Error while processing method"); |
| } |
| throw ExceptionWithContext.withContext(ex, "Error while processing method %s", methodString); |
| } |
| } |
| |
| public static void writeEmptyMethodTo(IndentingWriter writer, Method method, |
| BaksmaliOptions options) throws IOException { |
| writer.write(".method "); |
| writeAccessFlags(writer, method.getAccessFlags()); |
| writer.write(method.getName()); |
| writer.write("("); |
| ImmutableList<MethodParameter> methodParameters = ImmutableList.copyOf(method.getParameters()); |
| for (MethodParameter parameter: methodParameters) { |
| writer.write(parameter.getType()); |
| } |
| writer.write(")"); |
| writer.write(method.getReturnType()); |
| writer.write('\n'); |
| |
| writer.indent(4); |
| writeParameters(writer, method, methodParameters, options); |
| |
| String containingClass = null; |
| if (options.implicitReferences) { |
| containingClass = method.getDefiningClass(); |
| } |
| AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass); |
| |
| writer.deindent(4); |
| writer.write(".end method\n"); |
| } |
| |
| public void writeTo(IndentingWriter writer) throws IOException { |
| int parameterRegisterCount = 0; |
| if (!AccessFlags.STATIC.isSet(method.getAccessFlags())) { |
| parameterRegisterCount++; |
| } |
| |
| writer.write(".method "); |
| writeAccessFlags(writer, method.getAccessFlags()); |
| writer.write(method.getName()); |
| writer.write("("); |
| for (MethodParameter parameter: methodParameters) { |
| String type = parameter.getType(); |
| writer.write(type); |
| parameterRegisterCount++; |
| if (TypeUtils.isWideType(type)) { |
| parameterRegisterCount++; |
| } |
| } |
| writer.write(")"); |
| writer.write(method.getReturnType()); |
| writer.write('\n'); |
| |
| writer.indent(4); |
| if (classDef.options.localsDirective) { |
| writer.write(".locals "); |
| writer.printSignedIntAsDec(methodImpl.getRegisterCount() - parameterRegisterCount); |
| } else { |
| writer.write(".registers "); |
| writer.printSignedIntAsDec(methodImpl.getRegisterCount()); |
| } |
| writer.write('\n'); |
| writeParameters(writer, method, methodParameters, classDef.options); |
| |
| if (registerFormatter == null) { |
| registerFormatter = new RegisterFormatter(classDef.options, methodImpl.getRegisterCount(), |
| parameterRegisterCount); |
| } |
| |
| String containingClass = null; |
| if (classDef.options.implicitReferences) { |
| containingClass = method.getDefiningClass(); |
| } |
| AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass); |
| |
| writer.write('\n'); |
| |
| List<MethodItem> methodItems = getMethodItems(); |
| for (MethodItem methodItem: methodItems) { |
| if (methodItem.writeTo(writer)) { |
| writer.write('\n'); |
| } |
| } |
| writer.deindent(4); |
| writer.write(".end method\n"); |
| } |
| |
| public Instruction findSwitchPayload(int targetOffset, Opcode type) { |
| int targetIndex; |
| try { |
| targetIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(targetOffset); |
| } catch (InvalidInstructionOffset ex) { |
| throw new InvalidSwitchPayload(targetOffset); |
| } |
| |
| //TODO: does dalvik let you pad with multiple nops? |
| //TODO: does dalvik let a switch instruction point to a non-payload instruction? |
| |
| Instruction instruction = instructions.get(targetIndex); |
| if (instruction.getOpcode() != type) { |
| // maybe it's pointing to a NOP padding instruction. Look at the next instruction |
| if (instruction.getOpcode() == Opcode.NOP) { |
| targetIndex += 1; |
| if (targetIndex < instructions.size()) { |
| instruction = instructions.get(targetIndex); |
| if (instruction.getOpcode() == type) { |
| return instruction; |
| } |
| } |
| } |
| throw new InvalidSwitchPayload(targetOffset); |
| } else { |
| return instruction; |
| } |
| } |
| |
| public int findPayloadOffset(int targetOffset, Opcode type) { |
| int targetIndex; |
| try { |
| targetIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(targetOffset); |
| } catch (InvalidInstructionOffset ex) { |
| throw new InvalidSwitchPayload(targetOffset); |
| } |
| |
| //TODO: does dalvik let you pad with multiple nops? |
| //TODO: does dalvik let a switch instruction point to a non-payload instruction? |
| |
| Instruction instruction = instructions.get(targetIndex); |
| if (instruction.getOpcode() != type) { |
| // maybe it's pointing to a NOP padding instruction. Look at the next instruction |
| if (instruction.getOpcode() == Opcode.NOP) { |
| targetIndex += 1; |
| if (targetIndex < instructions.size()) { |
| instruction = instructions.get(targetIndex); |
| if (instruction.getOpcode() == type) { |
| return instructionOffsetMap.getInstructionCodeOffset(targetIndex); |
| } |
| } |
| } |
| throw new InvalidSwitchPayload(targetOffset); |
| } else { |
| return targetOffset; |
| } |
| } |
| |
| private static void writeAccessFlags(IndentingWriter writer, int accessFlags) |
| throws IOException { |
| for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForMethod(accessFlags)) { |
| writer.write(accessFlag.toString()); |
| writer.write(' '); |
| } |
| } |
| |
| private static void writeParameters(IndentingWriter writer, Method method, |
| List<? extends MethodParameter> parameters, |
| BaksmaliOptions options) throws IOException { |
| boolean isStatic = AccessFlags.STATIC.isSet(method.getAccessFlags()); |
| int registerNumber = isStatic?0:1; |
| for (MethodParameter parameter: parameters) { |
| String parameterType = parameter.getType(); |
| String parameterName = parameter.getName(); |
| Collection<? extends Annotation> annotations = parameter.getAnnotations(); |
| if ((options.debugInfo && parameterName != null) || annotations.size() != 0) { |
| writer.write(".param p"); |
| writer.printSignedIntAsDec(registerNumber); |
| |
| if (parameterName != null && options.debugInfo) { |
| writer.write(", "); |
| ReferenceFormatter.writeStringReference(writer, parameterName); |
| } |
| writer.write(" # "); |
| writer.write(parameterType); |
| writer.write("\n"); |
| if (annotations.size() > 0) { |
| writer.indent(4); |
| |
| String containingClass = null; |
| if (options.implicitReferences) { |
| containingClass = method.getDefiningClass(); |
| } |
| AnnotationFormatter.writeTo(writer, annotations, containingClass); |
| writer.deindent(4); |
| writer.write(".end param\n"); |
| } |
| } |
| |
| registerNumber++; |
| if (TypeUtils.isWideType(parameterType)) { |
| registerNumber++; |
| } |
| } |
| } |
| |
| @Nonnull public LabelCache getLabelCache() { |
| return labelCache; |
| } |
| |
| public int getPackedSwitchBaseAddress(int packedSwitchPayloadCodeOffset) { |
| return packedSwitchMap.get(packedSwitchPayloadCodeOffset, -1); |
| } |
| |
| public int getSparseSwitchBaseAddress(int sparseSwitchPayloadCodeOffset) { |
| return sparseSwitchMap.get(sparseSwitchPayloadCodeOffset, -1); |
| } |
| |
| private List<MethodItem> getMethodItems() { |
| ArrayList<MethodItem> methodItems = new ArrayList<MethodItem>(); |
| |
| if ((classDef.options.registerInfo != 0) || (classDef.options.normalizeVirtualMethods) || |
| (classDef.options.deodex && needsAnalyzed())) { |
| addAnalyzedInstructionMethodItems(methodItems); |
| } else { |
| addInstructionMethodItems(methodItems); |
| } |
| |
| addTries(methodItems); |
| if (classDef.options.debugInfo) { |
| addDebugInfo(methodItems); |
| } |
| |
| if (classDef.options.sequentialLabels) { |
| setLabelSequentialNumbers(); |
| } |
| |
| for (LabelMethodItem labelMethodItem: labelCache.getLabels()) { |
| methodItems.add(labelMethodItem); |
| } |
| |
| Collections.sort(methodItems); |
| |
| return methodItems; |
| } |
| |
| private boolean needsAnalyzed() { |
| for (Instruction instruction: methodImpl.getInstructions()) { |
| if (instruction.getOpcode().odexOnly()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void addInstructionMethodItems(List<MethodItem> methodItems) { |
| int currentCodeAddress = 0; |
| |
| for (int i=0; i<effectiveInstructions.size(); i++) { |
| Instruction instruction = effectiveInstructions.get(i); |
| |
| MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this, |
| currentCodeAddress, instruction); |
| |
| methodItems.add(methodItem); |
| |
| if (i != effectiveInstructions.size() - 1) { |
| methodItems.add(new BlankMethodItem(currentCodeAddress)); |
| } |
| |
| if (classDef.options.codeOffsets) { |
| methodItems.add(new MethodItem(currentCodeAddress) { |
| |
| @Override |
| public double getSortOrder() { |
| return -1000; |
| } |
| |
| @Override |
| public boolean writeTo(IndentingWriter writer) throws IOException { |
| writer.write("#@"); |
| writer.printUnsignedLongAsHex(codeAddress & 0xFFFFFFFFL); |
| return true; |
| } |
| }); |
| } |
| |
| if (classDef.options.accessorComments && classDef.options.syntheticAccessorResolver != null && |
| (instruction instanceof ReferenceInstruction)) { |
| Opcode opcode = instruction.getOpcode(); |
| |
| if (opcode.referenceType == ReferenceType.METHOD) { |
| MethodReference methodReference = null; |
| try { |
| methodReference = (MethodReference)((ReferenceInstruction)instruction).getReference(); |
| } catch (InvalidItemIndex ex) { |
| // just ignore it for now. We'll deal with it later, when processing the instructions |
| // themselves |
| } |
| |
| if (methodReference != null && |
| SyntheticAccessorResolver.looksLikeSyntheticAccessor(methodReference.getName())) { |
| AccessedMember accessedMember = |
| classDef.options.syntheticAccessorResolver.getAccessedMember(methodReference); |
| if (accessedMember != null) { |
| methodItems.add(new SyntheticAccessCommentMethodItem(accessedMember, currentCodeAddress)); |
| } |
| } |
| } |
| } |
| |
| currentCodeAddress += instruction.getCodeUnits(); |
| } |
| } |
| |
| private void addAnalyzedInstructionMethodItems(List<MethodItem> methodItems) { |
| MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classDef.options.classPath, method, |
| classDef.options.inlineResolver, classDef.options.normalizeVirtualMethods); |
| |
| AnalysisException analysisException = methodAnalyzer.getAnalysisException(); |
| if (analysisException != null) { |
| // TODO: need to keep track of whether any errors occurred, so we can exit with a non-zero result |
| methodItems.add(new CommentMethodItem( |
| String.format("AnalysisException: %s", analysisException.getMessage()), |
| analysisException.codeAddress, Integer.MIN_VALUE)); |
| analysisException.printStackTrace(System.err); |
| } |
| |
| List<AnalyzedInstruction> instructions = methodAnalyzer.getAnalyzedInstructions(); |
| |
| int currentCodeAddress = 0; |
| for (int i=0; i<instructions.size(); i++) { |
| AnalyzedInstruction instruction = instructions.get(i); |
| |
| MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem( |
| this, currentCodeAddress, instruction.getInstruction()); |
| |
| methodItems.add(methodItem); |
| |
| if (instruction.getInstruction().getOpcode().format == Format.UnresolvedOdexInstruction) { |
| methodItems.add(new CommentedOutMethodItem( |
| InstructionMethodItemFactory.makeInstructionFormatMethodItem( |
| this, currentCodeAddress, instruction.getOriginalInstruction()))); |
| } |
| |
| if (i != instructions.size() - 1) { |
| methodItems.add(new BlankMethodItem(currentCodeAddress)); |
| } |
| |
| if (classDef.options.codeOffsets) { |
| methodItems.add(new MethodItem(currentCodeAddress) { |
| |
| @Override |
| public double getSortOrder() { |
| return -1000; |
| } |
| |
| @Override |
| public boolean writeTo(IndentingWriter writer) throws IOException { |
| writer.write("#@"); |
| writer.printUnsignedLongAsHex(codeAddress & 0xFFFFFFFFL); |
| return true; |
| } |
| }); |
| } |
| |
| if (classDef.options.registerInfo != 0 && |
| !instruction.getInstruction().getOpcode().format.isPayloadFormat) { |
| methodItems.add( |
| new PreInstructionRegisterInfoMethodItem(classDef.options.registerInfo, |
| methodAnalyzer, registerFormatter, instruction, currentCodeAddress)); |
| |
| methodItems.add( |
| new PostInstructionRegisterInfoMethodItem(registerFormatter, instruction, currentCodeAddress)); |
| } |
| |
| currentCodeAddress += instruction.getInstruction().getCodeUnits(); |
| } |
| } |
| |
| private void addTries(List<MethodItem> methodItems) { |
| List<? extends TryBlock<? extends ExceptionHandler>> tryBlocks = methodImpl.getTryBlocks(); |
| if (tryBlocks.size() == 0) { |
| return; |
| } |
| |
| int lastInstructionAddress = instructionOffsetMap.getInstructionCodeOffset(instructions.size() - 1); |
| int codeSize = lastInstructionAddress + instructions.get(instructions.size() - 1).getCodeUnits(); |
| |
| for (TryBlock<? extends ExceptionHandler> tryBlock: tryBlocks) { |
| int startAddress = tryBlock.getStartCodeAddress(); |
| int endAddress = startAddress + tryBlock.getCodeUnitCount(); |
| |
| if (startAddress >= codeSize) { |
| throw new RuntimeException(String.format("Try start offset %d is past the end of the code block.", |
| startAddress)); |
| } |
| // Note: not >=. endAddress == codeSize is valid, when the try covers the last instruction |
| if (endAddress > codeSize) { |
| throw new RuntimeException(String.format("Try end offset %d is past the end of the code block.", |
| endAddress)); |
| } |
| |
| /** |
| * The end address points to the address immediately after the end of the last |
| * instruction that the try block covers. We want the .catch directive and end_try |
| * label to be associated with the last covered instruction, so we need to get |
| * the address for that instruction |
| */ |
| |
| int lastCoveredIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(endAddress - 1, false); |
| int lastCoveredAddress = instructionOffsetMap.getInstructionCodeOffset(lastCoveredIndex); |
| |
| for (ExceptionHandler handler: tryBlock.getExceptionHandlers()) { |
| int handlerAddress = handler.getHandlerCodeAddress(); |
| if (handlerAddress >= codeSize) { |
| throw new ExceptionWithContext( |
| "Exception handler offset %d is past the end of the code block.", handlerAddress); |
| } |
| |
| //use the address from the last covered instruction |
| CatchMethodItem catchMethodItem = new CatchMethodItem(classDef.options, labelCache, lastCoveredAddress, |
| handler.getExceptionType(), startAddress, endAddress, handlerAddress); |
| methodItems.add(catchMethodItem); |
| } |
| } |
| } |
| |
| private void addDebugInfo(final List<MethodItem> methodItems) { |
| for (DebugItem debugItem: methodImpl.getDebugItems()) { |
| methodItems.add(DebugMethodItem.build(registerFormatter, debugItem)); |
| } |
| } |
| |
| private void setLabelSequentialNumbers() { |
| HashMap<String, Integer> nextLabelSequenceByType = new HashMap<String, Integer>(); |
| ArrayList<LabelMethodItem> sortedLabels = new ArrayList<LabelMethodItem>(labelCache.getLabels()); |
| |
| //sort the labels by their location in the method |
| Collections.sort(sortedLabels); |
| |
| for (LabelMethodItem labelMethodItem: sortedLabels) { |
| Integer labelSequence = nextLabelSequenceByType.get(labelMethodItem.getLabelPrefix()); |
| if (labelSequence == null) { |
| labelSequence = 0; |
| } |
| labelMethodItem.setLabelSequence(labelSequence); |
| nextLabelSequenceByType.put(labelMethodItem.getLabelPrefix(), labelSequence + 1); |
| } |
| } |
| |
| @Nullable |
| private String getContainingClassForImplicitReference() { |
| if (classDef.options.implicitReferences) { |
| return classDef.classDef.getType(); |
| } |
| return null; |
| } |
| |
| public static class LabelCache { |
| protected HashMap<LabelMethodItem, LabelMethodItem> labels = new HashMap<LabelMethodItem, LabelMethodItem>(); |
| |
| public LabelCache() { |
| } |
| |
| public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) { |
| LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem); |
| if (internedLabelMethodItem != null) { |
| return internedLabelMethodItem; |
| } |
| labels.put(labelMethodItem, labelMethodItem); |
| return labelMethodItem; |
| } |
| |
| |
| public Collection<LabelMethodItem> getLabels() { |
| return labels.values(); |
| } |
| } |
| |
| public static class InvalidSwitchPayload extends ExceptionWithContext { |
| private final int payloadOffset; |
| |
| public InvalidSwitchPayload(int payloadOffset) { |
| super("No switch payload at offset: %d", payloadOffset); |
| this.payloadOffset = payloadOffset; |
| } |
| |
| public int getPayloadOffset() { |
| return payloadOffset; |
| } |
| } |
| } |