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 af6cf07cd..13141bc2d 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -10,6 +10,7 @@ import java.util.Objects; import java.util.Set; import com.android.dx.rop.code.AccessFlags; +import com.google.common.collect.Streams; import jadx.api.JadxArgs; import jadx.core.dex.attributes.AFlag; @@ -130,7 +131,8 @@ public class ClassGen { annotationGen.addForClass(clsCode); insertRenameInfo(clsCode, cls); CodeGenUtils.addSourceFileInfo(clsCode, cls); - clsCode.startLine(af.makeString()); + clsCode.startLineWithNum(cls.getSourceLine()); + clsCode.add(af.makeString()); if (af.isInterface()) { if (af.isAnnotation()) { clsCode.add('@'); @@ -222,21 +224,32 @@ public class ClassGen { clsDeclLine = clsCode.getLine(); clsCode.incIndent(); addFields(clsCode); - addInnerClasses(clsCode, cls); - addMethods(clsCode); + addInnerClsAndMethods(clsCode); clsCode.decIndent(); clsCode.startLine('}'); } - private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException { - for (ClassNode innerCls : cls.getInnerClasses()) { - if (innerCls.contains(AFlag.DONT_GENERATE)) { - continue; - } + private void addInnerClsAndMethods(CodeWriter clsCode) { + Streams.concat(cls.getInnerClasses().stream(), cls.getMethods().stream()) + .filter(node -> !node.contains(AFlag.DONT_GENERATE)) + .sorted(Comparator.comparingInt(LineAttrNode::getSourceLine)) + .forEach(node -> { + if (node instanceof ClassNode) { + addInnerClass(clsCode, (ClassNode) node); + } else { + addMethod(clsCode, (MethodNode) node); + } + }); + } + + private void addInnerClass(CodeWriter code, ClassNode innerCls) { + try { ClassGen inClGen = new ClassGen(innerCls, getParentGen()); code.newLine(); inClGen.addClassCode(code); imports.addAll(inClGen.getImports()); + } catch (Exception e) { + ErrorsCounter.classError(innerCls, "Inner class code generation error", e); } } @@ -249,36 +262,24 @@ public class ClassGen { return false; } - private void addMethods(CodeWriter code) { - List methods = sortMethodsByLine(cls.getMethods()); - for (MethodNode mth : methods) { - if (mth.contains(AFlag.DONT_GENERATE)) { - continue; - } - if (code.getLine() != clsDeclLine) { - code.newLine(); - } - int savedIndent = code.getIndent(); - try { - addMethod(code, mth); - } catch (Exception e) { - if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) { - throw new JadxRuntimeException("Method generation error", e); - } - code.newLine().add("/*"); - code.newLine().addMultiLine(ErrorsCounter.methodError(mth, "Method generation error", e)); - Utils.appendStackTrace(code, e); - code.newLine().add("*/"); - code.setIndent(savedIndent); - mth.addError("Method generation error: " + e.getMessage(), e); - } + private void addMethod(CodeWriter code, MethodNode mth) { + if (code.getLine() != clsDeclLine) { + code.newLine(); + } + int savedIndent = code.getIndent(); + try { + addMethodCode(code, mth); + } catch (Exception e) { + if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) { + throw new JadxRuntimeException("Method generation error", e); + } + code.newLine().add("/*"); + code.newLine().addMultiLine(ErrorsCounter.methodError(mth, "Method generation error", e)); + Utils.appendStackTrace(code, e); + code.newLine().add("*/"); + code.setIndent(savedIndent); + mth.addError("Method generation error: " + e.getMessage(), e); } - } - - private static List sortMethodsByLine(List methods) { - List out = new ArrayList<>(methods); - out.sort(Comparator.comparingInt(LineAttrNode::getSourceLine)); - return out; } private boolean isMethodsPresents() { @@ -290,7 +291,7 @@ public class ClassGen { return false; } - public void addMethod(CodeWriter code, MethodNode mth) throws CodegenException { + public void addMethodCode(CodeWriter code, MethodNode mth) throws CodegenException { CodeGenUtils.addComments(code, mth); if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) { MethodGen mthGen = new MethodGen(this, mth); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java b/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java index 987f93365..5f3b33c95 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java @@ -8,9 +8,12 @@ import java.util.Set; import org.jetbrains.annotations.Nullable; +import com.google.common.collect.Streams; + import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.nodes.DeclareVariablesAttr; +import jadx.core.dex.attributes.nodes.LineAttrNode; import jadx.core.dex.instructions.ArithNode; import jadx.core.dex.instructions.ArithOp; import jadx.core.dex.instructions.InsnType; @@ -20,6 +23,7 @@ import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.mods.ConstructorInsn; import jadx.core.dex.instructions.mods.TernaryInsn; import jadx.core.dex.nodes.BlockNode; +import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.InsnContainer; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; @@ -44,6 +48,14 @@ import jadx.core.utils.exceptions.JadxException; ) public class PrepareForCodeGen extends AbstractVisitor { + @Override + public boolean visit(ClassNode cls) throws JadxException { + if (cls.root().getArgs().isDebugInfo()) { + setClassSourceLine(cls); + } + return true; + } + @Override public void visit(MethodNode mth) throws JadxException { List blocks = mth.getBasicBlocks(); @@ -246,4 +258,26 @@ public class PrepareForCodeGen extends AbstractVisitor { } return null; } + + /** + * Use source line from top method + */ + private void setClassSourceLine(ClassNode cls) { + for (ClassNode innerClass : cls.getInnerClasses()) { + setClassSourceLine(innerClass); + } + + int minLine = Streams.concat( + cls.getMethods().stream(), + cls.getInnerClasses().stream(), + cls.getFields().stream()) + .filter(mth -> !mth.contains(AFlag.DONT_GENERATE)) + .filter(mth -> mth.getSourceLine() != 0) + .mapToInt(LineAttrNode::getSourceLine) + .min() + .orElse(0); + if (minLine != 0) { + cls.setSourceLine(minLine - 1); + } + } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestLineNumbers2.java b/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestLineNumbers2.java index 192c74f8d..32bee11cb 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestLineNumbers2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestLineNumbers2.java @@ -39,7 +39,7 @@ public class TestLineNumbers2 extends IntegrationTest { ClassNode cls = getClassNode(TestCls.class); Map lineMapping = cls.getCode().getLineMapping(); - assertEquals("{8=18, 11=22, 12=23, 13=24, 14=28, 16=25, 17=26, 18=28, 21=31, 22=32}", + assertEquals("{5=17, 8=18, 11=22, 12=23, 13=24, 14=28, 16=25, 17=26, 18=28, 21=31, 22=32}", lineMapping.toString()); } }