diff --git a/jadx-core/src/main/java/jadx/core/Consts.java b/jadx-core/src/main/java/jadx/core/Consts.java index a71c69dd5..e8dd88093 100644 --- a/jadx-core/src/main/java/jadx/core/Consts.java +++ b/jadx-core/src/main/java/jadx/core/Consts.java @@ -20,4 +20,5 @@ public class Consts { public static final String DALVIK_ANNOTATION_DEFAULT = "dalvik.annotation.AnnotationDefault"; public static final String DEFAULT_PACKAGE_NAME = "defpackage"; + public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass_"; } 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 dd6946486..455a838bf 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -243,7 +243,7 @@ public class ClassGen { mthGen.makeMethodDump(code); } mthGen.addDefinition(code); - code.add(" {"); + code.add('{'); insertSourceFileInfo(code, mth); code.add(mthGen.makeInstructions(code.getIndent())); code.startLine('}'); diff --git a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java index 5129c3350..e72f24d9c 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -33,6 +33,7 @@ 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; @@ -523,13 +524,18 @@ public class InsnGen { if (cls != null && cls.isAnonymous()) { // anonymous class construction ClassInfo parent; - if (cls.getSuperClass() != null - && !cls.getSuperClass().getFullName().equals("java.lang.Object")) + if (cls.getSuperClass() != null && !cls.getSuperClass().isObject()) { parent = cls.getSuperClass(); - else + } else { parent = cls.getInterfaces().get(0); - - code.add("new ").add(useClass(parent)).add("()"); + } + MethodNode defCtr = cls.getDefaultConstructor(); + if (RegionUtils.notEmpty(defCtr.getRegion())) { + defCtr.getAttributes().add(AttributeFlag.ANONYMOUS_CONSTRUCTOR); + } else { + defCtr.getAttributes().add(AttributeFlag.DONT_GENERATE); + } + code.add("new ").add(useClass(parent)).add("() "); code.incIndent(2); new ClassGen(cls, mgen.getClassGen().getParentGen(), fallback).makeClassBody(code); code.decIndent(2); diff --git a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java index 8ee62c40d..216667b3e 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -1,6 +1,7 @@ package jadx.core.codegen; import jadx.core.Consts; +import jadx.core.dex.attributes.AttributeFlag; import jadx.core.dex.attributes.AttributeType; import jadx.core.dex.attributes.AttributesList; import jadx.core.dex.attributes.JadxErrorAttr; @@ -57,52 +58,61 @@ public class MethodGen { } public void addDefinition(CodeWriter code) { + if (mth.getMethodInfo().isClassInit()) { code.startLine("static"); - } else { - annotationGen.addForMethod(code, mth); - - AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags(); - AccessInfo ai = mth.getAccessFlags(); - // don't add 'abstract' to methods in interface - if (clsAccFlags.isInterface()) { - ai = ai.remove(AccessFlags.ACC_ABSTRACT); - } - // don't add 'public' for annotations - if (clsAccFlags.isAnnotation()) { - ai = ai.remove(AccessFlags.ACC_PUBLIC); - } - code.startLine(ai.makeString()); - - if (classGen.makeGenericMap(code, mth.getGenericMap())) - code.add(' '); - - if (mth.getAccessFlags().isConstructor()) { - code.add(classGen.getClassNode().getShortName()); // constructor - } else { - code.add(TypeGen.translate(classGen, mth.getReturnType())); - code.add(' '); - code.add(mth.getName()); - } - code.add('('); - - List args = mth.getArguments(false); - if (mth.getMethodInfo().isConstructor() - && mth.getParentClass().getAttributes().contains(AttributeType.ENUM_CLASS)) { - if (args.size() == 2) - args.clear(); - else if (args.size() > 2) - args = args.subList(2, args.size()); - else - LOG.warn(ErrorsCounter.formatErrorMsg(mth, - "Incorrect number of args for enum constructor: " + args.size() - + " (expected >= 2)")); - } - code.add(makeArguments(args)); - code.add(")"); - - annotationGen.addThrows(mth, code); + code.attachAnnotation(mth); + return; } + if (mth.getAttributes().contains(AttributeFlag.ANONYMOUS_CONSTRUCTOR)) { + // don't add method name and arguments + code.startLine(); + code.attachAnnotation(mth); + return; + } + annotationGen.addForMethod(code, mth); + + AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags(); + AccessInfo ai = mth.getAccessFlags(); + // don't add 'abstract' to methods in interface + if (clsAccFlags.isInterface()) { + ai = ai.remove(AccessFlags.ACC_ABSTRACT); + } + // don't add 'public' for annotations + if (clsAccFlags.isAnnotation()) { + ai = ai.remove(AccessFlags.ACC_PUBLIC); + } + code.startLine(ai.makeString()); + + if (classGen.makeGenericMap(code, mth.getGenericMap())) { + code.add(' '); + } + if (mth.getAccessFlags().isConstructor()) { + code.add(classGen.getClassNode().getShortName()); // constructor + } else { + code.add(TypeGen.translate(classGen, mth.getReturnType())); + code.add(' '); + code.add(mth.getName()); + } + code.add('('); + + List args = mth.getArguments(false); + if (mth.getMethodInfo().isConstructor() + && mth.getParentClass().getAttributes().contains(AttributeType.ENUM_CLASS)) { + if (args.size() == 2) { + args.clear(); + } else if (args.size() > 2) { + args = args.subList(2, args.size()); + } else { + LOG.warn(ErrorsCounter.formatErrorMsg(mth, + "Incorrect number of args for enum constructor: " + args.size() + + " (expected >= 2)")); + } + } + code.add(makeArguments(args)); + code.add(") "); + + annotationGen.addThrows(mth, code); code.attachAnnotation(mth); } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeFlag.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeFlag.java index 6a26f0f60..43fbc5a08 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeFlag.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeFlag.java @@ -17,6 +17,7 @@ public enum AttributeFlag { SKIP, SKIP_FIRST_ARG, + ANONYMOUS_CONSTRUCTOR, INCONSISTENT_CODE, // warning about incorrect decompilation } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java index 658715184..9c08678a7 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java @@ -80,7 +80,7 @@ public final class ClassInfo { char firstChar = name.charAt(0); if (Character.isDigit(firstChar)) { - name = "AnonymousClass_" + name; + name = Consts.ANONYMOUS_CLASS_PREFIX + name; } else if (firstChar == '$') { name = "_" + name; } @@ -101,6 +101,10 @@ public final class ClassInfo { return fullName; } + public boolean isObject() { + return fullName.equals(Consts.CLASS_OBJECT); + } + public String getShortName() { return name; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index ff89d89a4..c6b1e3250 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -338,15 +338,19 @@ public class ClassNode extends LineAttrNode implements ILoadable { } public boolean isAnonymous() { - boolean simple = false; - for (MethodNode m : methods) { - MethodInfo mi = m.getMethodInfo(); - if (mi.isConstructor() && mi.getArgumentsTypes().size() == 0) { - simple = true; - break; + MethodNode defConstrExists = getDefaultConstructor(); + return defConstrExists != null && getShortName().startsWith(Consts.ANONYMOUS_CLASS_PREFIX); + } + + public MethodNode getDefaultConstructor() { + for (MethodNode mth : methods) { + if (mth.getAccessFlags().isConstructor() + && mth.getMethodInfo().isConstructor() + && mth.getArguments(false).isEmpty()) { + return mth; } } - return simple && Character.isDigit(getShortName().charAt(0)); + return null; } public AccessInfo getAccessFlags() { diff --git a/jadx-core/src/test/java/jadx/api/InternalJadxTest.java b/jadx-core/src/test/java/jadx/api/InternalJadxTest.java index 1674ec274..1593da324 100644 --- a/jadx-core/src/test/java/jadx/api/InternalJadxTest.java +++ b/jadx-core/src/test/java/jadx/api/InternalJadxTest.java @@ -140,6 +140,8 @@ public abstract class InternalJadxTest { } } + // Use only for debug purpose + @Deprecated protected void setOutputCFG() { this.outputCFG = true; } diff --git a/jadx-core/src/test/java/jadx/tests/internal/TestAnonymousClass.java b/jadx-core/src/test/java/jadx/tests/internal/TestAnonymousClass.java new file mode 100644 index 000000000..5447dc0d5 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/internal/TestAnonymousClass.java @@ -0,0 +1,41 @@ +package jadx.tests.internal; + +import jadx.api.InternalJadxTest; +import jadx.core.dex.nodes.ClassNode; + +import java.io.File; +import java.io.FilenameFilter; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.not; +import static org.junit.Assert.assertThat; + +public class TestAnonymousClass extends InternalJadxTest { + + public static class TestCls { + + public int test() { + String[] files = new File("a").list(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.equals("a"); + } + }); + return files.length; + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, containsString("new File(\"a\").list(new FilenameFilter()")); + assertThat(code, not(containsString("synthetic"))); + assertThat(code, not(containsString("this"))); + assertThat(code, not(containsString("null"))); + assertThat(code, not(containsString("AnonymousClass_"))); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/internal/TestLoopCondition.java b/jadx-core/src/test/java/jadx/tests/internal/TestLoopCondition.java index c149a34b9..bc2ec6028 100644 --- a/jadx-core/src/test/java/jadx/tests/internal/TestLoopCondition.java +++ b/jadx-core/src/test/java/jadx/tests/internal/TestLoopCondition.java @@ -53,8 +53,6 @@ public class TestLoopCondition extends InternalJadxTest { @Test public void test() { - setOutputCFG(); - ClassNode cls = getClassNode(TestCls.class); String code = cls.getCode().toString(); diff --git a/jadx-samples/src/main/java/jadx/samples/TestInner.java b/jadx-samples/src/main/java/jadx/samples/TestInner.java index 87738b920..fc7ba6131 100644 --- a/jadx-samples/src/main/java/jadx/samples/TestInner.java +++ b/jadx-samples/src/main/java/jadx/samples/TestInner.java @@ -51,6 +51,18 @@ public class TestInner extends AbstractTest { }.run(); } + public void func2() { + new Runnable() { + { + count += 5; + } + @Override + public void run() { + count += 6; + } + }.run(); + } + @SuppressWarnings("serial") public static class MyException extends Exception { public MyException(String str, Exception e) { @@ -63,6 +75,7 @@ public class TestInner extends AbstractTest { TestInner c = new TestInner(); TestInner.count = 0; c.func(); + c.func2(); Runnable myRunnable = new Runnable() { @Override @@ -81,6 +94,6 @@ public class TestInner extends AbstractTest { thread.join(); thread2.join(); - return TestInner.count == 15; + return TestInner.count == 26; } }