diff --git a/dexlib2/src/main/java/org/jf/dexlib2/builder/BuilderInstruction.java b/dexlib2/src/main/java/org/jf/dexlib2/builder/BuilderInstruction.java index 63f1a68d..d06f7a8b 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/builder/BuilderInstruction.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/builder/BuilderInstruction.java @@ -59,6 +59,7 @@ public abstract class BuilderInstruction implements Instruction { return getFormat().size / 2; } + @Nonnull public MethodLocation getLocation() { if (location == null) { throw new IllegalStateException("Cannot get the location of an instruction that hasn't been added to a " + diff --git a/dexlib2/src/main/java/org/jf/dexlib2/builder/MutableMethodImplementation.java b/dexlib2/src/main/java/org/jf/dexlib2/builder/MutableMethodImplementation.java index 1f0579d0..ad11f270 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/builder/MutableMethodImplementation.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/builder/MutableMethodImplementation.java @@ -4,16 +4,19 @@ import com.google.common.base.Function; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import org.jf.dexlib2.DebugItemType; import org.jf.dexlib2.Opcode; -import org.jf.dexlib2.builder.instruction.BuilderInstruction10x; -import org.jf.dexlib2.builder.instruction.BuilderInstruction20t; -import org.jf.dexlib2.builder.instruction.BuilderInstruction30t; +import org.jf.dexlib2.builder.debug.*; +import org.jf.dexlib2.builder.instruction.*; import org.jf.dexlib2.iface.ExceptionHandler; import org.jf.dexlib2.iface.MethodImplementation; import org.jf.dexlib2.iface.TryBlock; -import org.jf.dexlib2.iface.debug.DebugItem; +import org.jf.dexlib2.iface.debug.*; import org.jf.dexlib2.iface.instruction.Instruction; +import org.jf.dexlib2.iface.instruction.SwitchElement; +import org.jf.dexlib2.iface.instruction.formats.*; import org.jf.dexlib2.iface.reference.TypeReference; +import org.jf.util.ExceptionWithContext; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -26,7 +29,71 @@ public class MutableMethodImplementation implements MethodImplementation { private boolean fixInstructions = true; public MutableMethodImplementation(@Nonnull MethodImplementation methodImplementation) { - throw new UnsupportedOperationException("not implemented yet."); + this.registerCount = methodImplementation.getRegisterCount(); + + int codeAddress = 0; + int index = 0; + + for (Instruction instruction: methodImplementation.getInstructions()) { + codeAddress += instruction.getCodeUnits(); + index++; + + instructionList.add(new MethodLocation(null, codeAddress, index)); + } + + final int[] codeAddressToIndex = new int[codeAddress+1]; + Arrays.fill(codeAddressToIndex, -1); + + for (int i=0; i switchPayloadTasks = Lists.newArrayList(); + index = 0; + for (final Instruction instruction: methodImplementation.getInstructions()) { + final MethodLocation location = instructionList.get(index); + final Opcode opcode = instruction.getOpcode(); + if (opcode == Opcode.PACKED_SWITCH_PAYLOAD || opcode == Opcode.SPARSE_SWITCH_PAYLOAD) { + switchPayloadTasks.add(new Task() { + @Override public void perform() { + convertAndSetInstruction(location, codeAddressToIndex, instruction); + } + }); + } else { + convertAndSetInstruction(location, codeAddressToIndex, instruction); + } + index++; + } + + // the switch instructions must be converted last, so that any switch statements that refer to them have + // created the referring labels that we look for + for (Task switchPayloadTask: switchPayloadTasks) { + switchPayloadTask.perform(); + } + + for (DebugItem debugItem: methodImplementation.getDebugItems()) { + int debugCodeAddress = debugItem.getCodeAddress(); + int locationIndex = mapCodeAddressToIndex(codeAddressToIndex, debugCodeAddress); + MethodLocation debugLocation = instructionList.get(locationIndex); + BuilderDebugItem builderDebugItem = convertDebugItem(debugLocation, debugItem); + debugLocation.getDebugItems().add(builderDebugItem); + builderDebugItem.location = debugLocation; + } + + for (TryBlock tryBlock: methodImplementation.getTryBlocks()) { + Label startLabel = newLabel(codeAddressToIndex, tryBlock.getStartCodeAddress()); + Label endLabel = newLabel(codeAddressToIndex, tryBlock.getStartCodeAddress() + tryBlock.getCodeUnitCount()); + + for (ExceptionHandler exceptionHandler: tryBlock.getExceptionHandlers()) { + tryBlocks.add(new BuilderTryBlock(startLabel, endLabel, + exceptionHandler.getExceptionTypeReference(), + newLabel(codeAddressToIndex, exceptionHandler.getHandlerCodeAddress()))); + } + } + } + + private interface Task { + void perform(); } public MutableMethodImplementation(int registerCount) { @@ -361,4 +428,485 @@ public class MutableMethodImplementation implements MethodImplementation { fixInstructions = false; } + + private int mapCodeAddressToIndex(@Nonnull int[] codeAddressToIndex, int codeAddress) { + int index; + do { + index = codeAddressToIndex[codeAddress]; + if (index < 0) { + codeAddress--; + } else { + return index; + } + } while (true); + } + + @Nonnull + private Label newLabel(@Nonnull int[] codeAddressToIndex, int codeAddress) { + MethodLocation referent = instructionList.get(mapCodeAddressToIndex(codeAddressToIndex, codeAddress)); + return referent.addNewLabel(); + } + + private static class SwitchPayloadReferenceLabel extends Label { + @Nonnull public MethodLocation switchLocation; + } + + @Nonnull + public Label newSwitchPayloadReferenceLabel(@Nonnull int[] codeAddressToIndex, int codeAddress) { + MethodLocation referent = instructionList.get(mapCodeAddressToIndex(codeAddressToIndex, codeAddress)); + Label label = new SwitchPayloadReferenceLabel(); + referent.getLabels().add(label); + return label; + } + + private void setInstruction(@Nonnull MethodLocation location, @Nonnull BuilderInstruction instruction) { + location.instruction = instruction; + instruction.location = location; + } + + private void convertAndSetInstruction(@Nonnull MethodLocation location, int[] codeAddressToIndex, + @Nonnull Instruction instruction) { + switch (instruction.getOpcode().format) { + case Format10t: + setInstruction(location, newBuilderInstruction10t(location.codeAddress, codeAddressToIndex, + (Instruction10t)instruction)); + return; + case Format10x: + setInstruction(location, newBuilderInstruction10x((Instruction10x)instruction)); + return; + case Format11n: + setInstruction(location, newBuilderInstruction11n((Instruction11n)instruction)); + return; + case Format11x: + setInstruction(location, newBuilderInstruction11x((Instruction11x)instruction)); + return; + case Format12x: + setInstruction(location, newBuilderInstruction12x((Instruction12x)instruction)); + return; + case Format20bc: + setInstruction(location, newBuilderInstruction20bc((Instruction20bc)instruction)); + return; + case Format20t: + setInstruction(location, newBuilderInstruction20t(location.codeAddress, codeAddressToIndex, + (Instruction20t)instruction)); + return; + case Format21c: + setInstruction(location, newBuilderInstruction21c((Instruction21c)instruction)); + return; + case Format21ih: + setInstruction(location, newBuilderInstruction21ih((Instruction21ih)instruction)); + return; + case Format21lh: + setInstruction(location, newBuilderInstruction10x((Instruction10x)instruction)); + return; + case Format21s: + setInstruction(location, newBuilderInstruction21s((Instruction21s)instruction)); + return; + case Format21t: + setInstruction(location, newBuilderInstruction21t(location.codeAddress, codeAddressToIndex, + (Instruction21t)instruction)); + return; + case Format22b: + setInstruction(location, newBuilderInstruction22b((Instruction22b)instruction)); + return; + case Format22c: + setInstruction(location, newBuilderInstruction22c((Instruction22c)instruction)); + return; + case Format22s: + setInstruction(location, newBuilderInstruction22s((Instruction22s)instruction)); + return; + case Format22t: + setInstruction(location, newBuilderInstruction22t(location.codeAddress, codeAddressToIndex, + (Instruction22t)instruction)); + return; + case Format22x: + setInstruction(location, newBuilderInstruction22x((Instruction22x)instruction)); + return; + case Format23x: + setInstruction(location, newBuilderInstruction23x((Instruction23x)instruction)); + return; + case Format30t: + setInstruction(location, newBuilderInstruction30t(location.codeAddress, codeAddressToIndex, + (Instruction30t)instruction)); + return; + case Format31c: + setInstruction(location, newBuilderInstruction31c((Instruction31c)instruction)); + return; + case Format31i: + setInstruction(location, newBuilderInstruction31i((Instruction31i)instruction)); + return; + case Format31t: + setInstruction(location, newBuilderInstruction31t(location.codeAddress, codeAddressToIndex, + (Instruction31t)instruction)); + return; + case Format32x: + setInstruction(location, newBuilderInstruction32x((Instruction32x)instruction)); + return; + case Format35c: + setInstruction(location, newBuilderInstruction35c((Instruction35c)instruction)); + return; + case Format3rc: + setInstruction(location, newBuilderInstruction3rc((Instruction3rc)instruction)); + return; + case Format51l: + setInstruction(location, newBuilderInstruction51l((Instruction51l)instruction)); + return; + case PackedSwitchPayload: + setInstruction(location, + newBuilderPackedSwitchPayload(location, codeAddressToIndex, (PackedSwitchPayload)instruction)); + return; + case SparseSwitchPayload: + setInstruction(location, + newBuilderSparseSwitchPayload(location, codeAddressToIndex, (SparseSwitchPayload)instruction)); + case ArrayPayload: + setInstruction(location, newBuilderArrayPayload((ArrayPayload)instruction)); + default: + throw new ExceptionWithContext("Instruction format %s not supported", instruction.getOpcode().format); + } + } + + @Nonnull + private BuilderInstruction10t newBuilderInstruction10t(int codeAddress, int[] codeAddressToIndex, + @Nonnull Instruction10t instruction) { + return new BuilderInstruction10t( + instruction.getOpcode(), + newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset())); + } + + @Nonnull + private BuilderInstruction10x newBuilderInstruction10x(@Nonnull Instruction10x instruction) { + return new BuilderInstruction10x( + instruction.getOpcode()); + } + + @Nonnull + private BuilderInstruction11n newBuilderInstruction11n(@Nonnull Instruction11n instruction) { + return new BuilderInstruction11n( + instruction.getOpcode(), + instruction.getRegisterA(), + instruction.getNarrowLiteral()); + } + + @Nonnull + private BuilderInstruction11x newBuilderInstruction11x(@Nonnull Instruction11x instruction) { + return new BuilderInstruction11x( + instruction.getOpcode(), + instruction.getRegisterA()); + } + + @Nonnull + private BuilderInstruction12x newBuilderInstruction12x(@Nonnull Instruction12x instruction) { + return new BuilderInstruction12x( + instruction.getOpcode(), + instruction.getRegisterA(), + instruction.getRegisterB()); + } + + @Nonnull + private BuilderInstruction20bc newBuilderInstruction20bc(@Nonnull Instruction20bc instruction) { + return new BuilderInstruction20bc( + instruction.getOpcode(), + instruction.getVerificationError(), + instruction.getReference()); + } + + @Nonnull + private BuilderInstruction20t newBuilderInstruction20t(int codeAddress, int[] codeAddressToIndex, + @Nonnull Instruction20t instruction) { + return new BuilderInstruction20t( + instruction.getOpcode(), + newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset())); + } + + @Nonnull + private BuilderInstruction21c newBuilderInstruction21c(@Nonnull Instruction21c instruction) { + return new BuilderInstruction21c( + instruction.getOpcode(), + instruction.getRegisterA(), + instruction.getReference()); + } + + @Nonnull + private BuilderInstruction21ih newBuilderInstruction21ih(@Nonnull Instruction21ih instruction) { + return new BuilderInstruction21ih( + instruction.getOpcode(), + instruction.getRegisterA(), + instruction.getHatLiteral()); + } + + @Nonnull + private BuilderInstruction21lh newBuilderInstruction21lh(@Nonnull Instruction21lh instruction) { + return new BuilderInstruction21lh( + instruction.getOpcode(), + instruction.getRegisterA(), + instruction.getHatLiteral()); + } + + @Nonnull + private BuilderInstruction21s newBuilderInstruction21s(@Nonnull Instruction21s instruction) { + return new BuilderInstruction21s( + instruction.getOpcode(), + instruction.getRegisterA(), + instruction.getNarrowLiteral()); + } + + @Nonnull + private BuilderInstruction21t newBuilderInstruction21t(int codeAddress, int[] codeAddressToIndex, + @Nonnull Instruction21t instruction) { + return new BuilderInstruction21t( + instruction.getOpcode(), + instruction.getRegisterA(), + newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset())); + } + + @Nonnull + private BuilderInstruction22b newBuilderInstruction22b(@Nonnull Instruction22b instruction) { + return new BuilderInstruction22b( + instruction.getOpcode(), + instruction.getRegisterA(), + instruction.getRegisterB(), + instruction.getNarrowLiteral()); + } + + @Nonnull + private BuilderInstruction22c newBuilderInstruction22c(@Nonnull Instruction22c instruction) { + return new BuilderInstruction22c( + instruction.getOpcode(), + instruction.getRegisterA(), + instruction.getRegisterB(), + instruction.getReference()); + } + + @Nonnull + private BuilderInstruction22s newBuilderInstruction22s(@Nonnull Instruction22s instruction) { + return new BuilderInstruction22s( + instruction.getOpcode(), + instruction.getRegisterA(), + instruction.getRegisterB(), + instruction.getNarrowLiteral()); + } + + @Nonnull + private BuilderInstruction22t newBuilderInstruction22t(int codeAddress, int[] codeAddressToIndex, + @Nonnull Instruction22t instruction) { + return new BuilderInstruction22t( + instruction.getOpcode(), + instruction.getRegisterA(), + instruction.getRegisterB(), + newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset())); + } + + @Nonnull + private BuilderInstruction22x newBuilderInstruction22x(@Nonnull Instruction22x instruction) { + return new BuilderInstruction22x( + instruction.getOpcode(), + instruction.getRegisterA(), + instruction.getRegisterB()); + } + + @Nonnull + private BuilderInstruction23x newBuilderInstruction23x(@Nonnull Instruction23x instruction) { + return new BuilderInstruction23x( + instruction.getOpcode(), + instruction.getRegisterA(), + instruction.getRegisterB(), + instruction.getRegisterC()); + } + + @Nonnull + private BuilderInstruction30t newBuilderInstruction30t(int codeAddress, int[] codeAddressToIndex, + @Nonnull Instruction30t instruction) { + return new BuilderInstruction30t( + instruction.getOpcode(), + newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset())); + } + + @Nonnull + private BuilderInstruction31c newBuilderInstruction31c(@Nonnull Instruction31c instruction) { + return new BuilderInstruction31c( + instruction.getOpcode(), + instruction.getRegisterA(), + instruction.getReference()); + } + + @Nonnull + private BuilderInstruction31i newBuilderInstruction31i(@Nonnull Instruction31i instruction) { + return new BuilderInstruction31i( + instruction.getOpcode(), + instruction.getRegisterA(), + instruction.getNarrowLiteral()); + } + + @Nonnull + private BuilderInstruction31t newBuilderInstruction31t(int codeAddress, int[] codeAddressToIndex, + @Nonnull Instruction31t instruction) { + Label newLabel; + if (instruction.getOpcode() != Opcode.FILL_ARRAY_DATA) { + // if it's a sparse switch or packed switch + newLabel = newSwitchPayloadReferenceLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset()); + } else { + newLabel = newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset()); + } + return new BuilderInstruction31t( + instruction.getOpcode(), + instruction.getRegisterA(), + newLabel); + } + + @Nonnull + private BuilderInstruction32x newBuilderInstruction32x(@Nonnull Instruction32x instruction) { + return new BuilderInstruction32x( + instruction.getOpcode(), + instruction.getRegisterA(), + instruction.getRegisterB()); + } + + @Nonnull + private BuilderInstruction35c newBuilderInstruction35c(@Nonnull Instruction35c instruction) { + return new BuilderInstruction35c( + instruction.getOpcode(), + instruction.getRegisterCount(), + instruction.getRegisterC(), + instruction.getRegisterD(), + instruction.getRegisterE(), + instruction.getRegisterF(), + instruction.getRegisterG(), + instruction.getReference()); + } + + @Nonnull + private BuilderInstruction3rc newBuilderInstruction3rc(@Nonnull Instruction3rc instruction) { + return new BuilderInstruction3rc( + instruction.getOpcode(), + instruction.getStartRegister(), + instruction.getRegisterCount(), + instruction.getReference()); + } + + @Nonnull + private BuilderInstruction51l newBuilderInstruction51l(@Nonnull Instruction51l instruction) { + return new BuilderInstruction51l( + instruction.getOpcode(), + instruction.getRegisterA(), + instruction.getWideLiteral()); + } + + @Nullable + private MethodLocation findSwitchForPayload(@Nonnull MethodLocation payloadLocation) { + MethodLocation location = payloadLocation; + MethodLocation switchLocation = null; + do { + for (Label label: location.getLabels()) { + if (label instanceof SwitchPayloadReferenceLabel) { + if (switchLocation != null) { + throw new IllegalStateException("Multiple switch instructions refer to the same payload. " + + "This is not currently supported. Please file a bug :)"); + } + switchLocation = ((SwitchPayloadReferenceLabel)label).switchLocation; + } + } + + // A switch instruction can refer to the payload instruction itself, or to a nop before the payload + // instruction. + // We need to search for all occurrences of a switch reference, so we can detect when multiple switch + // statements refer to the same payload + // TODO: confirm that it could refer to the first NOP in a series of NOPs preceding the payload + if (location.index == 0) { + return switchLocation; + } + location = instructionList.get(location.index - 1); + if (location.instruction == null || location.instruction.getOpcode() != Opcode.NOP) { + return switchLocation; + } + } while (true); + } + + @Nonnull + private BuilderPackedSwitchPayload newBuilderPackedSwitchPayload(@Nonnull MethodLocation location, + @Nonnull int[] codeAddressToIndex, + @Nonnull PackedSwitchPayload instruction) { + List switchElements = instruction.getSwitchElements(); + if (switchElements.size() == 0) { + return new BuilderPackedSwitchPayload(0, null); + } + + MethodLocation switchLocation = findSwitchForPayload(location); + int baseAddress; + if (switchLocation == null) { + baseAddress = 0; + } else { + baseAddress = switchLocation.codeAddress; + } + + List