diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/CodeItemPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/CodeItemPool.java index 909aa177..3a67ad3f 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/writer/CodeItemPool.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/CodeItemPool.java @@ -43,9 +43,9 @@ import org.jf.dexlib2.iface.instruction.ReferenceInstruction; import org.jf.dexlib2.iface.instruction.SwitchElement; import org.jf.dexlib2.iface.instruction.formats.*; import org.jf.dexlib2.iface.reference.*; -import org.jf.dexlib2.util.InstructionUtil; import org.jf.dexlib2.util.MethodUtil; import org.jf.dexlib2.util.ReferenceUtil; +import org.jf.dexlib2.writer.util.InstructionWriteUtil; import org.jf.util.ExceptionWithContext; import javax.annotation.Nonnull; @@ -149,30 +149,15 @@ public class CodeItemPool { writer.writeUshort(methodImpl.getRegisterCount()); writer.writeUshort(MethodUtil.getParameterRegisterCount(method, MethodUtil.isStatic(method))); - int maxOutParamCount = 0; - int codeUnitCount = 0; - for (Instruction instruction: methodImpl.getInstructions()) { - codeUnitCount += instruction.getCodeUnits(); - if (instruction.getOpcode().referenceType == ReferenceType.METHOD) { - ReferenceInstruction refInsn = (ReferenceInstruction)instruction; - MethodReference methodRef = (MethodReference)refInsn.getReference(); - int paramCount = MethodUtil.getParameterRegisterCount(methodRef, - InstructionUtil.isInvokeStatic(instruction.getOpcode())); - if (paramCount > maxOutParamCount) { - maxOutParamCount = paramCount; - } - } - } - writer.writeUshort(maxOutParamCount); + InstructionWriteUtil instrWriteUtil = new InstructionWriteUtil(methodImpl, dexFile.stringPool); + writer.writeUshort(instrWriteUtil.getOutParamCount()); List tryBlocks = methodImpl.getTryBlocks(); writer.writeUshort(tryBlocks.size()); writer.writeInt(dexFile.debugInfoPool.getOffset(method)); - writer.writeInt(codeUnitCount); + writer.writeInt(instrWriteUtil.getCodeUnitCount()); - // TODO: need to fix up instructions. Add alignment nops, convert to const-string/jumbos, etc. - - for (Instruction instruction: methodImpl.getInstructions()) { + for (Instruction instruction: instrWriteUtil.getInstructions()) { switch (instruction.getOpcode().format) { case Format10t: writeFormat10t(writer, (Instruction10t)instruction); @@ -274,8 +259,15 @@ public class CodeItemPool { DexWriter.writeUleb128(ehBuf, exceptionHandlerOffsetMap.size()); for (TryBlock tryBlock: tryBlocks) { - writer.writeInt(tryBlock.getStartCodeAddress()); - writer.writeUshort(tryBlock.getCodeUnitCount()); + int startAddress = tryBlock.getStartCodeAddress(); + int endAddress = startAddress + tryBlock.getCodeUnitCount(); + + startAddress += instrWriteUtil.codeOffsetShift(startAddress); + endAddress += instrWriteUtil.codeOffsetShift(endAddress); + int tbCodeUnitCount = endAddress - startAddress; + + writer.writeInt(startAddress); + writer.writeUshort(tbCodeUnitCount); if (tryBlock.getExceptionHandlers().size() == 0) { throw new ExceptionWithContext("No exception handlers for the try block!"); @@ -303,6 +295,7 @@ public class CodeItemPool { for (ExceptionHandler eh : tryBlock.getExceptionHandlers()) { String exceptionType = eh.getExceptionType(); int codeAddress = eh.getHandlerCodeAddress(); + codeAddress += instrWriteUtil.codeOffsetShift(codeAddress); if (exceptionType != null) { //regular exception handling diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/StringPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/StringPool.java index 5af1a276..7a31f2df 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/writer/StringPool.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/StringPool.java @@ -46,7 +46,7 @@ import java.util.Map; public class StringPool { public final static int STRING_ID_ITEM_SIZE = 0x04; - @Nonnull private final Map internedStringIdItems = Maps.newHashMap(); + @Nonnull protected final Map internedStringIdItems = Maps.newHashMap(); private int indexSectionOffset = -1; private int dataSectionOffset = -1; diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/util/InstructionWriteUtil.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/util/InstructionWriteUtil.java new file mode 100644 index 00000000..3323b4d3 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/util/InstructionWriteUtil.java @@ -0,0 +1,397 @@ +/* + * Copyright 2013, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.dexlib2.writer.util; + +import com.google.common.collect.Lists; +import org.jf.dexlib2.Format; +import org.jf.dexlib2.Opcode; +import org.jf.dexlib2.ReferenceType; +import org.jf.dexlib2.iface.MethodImplementation; +import org.jf.dexlib2.iface.instruction.Instruction; +import org.jf.dexlib2.iface.instruction.ReferenceInstruction; +import org.jf.dexlib2.iface.instruction.SwitchElement; +import org.jf.dexlib2.iface.instruction.SwitchPayload; +import org.jf.dexlib2.iface.instruction.formats.*; +import org.jf.dexlib2.iface.reference.*; +import org.jf.dexlib2.immutable.instruction.*; +import org.jf.dexlib2.util.InstructionUtil; +import org.jf.dexlib2.util.MethodUtil; +import org.jf.dexlib2.writer.StringPool; +import org.jf.util.ExceptionWithContext; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class InstructionWriteUtil { + private final StringPool stringPool; + MethodImplementation methodImplementation; + + private List instructions; + private ArrayList codeOffsetShifts; + private HashMap offsetToNewInstructionMap; + + private int codeUnitCount; + private int outParamCount; + + public InstructionWriteUtil(@Nonnull MethodImplementation methodImpl, @Nonnull StringPool stringPool) { + this.stringPool = stringPool; + methodImplementation = methodImpl; + + calculateMaxOutParamCount(); + findCodeOffsetShifts(); + modifyInstructions(); + } + + private void calculateMaxOutParamCount() { + for (Instruction instruction: methodImplementation.getInstructions()) { + codeUnitCount += instruction.getCodeUnits(); + if (instruction.getOpcode().referenceType == ReferenceType.METHOD) { + ReferenceInstruction refInsn = (ReferenceInstruction)instruction; + MethodReference methodRef = (MethodReference)refInsn.getReference(); + int paramCount = MethodUtil.getParameterRegisterCount(methodRef, InstructionUtil.isInvokeStatic(instruction.getOpcode())); + if (paramCount > outParamCount) { + outParamCount = paramCount; + } + } + } + } + + public Iterable getInstructions() { + if (instructions != null) { + return instructions; + } else { + return methodImplementation.getInstructions(); + } + } + + public int getCodeUnitCount() { + return codeUnitCount; + } + + public int getOutParamCount() { + return outParamCount; + } + + private int targetOffsetShift(int instrOffset, int targetOffset) { + int targetOffsetShift = 0; + if (codeOffsetShifts != null) { + int instrShift = codeOffsetShift(instrOffset); + int targetShift = codeOffsetShift(instrOffset+targetOffset); + targetOffsetShift = targetShift - instrShift; + } + return targetOffsetShift; + } + + public int codeOffsetShift(int offset) { + int shift = 0; + if (codeOffsetShifts != null) { + int numCodeOffsetShifts = codeOffsetShifts.size(); + if (numCodeOffsetShifts > 0) { + if (offset >= codeOffsetShifts.get(numCodeOffsetShifts-1)) { + shift = numCodeOffsetShifts; + } else if (numCodeOffsetShifts>1) { + for (int i=1;i= codeOffsetShifts.get(i-1) && offset < codeOffsetShifts.get(i)) { + shift = i; + break; + } + } + } + } + } + return shift; + } + + /* + * This method creates a list of code offsets of instructions, whose (and subsequent instructions') + * code offset will get shifted by one code unit with respect to previous instruction(s). + * This happens when the previous instruction has to be changed to a larger sized one + * to fit the new value or payload instruction has to be prepended by nop to ensure alignment. + */ + private void findCodeOffsetShifts() { + // first, process const-string to const-string/jumbo conversions + int currentCodeOffset = 0; + for (Instruction instruction: methodImplementation.getInstructions()) { + if (instruction.getOpcode().equals(Opcode.CONST_STRING)) { + ReferenceInstruction refInstr = (ReferenceInstruction) instruction; + int referenceIndex = stringPool.getIndex((StringReference)refInstr.getReference()); + if (referenceIndex > 0xFFFF) { + if (codeOffsetShifts == null) { + codeOffsetShifts = new ArrayList(); + } + if (offsetToNewInstructionMap == null) { + offsetToNewInstructionMap = new HashMap(); + } + codeOffsetShifts.add(currentCodeOffset+instruction.getCodeUnits()); + offsetToNewInstructionMap.put(currentCodeOffset, Opcode.CONST_STRING_JUMBO.format); + } + } + currentCodeOffset += instruction.getCodeUnits(); + } + + if (codeOffsetShifts == null) { + return; + } + + // next, let's check if this caused any conversions in goto instructions due to changes in offset values + // since code offset delta is equivalent to the position of instruction's code offset in the shift list, + // we use it as a position here + // we also check if we will have to insert nops to ensure 4-byte alignment for switch statements and packed arrays + boolean shiftsInserted; + do { + currentCodeOffset = 0; + shiftsInserted = false; + for (Instruction instruction: methodImplementation.getInstructions()) { + if (instruction.getOpcode().format.equals(Format.Format10t) && !offsetToNewInstructionMap.containsKey(currentCodeOffset)) { + int targetOffset = ((Instruction10t)instruction).getCodeOffset(); + int codeOffsetDelta = codeOffsetShift(currentCodeOffset); + int newTargetOffset = targetOffset + targetOffsetShift(currentCodeOffset, targetOffset); + if ((byte)newTargetOffset != newTargetOffset) { + if ((short)newTargetOffset != newTargetOffset) { + // handling very small (negligible) possibility of goto becoming goto/32 + // we insert extra 1 code unit shift referring to the same position + // this will cause subsequent code offsets to be shifted by 2 code units + codeOffsetShifts.add(codeOffsetDelta, currentCodeOffset+instruction.getCodeUnits()); + offsetToNewInstructionMap.put(currentCodeOffset, Format.Format30t); + } else { + offsetToNewInstructionMap.put(currentCodeOffset, Format.Format20t); + } + codeOffsetShifts.add(codeOffsetDelta, currentCodeOffset+instruction.getCodeUnits()); + shiftsInserted = true; + } + } else if (instruction.getOpcode().format.equals(Format.Format20t) && !offsetToNewInstructionMap.containsKey(currentCodeOffset)) { + int targetOffset = ((Instruction20t)instruction).getCodeOffset(); + int codeOffsetDelta = codeOffsetShift(currentCodeOffset); + int newTargetOffset = targetOffset + targetOffsetShift(currentCodeOffset, targetOffset); + if ((short)newTargetOffset != newTargetOffset) { + codeOffsetShifts.add(codeOffsetDelta, currentCodeOffset+instruction.getCodeUnits()); + offsetToNewInstructionMap.put(currentCodeOffset, Format.Format30t); + shiftsInserted = true; + } + } else if (instruction.getOpcode().format.equals(Format.ArrayPayload) + || instruction.getOpcode().format.equals(Format.SparseSwitchPayload) + || instruction.getOpcode().format.equals(Format.PackedSwitchPayload)) { + int codeOffsetDelta = codeOffsetShift(currentCodeOffset); + if ((currentCodeOffset+codeOffsetDelta)%2 != 0) { + if (codeOffsetShifts.contains(currentCodeOffset)) { + codeOffsetShifts.remove(codeOffsetDelta-1); + offsetToNewInstructionMap.remove(currentCodeOffset); + } else { + codeOffsetShifts.add(codeOffsetDelta, currentCodeOffset); + offsetToNewInstructionMap.put(currentCodeOffset, Format.Format10x); + shiftsInserted = true; + } + } + } + currentCodeOffset += instruction.getCodeUnits(); + } + } while (shiftsInserted); + + codeUnitCount += codeOffsetShifts.size(); + } + + private void modifyInstructions() { + if (codeOffsetShifts == null) { + return; + } + + instructions = Lists.newArrayList(); + int currentCodeOffset = 0; + for (Instruction instruction: methodImplementation.getInstructions()) { + Instruction modifiedInstruction = null; + switch (instruction.getOpcode().format) { + case Format10t: { + Instruction10t instr = (Instruction10t)instruction; + int targetOffset = instr.getCodeOffset(); + int newTargetOffset = targetOffset + targetOffsetShift(currentCodeOffset, targetOffset); + Format newInstructionFormat = offsetToNewInstructionMap.get(currentCodeOffset); + if (newInstructionFormat != null) { + if (newInstructionFormat.equals(Format.Format30t)) { + modifiedInstruction = new ImmutableInstruction30t(Opcode.GOTO_32, newTargetOffset); + } else if (newInstructionFormat.equals(Format.Format20t)) { + modifiedInstruction = new ImmutableInstruction20t(Opcode.GOTO_16, newTargetOffset); + } + } else if (newTargetOffset != targetOffset) { + modifiedInstruction = new ImmutableInstruction10t(instr.getOpcode(), newTargetOffset); + } + break; + } + case Format20t: { + Instruction20t instr = (Instruction20t)instruction; + int targetOffset = instr.getCodeOffset(); + int newTargetOffset = targetOffset + targetOffsetShift(currentCodeOffset, targetOffset); + Format newInstructionFormat = offsetToNewInstructionMap.get(currentCodeOffset); + if (newInstructionFormat != null && newInstructionFormat.equals(Format.Format30t)) { + modifiedInstruction = new ImmutableInstruction30t(Opcode.GOTO_32, newTargetOffset); + } else if (newTargetOffset != targetOffset) { + modifiedInstruction = new ImmutableInstruction20t(Opcode.GOTO_16, newTargetOffset); + } + break; + } + case Format21c: { + Instruction21c instr = (Instruction21c)instruction; + if (instr.getOpcode().equals(Opcode.CONST_STRING)) { + int referenceIndex = stringPool.getIndex((StringReference)instr.getReference()); + if (referenceIndex > 0xFFFF) { + modifiedInstruction = new ImmutableInstruction31c(Opcode.CONST_STRING_JUMBO, instr.getRegisterA(), instr.getReference()); + } + } + break; + } + case Format21t: { + Instruction21t instr = (Instruction21t)instruction; + int targetOffset = instr.getCodeOffset(); + int newTargetOffset = targetOffset + targetOffsetShift(currentCodeOffset, targetOffset); + if (newTargetOffset != targetOffset) { + modifiedInstruction = new ImmutableInstruction21t(instr.getOpcode(), instr.getRegisterA(), newTargetOffset); + } + break; + } + case Format22t: { + Instruction22t instr = (Instruction22t)instruction; + int targetOffset = instr.getCodeOffset(); + int newTargetOffset = targetOffset + targetOffsetShift(currentCodeOffset, targetOffset); + if (newTargetOffset != targetOffset) { + modifiedInstruction = new ImmutableInstruction22t(instr.getOpcode(), instr.getRegisterA(), instr.getRegisterB(), newTargetOffset); + } + break; + } + case Format30t: { + Instruction30t instr = (Instruction30t)instruction; + int targetOffset = instr.getCodeOffset(); + int newTargetOffset = targetOffset + targetOffsetShift(currentCodeOffset, targetOffset); + if (newTargetOffset != targetOffset) { + modifiedInstruction = new ImmutableInstruction30t(instr.getOpcode(), newTargetOffset); + } + break; + } + case Format31t: { + Instruction31t instr = (Instruction31t)instruction; + int targetOffset = instr.getCodeOffset(); + int newTargetOffset = targetOffset + targetOffsetShift(currentCodeOffset, targetOffset); + if (newTargetOffset != targetOffset) { + modifiedInstruction = new ImmutableInstruction31t(instr.getOpcode(), instr.getRegisterA(), newTargetOffset); + } + break; + } + case SparseSwitchPayload: { + alignPayload(currentCodeOffset); + int switchInstructionOffset = findSwitchInstructionOffset(currentCodeOffset); + SwitchPayload payload = (SwitchPayload)instruction; + if (isSwitchTargetOffsetChanged(payload, switchInstructionOffset)) { + List newSwitchElements = modifySwitchElements(payload, switchInstructionOffset); + modifiedInstruction = new ImmutableSparseSwitchPayload(newSwitchElements); + } + break; + } + case PackedSwitchPayload: { + alignPayload(currentCodeOffset); + int switchInstructionOffset = findSwitchInstructionOffset(currentCodeOffset); + SwitchPayload payload = (SwitchPayload)instruction; + if (isSwitchTargetOffsetChanged(payload, switchInstructionOffset)) { + List newSwitchElements = modifySwitchElements(payload, switchInstructionOffset); + modifiedInstruction = new ImmutablePackedSwitchPayload(newSwitchElements); + } + break; + } + case ArrayPayload: { + alignPayload(currentCodeOffset); + break; + } + } + + if (modifiedInstruction != null) { + instructions.add(modifiedInstruction); + } else { + instructions.add(instruction); + } + + currentCodeOffset += instruction.getCodeUnits(); + } + } + + private void alignPayload(int codeOffset) { + Format newInstructionFormat = offsetToNewInstructionMap.get(codeOffset); + if (newInstructionFormat != null && newInstructionFormat.equals(Format.Format10x)) { + instructions.add(new ImmutableInstruction10x(Opcode.NOP)); + } + } + + private int findSwitchInstructionOffset(int payloadOffset) { + int currentCodeOffset = 0; + int switchInstructionOffset = -1; + for (Instruction instruction: methodImplementation.getInstructions()) { + if (instruction.getOpcode().equals(Opcode.PACKED_SWITCH) + || instruction.getOpcode().equals(Opcode.SPARSE_SWITCH)) { + int targetOffset = currentCodeOffset + ((Instruction31t)instruction).getCodeOffset(); + if (targetOffset == payloadOffset) { + if (switchInstructionOffset < 0) { + switchInstructionOffset = currentCodeOffset; + } else { + throw new ExceptionWithContext("Multiple switch instructions refer to the same switch payload!"); + } + } + } + currentCodeOffset += instruction.getCodeUnits(); + } + return switchInstructionOffset; + } + + private boolean isSwitchTargetOffsetChanged(SwitchPayload payload, int switchInstructionOffset) { + for (SwitchElement switchElement: payload.getSwitchElements()) { + if (targetOffsetShift(switchInstructionOffset, switchElement.getOffset()) != 0) { + return true; + } + } + return false; + } + + private ArrayList modifySwitchElements(SwitchPayload payload, int switchInstructionOffset) { + ArrayList switchElements = Lists.newArrayList(); + for (SwitchElement switchElement: payload.getSwitchElements()) { + int targetOffset = switchElement.getOffset(); + int newTargetOffset = targetOffset + targetOffsetShift(switchInstructionOffset, targetOffset); + if (newTargetOffset != targetOffset) { + ImmutableSwitchElement immuSwitchElement = new ImmutableSwitchElement(switchElement.getKey(), newTargetOffset); + switchElements.add(immuSwitchElement); + } else { + switchElements.add(switchElement); + } + } + return switchElements; + } + +} + + diff --git a/dexlib2/src/test/java/org/jf/dexlib2/writer/JumboStringConversionTest.java b/dexlib2/src/test/java/org/jf/dexlib2/writer/JumboStringConversionTest.java new file mode 100644 index 00000000..5e0cf8f7 --- /dev/null +++ b/dexlib2/src/test/java/org/jf/dexlib2/writer/JumboStringConversionTest.java @@ -0,0 +1,365 @@ +/* + * Copyright 2012, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.dexlib2.writer; + +import com.google.common.collect.Lists; +import junit.framework.Assert; +import org.jf.dexlib2.Opcode; +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.immutable.ImmutableMethodImplementation; +import org.jf.dexlib2.immutable.instruction.*; +import org.jf.dexlib2.immutable.reference.ImmutableStringReference; +import org.jf.dexlib2.writer.util.InstructionWriteUtil; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; + +public class JumboStringConversionTest { + private static final int MIN_NUM_JUMBO_STRINGS = 2; + + private MockStringPool mStringPool; + ArrayList mJumboStrings; + + @Before + public void setup() { + mStringPool = new MockStringPool(); + StringBuilder stringBuilder = new StringBuilder("a"); + mJumboStrings = Lists.newArrayList(); + int index = 0; + + // populate StringPool, make sure there are more than 64k+MIN_NUM_JUMBO_STRINGS strings + while (mJumboStrings.size()=0;pos--) { + for (char ch='a';ch<='z';ch++) { + stringBuilder.setCharAt(pos, ch); + mStringPool.intern(stringBuilder.toString(), index++); + if (mStringPool.getNumItems()>0xFFFF) { + mJumboStrings.add(stringBuilder.toString()); + } + } + } + + stringBuilder.setLength(stringBuilder.length()+1); + for (int pos=0;pos instructions = Lists.newArrayList(); + instructions.add(new ImmutableInstruction21c(Opcode.CONST_STRING, 0, new ImmutableStringReference(mJumboStrings.get(0)))); + + ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null); + InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool); + + for (Instruction instr: writeUtil.getInstructions()) { + Assert.assertEquals("Jumbo string conversion was not performed!", instr.getOpcode(), Opcode.CONST_STRING_JUMBO); + } + } + + private ArrayList createSimpleInstructionList() { + ArrayList instructions = Lists.newArrayList(); + instructions.add(new ImmutableInstruction21c(Opcode.CONST_STRING, 0, new ImmutableStringReference(mJumboStrings.get(0)))); + instructions.add(new ImmutableInstruction21c(Opcode.CONST_STRING, 0, new ImmutableStringReference(mJumboStrings.get(1)))); + instructions.add(new ImmutableInstruction10x(Opcode.NOP)); + + ArrayList switchElements = Lists.newArrayList(); + switchElements.add(new ImmutableSwitchElement(0, 5)); + instructions.add(new ImmutablePackedSwitchPayload(switchElements)); + instructions.add(new ImmutableSparseSwitchPayload(switchElements)); + + return instructions; + } + + @Test + public void testInstruction10tSimple() { + ArrayList instructions = createSimpleInstructionList(); + instructions.add(1, new ImmutableInstruction10t(Opcode.GOTO, 3)); + + ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null); + InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool); + + for (Instruction instr: writeUtil.getInstructions()) { + if (instr instanceof Instruction10t) { + Instruction10t instruction = (Instruction10t) instr; + Assert.assertEquals("goto (Format10t) target was not modified properly", instruction.getCodeOffset(), 4); + break; + } + } + } + + @Test + public void testInstruction20tSimple() { + ArrayList instructions = createSimpleInstructionList(); + instructions.add(1, new ImmutableInstruction20t(Opcode.GOTO_16, 4)); + + ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null); + InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool); + + for (Instruction instr: writeUtil.getInstructions()) { + if (instr instanceof Instruction20t) { + Instruction20t instruction = (Instruction20t) instr; + Assert.assertEquals("goto/16 (Format20t) target was not modified properly", instruction.getCodeOffset(), 5); + break; + } + } + } + + @Test + public void testInstruction30t() { + ArrayList instructions = createSimpleInstructionList(); + instructions.add(1, new ImmutableInstruction30t(Opcode.GOTO_32, 5)); + + ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null); + InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool); + + for (Instruction instr: writeUtil.getInstructions()) { + if (instr instanceof Instruction30t) { + Instruction30t instruction = (Instruction30t) instr; + Assert.assertEquals("goto/32 (Format30t) target was not modified properly", instruction.getCodeOffset(), 6); + break; + } + } + } + + @Test + public void testInstruction21t() { + ArrayList instructions = createSimpleInstructionList(); + instructions.add(1, new ImmutableInstruction21t(Opcode.IF_EQZ, 0, 4)); + + ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null); + InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool); + + for (Instruction instr: writeUtil.getInstructions()) { + if (instr instanceof Instruction21t) { + Instruction21t instruction = (Instruction21t) instr; + Assert.assertEquals("branch instruction (Format21t) target was not modified properly", instruction.getCodeOffset(), 5); + break; + } + } + } + + @Test + public void testInstruction22t() { + ArrayList instructions = createSimpleInstructionList(); + instructions.add(1, new ImmutableInstruction22t(Opcode.IF_EQ, 0, 1, 4)); + + ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null); + InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool); + + for (Instruction instr: writeUtil.getInstructions()) { + if (instr instanceof Instruction22t) { + Instruction22t instruction = (Instruction22t) instr; + Assert.assertEquals("branch instruction (Format22t) target was not modified properly", instruction.getCodeOffset(), 5); + break; + } + } + } + + @Test + public void testInstruction31t() { + ArrayList instructions = createSimpleInstructionList(); + instructions.add(1, new ImmutableInstruction31t(Opcode.PACKED_SWITCH, 0, 5)); + + ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null); + InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool); + + for (Instruction instr: writeUtil.getInstructions()) { + if (instr instanceof Instruction31t) { + Instruction31t instruction = (Instruction31t) instr; + Assert.assertEquals("branch instruction (Format31t) target was not modified properly", instruction.getCodeOffset(), 6); + break; + } + } + } + + @Test + public void testPackedSwitchPayload() { + ArrayList instructions = createSimpleInstructionList(); + instructions.add(1, new ImmutableInstruction31t(Opcode.PACKED_SWITCH, 0, 6)); + + ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null); + InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool); + + for (Instruction instr: writeUtil.getInstructions()) { + if (instr instanceof PackedSwitchPayload) { + PackedSwitchPayload instruction = (PackedSwitchPayload) instr; + for (SwitchElement switchElement: instruction.getSwitchElements()) { + Assert.assertEquals("packed switch payload offset was not modified properly", switchElement.getOffset(), 6); + } + break; + } + } + } + + @Test + public void testSparseSwitchPayload() { + ArrayList instructions = createSimpleInstructionList(); + instructions.add(1, new ImmutableInstruction31t(Opcode.SPARSE_SWITCH, 0, 12)); + + ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null); + InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool); + + for (Instruction instr: writeUtil.getInstructions()) { + if (instr instanceof SparseSwitchPayload) { + SparseSwitchPayload instruction = (SparseSwitchPayload) instr; + for (SwitchElement switchElement: instruction.getSwitchElements()) { + Assert.assertEquals("packed switch payload offset was not modified properly", switchElement.getOffset(), 6); + } + break; + } + } + } + + @Test + public void testArrayPayloadAlignment() { + ArrayList instructions = createSimpleInstructionList(); + // add misaligned array payload + instructions.add(new ImmutableInstruction10x(Opcode.NOP)); + instructions.add(new ImmutableArrayPayload(4, null)); + + ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null); + InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool); + + int codeOffset = 0; + for (Instruction instr: writeUtil.getInstructions()) { + if (codeOffset == 21) { + Assert.assertEquals("array payload was not aligned properly", instr.getOpcode(), Opcode.NOP); + } + codeOffset += instr.getCodeUnits(); + } + } + + @Test + public void testPackedSwitchAlignment() { + ArrayList instructions = createSimpleInstructionList(); + // packed switch instruction is already misaligned + + ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null); + InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool); + + int codeOffset = 0; + for (Instruction instr: writeUtil.getInstructions()) { + if (codeOffset == 7) { + Assert.assertEquals("packed switch payload was not aligned properly", instr.getOpcode(), Opcode.NOP); + } + codeOffset += instr.getCodeUnits(); + } + } + + @Test + public void testSparseSwitchAlignment() { + ArrayList instructions = createSimpleInstructionList(); + // insert a nop to mis-align sparse switch payload + instructions.add(4, new ImmutableInstruction10x(Opcode.NOP)); + + ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null); + InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool); + + int codeOffset = 0; + for (Instruction instr: writeUtil.getInstructions()) { + if (codeOffset == 15) { + Assert.assertEquals("packed switch payload was not aligned properly", instr.getOpcode(), Opcode.NOP); + } + codeOffset += instr.getCodeUnits(); + } + } + + @Test + public void testGotoToGoto16() { + ArrayList instructions = Lists.newArrayList(); + instructions.add(new ImmutableInstruction10t(Opcode.GOTO, 127)); + instructions.add(new ImmutableInstruction21c(Opcode.CONST_STRING, 0, new ImmutableStringReference(mJumboStrings.get(0)))); + for (int i=0;i<127;i++) { + instructions.add(new ImmutableInstruction10x(Opcode.NOP)); + } + + ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null); + InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool); + + Instruction instr = writeUtil.getInstructions().iterator().next(); + Assert.assertEquals("goto was not converted to goto/16 properly", instr.getOpcode(), Opcode.GOTO_16); + } + + @Test + public void testGoto16ToGoto32() { + ArrayList instructions = Lists.newArrayList(); + instructions.add(new ImmutableInstruction20t(Opcode.GOTO_16, Short.MAX_VALUE)); + instructions.add(new ImmutableInstruction21c(Opcode.CONST_STRING, 0, new ImmutableStringReference(mJumboStrings.get(0)))); + for (int i=0;i instructions = Lists.newArrayList(); + + instructions.add(new ImmutableInstruction10t(Opcode.GOTO, 126)); + instructions.add(new ImmutableInstruction10t(Opcode.GOTO, 127)); + instructions.add(new ImmutableInstruction21c(Opcode.CONST_STRING, 0, new ImmutableStringReference(mJumboStrings.get(0)))); + for (int i=0;i<122;i++) { + instructions.add(new ImmutableInstruction10x(Opcode.NOP)); + } + instructions.add(new ImmutableInstruction21c(Opcode.CONST_STRING, 0, new ImmutableStringReference(mJumboStrings.get(1)))); + instructions.add(new ImmutableInstruction10x(Opcode.NOP)); + + // this misaligned array payload will cause nop insertion on the first pass and its removal on the second pass + instructions.add(new ImmutableInstruction10x(Opcode.NOP)); + instructions.add(new ImmutableArrayPayload(4, null)); + + ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null); + InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool); + + Instruction instr = writeUtil.getInstructions().iterator().next(); + Assert.assertEquals("goto was not converted to goto/16 properly", instr.getOpcode(), Opcode.GOTO_16); + + int codeOffset = 0; + for (Instruction instruction: writeUtil.getInstructions()) { + if (instruction instanceof ArrayPayload) { + Assert.assertEquals("packed switch payload was not aligned properly", codeOffset%2, 0); + } + codeOffset += instruction.getCodeUnits(); + } + } +} diff --git a/dexlib2/src/test/java/org/jf/dexlib2/writer/MockStringPool.java b/dexlib2/src/test/java/org/jf/dexlib2/writer/MockStringPool.java new file mode 100644 index 00000000..01d47789 --- /dev/null +++ b/dexlib2/src/test/java/org/jf/dexlib2/writer/MockStringPool.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.dexlib2.writer; + +import javax.annotation.Nonnull; + +public class MockStringPool extends StringPool { + public void intern(@Nonnull CharSequence string, int index) { + internedStringIdItems.put(string.toString(), index); + } +}