fix: restore enum for several blocks in class init method

This commit is contained in:
Skylot
2020-02-14 18:05:48 +00:00
parent 87320348dd
commit 57c28c61e0
7 changed files with 133 additions and 32 deletions
+1 -1
View File
@@ -105,12 +105,12 @@ public class Jadx {
passes.add(new SimplifyVisitor());
passes.add(new CheckRegions());
passes.add(new EnumVisitor());
passes.add(new ExtractFieldInit());
passes.add(new FixAccessModifiers());
passes.add(new ProcessAnonymous());
passes.add(new ClassModifier());
passes.add(new MethodInlineVisitor());
passes.add(new EnumVisitor());
passes.add(new LoopRegionVisitor());
passes.add(new ProcessVariables());
@@ -174,6 +174,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
private void loadStaticValues(ClassDef cls, List<FieldNode> staticFields) throws DecodeException {
for (FieldNode f : staticFields) {
if (f.getAccessFlags().isFinal()) {
// incorrect initialization will be removed if assign found in constructor
f.addAttr(FieldInitAttr.NULL_VALUE);
}
}
@@ -37,6 +37,7 @@ import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.utils.BlockInsnPair;
import jadx.core.utils.InsnRemover;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.JadxException;
@@ -44,7 +45,8 @@ import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
name = "EnumVisitor",
desc = "Restore enum classes",
runAfter = { CodeShrinkVisitor.class, ModVisitor.class }
runAfter = { CodeShrinkVisitor.class, ModVisitor.class },
runBefore = { ExtractFieldInit.class }
)
public class EnumVisitor extends AbstractVisitor {
@@ -72,8 +74,6 @@ public class EnumVisitor extends AbstractVisitor {
if (classInitMth.getBasicBlocks().isEmpty()) {
return false;
}
BlockNode staticBlock = classInitMth.getBasicBlocks().get(0);
ArgType clsType = cls.getClassInfo().getType();
// search "$VALUES" field (holds all enum values)
@@ -104,34 +104,32 @@ public class EnumVisitor extends AbstractVisitor {
List<InsnNode> toRemove = new ArrayList<>();
// search "$VALUES" array init and collect enum fields
BlockInsnPair valuesInitPair = getValuesInitInsn(classInitMth, valuesField);
if (valuesInitPair == null) {
return false;
}
BlockNode staticBlock = valuesInitPair.getBlock();
InsnNode valuesInitInsn = valuesInitPair.getInsn();
List<EnumField> enumFields = null;
for (InsnNode insn : staticBlock.getInstructions()) {
if (insn.getType() != InsnType.SPUT) {
continue;
}
FieldInfo f = (FieldInfo) ((IndexInsnNode) insn).getIndex();
if (f.equals(valuesField.getFieldInfo())) {
InsnArg arrArg = insn.getArg(0);
if (arrArg.isInsnWrap()) {
InsnNode arrFillInsn = ((InsnWrapArg) arrArg).getWrapInsn();
InsnType insnType = arrFillInsn.getType();
if (insnType == InsnType.FILLED_NEW_ARRAY) {
enumFields = extractEnumFields(cls, arrFillInsn, staticBlock, toRemove);
} else if (insnType == InsnType.NEW_ARRAY) {
// empty enum
InsnArg arg = arrFillInsn.getArg(0);
if (arg.isLiteral() && ((LiteralArg) arg).getLiteral() == 0) {
enumFields = Collections.emptyList();
}
}
InsnArg arrArg = valuesInitInsn.getArg(0);
if (arrArg.isInsnWrap()) {
InsnNode arrFillInsn = ((InsnWrapArg) arrArg).getWrapInsn();
InsnType insnType = arrFillInsn.getType();
if (insnType == InsnType.FILLED_NEW_ARRAY) {
enumFields = extractEnumFields(cls, arrFillInsn, staticBlock, toRemove);
} else if (insnType == InsnType.NEW_ARRAY) {
// empty enum
InsnArg arg = arrFillInsn.getArg(0);
if (arg.isLiteral() && ((LiteralArg) arg).getLiteral() == 0) {
enumFields = Collections.emptyList();
}
toRemove.add(insn);
break;
}
}
if (enumFields == null) {
return false;
}
toRemove.add(valuesInitInsn);
// all checks complete, perform transform
EnumClassAttr attr = new EnumClassAttr(enumFields.size());
@@ -169,6 +167,22 @@ public class EnumVisitor extends AbstractVisitor {
return true;
}
private BlockInsnPair getValuesInitInsn(MethodNode classInitMth, FieldNode valuesField) {
FieldInfo searchField = valuesField.getFieldInfo();
for (BlockNode blockNode : classInitMth.getBasicBlocks()) {
for (InsnNode insn : blockNode.getInstructions()) {
if (insn.getType() == InsnType.SPUT) {
IndexInsnNode indexInsnNode = (IndexInsnNode) insn;
FieldInfo f = (FieldInfo) indexInsnNode.getIndex();
if (f.equals(searchField)) {
return new BlockInsnPair(blockNode, indexInsnNode);
}
}
}
}
return null;
}
private List<EnumField> extractEnumFields(ClassNode cls, InsnNode arrFillInsn, BlockNode staticBlock, List<InsnNode> toRemove) {
List<EnumField> enumFields = new ArrayList<>();
for (InsnArg arg : arrFillInsn.getArguments()) {
@@ -35,9 +35,6 @@ public class ExtractFieldInit extends AbstractVisitor {
@Override
public boolean visit(ClassNode cls) throws JadxException {
if (cls.isEnum()) {
return false;
}
for (ClassNode inner : cls.getInnerClasses()) {
visit(inner);
}
@@ -66,12 +63,11 @@ public class ExtractFieldInit extends AbstractVisitor {
}
/**
* Remove final field in place initialization if it assign in class init method
* Remove a final field in place initialization if it an assign found in class init method
*/
private static void processStaticFieldAssign(ClassNode cls, IndexInsnNode insn) {
FieldInfo field = (FieldInfo) insn.getIndex();
String thisClass = cls.getClassInfo().getFullName();
if (field.getDeclClass().getFullName().equals(thisClass)) {
if (field.getDeclClass().equals(cls.getClassInfo())) {
FieldNode fn = cls.searchField(field);
if (fn != null && fn.getAccessFlags().isFinal()) {
fn.remove(AType.FIELD_INIT);
@@ -0,0 +1,46 @@
package jadx.core.utils;
import java.util.Objects;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
public class BlockInsnPair {
private final BlockNode block;
private final InsnNode insn;
public BlockInsnPair(BlockNode block, InsnNode insn) {
this.block = block;
this.insn = insn;
}
public BlockNode getBlock() {
return block;
}
public InsnNode getInsn() {
return insn;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof BlockInsnPair)) {
return false;
}
BlockInsnPair that = (BlockInsnPair) o;
return block.equals(that.block) && insn.equals(that.insn);
}
@Override
public int hashCode() {
return Objects.hash(block, insn);
}
@Override
public String toString() {
return "BlockInsnPair{" + block + ": " + insn + '}';
}
}
@@ -0,0 +1,44 @@
package jadx.tests.integration.enums;
import org.junit.jupiter.api.Test;
import jadx.NotYetImplemented;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestEnumsWithAssert extends IntegrationTest {
public static class TestCls {
public enum Numbers {
ONE(1), TWO(2), THREE(3);
private final int num;
Numbers(int n) {
this.num = n;
}
public int getNum() {
assert num > 0;
return num;
}
}
}
@Test
public void test() {
assertThat(getClassNode(TestCls.class)).code()
.containsOne("ONE(1)")
.doesNotContain("Failed to restore enum class");
}
@NotYetImplemented("handle java assert")
@Test
public void testNYI() {
assertThat(getClassNode(TestCls.class)).code()
.containsOne("assert num > 0;")
.doesNotContain("$assertionsDisabled")
.doesNotContain("throw new AssertionError()");
}
}
@@ -14,7 +14,7 @@ public class TestEnumsWithStaticFields extends SmaliTest {
assertThat(getClassNodeFromSmali())
.code()
.containsOnlyOnce("INSTANCE;")
.containsOnlyOnce("private static c sB;")
.containsOnlyOnce("private static c sB")
.doesNotContain(" sA")
.doesNotContain(" sC")
.doesNotContain("private TestEnumsWithStaticFields(String str) {");