Merge pull request #9 from izzytwosheds/jumbos_nops_offsets

Handling jumbo string conversions and consequent offset adjustments for ...
This commit is contained in:
Ben Gruver 2013-04-02 20:59:44 -07:00
commit 45972c352c
5 changed files with 818 additions and 23 deletions

View File

@ -43,9 +43,9 @@ import org.jf.dexlib2.iface.instruction.ReferenceInstruction;
import org.jf.dexlib2.iface.instruction.SwitchElement; import org.jf.dexlib2.iface.instruction.SwitchElement;
import org.jf.dexlib2.iface.instruction.formats.*; import org.jf.dexlib2.iface.instruction.formats.*;
import org.jf.dexlib2.iface.reference.*; import org.jf.dexlib2.iface.reference.*;
import org.jf.dexlib2.util.InstructionUtil;
import org.jf.dexlib2.util.MethodUtil; import org.jf.dexlib2.util.MethodUtil;
import org.jf.dexlib2.util.ReferenceUtil; import org.jf.dexlib2.util.ReferenceUtil;
import org.jf.dexlib2.writer.util.InstructionWriteUtil;
import org.jf.util.ExceptionWithContext; import org.jf.util.ExceptionWithContext;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -149,30 +149,15 @@ public class CodeItemPool {
writer.writeUshort(methodImpl.getRegisterCount()); writer.writeUshort(methodImpl.getRegisterCount());
writer.writeUshort(MethodUtil.getParameterRegisterCount(method, MethodUtil.isStatic(method))); writer.writeUshort(MethodUtil.getParameterRegisterCount(method, MethodUtil.isStatic(method)));
int maxOutParamCount = 0; InstructionWriteUtil instrWriteUtil = new InstructionWriteUtil(methodImpl, dexFile.stringPool);
int codeUnitCount = 0; writer.writeUshort(instrWriteUtil.getOutParamCount());
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);
List<? extends TryBlock> tryBlocks = methodImpl.getTryBlocks(); List<? extends TryBlock> tryBlocks = methodImpl.getTryBlocks();
writer.writeUshort(tryBlocks.size()); writer.writeUshort(tryBlocks.size());
writer.writeInt(dexFile.debugInfoPool.getOffset(method)); 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: instrWriteUtil.getInstructions()) {
for (Instruction instruction: methodImpl.getInstructions()) {
switch (instruction.getOpcode().format) { switch (instruction.getOpcode().format) {
case Format10t: case Format10t:
writeFormat10t(writer, (Instruction10t)instruction); writeFormat10t(writer, (Instruction10t)instruction);
@ -274,8 +259,15 @@ public class CodeItemPool {
DexWriter.writeUleb128(ehBuf, exceptionHandlerOffsetMap.size()); DexWriter.writeUleb128(ehBuf, exceptionHandlerOffsetMap.size());
for (TryBlock tryBlock: tryBlocks) { for (TryBlock tryBlock: tryBlocks) {
writer.writeInt(tryBlock.getStartCodeAddress()); int startAddress = tryBlock.getStartCodeAddress();
writer.writeUshort(tryBlock.getCodeUnitCount()); 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) { if (tryBlock.getExceptionHandlers().size() == 0) {
throw new ExceptionWithContext("No exception handlers for the try block!"); throw new ExceptionWithContext("No exception handlers for the try block!");
@ -303,6 +295,7 @@ public class CodeItemPool {
for (ExceptionHandler eh : tryBlock.getExceptionHandlers()) { for (ExceptionHandler eh : tryBlock.getExceptionHandlers()) {
String exceptionType = eh.getExceptionType(); String exceptionType = eh.getExceptionType();
int codeAddress = eh.getHandlerCodeAddress(); int codeAddress = eh.getHandlerCodeAddress();
codeAddress += instrWriteUtil.codeOffsetShift(codeAddress);
if (exceptionType != null) { if (exceptionType != null) {
//regular exception handling //regular exception handling

View File

@ -46,7 +46,7 @@ import java.util.Map;
public class StringPool { public class StringPool {
public final static int STRING_ID_ITEM_SIZE = 0x04; public final static int STRING_ID_ITEM_SIZE = 0x04;
@Nonnull private final Map<String, Integer> internedStringIdItems = Maps.newHashMap(); @Nonnull protected final Map<String, Integer> internedStringIdItems = Maps.newHashMap();
private int indexSectionOffset = -1; private int indexSectionOffset = -1;
private int dataSectionOffset = -1; private int dataSectionOffset = -1;

View File

@ -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<Instruction> instructions;
private ArrayList<Integer> codeOffsetShifts;
private HashMap<Integer,Format> 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<? extends Instruction> 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<numCodeOffsetShifts;i++) {
if (offset >= 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<Integer>();
}
if (offsetToNewInstructionMap == null) {
offsetToNewInstructionMap = new HashMap<Integer,Format>();
}
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<SwitchElement> 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<SwitchElement> 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<SwitchElement> modifySwitchElements(SwitchPayload payload, int switchInstructionOffset) {
ArrayList<SwitchElement> 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;
}
}

View File

@ -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<String> 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()<MIN_NUM_JUMBO_STRINGS) {
for (int pos=stringBuilder.length()-1;pos>=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<stringBuilder.length();pos++) {
stringBuilder.setCharAt(pos, 'a');
}
}
}
@Test
public void testInstruction21c() {
ArrayList<ImmutableInstruction> 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<ImmutableInstruction> createSimpleInstructionList() {
ArrayList<ImmutableInstruction> 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<SwitchElement> 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<ImmutableInstruction> 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<ImmutableInstruction> 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<ImmutableInstruction> 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<ImmutableInstruction> 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<ImmutableInstruction> 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<ImmutableInstruction> 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<ImmutableInstruction> 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<ImmutableInstruction> 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<ImmutableInstruction> 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<ImmutableInstruction> 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<ImmutableInstruction> 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<ImmutableInstruction> 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<ImmutableInstruction> 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<Short.MAX_VALUE;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/16 was not converted to goto/32 properly", instr.getOpcode(), Opcode.GOTO_32);
}
@Test
public void testGotoIterative() {
ArrayList<ImmutableInstruction> 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();
}
}
}

View File

@ -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);
}
}