From 7654661b776c76abcb97915b46b9d1ace41c7fc9 Mon Sep 17 00:00:00 2001 From: Skylot Date: Fri, 15 Mar 2019 22:12:03 +0300 Subject: [PATCH] fix: inline desugared lambda classes (#467) --- .../main/java/jadx/api/JadxDecompiler.java | 14 +++--- .../src/main/java/jadx/core/ProcessClass.java | 12 ++--- .../main/java/jadx/core/codegen/ClassGen.java | 2 +- .../main/java/jadx/core/codegen/CodeGen.java | 17 ++++--- .../java/jadx/core/codegen/CodeWriter.java | 7 ++- .../main/java/jadx/core/codegen/InsnGen.java | 13 ++--- .../java/jadx/core/dex/nodes/ClassNode.java | 26 +++++++++- .../jadx/core/dex/visitors/ClassModifier.java | 49 +++++++++++-------- .../dex/visitors/DependencyCollector.java | 4 ++ .../java/jadx/core/utils/RegionUtils.java | 7 ++- .../java/jadx/tests/api/IntegrationTest.java | 4 +- .../jadx/tests/external/BaseExternalTest.java | 12 ++--- 12 files changed, 100 insertions(+), 67 deletions(-) diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index db84e02e7..ccd40ab34 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -17,7 +17,6 @@ import org.slf4j.LoggerFactory; import jadx.core.Jadx; import jadx.core.ProcessClass; -import jadx.core.codegen.CodeGen; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.FieldNode; @@ -59,7 +58,6 @@ public final class JadxDecompiler { private RootNode root; private List passes; - private CodeGen codeGen; private List classes; private List resources; @@ -97,7 +95,6 @@ public final class JadxDecompiler { void init() { this.passes = Jadx.getPassesList(args); - this.codeGen = new CodeGen(); } void reset() { @@ -106,7 +103,6 @@ public final class JadxDecompiler { xmlParser = null; root = null; passes = null; - codeGen = null; } public static String getVersion() { @@ -215,9 +211,11 @@ public final class JadxDecompiler { List clsList = new ArrayList<>(classNodeList.size()); classesMap.clear(); for (ClassNode classNode : classNodeList) { - JavaClass javaClass = new JavaClass(classNode, this); - clsList.add(javaClass); - classesMap.put(classNode, javaClass); + if (!classNode.contains(AFlag.DONT_GENERATE)) { + JavaClass javaClass = new JavaClass(classNode, this); + clsList.add(javaClass); + classesMap.put(classNode, javaClass); + } } classes = Collections.unmodifiableList(clsList); } @@ -289,7 +287,7 @@ public final class JadxDecompiler { } void processClass(ClassNode cls) { - ProcessClass.process(cls, passes, codeGen); + ProcessClass.process(cls, passes, true); } RootNode getRoot() { diff --git a/jadx-core/src/main/java/jadx/core/ProcessClass.java b/jadx-core/src/main/java/jadx/core/ProcessClass.java index 985510e9b..d35067806 100644 --- a/jadx-core/src/main/java/jadx/core/ProcessClass.java +++ b/jadx-core/src/main/java/jadx/core/ProcessClass.java @@ -2,8 +2,6 @@ package jadx.core; import java.util.List; -import org.jetbrains.annotations.Nullable; - import jadx.core.codegen.CodeGen; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.visitors.DepthTraversal; @@ -19,8 +17,8 @@ public final class ProcessClass { private ProcessClass() { } - public static void process(ClassNode cls, List passes, @Nullable CodeGen codeGen) { - if (codeGen == null && cls.getState() == PROCESSED) { + public static void process(ClassNode cls, List passes, boolean generateCode) { + if (!generateCode && cls.getState() == PROCESSED) { return; } synchronized (getSyncObj(cls)) { @@ -33,9 +31,9 @@ public final class ProcessClass { } cls.setState(PROCESSED); } - if (cls.getState() == PROCESSED && codeGen != null) { + if (cls.getState() == PROCESSED && generateCode) { processDependencies(cls, passes); - codeGen.visit(cls); + CodeGen.generate(cls); } } catch (Exception e) { ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e); @@ -48,6 +46,6 @@ public final class ProcessClass { } private static void processDependencies(ClassNode cls, List passes) { - cls.getDependencies().forEach(depCls -> process(depCls, passes, null)); + cls.getDependencies().forEach(depCls -> process(depCls, passes, false)); } } 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 dec89c983..b06c5aa4b 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -98,7 +98,7 @@ public class ClassGen { imports.clear(); } clsCode.add(clsBody); - return clsCode; + return clsCode.finish(); } public void addClassCode(CodeWriter code) throws CodegenException { diff --git a/jadx-core/src/main/java/jadx/core/codegen/CodeGen.java b/jadx-core/src/main/java/jadx/core/codegen/CodeGen.java index eae73cb09..926443efc 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/CodeGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/CodeGen.java @@ -1,15 +1,20 @@ package jadx.core.codegen; +import jadx.core.dex.attributes.AFlag; import jadx.core.dex.nodes.ClassNode; import jadx.core.utils.exceptions.CodegenException; public class CodeGen { - public boolean visit(ClassNode cls) throws CodegenException { - ClassGen clsGen = new ClassGen(cls, cls.root().getArgs()); - CodeWriter clsCode = clsGen.makeClass(); - clsCode.finish(); - cls.setCode(clsCode); - return false; + public static void generate(ClassNode cls) throws CodegenException { + if (cls.contains(AFlag.DONT_GENERATE)) { + cls.setCode(CodeWriter.EMPTY); + } else { + ClassGen clsGen = new ClassGen(cls, cls.root().getArgs()); + cls.setCode(clsGen.makeClass()); + } + } + + private CodeGen() { } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java b/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java index 31a2e2783..4f5adc847 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java +++ b/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java @@ -24,6 +24,8 @@ public class CodeWriter { public static final String NL = System.getProperty("line.separator"); public static final String INDENT_STR = " "; + public static final CodeWriter EMPTY = new CodeWriter().finish(); + private static final boolean ADD_LINE_NUMBERS = false; private static final String[] INDENT_CACHE = { @@ -250,7 +252,7 @@ public class CodeWriter { return lineMap; } - public void finish() { + public CodeWriter finish() { removeFirstEmptyLine(); buf.trimToSize(); code = buf.toString(); @@ -266,11 +268,12 @@ public class CodeWriter { it.remove(); } } + return this; } private void removeFirstEmptyLine() { int len = NL.length(); - if (buf.substring(0, len).equals(NL)) { + if (buf.length() > len && buf.substring(0, len).equals(NL)) { buf.delete(0, len); } } 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 e148f1c7f..decaabd21 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -48,7 +48,6 @@ 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.RegionUtils; import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -559,7 +558,7 @@ public class InsnGen { throws CodegenException { ClassNode cls = mth.dex().resolveClass(insn.getClassType()); if (cls != null && cls.contains(AFlag.ANONYMOUS_CLASS) && !fallback) { - inlineAnonymousConstr(code, cls, insn); + inlineAnonymousConstructor(code, cls, insn); return; } if (insn.isSelf()) { @@ -577,20 +576,14 @@ public class InsnGen { generateMethodArguments(code, insn, 0, callMth); } - private void inlineAnonymousConstr(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException { - // anonymous class construction - if (cls.contains(AFlag.DONT_GENERATE)) { - code.add("/* anonymous class already generated */"); - ErrorsCounter.methodWarn(mth, "Anonymous class already generated: " + cls); - return; - } + private void inlineAnonymousConstructor(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException { + cls.add(AFlag.DONT_GENERATE); ArgType parent; if (cls.getInterfaces().size() == 1) { parent = cls.getInterfaces().get(0); } else { parent = cls.getSuperClass(); } - cls.add(AFlag.DONT_GENERATE); MethodNode defCtr = cls.getDefaultConstructor(); if (defCtr != null) { if (RegionUtils.notEmpty(defCtr.getRegion())) { 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 73b028650..3ae9e8de3 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 @@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory; import jadx.core.Consts; import jadx.core.codegen.CodeWriter; +import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.annotations.Annotation; import jadx.core.dex.attributes.nodes.LineAttrNode; import jadx.core.dex.attributes.nodes.SourceFileAttr; @@ -123,7 +124,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { accFlagsValue = cls.getAccessFlags(); } this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS); - + markAnonymousClass(this); buildCache(); } catch (Exception e) { throw new JadxRuntimeException("Error decode class: " + clsInfo, e); @@ -394,6 +395,29 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { && getDefaultConstructor() != null; } + public boolean isLambdaCls() { + return accessFlags.isSynthetic() && accessFlags.isFinal() + && clsInfo.getType().getObject().contains(".-$$Lambda$") + && countStaticFields() == 0; + } + + private int countStaticFields() { + int c = 0; + for (FieldNode field : fields) { + if (field.getAccessFlags().isStatic()) { + c++; + } + } + return c; + } + + private static void markAnonymousClass(ClassNode cls) { + if (cls.isAnonymous() || cls.isLambdaCls()) { + cls.add(AFlag.ANONYMOUS_CLASS); + cls.add(AFlag.DONT_GENERATE); + } + } + @Nullable public MethodNode getClassInitMth() { return searchMethodByName("()V"); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java index 04a493bcd..963130f4d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java @@ -54,40 +54,46 @@ public class ClassModifier extends AbstractVisitor { cls.add(AFlag.DONT_GENERATE); return false; } - removeSyntheticFields(cls); - cls.getMethods().forEach(mth -> removeSyntheticMethods(cls, mth)); - cls.getMethods().forEach(ClassModifier::removeEmptyMethods); - markAnonymousClass(cls); + removeSyntheticFields(cls); + cls.getMethods().forEach(ClassModifier::removeSyntheticMethods); + cls.getMethods().forEach(ClassModifier::removeEmptyMethods); return false; } private void markAnonymousClass(ClassNode cls) { if (cls.isAnonymous()) { cls.add(AFlag.ANONYMOUS_CLASS); + cls.add(AFlag.DONT_GENERATE); } } + /** + * Remove synthetic fields if type is outer class or class will be inlined (anonymous) + */ private static void removeSyntheticFields(ClassNode cls) { - if (!cls.getClassInfo().isInner() || cls.getAccessFlags().isStatic()) { + if (cls.getAccessFlags().isStatic()) { return; } - // remove fields if it is synthetic and type is a outer class - for (FieldNode field : cls.getFields()) { - if (field.getAccessFlags().isSynthetic() && field.getType().isObject()) { - ClassInfo clsInfo = ClassInfo.fromType(cls.root(), field.getType()); - ClassNode fieldsCls = cls.dex().resolveClass(clsInfo); - ClassInfo parentClass = cls.getClassInfo().getParentClass(); - if (fieldsCls != null && parentClass.equals(fieldsCls.getClassInfo())) { - int found = 0; - for (MethodNode mth : cls.getMethods()) { - if (removeFieldUsageFromConstructor(mth, field, fieldsCls)) { - found++; + boolean inline = cls.contains(AFlag.ANONYMOUS_CLASS); + if (inline || cls.getClassInfo().isInner()) { + for (FieldNode field : cls.getFields()) { + if (field.getAccessFlags().isSynthetic() && field.getType().isObject()) { + ClassInfo clsInfo = ClassInfo.fromType(cls.root(), field.getType()); + ClassNode fieldsCls = cls.dex().resolveClass(clsInfo); + ClassInfo parentClass = cls.getClassInfo().getParentClass(); + if (fieldsCls != null + && (inline || parentClass.equals(fieldsCls.getClassInfo()))) { + int found = 0; + for (MethodNode mth : cls.getMethods()) { + if (removeFieldUsageFromConstructor(mth, field, fieldsCls)) { + found++; + } + } + if (found != 0) { + field.addAttr(new FieldReplaceAttr(fieldsCls.getClassInfo())); + field.add(AFlag.DONT_GENERATE); } - } - if (found != 0) { - field.addAttr(new FieldReplaceAttr(parentClass)); - field.add(AFlag.DONT_GENERATE); } } } @@ -133,7 +139,7 @@ public class ClassModifier extends AbstractVisitor { return true; } - private static void removeSyntheticMethods(ClassNode cls, MethodNode mth) { + private static void removeSyntheticMethods(MethodNode mth) { if (mth.isNoCode()) { return; } @@ -141,6 +147,7 @@ public class ClassModifier extends AbstractVisitor { if (!af.isSynthetic()) { return; } + ClassNode cls = mth.getParentClass(); if (removeBridgeMethod(cls, mth)) { if (Consts.DEBUG) { mth.addAttr(AType.COMMENTS, "Removed as synthetic bridge method"); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java index 558ef3fb4..92cffd845 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java @@ -11,6 +11,7 @@ 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.RegisterArg; +import jadx.core.dex.instructions.mods.ConstructorInsn; import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.DexNode; @@ -90,6 +91,9 @@ public class DependencyCollector extends AbstractVisitor { } else if (insn instanceof InvokeNode) { ClassInfo declClass = ((InvokeNode) insn).getCallMth().getDeclClass(); addDep(dex, depList, declClass); + } else if (insn instanceof ConstructorInsn) { + ClassInfo declClass = ((ConstructorInsn) insn).getCallMth().getDeclClass(); + addDep(dex, depList, declClass); } } diff --git a/jadx-core/src/main/java/jadx/core/utils/RegionUtils.java b/jadx-core/src/main/java/jadx/core/utils/RegionUtils.java index f4a9b5d0a..5ded8fe65 100644 --- a/jadx-core/src/main/java/jadx/core/utils/RegionUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/RegionUtils.java @@ -5,6 +5,8 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import org.jetbrains.annotations.Nullable; + import jadx.core.dex.attributes.AType; import jadx.core.dex.instructions.InsnType; import jadx.core.dex.nodes.BlockNode; @@ -136,7 +138,10 @@ public class RegionUtils { return !notEmpty(container); } - public static boolean notEmpty(IContainer container) { + public static boolean notEmpty(@Nullable IContainer container) { + if (container == null) { + return false; + } if (container instanceof IBlock) { return !((IBlock) container).getInstructions().isEmpty(); } else if (container instanceof IRegion) { diff --git a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java index 32fa837d3..de77a9773 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -139,7 +139,7 @@ public abstract class IntegrationTest extends TestUtils { protected void decompile(JadxDecompiler jadx, ClassNode cls) { List passes = getPassesList(jadx); - ProcessClass.process(cls, passes, new CodeGen()); + ProcessClass.process(cls, passes, true); } protected void decompileWithoutUnload(JadxDecompiler jadx, ClassNode cls) { @@ -168,7 +168,7 @@ public abstract class IntegrationTest extends TestUtils { protected void generateClsCode(ClassNode cls) { try { - new CodeGen().visit(cls); + CodeGen.generate(cls); } catch (Exception e) { e.printStackTrace(); fail(e.getMessage()); diff --git a/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java b/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java index 58b29cc5d..17dd03908 100644 --- a/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java +++ b/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java @@ -15,12 +15,11 @@ import jadx.api.JadxDecompiler; import jadx.api.JadxInternalAccess; import jadx.api.JavaClass; import jadx.core.Jadx; -import jadx.core.codegen.CodeGen; +import jadx.core.ProcessClass; import jadx.core.codegen.CodeWriter; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; -import jadx.core.dex.visitors.DepthTraversal; import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.tests.api.IntegrationTest; @@ -77,7 +76,7 @@ public abstract class BaseExternalTest extends IntegrationTest { for (ClassNode classNode : root.getClasses(true)) { String clsFullName = classNode.getClassInfo().getFullName(); if (clsPattern.matcher(clsFullName).matches()) { - if (processCls(mthPattern, passes, classNode)) { + if (processCls(jadx, mthPattern, passes, classNode)) { processed++; } } @@ -85,7 +84,7 @@ public abstract class BaseExternalTest extends IntegrationTest { assertThat("No classes processed", processed, greaterThan(0)); } - private boolean processCls(@Nullable Pattern mthPattern, List passes, ClassNode classNode) { + private boolean processCls(JadxDecompiler jadx, @Nullable Pattern mthPattern, List passes, ClassNode classNode) { classNode.load(); boolean decompile = false; if (mthPattern == null) { @@ -101,11 +100,8 @@ public abstract class BaseExternalTest extends IntegrationTest { if (!decompile) { return false; } - for (IDexTreeVisitor visitor : passes) { - DepthTraversal.visit(visitor, classNode); - } try { - new CodeGen().visit(classNode); + ProcessClass.process(classNode, passes, true); } catch (Exception e) { throw new JadxRuntimeException("Codegen failed", e); }