Commit 8a6cdec7 authored by Skylot's avatar Skylot

core: refactor fill-array instruction processing and constants replace (fix #48)

parent c5c4499a
......@@ -13,6 +13,7 @@ import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.FillArrayNode;
import jadx.core.dex.instructions.FilledNewArrayNode;
import jadx.core.dex.instructions.GotoNode;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IndexInsnNode;
......@@ -34,14 +35,13 @@ import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
......@@ -359,12 +359,8 @@ public class InsnGen {
code.add(".length");
break;
case FILL_ARRAY:
fillArray((FillArrayNode) insn, code);
break;
case FILLED_NEW_ARRAY:
filledNewArray(insn, code);
filledNewArray((FilledNewArrayNode) insn, code);
break;
case AGET:
......@@ -490,6 +486,25 @@ public class InsnGen {
code.startLine('}');
break;
case FILL_ARRAY:
assert isFallback();
FillArrayNode arrayNode = (FillArrayNode) insn;
Object data = arrayNode.getData();
String arrStr;
if (data instanceof int[]) {
arrStr = Arrays.toString((int[]) data);
} else if (data instanceof short[]) {
arrStr = Arrays.toString((short[]) data);
} else if (data instanceof byte[]) {
arrStr = Arrays.toString((byte[]) data);
} else if (data instanceof long[]) {
arrStr = Arrays.toString((long[]) data);
} else {
arrStr = "?";
}
code.add('{').add(arrStr.substring(1, arrStr.length() - 1)).add('}');
break;
case NEW_INSTANCE:
// only fallback - make new instance in constructor invoke
assert isFallback();
......@@ -501,11 +516,11 @@ public class InsnGen {
}
}
private void filledNewArray(InsnNode insn, CodeWriter code) throws CodegenException {
int c = insn.getArgsCount();
private void filledNewArray(FilledNewArrayNode insn, CodeWriter code) throws CodegenException {
code.add("new ");
useType(code, insn.getResult().getType());
useType(code, insn.getArrayType());
code.add('{');
int c = insn.getArgsCount();
for (int i = 0; i < c; i++) {
addArg(code, insn.getArg(i), false);
if (i + 1 < c) {
......@@ -515,76 +530,6 @@ public class InsnGen {
code.add('}');
}
private void fillArray(FillArrayNode insn, CodeWriter code) throws CodegenException {
String filledArray = makeArrayElements(insn);
code.add("new ");
useType(code, insn.getElementType());
code.add("[]{").add(filledArray).add('}');
}
private String makeArrayElements(FillArrayNode insn) throws CodegenException {
ArgType insnArrayType = insn.getResult().getType();
ArgType insnElementType = insnArrayType.getArrayElement();
ArgType elType = insn.getElementType();
if (!elType.equals(insnElementType) && !insnArrayType.equals(ArgType.OBJECT)) {
ErrorsCounter.methodError(mth,
"Incorrect type for fill-array insn " + InsnUtils.formatOffset(insn.getOffset())
+ ", element type: " + elType + ", insn element type: " + insnElementType
);
}
if (!elType.isTypeKnown()) {
LOG.warn("Unknown array element type: {} in mth: {}", elType, mth);
elType = insnElementType.isTypeKnown() ? insnElementType : elType.selectFirst();
if (elType == null) {
throw new JadxRuntimeException("Null array element type");
}
}
insn.mergeElementType(elType);
StringBuilder str = new StringBuilder();
Object data = insn.getData();
switch (elType.getPrimitiveType()) {
case BOOLEAN:
case BYTE:
byte[] array = (byte[]) data;
for (byte b : array) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
}
break;
case SHORT:
case CHAR:
short[] sarray = (short[]) data;
for (short b : sarray) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
}
break;
case INT:
case FLOAT:
int[] iarray = (int[]) data;
for (int b : iarray) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
}
break;
case LONG:
case DOUBLE:
long[] larray = (long[]) data;
for (long b : larray) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
}
break;
default:
throw new CodegenException(mth, "Unknown type: " + elType);
}
int len = str.length();
str.delete(len - 2, len);
return str.toString();
}
private void makeConstructor(ConstructorInsn insn, CodeWriter code)
throws CodegenException {
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
......@@ -734,9 +679,6 @@ public class InsnGen {
}
}
return true;
} else if (insn.getType() == InsnType.FILL_ARRAY) {
code.add(makeArrayElements((FillArrayNode) insn));
return true;
}
return false;
}
......
......@@ -115,9 +115,7 @@ public class NameGen {
}
private String getFallbackName(RegisterArg arg) {
String name = arg.getName();
String base = "r" + arg.getRegNum();
return name != null ? base + "_" + name : base;
return "r" + arg.getRegNum();
}
private static String makeNameForType(ArgType type) {
......
......@@ -2,15 +2,20 @@ package jadx.core.dex.instructions;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
import java.util.List;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
public final class FillArrayNode extends InsnNode {
private final Object data;
private final int size;
private ArgType elemType;
public FillArrayNode(int resReg, FillArrayDataPayloadDecodedInstruction payload) {
......@@ -36,6 +41,7 @@ public final class FillArrayNode extends InsnNode {
setResult(InsnArg.reg(resReg, ArgType.array(elType)));
this.data = payload.getData();
this.size = payload.getSize();
this.elemType = elType;
}
......@@ -43,6 +49,10 @@ public final class FillArrayNode extends InsnNode {
return data;
}
public int getSize() {
return size;
}
public ArgType getElementType() {
return elemType;
}
......@@ -54,6 +64,31 @@ public final class FillArrayNode extends InsnNode {
}
}
public List<LiteralArg> getLiteralArgs() {
List<LiteralArg> list = new ArrayList<LiteralArg>(size);
Object array = data;
if (array instanceof int[]) {
for (int b : (int[]) array) {
list.add(InsnArg.lit(b, elemType));
}
} else if (array instanceof byte[]) {
for (byte b : (byte[]) array) {
list.add(InsnArg.lit(b, elemType));
}
} else if (array instanceof short[]) {
for (short b : (short[]) array) {
list.add(InsnArg.lit(b, elemType));
}
} else if (array instanceof long[]) {
for (long b : (long[]) array) {
list.add(InsnArg.lit(b, elemType));
}
} else {
throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + elemType);
}
return list;
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
......
package jadx.core.dex.instructions;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.InsnNode;
public class FilledNewArrayNode extends InsnNode {
private final ArgType elemType;
public FilledNewArrayNode(ArgType elemType, int size) {
super(InsnType.FILLED_NEW_ARRAY, size);
this.elemType = elemType;
}
public ArgType getElemType() {
return elemType;
}
public ArgType getArrayType() {
return ArgType.array(elemType);
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof FilledNewArrayNode) || !super.isSame(obj)) {
return false;
}
FilledNewArrayNode other = (FilledNewArrayNode) obj;
return elemType == other.elemType;
}
@Override
public String toString() {
return super.toString() + " elemType: " + elemType;
}
}
......@@ -620,9 +620,14 @@ public class InsnDecoder {
regs[i] = InsnArg.reg(regNum, elType, typeImmutable);
}
}
return insn(InsnType.FILLED_NEW_ARRAY,
resReg == -1 ? null : InsnArg.reg(resReg, arrType),
regs);
InsnNode node = new FilledNewArrayNode(elType, regs == null ? 0 : regs.length);
node.setResult(resReg == -1 ? null : InsnArg.reg(resReg, arrType));
if (regs != null) {
for (InsnArg arg : regs) {
node.addArg(arg);
}
}
return node;
}
private InsnNode cmp(DecodedInstruction insn, InsnType itype, ArgType argType) {
......@@ -690,17 +695,6 @@ public class InsnDecoder {
return node;
}
private InsnNode insn(InsnType type, RegisterArg res, InsnArg... args) {
InsnNode node = new InsnNode(type, args == null ? 0 : args.length);
node.setResult(res);
if (args != null) {
for (InsnArg arg : args) {
node.addArg(arg);
}
}
return node;
}
private int getMoveResultRegister(DecodedInstruction[] insnArr, int offset) {
int nextOffset = getNextInsnOffset(insnArr, offset);
if (nextOffset >= 0) {
......
......@@ -4,9 +4,11 @@ import jadx.core.codegen.TypeGen;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.FillArrayNode;
import jadx.core.dex.instructions.FilledNewArrayNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
......@@ -25,9 +27,13 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.InstructionRemover;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -95,20 +101,64 @@ public class ModVisitor extends AbstractVisitor {
}
break;
case RETURN:
if (insn.getArgsCount() > 0 && insn.getArg(0).isLiteral()) {
LiteralArg arg = (LiteralArg) insn.getArg(0);
FieldNode f = parentClass.getConstFieldByLiteralArg(arg);
if (f != null) {
arg.wrapInstruction(new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0));
case NEW_ARRAY:
// create array in 'fill-array' instruction
int next = i + 1;
if (next < size) {
InsnNode ni = block.getInstructions().get(next);
if (ni.getType() == InsnType.FILL_ARRAY) {
ni.getResult().merge(insn.getResult());
((FillArrayNode) ni).mergeElementType(insn.getResult().getType().getArrayElement());
remover.add(insn);
}
}
break;
case FILL_ARRAY:
InsnNode filledArr = makeFilledArrayInsn(mth, (FillArrayNode) insn);
replaceInsn(block, i, filledArr);
break;
case MOVE_EXCEPTION:
processMoveException(mth, block, insn, remover);
break;
case ARITH:
ArithNode arithNode = (ArithNode) insn;
if (arithNode.getArgsCount() == 2) {
InsnArg litArg = arithNode.getArg(1);
if (litArg.isLiteral()) {
FieldNode f = parentClass.getConstFieldByLiteralArg((LiteralArg) litArg);
if (f != null) {
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
insn.replaceArg(litArg, InsnArg.wrapArg(fGet));
}
}
}
break;
default:
break;
}
}
remover.perform();
}
}
/**
* Remove unnecessary instructions
*/
private static void removeStep(MethodNode mth, InstructionRemover remover) {
for (BlockNode block : mth.getBasicBlocks()) {
remover.setBlock(block);
for (InsnNode insn : block.getInstructions()) {
switch (insn.getType()) {
case NOP:
case GOTO:
case NEW_INSTANCE:
remover.add(insn);
break;
default:
break;
}
......@@ -122,7 +172,9 @@ public class ModVisitor extends AbstractVisitor {
InsnNode insn = block.getInstructions().get(insnNumber);
InvokeNode inv = (InvokeNode) insn;
MethodInfo callMth = inv.getCallMth();
if (callMth.isConstructor()) {
if (!callMth.isConstructor()) {
return;
}
InsnNode instArgAssignInsn = ((RegisterArg) inv.getArg(0)).getAssignInsn();
ConstructorInsn co = new ConstructorInsn(mth, inv);
boolean remove = false;
......@@ -141,7 +193,8 @@ public class ModVisitor extends AbstractVisitor {
}
if (remove) {
remover.add(insn);
} else {
return;
}
replaceInsn(block, insnNumber, co);
if (co.isNewInstance()) {
InsnNode newInstInsn = removeAssignChain(instArgAssignInsn, remover, InsnType.NEW_INSTANCE);
......@@ -165,8 +218,6 @@ public class ModVisitor extends AbstractVisitor {
replaceInsn(block, insnNumber, replace);
}
}
}
}
/**
* Replace call of synthetic constructor
......@@ -193,6 +244,41 @@ public class ModVisitor extends AbstractVisitor {
return newInsn;
}
private static InsnNode makeFilledArrayInsn(MethodNode mth, FillArrayNode insn) {
ArgType insnArrayType = insn.getResult().getType();
ArgType insnElementType = insnArrayType.getArrayElement();
ArgType elType = insn.getElementType();
if (!elType.equals(insnElementType) && !insnArrayType.equals(ArgType.OBJECT)) {
ErrorsCounter.methodError(mth,
"Incorrect type for fill-array insn " + InsnUtils.formatOffset(insn.getOffset())
+ ", element type: " + elType + ", insn element type: " + insnElementType
);
}
if (!elType.isTypeKnown()) {
LOG.warn("Unknown array element type: {} in mth: {}", elType, mth);
elType = insnElementType.isTypeKnown() ? insnElementType : elType.selectFirst();
if (elType == null) {
throw new JadxRuntimeException("Null array element type");
}
}
insn.mergeElementType(elType);
elType = insn.getElementType();
List<LiteralArg> list = insn.getLiteralArgs();
InsnNode filledArr = new FilledNewArrayNode(elType, list.size());
filledArr.setResult(insn.getResult());
for (LiteralArg arg : list) {
FieldNode f = mth.getParentClass().getConstFieldByLiteralArg(arg);
if (f != null) {
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
filledArr.addArg(InsnArg.wrapArg(fGet));
} else {
filledArr.addArg(arg);
}
}
return filledArr;
}
private static boolean allArgsNull(InsnNode insn) {
for (InsnArg insnArg : insn.getArguments()) {
if (insnArg.isLiteral()) {
......@@ -226,53 +312,6 @@ public class ModVisitor extends AbstractVisitor {
return null;
}
/**
* Remove unnecessary instructions
*/
private static void removeStep(MethodNode mth, InstructionRemover remover) {
for (BlockNode block : mth.getBasicBlocks()) {
remover.setBlock(block);
int size = block.getInstructions().size();
for (int i = 0; i < size; i++) {
InsnNode insn = block.getInstructions().get(i);
switch (insn.getType()) {
case NOP:
case GOTO:
case NEW_INSTANCE:
remover.add(insn);
break;
case NEW_ARRAY:
// create array in 'fill-array' instruction
int next = i + 1;
if (next < size) {
InsnNode ni = block.getInstructions().get(next);
if (ni.getType() == InsnType.FILL_ARRAY) {
ni.getResult().merge(insn.getResult());
((FillArrayNode) ni).mergeElementType(insn.getResult().getType().getArrayElement());
remover.add(insn);
}
}
break;
case RETURN:
if (insn.getArgsCount() == 0) {
if (mth.getBasicBlocks().size() == 1 && i == size - 1) {
remover.add(insn);
} else if (mth.getMethodInfo().isClassInit()) {
remover.add(insn);
}
}
break;
default:
break;
}
}
remover.perform();
}
}
private static void processMoveException(MethodNode mth, BlockNode block, InsnNode insn,
InstructionRemover remover) {
ExcHandlerAttr excHandlerAttr = block.get(AType.EXC_HANDLER);
......@@ -310,6 +349,7 @@ public class ModVisitor extends AbstractVisitor {
private static void replaceInsn(BlockNode block, int i, InsnNode insn) {
InsnNode prevInsn = block.getInstructions().get(i);
insn.copyAttributesFrom(prevInsn);
insn.setSourceLine(prevInsn.getSourceLine());
block.getInstructions().set(i, insn);
}
......
......@@ -5,10 +5,12 @@ import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.EnumMapAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.FilledNewArrayNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg;
......@@ -78,7 +80,8 @@ public class ReSugarCode extends AbstractVisitor {
if (len <= 0 || i + len >= size || instructions.get(i + len).getType() != InsnType.APUT) {
return null;
}
InsnNode filledArr = new InsnNode(InsnType.FILLED_NEW_ARRAY, len);
ArgType arrType = insn.getResult().getType();
InsnNode filledArr = new FilledNewArrayNode(arrType.getArrayElement(), len);
filledArr.setResult(insn.getResult());
for (int j = 0; j < len; j++) {
InsnNode put = instructions.get(i + 1 + j);
......
......@@ -17,8 +17,6 @@ import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.InstructionRemover;
import java.util.Iterator;
public class BlockExceptionHandler extends AbstractVisitor {
@Override
......@@ -38,16 +36,6 @@ public class BlockExceptionHandler extends AbstractVisitor {
for (BlockNode block : mth.getBasicBlocks()) {
processTryCatchBlocks(mth, block);
}
for (BlockNode block : mth.getBasicBlocks()) {
Iterator<InsnNode> it = block.getInstructions().iterator();
while (it.hasNext()) {
InsnNode insn = it.next();
if (insn.getType() == InsnType.NOP) {
it.remove();
}
}
}
}
/**
......
package jadx.tests.integration.arrays;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import org.junit.Test;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.junit.Assert.assertThat;
public class TestArrayFillConstReplace extends IntegrationTest {
public static class TestCls {
public static final int CONST_INT = 0xffff;
public int[] test() {
return new int[]{127, 129, CONST_INT};
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsOne("return new int[]{127, 129, CONST_INT};"));
}
}
package jadx.tests.integration.arrays;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import org.junit.Test;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertThat;
public class TestArrays2 extends IntegrationTest {
public static class TestCls {
private static Object test4(int type) {
if (type == 1) {
return new int[]{1, 2};
} else if (type == 2) {
return new float[]{1, 2};
} else if (type == 3) {
return new short[]{1, 2};
} else if (type == 4) {
return new byte[]{1, 2};
} else {
return null;
}
}
public void check() {
assertThat(test4(4), instanceOf(byte[].class));
}
}
@Test
public void test() {
noDebugInfo();
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsOne("new int[]{1, 2}"));
}
}
......@@ -30,7 +30,7 @@ public class TestFallbackMode extends IntegrationTest {
String code = cls.getCode().toString();
assertThat(code, containsString("public int test(int r2) {"));
assertThat(code, containsString("r1_this = this;"));
assertThat(code, containsString("r1 = this;"));
assertThat(code, containsString("L_0x0004:"));
assertThat(code, not(containsString("throw new UnsupportedOperationException")));
}
......
package jadx.tests.smali;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.SmaliTest;
import org.junit.Test;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.junit.Assert.assertThat;
public class TestArithConst extends SmaliTest {
@Test
public void test() {
noDebugInfo();
ClassNode cls = getClassNodeFromSmali("TestArithConst");
String code = cls.getCode().toString();
assertThat(code, containsOne("return i + CONST_INT;"));
}
}
.class public LTestArithConst;
.super Ljava/lang/Object;
.field public static final CONST_INT:I = 0xff
.method private test(I)I
.registers 2
add-int/lit16 v0, p1, 0xff
return v0
.end method
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment