mirror of
https://github.com/revanced/smali.git
synced 2025-05-06 09:24:33 +02:00
Change the way unresolvable odex instructions are handled, so that it doesn't cause problems with try/catch blocks
git-svn-id: https://smali.googlecode.com/svn/trunk@737 55b6fa8a-2a1e-11de-a435-ffa8d773f76a
This commit is contained in:
parent
f7344d33d0
commit
e01409c11f
@ -46,18 +46,16 @@ public class CatchMethodItem extends MethodItem {
|
||||
this.exceptionType = exceptionType;
|
||||
|
||||
tryStartLabel = labelCache.internLabel(new LabelMethodItem(startAddress, "try_start_"));
|
||||
tryStartLabel.setUncommented();
|
||||
|
||||
//use the address from the last covered instruction, but make the label
|
||||
//name refer to the address of the next instruction
|
||||
tryEndLabel = labelCache.internLabel(new EndTryLabelMethodItem(codeAddress, endAddress));
|
||||
tryEndLabel.setUncommented();
|
||||
|
||||
if (exceptionType == null) {
|
||||
handlerLabel = labelCache.internLabel(new LabelMethodItem(handlerAddress, "catchall_"));
|
||||
} else {
|
||||
handlerLabel = labelCache.internLabel(new LabelMethodItem(handlerAddress, "catch_"));
|
||||
}
|
||||
handlerLabel.setUncommented();
|
||||
}
|
||||
|
||||
public LabelMethodItem getTryStartLabel() {
|
||||
|
@ -37,55 +37,30 @@ import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class ArrayDataMethodItem extends InstructionMethodItem<ArrayDataPseudoInstruction> {
|
||||
private final boolean dead;
|
||||
|
||||
public ArrayDataMethodItem(CodeItem codeItem, int codeAddress, boolean dead,
|
||||
ArrayDataPseudoInstruction instruction) {
|
||||
public ArrayDataMethodItem(CodeItem codeItem, int codeAddress, ArrayDataPseudoInstruction instruction) {
|
||||
super(codeItem, codeAddress, instruction);
|
||||
this.dead = dead;
|
||||
}
|
||||
|
||||
public boolean writeTo(IndentingWriter writer) throws IOException {
|
||||
if (dead) {
|
||||
writer.write("#.array-data 0x");
|
||||
writer.printLongAsHex(instruction.getElementWidth());
|
||||
writer.write('\n');
|
||||
writer.write(".array-data 0x");
|
||||
writer.printLongAsHex(instruction.getElementWidth());
|
||||
writer.write('\n');
|
||||
|
||||
Iterator<ArrayDataPseudoInstruction.ArrayElement> iterator = instruction.getElements();
|
||||
while (iterator.hasNext()) {
|
||||
ArrayDataPseudoInstruction.ArrayElement element = iterator.next();
|
||||
writer.indent(4);
|
||||
Iterator<ArrayDataPseudoInstruction.ArrayElement> iterator = instruction.getElements();
|
||||
while (iterator.hasNext()) {
|
||||
ArrayDataPseudoInstruction.ArrayElement element = iterator.next();
|
||||
|
||||
writer.write("# ");
|
||||
for (int i=0; i<element.elementWidth; i++) {
|
||||
if (i!=0) {
|
||||
writer.write(' ');
|
||||
}
|
||||
ByteRenderer.writeUnsignedTo(writer, element.buffer[element.bufferIndex+i]);
|
||||
for (int i=0; i<element.elementWidth; i++) {
|
||||
if (i!=0) {
|
||||
writer.write(' ');
|
||||
}
|
||||
writer.write('\n');
|
||||
ByteRenderer.writeUnsignedTo(writer, element.buffer[element.bufferIndex+i]);
|
||||
}
|
||||
writer.write("#.end array-data");
|
||||
} else {
|
||||
writer.write(".array-data 0x");
|
||||
writer.printLongAsHex(instruction.getElementWidth());
|
||||
writer.write('\n');
|
||||
|
||||
writer.indent(4);
|
||||
Iterator<ArrayDataPseudoInstruction.ArrayElement> iterator = instruction.getElements();
|
||||
while (iterator.hasNext()) {
|
||||
ArrayDataPseudoInstruction.ArrayElement element = iterator.next();
|
||||
|
||||
for (int i=0; i<element.elementWidth; i++) {
|
||||
if (i!=0) {
|
||||
writer.write(' ');
|
||||
}
|
||||
ByteRenderer.writeUnsignedTo(writer, element.buffer[element.bufferIndex+i]);
|
||||
}
|
||||
writer.write('\n');
|
||||
}
|
||||
writer.deindent(4);
|
||||
writer.write(".end array-data");
|
||||
}
|
||||
writer.deindent(4);
|
||||
writer.write(".end array-data");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -29,10 +29,7 @@
|
||||
package org.jf.baksmali.Adaptors.Format;
|
||||
|
||||
import org.jf.baksmali.Adaptors.MethodDefinition;
|
||||
import org.jf.dexlib.Code.Format.ArrayDataPseudoInstruction;
|
||||
import org.jf.dexlib.Code.Format.PackedSwitchDataPseudoInstruction;
|
||||
import org.jf.dexlib.Code.Format.SparseSwitchDataPseudoInstruction;
|
||||
import org.jf.dexlib.Code.Format.UnresolvedNullReference;
|
||||
import org.jf.dexlib.Code.Format.*;
|
||||
import org.jf.dexlib.Code.Instruction;
|
||||
import org.jf.dexlib.Code.OffsetInstruction;
|
||||
import org.jf.dexlib.CodeItem;
|
||||
@ -52,46 +49,17 @@ public class InstructionMethodItemFactory {
|
||||
|
||||
switch (instruction.getFormat()) {
|
||||
case ArrayData:
|
||||
return new ArrayDataMethodItem(codeItem, codeAddress, false,
|
||||
return new ArrayDataMethodItem(codeItem, codeAddress,
|
||||
(ArrayDataPseudoInstruction)instruction);
|
||||
case PackedSwitchData:
|
||||
return new PackedSwitchMethodItem(methodDefinition, codeItem, codeAddress, false,
|
||||
return new PackedSwitchMethodItem(methodDefinition, codeItem, codeAddress,
|
||||
(PackedSwitchDataPseudoInstruction)instruction);
|
||||
case SparseSwitchData:
|
||||
return new SparseSwitchMethodItem(methodDefinition, codeItem, codeAddress, false,
|
||||
return new SparseSwitchMethodItem(methodDefinition, codeItem, codeAddress,
|
||||
(SparseSwitchDataPseudoInstruction)instruction);
|
||||
case UnresolvedNullReference:
|
||||
assert false;
|
||||
throw new RuntimeException("UnresolvedNullReference not supported, use " +
|
||||
"makeAnalyzedInstructionFormatMethodItem instead");
|
||||
default:
|
||||
return new InstructionMethodItem(codeItem, codeAddress, instruction);
|
||||
}
|
||||
}
|
||||
|
||||
public static InstructionMethodItem makeAnalyzedInstructionFormatMethodItem(MethodDefinition methodDefinition,
|
||||
CodeItem codeItem, int codeAddress,
|
||||
boolean isDead,
|
||||
Instruction instruction,
|
||||
boolean isLastInstruction) {
|
||||
if (instruction instanceof OffsetInstruction) {
|
||||
return new OffsetInstructionFormatMethodItem(methodDefinition.getLabelCache(), codeItem, codeAddress,
|
||||
instruction);
|
||||
}
|
||||
|
||||
switch (instruction.getFormat()) {
|
||||
case ArrayData:
|
||||
return new ArrayDataMethodItem(codeItem, codeAddress, isDead,
|
||||
(ArrayDataPseudoInstruction)instruction);
|
||||
case PackedSwitchData:
|
||||
return new PackedSwitchMethodItem(methodDefinition, codeItem, codeAddress, isDead,
|
||||
(PackedSwitchDataPseudoInstruction)instruction);
|
||||
case SparseSwitchData:
|
||||
return new SparseSwitchMethodItem(methodDefinition, codeItem, codeAddress, isDead,
|
||||
(SparseSwitchDataPseudoInstruction)instruction);
|
||||
case UnresolvedNullReference:
|
||||
return new UnresolvedNullReferenceMethodItem(codeItem, codeAddress,
|
||||
(UnresolvedNullReference)instruction, isLastInstruction);
|
||||
case UnresolvedOdexInstruction:
|
||||
return new UnresolvedOdexInstructionMethodItem(codeItem, codeAddress,
|
||||
(UnresolvedOdexInstruction)instruction);
|
||||
default:
|
||||
return new InstructionMethodItem(codeItem, codeAddress, instruction);
|
||||
}
|
||||
|
@ -48,7 +48,6 @@ public class OffsetInstructionFormatMethodItem<T extends Instruction & OffsetIns
|
||||
|
||||
label = new LabelMethodItem(codeAddress + instruction.getTargetAddressOffset(), getLabelPrefix());
|
||||
label = labelCache.internLabel(label);
|
||||
label.setUncommented();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -43,9 +43,8 @@ import java.util.List;
|
||||
public class PackedSwitchMethodItem extends InstructionMethodItem<PackedSwitchDataPseudoInstruction>
|
||||
implements Iterable<LabelMethodItem> {
|
||||
private final List<LabelMethodItem> labels;
|
||||
private final boolean dead;
|
||||
|
||||
public PackedSwitchMethodItem(MethodDefinition methodDefinition, CodeItem codeItem, int codeAddress, boolean dead,
|
||||
public PackedSwitchMethodItem(MethodDefinition methodDefinition, CodeItem codeItem, int codeAddress,
|
||||
PackedSwitchDataPseudoInstruction instruction) {
|
||||
super(codeItem, codeAddress, instruction);
|
||||
|
||||
@ -58,36 +57,21 @@ public class PackedSwitchMethodItem extends InstructionMethodItem<PackedSwitchDa
|
||||
LabelMethodItem label = new LabelMethodItem(baseCodeAddress + target.targetAddressOffset, "pswitch_");
|
||||
label = methodDefinition.getLabelCache().internLabel(label);
|
||||
labels.add(label);
|
||||
label.setUncommented();
|
||||
}
|
||||
|
||||
this.dead = dead;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeTo(IndentingWriter writer) throws IOException {
|
||||
if (dead) {
|
||||
writer.write("#.packed-switch ");
|
||||
IntegerRenderer.writeTo(writer, instruction.getFirstKey());
|
||||
writer.write(".packed-switch ");
|
||||
IntegerRenderer.writeTo(writer, instruction.getFirstKey());
|
||||
writer.indent(4);
|
||||
writer.write('\n');
|
||||
for (LabelMethodItem label: labels) {
|
||||
label.writeTo(writer);
|
||||
writer.write('\n');
|
||||
for (LabelMethodItem label: labels) {
|
||||
writer.write("# ");
|
||||
label.writeTo(writer);
|
||||
writer.write('\n');
|
||||
}
|
||||
writer.write("#.end packed-switch");
|
||||
} else {
|
||||
writer.write(".packed-switch ");
|
||||
IntegerRenderer.writeTo(writer, instruction.getFirstKey());
|
||||
writer.indent(4);
|
||||
writer.write('\n');
|
||||
for (LabelMethodItem label: labels) {
|
||||
label.writeTo(writer);
|
||||
writer.write('\n');
|
||||
}
|
||||
writer.deindent(4);
|
||||
writer.write(".end packed-switch");
|
||||
}
|
||||
writer.deindent(4);
|
||||
writer.write(".end packed-switch");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -43,9 +43,8 @@ import java.util.List;
|
||||
public class SparseSwitchMethodItem extends InstructionMethodItem<SparseSwitchDataPseudoInstruction>
|
||||
implements Iterable<LabelMethodItem> {
|
||||
private final List<SparseSwitchTarget> targets;
|
||||
private final boolean dead;
|
||||
|
||||
public SparseSwitchMethodItem(MethodDefinition methodDefinition, CodeItem codeItem, int codeAddress, boolean dead,
|
||||
public SparseSwitchMethodItem(MethodDefinition methodDefinition, CodeItem codeItem, int codeAddress,
|
||||
SparseSwitchDataPseudoInstruction instruction) {
|
||||
super(codeItem, codeAddress, instruction);
|
||||
|
||||
@ -61,37 +60,23 @@ public class SparseSwitchMethodItem extends InstructionMethodItem<SparseSwitchDa
|
||||
LabelMethodItem label = new LabelMethodItem(baseCodeAddress + target.targetAddressOffset, "sswitch_");
|
||||
label = methodDefinition.getLabelCache().internLabel(label);
|
||||
sparseSwitchTarget.Target = label;
|
||||
label.setUncommented();
|
||||
|
||||
targets.add(sparseSwitchTarget);
|
||||
}
|
||||
|
||||
this.dead = dead;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeTo(IndentingWriter writer) throws IOException {
|
||||
if (dead) {
|
||||
writer.write("#.sparse-switch\n");
|
||||
for (SparseSwitchTarget target: targets) {
|
||||
IntegerRenderer.writeTo(writer, target.Key);
|
||||
writer.write(" -> ");
|
||||
target.Target.writeTo(writer);
|
||||
writer.write('\n');
|
||||
}
|
||||
writer.write("#.end sparse-switch");
|
||||
} else {
|
||||
writer.write(".sparse-switch\n");
|
||||
writer.indent(4);
|
||||
for (SparseSwitchTarget target: targets) {
|
||||
IntegerRenderer.writeTo(writer, target.Key);
|
||||
writer.write(" -> ");
|
||||
target.Target.writeTo(writer);
|
||||
writer.write('\n');
|
||||
}
|
||||
writer.deindent(4);
|
||||
writer.write(".end sparse-switch");
|
||||
writer.write(".sparse-switch\n");
|
||||
writer.indent(4);
|
||||
for (SparseSwitchTarget target: targets) {
|
||||
IntegerRenderer.writeTo(writer, target.Key);
|
||||
writer.write(" -> ");
|
||||
target.Target.writeTo(writer);
|
||||
writer.write('\n');
|
||||
}
|
||||
writer.deindent(4);
|
||||
writer.write(".end sparse-switch");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -29,49 +29,23 @@
|
||||
package org.jf.baksmali.Adaptors.Format;
|
||||
|
||||
import org.jf.baksmali.IndentingWriter;
|
||||
import org.jf.dexlib.Code.Format.UnresolvedNullReference;
|
||||
import org.jf.dexlib.Code.Format.UnresolvedOdexInstruction;
|
||||
import org.jf.dexlib.CodeItem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class UnresolvedNullReferenceMethodItem extends InstructionMethodItem<UnresolvedNullReference> {
|
||||
public final boolean isLastInstruction;
|
||||
|
||||
public UnresolvedNullReferenceMethodItem(CodeItem codeItem, int codeAddress, UnresolvedNullReference instruction,
|
||||
boolean isLastInstruction) {
|
||||
public class UnresolvedOdexInstructionMethodItem extends InstructionMethodItem<UnresolvedOdexInstruction> {
|
||||
public UnresolvedOdexInstructionMethodItem(CodeItem codeItem, int codeAddress, UnresolvedOdexInstruction instruction) {
|
||||
super(codeItem, codeAddress, instruction);
|
||||
this.isLastInstruction = isLastInstruction;
|
||||
}
|
||||
|
||||
public boolean writeTo(IndentingWriter writer) throws IOException {
|
||||
switch (instruction.OriginalInstruction.opcode)
|
||||
{
|
||||
case INVOKE_VIRTUAL_QUICK_RANGE:
|
||||
case INVOKE_SUPER_QUICK_RANGE:
|
||||
writeInvokeRangeTo(writer);
|
||||
return true;
|
||||
default:
|
||||
writeThrowTo(writer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeInvokeRangeTo(IndentingWriter writer) throws IOException {
|
||||
writer.write("#Replaced unresolvable optimized invoke-*-range-quick instruction\n");
|
||||
writer.write("#with a generic method call that will throw a NullPointerException\n");
|
||||
writer.write("invoke-virtual/range {");
|
||||
writeRegister(writer, instruction.ObjectRegisterNum);
|
||||
writer.write(" .. ");
|
||||
writeRegister(writer, instruction.ObjectRegisterNum);
|
||||
writer.write("}, Ljava/lang/Object;->hashCode()I");
|
||||
if (isLastInstruction) {
|
||||
writer.write('\n');
|
||||
writer.write("goto/32 0");
|
||||
}
|
||||
writeThrowTo(writer);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void writeThrowTo(IndentingWriter writer) throws IOException {
|
||||
writer.write("#Replaced unresolvable optimized instruction with a throw\n");
|
||||
writer.write("#Replaced unresolvable odex instruction with a throw\n");
|
||||
writer.write("throw ");
|
||||
writeRegister(writer, instruction.ObjectRegisterNum);
|
||||
}
|
@ -36,7 +36,6 @@ import java.io.IOException;
|
||||
public class LabelMethodItem extends MethodItem {
|
||||
private final String labelPrefix;
|
||||
private int labelSequence;
|
||||
private boolean isCommentedOut = true;
|
||||
|
||||
public LabelMethodItem(int codeAddress, String labelPrefix) {
|
||||
super(codeAddress);
|
||||
@ -47,14 +46,6 @@ public class LabelMethodItem extends MethodItem {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public boolean isCommentedOut() {
|
||||
return isCommentedOut;
|
||||
}
|
||||
|
||||
public void setUncommented() {
|
||||
this.isCommentedOut = false;
|
||||
}
|
||||
|
||||
public int compareTo(MethodItem methodItem) {
|
||||
int result = super.compareTo(methodItem);
|
||||
|
||||
|
@ -300,11 +300,7 @@ public class MethodDefinition {
|
||||
}
|
||||
|
||||
for (LabelMethodItem labelMethodItem: labelCache.getLabels()) {
|
||||
if (labelMethodItem.isCommentedOut()) {
|
||||
methodItems.add(new CommentedOutMethodItem(labelMethodItem));
|
||||
} else {
|
||||
methodItems.add(labelMethodItem);
|
||||
}
|
||||
methodItems.add(labelMethodItem);
|
||||
}
|
||||
|
||||
Collections.sort(methodItems);
|
||||
@ -372,51 +368,19 @@ public class MethodDefinition {
|
||||
|
||||
List<AnalyzedInstruction> instructions = methodAnalyzer.getInstructions();
|
||||
|
||||
AnalyzedInstruction lastInstruction = null;
|
||||
|
||||
for (int i=instructions.size()-1; i>=0; i--) {
|
||||
AnalyzedInstruction instruction = instructions.get(i);
|
||||
|
||||
if (!instruction.isDead()) {
|
||||
lastInstruction = instruction;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
boolean lastIsUnreachable = false;
|
||||
|
||||
int currentCodeAddress = 0;
|
||||
for (int i=0; i<instructions.size(); i++) {
|
||||
AnalyzedInstruction instruction = instructions.get(i);
|
||||
|
||||
MethodItem methodItem = InstructionMethodItemFactory.makeAnalyzedInstructionFormatMethodItem(this,
|
||||
encodedMethod.codeItem, currentCodeAddress, instruction.isDead(), instruction.getInstruction(),
|
||||
instruction == lastInstruction);
|
||||
MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
|
||||
encodedMethod.codeItem, currentCodeAddress, instruction.getInstruction());
|
||||
|
||||
if (instruction.isDead() && !instruction.getInstruction().getFormat().variableSizeFormat) {
|
||||
methodItems.add(new CommentedOutMethodItem(methodItem));
|
||||
lastIsUnreachable = false;
|
||||
} else if ( instruction.getPredecessorCount() == 0 &&
|
||||
!instruction.getInstruction().getFormat().variableSizeFormat &&
|
||||
!isInstructionPaddingNop(instructions, instruction)) {
|
||||
methodItems.add(methodItem);
|
||||
|
||||
if (!lastIsUnreachable) {
|
||||
methodItems.add(
|
||||
new CommentMethodItem("Unreachable code", currentCodeAddress, Double.MIN_VALUE));
|
||||
}
|
||||
|
||||
methodItems.add(new CommentedOutMethodItem(methodItem));
|
||||
lastIsUnreachable = true;
|
||||
} else {
|
||||
methodItems.add(methodItem);
|
||||
lastIsUnreachable = false;
|
||||
}
|
||||
|
||||
if (instruction.getInstruction().getFormat() == Format.UnresolvedNullReference) {
|
||||
if (instruction.getInstruction().getFormat() == Format.UnresolvedOdexInstruction) {
|
||||
methodItems.add(new CommentedOutMethodItem(
|
||||
InstructionMethodItemFactory.makeAnalyzedInstructionFormatMethodItem(this,
|
||||
encodedMethod.codeItem, currentCodeAddress, instruction.isDead(),
|
||||
instruction.getOriginalInstruction(), false)));
|
||||
InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
|
||||
encodedMethod.codeItem, currentCodeAddress, instruction.getOriginalInstruction())));
|
||||
}
|
||||
|
||||
if (i != instructions.size() - 1) {
|
||||
@ -651,9 +615,6 @@ public class MethodDefinition {
|
||||
public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) {
|
||||
LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem);
|
||||
if (internedLabelMethodItem != null) {
|
||||
if (!labelMethodItem.isCommentedOut()) {
|
||||
internedLabelMethodItem.setUncommented();
|
||||
}
|
||||
return internedLabelMethodItem;
|
||||
}
|
||||
labels.put(labelMethodItem, labelMethodItem);
|
||||
|
@ -13,129 +13,37 @@
|
||||
new-instance v2, Lsuperclass;
|
||||
invoke-direct {v2}, Lsuperclass;-><init>()V
|
||||
|
||||
if-eqz v0, :here2
|
||||
if-eqz v0, :here
|
||||
|
||||
|
||||
#this is the unresolvable instruction. v0 is always null,
|
||||
#and this will always throw an exception. Everything below
|
||||
#here, until the here2: label is dead code, and should be
|
||||
#commented out. This instruction itself should be be replaced
|
||||
#with a call to Ljava/lang/Object;->hashCode()I
|
||||
#and this will always throw an exception. It should be
|
||||
#replaced with throw v0.
|
||||
invoke-virtual {v0}, Lrandomclass;->getSuperclass()Lsuperclass;
|
||||
move-result-object v1
|
||||
|
||||
|
||||
#a branch to outside the dead code. The branch label should not
|
||||
#be commented out, because there is a non-dead instruction
|
||||
#that branches to it
|
||||
if-eqz v0, :here2
|
||||
|
||||
|
||||
#a branch to inside the dead code. the branch label should be
|
||||
#commented out
|
||||
if-eqz v0, :here
|
||||
|
||||
#another branch to outside the dead code. In this case, the "dead"
|
||||
#instruction is the first instruction that references the label.
|
||||
#the label should not be commented out, because it is referenced
|
||||
#be a non-dead instruction
|
||||
if-eqz v0, :here3
|
||||
|
||||
#one more branch to out the dead code. the branch label should be
|
||||
#commented out, because there are no other non-dead instructions
|
||||
#referenceding it
|
||||
if-eqz v0, :here4
|
||||
#this would normally be deodexable, except that it follows
|
||||
#the above un-deodexeable instruction, which prevents the
|
||||
#propagation of any register information. It can never be
|
||||
#reached, and should be replaced with throw v2
|
||||
invoke-virtual {v2}, Lsuperclass;->somemethod()V
|
||||
|
||||
#another odexed instruction that uses the result of the
|
||||
#first unresolveable odex instruction. this should
|
||||
#appear as a commented invoke-virtual-quick instruction
|
||||
#first unresolveable odex instruction. This should
|
||||
#be replaced with throw v1
|
||||
invoke-virtual {v1}, Lsuperclass;->somemethod()V
|
||||
|
||||
:here
|
||||
|
||||
#a resolveable odex instruction in the dead code. It should be resolved,
|
||||
#but still commented out
|
||||
invoke-virtual {v2}, Lsuperclass;->somemethod()V
|
||||
|
||||
|
||||
const v0, 1
|
||||
new-array v0, v0, [I
|
||||
|
||||
#this should be marked as dead, and so should the corresponding .sparse-switch structure
|
||||
fill-array-data v0, :array-data
|
||||
|
||||
const v0, 0
|
||||
|
||||
#this should be marked as dead, and so should the corresponding .sparse-switch structure
|
||||
sparse-switch v0, :sparse-switch
|
||||
|
||||
#this should be marked as dead, and so should the corresponding .packed-switch structure
|
||||
packed-switch v0, :packed-switch
|
||||
|
||||
|
||||
:here2
|
||||
|
||||
#and we're back to the non-dead code
|
||||
invoke-virtual {v2}, Lsuperclass;->somemethod()V
|
||||
|
||||
if-nez v0, :here3
|
||||
|
||||
|
||||
return-void
|
||||
|
||||
#this should be marked as dead
|
||||
:packed-switch
|
||||
.packed-switch 0x0
|
||||
:here
|
||||
.end packed-switch
|
||||
|
||||
#this should be marked as dead
|
||||
:sparse-switch
|
||||
.sparse-switch
|
||||
0x0 -> :here
|
||||
.end sparse-switch
|
||||
|
||||
#this should be marked as dead
|
||||
:array-data
|
||||
.array-data 0x4
|
||||
0x0t 0x0t 0x0t 0x0t
|
||||
.end array-data
|
||||
.end method
|
||||
|
||||
.method public static UnresolvedInstructionTest1()Lsuperclass;
|
||||
.registers 2
|
||||
const v0, 0
|
||||
|
||||
#this is an unresolvable instruction, due to v0 always being null
|
||||
#this instruction should be replaced with "throw v0", followed by
|
||||
#a "goto/32 0", since it would otherwise be the last instruction
|
||||
#in the method, which isn't allowed
|
||||
invoke-virtual/range {v0 .. v0}, Lrandomclass;->getSuperclass()Lsuperclass;
|
||||
|
||||
#the following instructions should be commented out
|
||||
move-result-object v1
|
||||
return-object v1
|
||||
.end method
|
||||
|
||||
.method public static UnresolvedInstructionTest2()Lsuperclass;
|
||||
.registers 2
|
||||
const v0, 0
|
||||
|
||||
if-eqz v0, :here
|
||||
|
||||
#this is an unresolvable instruction, due to v0 always being null
|
||||
#this instruction should be replaced with "throw v0". There shouldn't
|
||||
#be a "goto/32 0" afterwards, since it won't be the last instruction
|
||||
#in the method.
|
||||
invoke-virtual/range {v0 .. v0}, Lrandomclass;->getSuperclass()Lsuperclass;
|
||||
|
||||
#the following instructions should be commented out
|
||||
move-result-object v1
|
||||
return-object v1
|
||||
|
||||
#and now back to our normal programming
|
||||
:here
|
||||
return-object v0
|
||||
.end method
|
||||
|
||||
.method public static FirstInstructionTest(Lrandomclass;)V
|
||||
|
@ -73,24 +73,10 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
|
||||
*/
|
||||
protected final Instruction originalInstruction;
|
||||
|
||||
|
||||
/**
|
||||
* A dead instruction is one that is unreachable because it follows an odexed instruction that can't be deodexed
|
||||
* because it's object register is always null. In the non-odexed code that the odex was generated from, we would
|
||||
* have technically considered this code reachable and could verify it, even though the instruction that ended up
|
||||
* being odexed was always null, because we would assume both "paths" out of the instruction are valid - the one
|
||||
* where execution proceeds normally to the next instruction, and the one where an exception occurs and execution
|
||||
* either goes to a catch block, or out of the method.
|
||||
*
|
||||
* However, in the odexed case, we can't verify the code following an undeodexable instruction because we lack
|
||||
* the register information from the undeodexable instruction - because we don't know the actual method or field
|
||||
* that is being accessed.
|
||||
*
|
||||
* The undeodexable instruction is guaranteed to throw an NPE, so the following code is effectivetly unreachable.
|
||||
* Once we detect an undeodexeable instruction, the following code is marked as dead up until a non-dead execution
|
||||
* path merges in. Additionally, we remove the predecessors/successors of any dead instruction. For example, if
|
||||
* there is a dead goto instruction, then we would remove the target instruction as a successor, and we would
|
||||
* also remove the dead goto instruction as a predecessor to the target.
|
||||
* An analyzed instruction's "deadness" is set during analysis (i.e. MethodAnalyzer.analyzer()). A dead instruction
|
||||
* is one that the analyzer never reaches. This occurs either with natural "dead code" - code that simply has no
|
||||
* code path that can ever reach it, or code that follows an odexed instruction that can't be deodexed.
|
||||
*/
|
||||
protected boolean dead = false;
|
||||
|
||||
|
@ -184,7 +184,7 @@ public class MethodAnalyzer {
|
||||
instructionsToAnalyze.set(successor.instructionIndex);
|
||||
}
|
||||
|
||||
BitSet odexedInstructions = new BitSet(instructions.size());
|
||||
BitSet undeodexedInstructions = new BitSet(instructions.size());
|
||||
|
||||
do {
|
||||
boolean didSomething = false;
|
||||
@ -199,15 +199,18 @@ public class MethodAnalyzer {
|
||||
instructionToAnalyze.dead = false;
|
||||
try {
|
||||
if (instructionToAnalyze.originalInstruction.opcode.odexOnly()) {
|
||||
//if we had deodexed an odex instruction in a previous pass, we might have more specific
|
||||
//register information now, so let's restore the original odexed instruction and
|
||||
//re-deodex it
|
||||
instructionToAnalyze.restoreOdexedInstruction();
|
||||
}
|
||||
|
||||
if (!analyzeInstruction(instructionToAnalyze)) {
|
||||
odexedInstructions.set(i);
|
||||
undeodexedInstructions.set(i);
|
||||
continue;
|
||||
} else {
|
||||
didSomething = true;
|
||||
odexedInstructions.clear(i);
|
||||
undeodexedInstructions.clear(i);
|
||||
}
|
||||
} catch (ValidationException ex) {
|
||||
this.validationException = ex;
|
||||
@ -234,32 +237,32 @@ public class MethodAnalyzer {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!odexedInstructions.isEmpty()) {
|
||||
for (int i=odexedInstructions.nextSetBit(0); i>=0; i=odexedInstructions.nextSetBit(i+1)) {
|
||||
if (!undeodexedInstructions.isEmpty()) {
|
||||
for (int i=undeodexedInstructions.nextSetBit(0); i>=0; i=undeodexedInstructions.nextSetBit(i+1)) {
|
||||
instructionsToAnalyze.set(i);
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
|
||||
for (int i=odexedInstructions.nextSetBit(0); i>=0; i=odexedInstructions.nextSetBit(i+1)) {
|
||||
for (int i=0; i<instructions.size(); i++) {
|
||||
AnalyzedInstruction instruction = instructions.valueAt(i);
|
||||
|
||||
Instruction odexedInstruction = instruction.instruction;
|
||||
int objectRegisterNumber;
|
||||
|
||||
if (odexedInstruction.getFormat() == Format.Format22cs) {
|
||||
objectRegisterNumber = ((Instruction22cs)odexedInstruction).getRegisterB();
|
||||
} else if (odexedInstruction.getFormat() == Format.Format35ms) {
|
||||
objectRegisterNumber = ((Instruction35ms)odexedInstruction).getRegisterD();
|
||||
} else if (odexedInstruction.getFormat() == Format.Format3rms) {
|
||||
objectRegisterNumber = ((Instruction3rms)odexedInstruction).getStartRegister();
|
||||
} else {
|
||||
assert false;
|
||||
throw new ExceptionWithContext(String.format("Unexpected format %s for odexed instruction",
|
||||
odexedInstruction.getFormat().name()));
|
||||
switch (instruction.getInstruction().getFormat()) {
|
||||
case Format22cs:
|
||||
objectRegisterNumber = ((Instruction22cs)instruction.instruction).getRegisterB();
|
||||
break;
|
||||
case Format35ms:
|
||||
objectRegisterNumber = ((Instruction35ms)instruction.instruction).getRegisterD();
|
||||
break;
|
||||
case Format3rms:
|
||||
objectRegisterNumber = ((Instruction3rms)instruction.instruction).getStartRegister();
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
instruction.setDeodexedInstruction(new UnresolvedNullReference(odexedInstruction,
|
||||
instruction.setDeodexedInstruction(new UnresolvedOdexInstruction(instruction.instruction,
|
||||
objectRegisterNumber));
|
||||
}
|
||||
|
||||
@ -394,47 +397,6 @@ public class MethodAnalyzer {
|
||||
registerType);
|
||||
}
|
||||
|
||||
|
||||
private void setAndPropagateDeadness(AnalyzedInstruction analyzedInstruction) {
|
||||
BitSet instructionsToProcess = new BitSet(instructions.size());
|
||||
|
||||
analyzedInstruction.dead = true;
|
||||
|
||||
for (AnalyzedInstruction successor: analyzedInstruction.successors) {
|
||||
instructionsToProcess.set(successor.instructionIndex);
|
||||
}
|
||||
|
||||
instructionsToProcess.set(analyzedInstruction.instructionIndex);
|
||||
|
||||
while (!instructionsToProcess.isEmpty()) {
|
||||
for (int i=instructionsToProcess.nextSetBit(0); i>=0; i=instructionsToProcess.nextSetBit(i+1)) {
|
||||
AnalyzedInstruction currentInstruction = instructions.valueAt(i);
|
||||
instructionsToProcess.clear(i);
|
||||
|
||||
if (currentInstruction.dead) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean isDead = true;
|
||||
|
||||
for (AnalyzedInstruction predecessor: currentInstruction.predecessors) {
|
||||
if (!predecessor.dead) {
|
||||
isDead = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDead) {
|
||||
currentInstruction.dead = true;
|
||||
|
||||
for (AnalyzedInstruction successor: currentInstruction.successors) {
|
||||
instructionsToProcess.set(successor.instructionIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setPostRegisterTypeAndPropagateChanges(AnalyzedInstruction analyzedInstruction, int registerNumber,
|
||||
RegisterType registerType) {
|
||||
|
||||
@ -658,6 +620,10 @@ public class MethodAnalyzer {
|
||||
return exceptionHandlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false if analyzedInstruction is an odex instruction that couldn't be deodexed, due to its
|
||||
* object register being null
|
||||
*/
|
||||
private boolean analyzeInstruction(AnalyzedInstruction analyzedInstruction) {
|
||||
Instruction instruction = analyzedInstruction.instruction;
|
||||
|
||||
|
@ -62,7 +62,7 @@ public enum Format {
|
||||
ArrayData(null, -1, true),
|
||||
PackedSwitchData(null, -1, true),
|
||||
SparseSwitchData(null, -1, true),
|
||||
UnresolvedNullReference(null, -1, false),
|
||||
UnresolvedOdexInstruction(null, -1, false),
|
||||
;
|
||||
|
||||
public final Instruction.InstructionFactory Factory;
|
||||
|
@ -36,12 +36,12 @@ import org.jf.dexlib.Util.AnnotatedOutput;
|
||||
* can't be determined. Typically, these are replaced by an equivalent instruction that would have the same
|
||||
* effect (namely, an NPE)
|
||||
*/
|
||||
public class UnresolvedNullReference extends Instruction {
|
||||
public class UnresolvedOdexInstruction extends Instruction {
|
||||
public final Instruction OriginalInstruction;
|
||||
//the register number that holds the (null) reference type that the instruction operates on
|
||||
public final int ObjectRegisterNum;
|
||||
|
||||
public UnresolvedNullReference(Instruction originalInstruction, int objectRegisterNumber) {
|
||||
public UnresolvedOdexInstruction(Instruction originalInstruction, int objectRegisterNumber) {
|
||||
super(originalInstruction.opcode);
|
||||
this.OriginalInstruction = originalInstruction;
|
||||
this.ObjectRegisterNum = objectRegisterNumber;
|
||||
@ -57,6 +57,6 @@ public class UnresolvedNullReference extends Instruction {
|
||||
}
|
||||
|
||||
public Format getFormat() {
|
||||
return Format.UnresolvedNullReference;
|
||||
return Format.UnresolvedOdexInstruction;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user