fix: support enum restore for constructor without args (#2821)
This commit is contained in:
@@ -34,6 +34,7 @@ import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
|
||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
@@ -164,12 +165,7 @@ public class ClassGen {
|
||||
if (af.isInterface()) {
|
||||
af = af.remove(AccessFlags.ABSTRACT)
|
||||
.remove(AccessFlags.STATIC);
|
||||
} else if (af.isEnum()) {
|
||||
af = af.remove(AccessFlags.FINAL)
|
||||
.remove(AccessFlags.ABSTRACT)
|
||||
.remove(AccessFlags.STATIC);
|
||||
}
|
||||
|
||||
// 'static' and 'private' modifier not allowed for top classes (not inner)
|
||||
if (!cls.getClassInfo().isInner()) {
|
||||
af = af.remove(AccessFlags.STATIC).remove(AccessFlags.PRIVATE);
|
||||
@@ -294,7 +290,7 @@ public class ClassGen {
|
||||
private void addInnerClsAndMethods(ICodeWriter clsCode) {
|
||||
Stream.of(cls.getInnerClasses(), cls.getMethods())
|
||||
.flatMap(Collection::stream)
|
||||
.filter(node -> !node.contains(AFlag.DONT_GENERATE) || fallback)
|
||||
.filter(node -> !skipNode(node))
|
||||
.sorted(Comparator.comparingInt(LineAttrNode::getSourceLine))
|
||||
.forEach(node -> {
|
||||
if (node instanceof ClassNode) {
|
||||
@@ -305,6 +301,18 @@ public class ClassGen {
|
||||
});
|
||||
}
|
||||
|
||||
private boolean skipNode(NotificationAttrNode node) {
|
||||
if (fallback) {
|
||||
return false;
|
||||
}
|
||||
if (Consts.DEBUG_ATTRIBUTES) {
|
||||
if (node.contains(AType.JADX_COMMENTS)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return node.contains(AFlag.DONT_GENERATE);
|
||||
}
|
||||
|
||||
private void addInnerClass(ICodeWriter code, ClassNode innerCls) {
|
||||
try {
|
||||
ClassGen inClGen = new ClassGen(innerCls, getParentGen());
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -29,6 +27,7 @@ import jadx.core.dex.attributes.nodes.JadxError;
|
||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
|
||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.ConstStringNode;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
@@ -109,10 +108,6 @@ public class MethodGen {
|
||||
if (clsAccFlags.isAnnotation()) {
|
||||
ai = ai.remove(AccessFlags.PUBLIC);
|
||||
}
|
||||
if (mth.getMethodInfo().isConstructor() && mth.getParentClass().isEnum()) {
|
||||
ai = ai.remove(AccessInfo.VISIBILITY_FLAGS);
|
||||
}
|
||||
|
||||
if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) {
|
||||
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
|
||||
}
|
||||
@@ -152,21 +147,7 @@ public class MethodGen {
|
||||
code.add(defMth.getAlias());
|
||||
}
|
||||
code.add('(');
|
||||
|
||||
List<RegisterArg> args = mth.getArgRegs();
|
||||
if (mth.getMethodInfo().isConstructor()
|
||||
&& mth.getParentClass().contains(AType.ENUM_CLASS)) {
|
||||
if (args.size() == 2) {
|
||||
args = Collections.emptyList();
|
||||
} else if (args.size() > 2) {
|
||||
args = args.subList(2, args.size());
|
||||
} else {
|
||||
mth.addWarnComment("Incorrect number of args for enum constructor: " + args.size() + " (expected >= 2)");
|
||||
}
|
||||
} else if (mth.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||
args = args.subList(1, args.size());
|
||||
}
|
||||
addMethodArguments(code, args);
|
||||
addMethodArguments(code);
|
||||
code.add(')');
|
||||
|
||||
annotationGen.addThrows(mth, code);
|
||||
@@ -209,12 +190,22 @@ public class MethodGen {
|
||||
}
|
||||
}
|
||||
|
||||
private void addMethodArguments(ICodeWriter code, List<RegisterArg> args) {
|
||||
private void addMethodArguments(ICodeWriter code) {
|
||||
List<RegisterArg> args = mth.getArgRegs();
|
||||
AnnotationMethodParamsAttr paramsAnnotation = mth.get(JadxAttrType.ANNOTATION_MTH_PARAMETERS);
|
||||
int i = 0;
|
||||
Iterator<RegisterArg> it = args.iterator();
|
||||
while (it.hasNext()) {
|
||||
RegisterArg mthArg = it.next();
|
||||
int argNum = -1;
|
||||
int lastArgNum = args.size() - 1;
|
||||
boolean first = true;
|
||||
for (RegisterArg mthArg : args) {
|
||||
argNum++;
|
||||
if (SkipMethodArgsAttr.isSkip(mth, argNum)) {
|
||||
continue;
|
||||
}
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
code.add(", ");
|
||||
}
|
||||
SSAVar ssaVar = mthArg.getSVar();
|
||||
CodeVar var;
|
||||
if (ssaVar == null) {
|
||||
@@ -226,7 +217,7 @@ public class MethodGen {
|
||||
|
||||
// add argument annotation
|
||||
if (paramsAnnotation != null) {
|
||||
annotationGen.addForParameter(code, paramsAnnotation, i);
|
||||
annotationGen.addForParameter(code, paramsAnnotation, argNum);
|
||||
}
|
||||
if (var.isFinal()) {
|
||||
code.add("final ");
|
||||
@@ -239,7 +230,7 @@ public class MethodGen {
|
||||
} else {
|
||||
argType = varType;
|
||||
}
|
||||
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
|
||||
if (argNum == lastArgNum && mth.getAccessFlags().isVarArgs()) {
|
||||
// change last array argument to varargs
|
||||
if (argType.isArray()) {
|
||||
ArgType elType = argType.getArrayElement();
|
||||
@@ -258,11 +249,6 @@ public class MethodGen {
|
||||
code.attachDefinition(VarNode.get(mth, var));
|
||||
}
|
||||
code.add(varName);
|
||||
|
||||
i++;
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ public enum AFlag {
|
||||
REMOVE_SUPER_CLASS, // don't add super class
|
||||
|
||||
HIDDEN, // instruction used inside other instruction but not listed in args
|
||||
CONVERTED_ENUM, // enum class successfully restored to original form
|
||||
|
||||
DONT_RENAME, // do not rename during deobfuscation
|
||||
FORCE_RAW_NAME, // force use of raw name instead alias
|
||||
|
||||
@@ -53,4 +53,9 @@ public class CodeFeaturesAttr implements IJadxAttribute {
|
||||
public String toAttrString() {
|
||||
return "CodeFeatures{" + codeFeatures + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toAttrString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
@@ -14,11 +16,13 @@ public class EnumClassAttr implements IJadxAttribute {
|
||||
public static class EnumField {
|
||||
private final FieldNode field;
|
||||
private final ConstructorInsn constrInsn;
|
||||
private final @Nullable String nameStr;
|
||||
private ClassNode cls;
|
||||
|
||||
public EnumField(FieldNode field, ConstructorInsn co) {
|
||||
public EnumField(FieldNode field, ConstructorInsn co, @Nullable String nameStr) {
|
||||
this.field = field;
|
||||
this.constrInsn = co;
|
||||
this.nameStr = nameStr;
|
||||
}
|
||||
|
||||
public FieldNode getField() {
|
||||
@@ -37,6 +41,10 @@ public class EnumClassAttr implements IJadxAttribute {
|
||||
this.cls = cls;
|
||||
}
|
||||
|
||||
public @Nullable String getNameStr() {
|
||||
return nameStr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return field + "(" + constrInsn + ") " + cls;
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.BitSet;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
@@ -34,6 +35,9 @@ public class SkipMethodArgsAttr extends PinnedAttribute {
|
||||
if (mth == null) {
|
||||
return false;
|
||||
}
|
||||
if (argNum == 0 && mth.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||
return true;
|
||||
}
|
||||
SkipMethodArgsAttr attr = mth.get(AType.SKIP_MTH_ARGS);
|
||||
if (attr == null) {
|
||||
return false;
|
||||
|
||||
@@ -44,7 +44,8 @@ import jadx.core.utils.exceptions.JadxException;
|
||||
runAfter = {
|
||||
ModVisitor.class,
|
||||
FixAccessModifiers.class,
|
||||
ProcessAnonymous.class
|
||||
ProcessAnonymous.class,
|
||||
ExtractFieldInit.class
|
||||
}
|
||||
)
|
||||
public class ClassModifier extends AbstractVisitor {
|
||||
@@ -326,8 +327,9 @@ public class ClassModifier extends AbstractVisitor {
|
||||
}
|
||||
AccessInfo af = mth.getAccessFlags();
|
||||
boolean publicConstructor = mth.isConstructor() && af.isPublic();
|
||||
boolean enumDefConstructor = mth.isConstructor() && mth.getParentClass().contains(AFlag.CONVERTED_ENUM);
|
||||
boolean clsInit = mth.getMethodInfo().isClassInit() && af.isStatic();
|
||||
if (publicConstructor || clsInit) {
|
||||
if (publicConstructor || enumDefConstructor || clsInit) {
|
||||
if (!BlockUtils.isAllBlocksEmpty(mth.getBasicBlocks())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ public class ConstructorVisitor extends AbstractVisitor {
|
||||
|
||||
private static boolean canRemoveConstructor(MethodNode mth, ConstructorInsn co) {
|
||||
ClassNode parentClass = mth.getParentClass();
|
||||
if (co.isSuper() && (co.getArgsCount() == 0 || parentClass.isEnum())) {
|
||||
if (co.isSuper() && co.getArgsCount() == 0) {
|
||||
return true;
|
||||
}
|
||||
if (co.isThis() && co.getArgsCount() == 0) {
|
||||
|
||||
@@ -73,6 +73,7 @@ import static jadx.core.utils.InsnUtils.getWrappedInsn;
|
||||
}
|
||||
)
|
||||
public class EnumVisitor extends AbstractVisitor {
|
||||
private static final String ENUM_SUPER_CONSTRUCTOR_ID = "java.lang.Enum.<init>(Ljava/lang/String;I)V";
|
||||
|
||||
private MethodInfo enumValueOfMth;
|
||||
private MethodInfo cloneMth;
|
||||
@@ -171,11 +172,8 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
cls.addAttr(attr);
|
||||
|
||||
for (EnumField enumField : attr.getFields()) {
|
||||
ConstructorInsn co = enumField.getConstrInsn();
|
||||
FieldNode fieldNode = enumField.getField();
|
||||
|
||||
// use string arg from the constructor as enum field name
|
||||
String name = getConstString(cls.root(), co.getArg(0));
|
||||
String name = enumField.getNameStr();
|
||||
if (name != null
|
||||
&& !fieldNode.getAlias().equals(name)
|
||||
&& NameMapper.isValidAndPrintable(name)
|
||||
@@ -193,9 +191,24 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
CodeShrinkVisitor.shrinkMethod(classInitMth);
|
||||
}
|
||||
removeEnumMethods(cls, data.valuesField);
|
||||
fixAccessFlags(cls);
|
||||
cls.add(AFlag.CONVERTED_ENUM);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void fixAccessFlags(ClassNode cls) {
|
||||
// remove invalid access flags
|
||||
cls.setAccessFlags(cls.getAccessFlags()
|
||||
.remove(AccessFlags.FINAL)
|
||||
.remove(AccessFlags.ABSTRACT)
|
||||
.remove(AccessFlags.STATIC));
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
if (mth.getMethodInfo().isConstructor()) {
|
||||
mth.setAccessFlags(mth.getAccessFlags().remove(AccessInfo.VISIBILITY_FLAGS));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search "$VALUES" field (holds all enum values)
|
||||
*/
|
||||
@@ -433,13 +446,9 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
return enumFieldNode;
|
||||
}
|
||||
|
||||
@SuppressWarnings("StatementWithEmptyBody")
|
||||
private EnumField createEnumFieldByConstructor(EnumData data, FieldNode enumFieldNode, ConstructorInsn co) {
|
||||
// usually constructor signature is '<init>(Ljava/lang/String;I)V'.
|
||||
// sometimes for one field enum second arg can be omitted
|
||||
if (co.getArgsCount() < 1) {
|
||||
return null;
|
||||
}
|
||||
// usually constructor signature is '<init>(Ljava/lang/String;I)V', sometimes one or both args can
|
||||
// be omitted
|
||||
ClassNode cls = data.cls;
|
||||
ClassInfo clsInfo = co.getClassType();
|
||||
ClassNode constrCls = cls.root().resolveClass(clsInfo);
|
||||
@@ -457,17 +466,45 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
if (ctrMth == null) {
|
||||
return null;
|
||||
}
|
||||
List<RegisterArg> regs = new ArrayList<>();
|
||||
co.getRegisterArgs(regs);
|
||||
if (!regs.isEmpty()) {
|
||||
ConstructorInsn replacedCo = inlineExternalRegs(data, co);
|
||||
if (replacedCo == null) {
|
||||
throw new JadxRuntimeException("Init of enum field '" + enumFieldNode.getName() + "' uses external variables");
|
||||
// usually constructor signature is '<init>(Ljava/lang/String;I)V'
|
||||
// sometimes one or both args can be inlined or omitted
|
||||
String nameStr = null;
|
||||
if (co.getArgsCount() == 0) {
|
||||
ConstructorInsn ctrInsn = searchEnumSuperCtrInsn(ctrMth);
|
||||
if (ctrInsn != null && ctrInsn.getArgsCount() != 0) {
|
||||
nameStr = getConstString(ctrMth.root(), ctrInsn.getArg(0));
|
||||
}
|
||||
} else {
|
||||
nameStr = getConstString(cls.root(), co.getArg(0));
|
||||
// verify and try to inline additional constructor args
|
||||
List<RegisterArg> regs = new ArrayList<>();
|
||||
co.getRegisterArgs(regs);
|
||||
if (!regs.isEmpty()) {
|
||||
ConstructorInsn replacedCo = inlineExternalRegs(data, co);
|
||||
if (replacedCo == null) {
|
||||
throw new JadxRuntimeException("Init of enum field '" + enumFieldNode.getName() + "' uses external variables");
|
||||
}
|
||||
data.toRemove.add(co);
|
||||
co = replacedCo;
|
||||
}
|
||||
data.toRemove.add(co);
|
||||
co = replacedCo;
|
||||
}
|
||||
return new EnumField(enumFieldNode, co);
|
||||
return new EnumField(enumFieldNode, co, nameStr);
|
||||
}
|
||||
|
||||
private @Nullable ConstructorInsn searchEnumSuperCtrInsn(MethodNode ctrMth) {
|
||||
for (BlockNode block : ctrMth.getBasicBlocks()) {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (insn.getType() == InsnType.CONSTRUCTOR) {
|
||||
ConstructorInsn ctrCall = (ConstructorInsn) insn;
|
||||
if (ctrCall.isSuper()
|
||||
&& ctrCall.getArgsCount() != 0
|
||||
&& ctrCall.getCallMth().getRawFullId().equals(ENUM_SUPER_CONSTRUCTOR_ID)) {
|
||||
return ctrCall;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ConstructorInsn inlineExternalRegs(EnumData data, ConstructorInsn co) {
|
||||
@@ -574,10 +611,16 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
}
|
||||
String shortId = mi.getShortId();
|
||||
if (mi.isConstructor()) {
|
||||
markArgsForSkip(mth);
|
||||
// remove super constructor call
|
||||
ConstructorInsn superCtrInsn = searchEnumSuperCtrInsn(mth);
|
||||
if (superCtrInsn != null) {
|
||||
superCtrInsn.add(AFlag.DONT_GENERATE);
|
||||
InsnRemover.remove(mth, superCtrInsn);
|
||||
}
|
||||
if (isDefaultConstructor(mth, shortId)) {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
markArgsForSkip(mth);
|
||||
} else if (mi.getShortId().equals(valuesMethodShortId)) {
|
||||
if (isValuesMethod(mth, clsType)) {
|
||||
valuesMethod = mth;
|
||||
@@ -757,4 +800,9 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
this.staticBlocks = staticBlocks;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "EnumVisitor";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package jadx.tests.integration.enums;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.RaungTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestEnums11 extends RaungTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
assertThat(getClassNodeFromRaung())
|
||||
.code()
|
||||
.containsLines("public enum TestEnums11 {", indent(1) + "UNKNOWN;")
|
||||
.containsOne("public final int a = -99;")
|
||||
.doesNotContain("TestEnums11() {");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisableEnumRestore() {
|
||||
// constructor method incorrectly removed
|
||||
getArgs().getDisabledPasses().add("EnumVisitor");
|
||||
disableCompilation();
|
||||
assertThat(getClassNodeFromRaung())
|
||||
.code()
|
||||
.containsOne("public TestEnums11() {");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
.version 52 # Java 8
|
||||
.class public final enum enums/TestEnums11
|
||||
.super java/lang/Enum
|
||||
.signature Ljava/lang/Enum<Lenums/TestEnums11;>;
|
||||
.source "SourceFile"
|
||||
|
||||
.field public static final enum A Lenums/TestEnums11;
|
||||
.field public static final synthetic b [Lenums/TestEnums11;
|
||||
.field public final a I
|
||||
|
||||
.method public static values()[Lenums/TestEnums11;
|
||||
.max stack 1
|
||||
.max locals 1
|
||||
|
||||
getstatic enums/TestEnums11 b [Lenums/TestEnums11;
|
||||
invokevirtual [Lenums/TestEnums11; clone ()Ljava/lang/Object;
|
||||
checkcast [Lenums/TestEnums11;
|
||||
areturn
|
||||
.end method
|
||||
|
||||
.method public static valueOf(Ljava/lang/String;)Lenums/TestEnums11;
|
||||
.max stack 2
|
||||
.max locals 1
|
||||
|
||||
ldc Lenums/TestEnums11;
|
||||
aload 0
|
||||
invokestatic java/lang/Enum valueOf (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
|
||||
checkcast enums/TestEnums11
|
||||
areturn
|
||||
.end method
|
||||
|
||||
.method public <init>()V
|
||||
.signature (I)V
|
||||
.max stack 4
|
||||
.max locals 1
|
||||
|
||||
aload 0
|
||||
dup
|
||||
ldc "UNKNOWN"
|
||||
iconst_0
|
||||
invokespecial java/lang/Enum <init> (Ljava/lang/String;I)V
|
||||
bipush -99
|
||||
putfield enums/TestEnums11 a I
|
||||
return
|
||||
.end method
|
||||
|
||||
.method public static <clinit>()V
|
||||
.max stack 4
|
||||
.max locals 1
|
||||
|
||||
new enums/TestEnums11
|
||||
dup
|
||||
dup
|
||||
astore 0
|
||||
invokespecial enums/TestEnums11 <init> ()V
|
||||
putstatic enums/TestEnums11 A Lenums/TestEnums11;
|
||||
iconst_1
|
||||
anewarray enums/TestEnums11
|
||||
dup
|
||||
iconst_0
|
||||
aload 0
|
||||
aastore
|
||||
putstatic enums/TestEnums11 b [Lenums/TestEnums11;
|
||||
return
|
||||
.end method
|
||||
|
||||
.method public getErrorCode()I
|
||||
.max stack 1
|
||||
.max locals 1
|
||||
|
||||
aload 0
|
||||
getfield enums/TestEnums11 a I
|
||||
ireturn
|
||||
.end method
|
||||
Reference in New Issue
Block a user