diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index 10df14db5..cbd81976d 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -459,7 +459,7 @@ public class ClassGen { } if (f.getCls() != null) { code.add(' '); - new ClassGen(f.getCls(), this).addClassBody(code); + new ClassGen(f.getCls(), this).addClassBody(code, true); } if (it.hasNext()) { code.add(','); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java index 2fdd998e5..638799c53 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java @@ -71,7 +71,14 @@ public class EnumVisitor extends AbstractVisitor { @Override public boolean visit(ClassNode cls) throws JadxException { - if (!convertToEnum(cls)) { + boolean converted; + try { + converted = convertToEnum(cls); + } catch (Exception e) { + cls.addWarnComment("Enum visitor error", e); + converted = false; + } + if (!converted) { AccessInfo accessFlags = cls.getAccessFlags(); if (accessFlags.isEnum()) { cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM)); @@ -179,8 +186,7 @@ public class EnumVisitor extends AbstractVisitor { if (!enumClsInfo.equals(cls.getClassInfo())) { ClassNode enumCls = cls.root().resolveClass(enumClsInfo); if (enumCls != null) { - processEnumCls(enumField, enumCls); - cls.addInlinedClass(enumCls); + processEnumCls(cls, enumField, enumCls); } } List regs = new ArrayList<>(); @@ -381,7 +387,11 @@ public class EnumVisitor extends AbstractVisitor { if (constrCls == null) { return null; } - if (!clsInfo.equals(cls.getClassInfo()) && !constrCls.getAccessFlags().isEnum()) { + if (constrCls.equals(cls)) { + // allow same class + } else if (constrCls.contains(AFlag.ANONYMOUS_CLASS)) { + // allow external class already marked as anonymous + } else { return null; } MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth()); @@ -466,7 +476,7 @@ public class EnumVisitor extends AbstractVisitor { return InsnUtils.searchInsn(mth, InsnType.SGET, insnTest) != null; } - private static void processEnumCls(EnumField field, ClassNode innerCls) { + private static void processEnumCls(ClassNode cls, EnumField field, ClassNode innerCls) { // remove constructor, because it is anonymous class for (MethodNode innerMth : innerCls.getMethods()) { if (innerMth.getAccessFlags().isConstructor()) { @@ -474,7 +484,11 @@ public class EnumVisitor extends AbstractVisitor { } } field.setCls(innerCls); - innerCls.add(AFlag.DONT_GENERATE); + if (!innerCls.getParentClass().equals(cls)) { + // not inner + cls.addInlinedClass(innerCls); + innerCls.add(AFlag.DONT_GENERATE); + } } private ConstructorInsn getConstructorInsn(InsnNode insn) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java index 26d8f4088..63e2b7982 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java @@ -42,10 +42,7 @@ public class ProcessAnonymous extends AbstractVisitor { } private static void markAnonymousClass(ClassNode cls) { - boolean synthetic = cls.getAccessFlags().isSynthetic() - || cls.getClassInfo().getShortName().contains("$") - || Character.isDigit(cls.getClassInfo().getShortName().charAt(0)); - if (!synthetic) { + if (!canBeAnonymous(cls)) { return; } MethodNode anonymousConstructor = checkUsage(cls); @@ -77,6 +74,22 @@ public class ProcessAnonymous extends AbstractVisitor { } } + private static boolean canBeAnonymous(ClassNode cls) { + if (cls.getAccessFlags().isSynthetic()) { + return true; + } + String shortName = cls.getClassInfo().getShortName(); + if (shortName.contains("$") || Character.isDigit(shortName.charAt(0))) { + return true; + } + if (cls.getUseIn().size() == 1 && cls.getUseInMth().size() == 1) { + MethodNode useMth = cls.getUseInMth().get(0); + // allow use in enum class init + return useMth.getMethodInfo().isClassInit() && useMth.getParentClass().isEnum(); + } + return false; + } + /** * Checks: * - class have only one constructor which used only once (allow common code for field init) diff --git a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java index b9cfb3d13..044b27246 100644 --- a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java +++ b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java @@ -52,19 +52,18 @@ public class JadxCodeAssertions extends AbstractStringAssert } String indent = TestUtils.indent(commonIndent); StringBuilder sb = new StringBuilder(); - boolean first = true; for (String line : lines) { - if (!line.isEmpty()) { - if (first) { - first = false; - } else { - sb.append(ICodeWriter.NL); - } - sb.append(indent); - sb.append(line); + sb.append(ICodeWriter.NL); + if (line.isEmpty()) { + // don't add common indent to empty lines + continue; } + String searchLine = indent + line; + sb.append(searchLine); + // check every line for easier debugging + contains(searchLine); } - return containsOnlyOnce(sb.toString()); + return containsOnlyOnce(sb.substring(ICodeWriter.NL.length())); } public JadxCodeAssertions removeBlockComments() { diff --git a/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums2.java b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums2.java index 65c9fc3b9..6ec5cdd72 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums2.java @@ -2,11 +2,10 @@ package jadx.tests.integration.enums; import org.junit.jupiter.api.Test; -import jadx.core.dex.nodes.ClassNode; +import jadx.api.CommentsLevel; import jadx.tests.api.IntegrationTest; -import jadx.tests.api.utils.JadxMatchers; -import static org.hamcrest.MatcherAssert.assertThat; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; public class TestEnums2 extends IntegrationTest { @@ -32,25 +31,25 @@ public class TestEnums2 extends IntegrationTest { @Test public void test() { - ClassNode cls = getClassNode(TestCls.class); - String code = removeLineComments(cls); - - assertThat(code, JadxMatchers.containsLines(1, - "public enum Operation {", - indent(1) + "PLUS {", - indent(2) + "@Override", - indent(2) + "public int apply(int x, int y) {", - indent(3) + "return x + y;", - indent(2) + '}', - indent(1) + "},", - indent(1) + "MINUS {", - indent(2) + "@Override", - indent(2) + "public int apply(int x, int y) {", - indent(3) + "return x - y;", - indent(2) + '}', - indent(1) + "};", - "", - indent(1) + "public abstract int apply(int i, int i2);", - "}")); + getArgs().setCommentsLevel(CommentsLevel.WARN); + assertThat(getClassNode(TestCls.class)) + .code() + .containsLines(1, + "public enum Operation {", + indent(1) + "PLUS {", + indent(2) + "@Override", + indent(2) + "public int apply(int x, int y) {", + indent(3) + "return x + y;", + indent(2) + '}', + indent(1) + "},", + indent(1) + "MINUS {", + indent(2) + "@Override", + indent(2) + "public int apply(int x, int y) {", + indent(3) + "return x - y;", + indent(2) + '}', + indent(1) + "};", + "", + indent(1) + "public abstract int apply(int i, int i2);", + "}"); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnumsInterface.java b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnumsInterface.java index a28ed65b9..5344ab146 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnumsInterface.java +++ b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnumsInterface.java @@ -2,11 +2,10 @@ package jadx.tests.integration.enums; import org.junit.jupiter.api.Test; -import jadx.core.dex.nodes.ClassNode; +import jadx.api.CommentsLevel; import jadx.tests.api.IntegrationTest; -import jadx.tests.api.utils.JadxMatchers; -import static org.hamcrest.MatcherAssert.assertThat; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; public class TestEnumsInterface extends IntegrationTest { @@ -34,23 +33,23 @@ public class TestEnumsInterface extends IntegrationTest { @Test public void test() { - ClassNode cls = getClassNode(TestCls.class); - String code = removeLineComments(cls); - - assertThat(code, JadxMatchers.containsLines(1, - "public enum Operation implements IOperation {", - indent(1) + "PLUS {", - indent(2) + "@Override", - indent(2) + "public int apply(int x, int y) {", - indent(3) + "return x + y;", - indent(2) + '}', - indent(1) + "},", - indent(1) + "MINUS {", - indent(2) + "@Override", - indent(2) + "public int apply(int x, int y) {", - indent(3) + "return x - y;", - indent(2) + '}', - indent(1) + '}', - "}")); + getArgs().setCommentsLevel(CommentsLevel.WARN); + assertThat(getClassNode(TestCls.class)) + .code() + .containsLines(1, + "public enum Operation implements IOperation {", + indent(1) + "PLUS {", + indent(2) + "@Override", + indent(2) + "public int apply(int x, int y) {", + indent(3) + "return x + y;", + indent(2) + '}', + indent(1) + "},", + indent(1) + "MINUS {", + indent(2) + "@Override", + indent(2) + "public int apply(int x, int y) {", + indent(3) + "return x - y;", + indent(2) + '}', + indent(1) + '}', + "}"); } }