diff --git a/README.md b/README.md index a4f76e6df..9d4f431e9 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,11 @@ options: --output-format - can be 'java' or 'json', default: java -e, --export-gradle - save as android gradle project -j, --threads-count - processing threads count, default: 4 + -m, --decompilation-mode - code output mode: + 'auto' - trying best options (default) + 'restructure' - restore code structure (normal java code) + 'simple' - simplified instructions (linear, with goto's) + 'fallback' - raw instructions without modifications --show-bad-code - show inconsistent code (incorrectly decompiled) --no-imports - disable use of imports, always write entire package name --no-debug-info - disable debug info @@ -115,7 +120,7 @@ options: --fs-case-sensitive - treat filesystem as case sensitive, false by default --cfg - save methods control flow graph to dot file --raw-cfg - save methods control flow graph (use raw instructions) - -f, --fallback - make simple dump (using goto instead of 'if', 'for', etc) + -f, --fallback - set '--decompilation-mode' to 'fallback' (deprecated) --use-dx - use dx/d8 to convert java bytecode --comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info --log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index 499d42013..0f2cf3363 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -15,6 +15,7 @@ import com.beust.jcommander.IStringConverter; import com.beust.jcommander.Parameter; import jadx.api.CommentsLevel; +import jadx.api.DecompilationMode; import jadx.api.JadxArgs; import jadx.api.JadxArgs.RenameEnum; import jadx.api.JadxArgs.UseKotlinMethodsForVarNames; @@ -58,6 +59,17 @@ public class JadxCLIArgs { @Parameter(names = { "-j", "--threads-count" }, description = "processing threads count") protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT; + @Parameter( + names = { "-m", "--decompilation-mode" }, + description = "code output mode:" + + "\n 'auto' - trying best options (default)" + + "\n 'restructure' - restore code structure (normal java code)" + + "\n 'simple' - simplified instructions (linear, with goto's)" + + "\n 'fallback' - raw instructions without modifications", + converter = RenameConverter.class + ) + protected DecompilationMode decompilationMode = DecompilationMode.AUTO; + @Parameter(names = { "--show-bad-code" }, description = "show inconsistent code (incorrectly decompiled)") protected boolean showInconsistentCode = false; @@ -148,7 +160,7 @@ public class JadxCLIArgs { @Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)") protected boolean rawCfgOutput = false; - @Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)") + @Parameter(names = { "-f", "--fallback" }, description = "set '--decompilation-mode' to 'fallback' (deprecated)") protected boolean fallbackMode = false; @Parameter(names = { "--use-dx" }, description = "use dx/d8 to convert java bytecode") @@ -236,7 +248,11 @@ public class JadxCLIArgs { args.setThreadsCount(threadsCount); args.setSkipSources(skipSources); args.setSkipResources(skipResources); - args.setFallbackMode(fallbackMode); + if (fallbackMode) { + args.setDecompilationMode(DecompilationMode.FALLBACK); + } else { + args.setDecompilationMode(decompilationMode); + } args.setShowInconsistentCode(showInconsistentCode); args.setCfgOutput(cfgOutput); args.setRawCFGOutput(rawCfgOutput); @@ -313,6 +329,10 @@ public class JadxCLIArgs { return useDx; } + public DecompilationMode getDecompilationMode() { + return decompilationMode; + } + public boolean isShowInconsistentCode() { return showInconsistentCode; } @@ -493,6 +513,19 @@ public class JadxCLIArgs { } } + public static class DecompilationModeConverter implements IStringConverter { + @Override + public DecompilationMode convert(String value) { + try { + return DecompilationMode.valueOf(value.toUpperCase()); + } catch (Exception e) { + throw new IllegalArgumentException( + '\'' + value + "' is unknown, possible values are: " + + JadxCLIArgs.enumValuesString(DecompilationMode.values())); + } + } + } + public static String enumValuesString(Enum[] values) { return Stream.of(values) .map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT)) diff --git a/jadx-core/src/main/java/jadx/api/DecompilationMode.java b/jadx-core/src/main/java/jadx/api/DecompilationMode.java new file mode 100644 index 000000000..bfb43875e --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/DecompilationMode.java @@ -0,0 +1,23 @@ +package jadx.api; + +public enum DecompilationMode { + /** + * Trying best options (default) + */ + AUTO, + + /** + * Restore code structure (normal java code) + */ + RESTRUCTURE, + + /** + * Simplified instructions (linear with goto's) + */ + SIMPLE, + + /** + * Raw instructions without modifications + */ + FALLBACK +} diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index 54adce1a7..1278681d3 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -38,7 +38,6 @@ public class JadxArgs { private boolean cfgOutput = false; private boolean rawCFGOutput = false; - private boolean fallbackMode = false; private boolean showInconsistentCode = false; private boolean useImports = true; @@ -85,6 +84,8 @@ public class JadxArgs { private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA; + private DecompilationMode decompilationMode = DecompilationMode.AUTO; + private ICodeData codeData; private CommentsLevel commentsLevel = CommentsLevel.INFO; @@ -175,11 +176,17 @@ public class JadxArgs { } public boolean isFallbackMode() { - return fallbackMode; + return decompilationMode == DecompilationMode.FALLBACK; } + /** + * Deprecated: use 'decompilation mode' property + */ + @Deprecated public void setFallbackMode(boolean fallbackMode) { - this.fallbackMode = fallbackMode; + if (fallbackMode) { + this.decompilationMode = DecompilationMode.FALLBACK; + } } public boolean isShowInconsistentCode() { @@ -422,6 +429,14 @@ public class JadxArgs { this.outputFormat = outputFormat; } + public DecompilationMode getDecompilationMode() { + return decompilationMode; + } + + public void setDecompilationMode(DecompilationMode decompilationMode) { + this.decompilationMode = decompilationMode; + } + public ICodeCache getCodeCache() { return codeCache; } @@ -493,9 +508,7 @@ public class JadxArgs { + ", outDirSrc=" + outDirSrc + ", outDirRes=" + outDirRes + ", threadsCount=" + threadsCount - + ", cfgOutput=" + cfgOutput - + ", rawCFGOutput=" + rawCFGOutput - + ", fallbackMode=" + fallbackMode + + ", decompilationMode=" + decompilationMode + ", showInconsistentCode=" + showInconsistentCode + ", useImports=" + useImports + ", skipResources=" + skipResources @@ -520,6 +533,8 @@ public class JadxArgs { + ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName() + ", useDxInput=" + useDxInput + ", pluginOptions=" + pluginOptions + + ", cfgOutput=" + cfgOutput + + ", rawCFGOutput=" + rawCFGOutput + '}'; } } diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index cdc8f5d39..8fd775c80 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory; import jadx.api.CommentsLevel; import jadx.api.JadxArgs; +import jadx.core.dex.attributes.AFlag; import jadx.core.dex.visitors.AnonymousClassVisitor; import jadx.core.dex.visitors.AttachCommentsVisitor; import jadx.core.dex.visitors.AttachMethodDetails; @@ -32,6 +33,7 @@ import jadx.core.dex.visitors.InitCodeVariables; import jadx.core.dex.visitors.InlineMethods; import jadx.core.dex.visitors.MarkMethodsForInline; import jadx.core.dex.visitors.MethodInvokeVisitor; +import jadx.core.dex.visitors.MethodVisitor; import jadx.core.dex.visitors.ModVisitor; import jadx.core.dex.visitors.MoveInlineVisitor; import jadx.core.dex.visitors.OverrideMethodVisitor; @@ -62,6 +64,7 @@ import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.dex.visitors.ssa.SSATransform; import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; import jadx.core.dex.visitors.usage.UsageInfoVisitor; +import jadx.core.utils.exceptions.JadxRuntimeException; public class Jadx { private static final Logger LOG = LoggerFactory.getLogger(Jadx.class); @@ -69,21 +72,20 @@ public class Jadx { private Jadx() { } - static { - if (Consts.DEBUG) { - LOG.info("debug enabled"); + public static List getPassesList(JadxArgs args) { + switch (args.getDecompilationMode()) { + case AUTO: + case RESTRUCTURE: + return getRegionsModePasses(args); + case SIMPLE: + return getSimpleModePasses(args); + case FALLBACK: + return getFallbackPassesList(); + default: + throw new JadxRuntimeException("Unknown decompilation mode: " + args.getDecompilationMode()); } } - public static List getFallbackPassesList() { - List passes = new ArrayList<>(); - passes.add(new AttachTryCatchVisitor()); - passes.add(new AttachCommentsVisitor()); - passes.add(new ProcessInstructionsVisitor()); - passes.add(new FallbackModeVisitor()); - return passes; - } - public static List getPreDecompilePassesList() { List passes = new ArrayList<>(); passes.add(new SignatureProcessor()); @@ -95,12 +97,8 @@ public class Jadx { return passes; } - public static List getPassesList(JadxArgs args) { - if (args.isFallbackMode()) { - return getFallbackPassesList(); - } + public static List getRegionsModePasses(JadxArgs args) { List passes = new ArrayList<>(); - // instructions IR passes.add(new CheckCode()); if (args.isDebugInfo()) { @@ -178,6 +176,60 @@ public class Jadx { return passes; } + public static List getSimpleModePasses(JadxArgs args) { + List passes = new ArrayList<>(); + if (args.isDebugInfo()) { + passes.add(new DebugInfoAttachVisitor()); + } + passes.add(new AttachTryCatchVisitor()); + if (args.getCommentsLevel() != CommentsLevel.NONE) { + passes.add(new AttachCommentsVisitor()); + } + passes.add(new AttachMethodDetails()); + passes.add(new ProcessInstructionsVisitor()); + + passes.add(new BlockSplitter()); + passes.add(new MethodVisitor(mth -> mth.add(AFlag.DISABLE_BLOCKS_LOCK))); + if (args.isRawCFGOutput()) { + passes.add(DotGraphVisitor.dumpRaw()); + } + passes.add(new MethodVisitor(mth -> mth.add(AFlag.DISABLE_BLOCKS_LOCK))); + passes.add(new BlockProcessor()); + passes.add(new SSATransform()); + passes.add(new MoveInlineVisitor()); + passes.add(new ConstructorVisitor()); + passes.add(new InitCodeVariables()); + passes.add(new ConstInlineVisitor()); + passes.add(new TypeInferenceVisitor()); + if (args.isDebugInfo()) { + passes.add(new DebugInfoApplyVisitor()); + } + passes.add(new CodeRenameVisitor()); + passes.add(new DeboxingVisitor()); + passes.add(new ModVisitor()); + passes.add(new CodeShrinkVisitor()); + passes.add(new ReSugarCode()); + passes.add(new CodeShrinkVisitor()); + passes.add(new SimplifyVisitor()); + passes.add(new MethodVisitor(mth -> mth.remove(AFlag.DONT_GENERATE))); + if (args.isRawCFGOutput()) { + passes.add(DotGraphVisitor.dumpRaw()); + } + if (args.isCfgOutput()) { + passes.add(DotGraphVisitor.dump()); + } + return passes; + } + + public static List getFallbackPassesList() { + List passes = new ArrayList<>(); + passes.add(new AttachTryCatchVisitor()); + passes.add(new AttachCommentsVisitor()); + passes.add(new ProcessInstructionsVisitor()); + passes.add(new FallbackModeVisitor()); + return passes; + } + public static final String VERSION_DEV = "dev"; private static String version; diff --git a/jadx-core/src/main/java/jadx/core/ProcessClass.java b/jadx-core/src/main/java/jadx/core/ProcessClass.java index 031f54ff2..f2fe8e386 100644 --- a/jadx-core/src/main/java/jadx/core/ProcessClass.java +++ b/jadx-core/src/main/java/jadx/core/ProcessClass.java @@ -1,13 +1,19 @@ package jadx.core; +import java.util.List; + import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import jadx.api.ICodeInfo; +import jadx.api.JadxArgs; import jadx.core.codegen.CodeGen; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.LoadStage; +import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.DepthTraversal; import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -18,13 +24,17 @@ import static jadx.core.dex.nodes.ProcessState.NOT_LOADED; import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE; import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED; -public final class ProcessClass { +public class ProcessClass { + private static final Logger LOG = LoggerFactory.getLogger(ProcessClass.class); - private ProcessClass() { + private final List passes; + + public ProcessClass(JadxArgs args) { + this.passes = Jadx.getPassesList(args); } @Nullable - private static ICodeInfo process(ClassNode cls, boolean codegen) { + private ICodeInfo process(ClassNode cls, boolean codegen) { if (!codegen && cls.getState() == PROCESS_COMPLETE) { // nothing to do return null; @@ -58,7 +68,7 @@ public final class ProcessClass { } if (cls.getState() == LOADED) { cls.setState(PROCESS_STARTED); - for (IDexTreeVisitor visitor : cls.root().getPasses()) { + for (IDexTreeVisitor visitor : passes) { DepthTraversal.visit(visitor, cls); } cls.setState(PROCESS_COMPLETE); @@ -83,7 +93,7 @@ public final class ProcessClass { } @NotNull - public static ICodeInfo generateCode(ClassNode cls) { + public ICodeInfo generateCode(ClassNode cls) { ClassNode topParentClass = cls.getTopParentClass(); if (topParentClass != cls) { return generateCode(topParentClass); @@ -107,4 +117,19 @@ public final class ProcessClass { throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e); } } + + public void initPasses(RootNode root) { + for (IDexTreeVisitor pass : passes) { + try { + pass.init(root); + } catch (Exception e) { + LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e); + } + } + } + + // TODO: make passes list private and not visible + public List getPasses() { + return passes; + } } 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 7b33af0dd..ff96d8783 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -356,7 +356,7 @@ public class ClassGen { badCode = false; } MethodGen mthGen; - if (badCode || fallback || mth.contains(AType.JADX_ERROR) || mth.getRegion() == null) { + if (badCode || fallback || mth.contains(AType.JADX_ERROR)) { mthGen = MethodGen.getFallbackMethodGen(mth); } else { mthGen = new MethodGen(this, mth); 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 dd5c61c22..e65ed3666 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -50,6 +50,7 @@ import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.SSAVar; 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.FieldNode; import jadx.core.dex.nodes.InsnNode; @@ -517,7 +518,7 @@ public class InsnGen { code.add(' '); code.add(ifInsn.getOp().getSymbol()).add(' '); addArg(code, insn.getArg(1)); - code.add(") goto ").add(MethodGen.getLabelName(ifInsn.getTarget())); + code.add(") goto ").add(MethodGen.getLabelName(ifInsn)); break; case GOTO: @@ -538,13 +539,24 @@ public class InsnGen { code.add(") {"); code.incIndent(); int[] keys = sw.getKeys(); - int[] targets = sw.getTargets(); - for (int i = 0; i < keys.length; i++) { - code.startLine("case ").add(Integer.toString(keys[i])).add(": goto "); - code.add(MethodGen.getLabelName(targets[i])).add(';'); + int size = keys.length; + BlockNode[] targetBlocks = sw.getTargetBlocks(); + if (targetBlocks != null) { + for (int i = 0; i < size; i++) { + code.startLine("case ").add(Integer.toString(keys[i])).add(": goto "); + code.add(MethodGen.getLabelName(targetBlocks[i])).add(';'); + } + code.startLine("default: goto "); + code.add(MethodGen.getLabelName(sw.getDefTargetBlock())).add(';'); + } else { + int[] targets = sw.getTargets(); + for (int i = 0; i < size; i++) { + code.startLine("case ").add(Integer.toString(keys[i])).add(": goto "); + code.add(MethodGen.getLabelName(targets[i])).add(';'); + } + code.startLine("default: goto "); + code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';'); } - code.startLine("default: goto "); - code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';'); code.decIndent(); code.startLine('}'); break; 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 178da01db..1fcd189fa 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -6,11 +6,13 @@ import java.util.List; import java.util.Objects; import java.util.stream.Stream; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.api.CommentsLevel; import jadx.api.ICodeWriter; +import jadx.api.JadxArgs; import jadx.api.data.annotations.InsnCodeOffset; import jadx.api.data.annotations.VarDeclareRef; import jadx.api.plugins.input.data.AccessFlags; @@ -32,13 +34,14 @@ import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.CodeVar; import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.SSAVar; +import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.trycatch.CatchAttr; +import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.visitors.DepthTraversal; import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.utils.CodeGenUtils; -import jadx.core.utils.InsnUtils; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.JadxOverflowException; @@ -252,12 +255,28 @@ public class MethodGen { } public void addInstructions(ICodeWriter code) throws CodegenException { - if (mth.root().getArgs().isFallbackMode()) { - addFallbackMethodCode(code, FALLBACK_MODE); - } else if (classGen.isFallbackMode()) { - dumpInstructions(code); - } else { - addRegionInsns(code); + JadxArgs args = mth.root().getArgs(); + switch (args.getDecompilationMode()) { + case AUTO: + if (classGen.isFallbackMode()) { + // TODO: try simple mode first + dumpInstructions(code); + } else { + addRegionInsns(code); + } + break; + + case RESTRUCTURE: + addRegionInsns(code); + break; + + case SIMPLE: + addSimpleMethodCode(code); + break; + + case FALLBACK: + addFallbackMethodCode(code, FALLBACK_MODE); + break; } } @@ -279,6 +298,59 @@ public class MethodGen { } } + private void addSimpleMethodCode(ICodeWriter code) { + if (mth.getBasicBlocks() == null) { + code.startLine("// Blocks not ready for simple mode, using fallback"); + addFallbackMethodCode(code, FALLBACK_MODE); + return; + } + JadxArgs args = mth.root().getArgs(); + ICodeWriter tmpCode = args.getCodeWriterProvider().apply(args); + try { + tmpCode.setIndent(code.getIndent()); + generateSimpleCode(tmpCode); + code.add(tmpCode); + } catch (Exception e) { + mth.addError("Simple mode code generation failed", e); + CodeGenUtils.addError(code, "Simple mode code generation failed", e); + dumpInstructions(code); + } + } + + private void generateSimpleCode(ICodeWriter code) throws CodegenException { + SimpleModeHelper helper = new SimpleModeHelper(mth); + List blocks = helper.prepareBlocks(); + InsnGen insnGen = new InsnGen(this, true); + for (BlockNode block : blocks) { + if (block.contains(AFlag.DONT_GENERATE)) { + continue; + } + if (helper.isNeedStartLabel(block)) { + code.decIndent(); + code.startLine(getLabelName(block)).add(':'); + code.incIndent(); + } + for (InsnNode insn : block.getInstructions()) { + if (!insn.contains(AFlag.DONT_GENERATE)) { + if (insn.getResult() != null) { + CodeVar codeVar = insn.getResult().getSVar().getCodeVar(); + if (!codeVar.isDeclared()) { + insn.add(AFlag.DECLARE_VAR); + codeVar.setDeclared(true); + } + } + InsnCodeOffset.attach(code, insn); + insnGen.makeInsn(insn, code); + addCatchComment(code, insn, false); + CodeGenUtils.addCodeComments(code, mth, insn); + } + } + if (helper.isNeedEndGoto(block)) { + code.startLine("goto ").add(getLabelName(block.getSuccessors().get(0))); + } + } + } + public void dumpInstructions(ICodeWriter code) { if (mth.checkCommentsLevel(CommentsLevel.ERROR)) { code.startLine("/*"); @@ -353,60 +425,81 @@ public class MethodGen { public static void addFallbackInsns(ICodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) { int startIndent = code.getIndent(); - InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true); + MethodGen methodGen = getFallbackMethodGen(mth); + InsnGen insnGen = new InsnGen(methodGen, true); InsnNode prevInsn = null; for (InsnNode insn : insnArr) { if (insn == null) { continue; } - if (insn.contains(AType.JADX_ERROR)) { - for (JadxError error : insn.getAll(AType.JADX_ERROR)) { - code.startLine("// ").add(error.getError()); - } - continue; + methodGen.dumpInsn(code, insnGen, option, startIndent, prevInsn, insn); + prevInsn = insn; + } + } + + private boolean dumpInsn(ICodeWriter code, InsnGen insnGen, FallbackOption option, int startIndent, + @Nullable InsnNode prevInsn, InsnNode insn) { + if (insn.contains(AType.JADX_ERROR)) { + for (JadxError error : insn.getAll(AType.JADX_ERROR)) { + code.startLine("// ").add(error.getError()); } - if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) { + return true; + } + if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) { + code.decIndent(); + code.startLine(getLabelName(insn.getOffset()) + ':'); + code.incIndent(); + } + if (insn.getType() == InsnType.NOP) { + return true; + } + try { + boolean escapeComment = isCommentEscapeNeeded(insn, option); + if (escapeComment) { code.decIndent(); - code.startLine(getLabelName(insn.getOffset()) + ':'); + code.startLine("*/"); + code.startLine("// "); + } else { + code.startLineWithNum(insn.getSourceLine()); + } + InsnCodeOffset.attach(code, insn); + RegisterArg resArg = insn.getResult(); + if (resArg != null) { + ArgType varType = resArg.getInitType(); + if (varType.isTypeKnown()) { + code.add(varType.toString()).add(' '); + } + } + insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE); + if (escapeComment) { + code.startLine("/*"); code.incIndent(); } - if (insn.getType() == InsnType.NOP) { - continue; - } - try { - boolean escapeComment = isCommentEscapeNeeded(insn, option); - if (escapeComment) { - code.decIndent(); - code.startLine("*/"); - code.startLine("// "); - } else { - code.startLineWithNum(insn.getSourceLine()); - } - InsnCodeOffset.attach(code, insn); - RegisterArg resArg = insn.getResult(); - if (resArg != null) { - ArgType varType = resArg.getInitType(); - if (varType.isTypeKnown()) { - code.add(varType.toString()).add(' '); - } - } - insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE); - if (escapeComment) { - code.startLine("/*"); - code.incIndent(); - } + addCatchComment(code, insn, true); + CodeGenUtils.addCodeComments(code, mth, insn); + } catch (Exception e) { + LOG.debug("Error generate fallback instruction: ", e.getCause()); + code.setIndent(startIndent); + code.startLine("// error: " + insn); + } + return false; + } - CatchAttr catchAttr = insn.get(AType.EXC_CATCH); - if (catchAttr != null) { - code.add(" // " + catchAttr); - } - CodeGenUtils.addCodeComments(code, mth, insn); - } catch (Exception e) { - LOG.debug("Error generate fallback instruction: ", e.getCause()); - code.setIndent(startIndent); - code.startLine("// error: " + insn); + private void addCatchComment(ICodeWriter code, InsnNode insn, boolean raw) { + CatchAttr catchAttr = insn.get(AType.EXC_CATCH); + if (catchAttr == null) { + return; + } + code.add(" // Catch:"); + for (ExceptionHandler handler : catchAttr.getHandlers()) { + code.add(' '); + classGen.useClass(code, handler.getArgType()); + code.add(" -> "); + if (raw) { + code.add(getLabelName(handler.getHandlerOffset())); + } else { + code.add(getLabelName(handler.getHandlerBlock())); } - prevInsn = insn; } } @@ -449,7 +542,22 @@ public class MethodGen { return new MethodGen(clsGen, mth); } + public static String getLabelName(BlockNode block) { + return String.format("L%d", block.getId()); + } + + public static String getLabelName(IfNode insn) { + BlockNode thenBlock = insn.getThenBlock(); + if (thenBlock != null) { + return getLabelName(thenBlock); + } + return getLabelName(insn.getTarget()); + } + public static String getLabelName(int offset) { - return "L_" + InsnUtils.formatOffset(offset); + if (offset < 0) { + return String.format("LB_%x", -offset); + } + return String.format("L%x", offset); } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/SimpleModeHelper.java b/jadx-core/src/main/java/jadx/core/codegen/SimpleModeHelper.java new file mode 100644 index 000000000..6e6720281 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/codegen/SimpleModeHelper.java @@ -0,0 +1,149 @@ +package jadx.core.codegen; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import org.jetbrains.annotations.Nullable; + +import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.instructions.IfNode; +import jadx.core.dex.instructions.TargetInsnNode; +import jadx.core.dex.nodes.BlockNode; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.trycatch.ExceptionHandler; +import jadx.core.dex.visitors.blocks.BlockProcessor; +import jadx.core.dex.visitors.blocks.BlockSplitter; +import jadx.core.utils.BlockUtils; + +public class SimpleModeHelper { + + private final MethodNode mth; + + private final BitSet startLabel; + private final BitSet endGoto; + + public SimpleModeHelper(MethodNode mth) { + this.mth = mth; + this.startLabel = BlockUtils.newBlocksBitSet(mth); + this.endGoto = BlockUtils.newBlocksBitSet(mth); + } + + public List prepareBlocks() { + removeEmptyBlocks(); + List blocksList = getSortedBlocks(); + blocksList.removeIf(b -> b.equals(mth.getEnterBlock()) || b.equals(mth.getExitBlock())); + unbindExceptionHandlers(); + if (blocksList.isEmpty()) { + return Collections.emptyList(); + } + @Nullable + BlockNode prev = null; + int blocksCount = blocksList.size(); + for (int i = 0; i < blocksCount; i++) { + BlockNode block = blocksList.get(i); + BlockNode nextBlock = i + 1 == blocksCount ? null : blocksList.get(i + 1); + List preds = block.getPredecessors(); + int predsCount = preds.size(); + if (predsCount > 1) { + startLabel.set(block.getId()); + } else if (predsCount == 1 && prev != null) { + if (!prev.equals(preds.get(0))) { + startLabel.set(block.getId()); + if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlocks(prev)) { + endGoto.set(prev.getId()); + } + } + } + InsnNode lastInsn = BlockUtils.getLastInsn(block); + if (lastInsn instanceof TargetInsnNode) { + processTargetInsn(block, lastInsn, nextBlock); + } + if (block.contains(AType.EXC_HANDLER)) { + startLabel.set(block.getId()); + } + if (nextBlock == null && !mth.isPreExitBlocks(block)) { + endGoto.set(block.getId()); + } + prev = block; + } + if (mth.isVoidReturn()) { + int last = blocksList.size() - 1; + if (blocksList.get(last).contains(AFlag.RETURN)) { + // remove trailing return + blocksList.remove(last); + } + } + return blocksList; + } + + private void removeEmptyBlocks() { + for (BlockNode block : mth.getBasicBlocks()) { + if (block.getInstructions().isEmpty() + && block.getPredecessors().size() > 0 + && block.getSuccessors().size() == 1) { + BlockNode successor = block.getSuccessors().get(0); + List predecessors = block.getPredecessors(); + BlockSplitter.removeConnection(block, successor); + if (predecessors.size() == 1) { + BlockSplitter.replaceConnection(predecessors.get(0), block, successor); + } else { + for (BlockNode pred : new ArrayList<>(predecessors)) { + BlockSplitter.replaceConnection(pred, block, successor); + } + } + block.add(AFlag.REMOVE); + } + } + BlockProcessor.removeMarkedBlocks(mth); + } + + private void unbindExceptionHandlers() { + if (mth.isNoExceptionHandlers()) { + return; + } + for (ExceptionHandler handler : mth.getExceptionHandlers()) { + BlockNode handlerBlock = handler.getHandlerBlock(); + if (handlerBlock != null) { + BlockSplitter.removePredecessors(handlerBlock); + } + } + } + + private void processTargetInsn(BlockNode block, InsnNode lastInsn, @Nullable BlockNode next) { + if (lastInsn instanceof IfNode) { + IfNode ifInsn = (IfNode) lastInsn; + BlockNode thenBlock = ifInsn.getThenBlock(); + if (Objects.equals(next, thenBlock)) { + ifInsn.invertCondition(); + startLabel.set(ifInsn.getThenBlock().getId()); + } else { + startLabel.set(thenBlock.getId()); + } + ifInsn.normalize(); + } else { + for (BlockNode successor : block.getSuccessors()) { + startLabel.set(successor.getId()); + } + } + } + + public boolean isNeedStartLabel(BlockNode block) { + return startLabel.get(block.getId()); + } + + public boolean isNeedEndGoto(BlockNode block) { + return endGoto.get(block.getId()); + } + + // DFS sort blocks to reduce goto count + private List getSortedBlocks() { + List list = new ArrayList<>(mth.getBasicBlocks().size()); + BlockUtils.dfsVisit(mth, list::add); + return list; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java index 3763f0855..c8aedde8f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java @@ -81,6 +81,8 @@ public enum AFlag { METHOD_CANDIDATE_FOR_INLINE, + DISABLE_BLOCKS_LOCK, + // Class processing flags RESTART_CODEGEN, // codegen must be executed again RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/JadxError.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/JadxError.java index 95338f26d..f644e9ca5 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/JadxError.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/JadxError.java @@ -50,11 +50,7 @@ public class JadxError implements Comparable { @Override public String toString() { StringBuilder str = new StringBuilder(); - str.append("JadxError: "); - if (error != null) { - str.append(error); - str.append(' '); - } + str.append("JadxError: ").append(error).append(' '); if (cause != null) { str.append(cause.getClass()); str.append(':'); diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/IfNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/IfNode.java index 091b2816d..5154c9ecb 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/IfNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/IfNode.java @@ -5,6 +5,7 @@ import java.util.List; import jadx.api.plugins.input.insns.InsnData; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; +import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.InsnNode; @@ -70,6 +71,15 @@ public class IfNode extends GotoNode { elseBlock = tmp; } + /** + * Change 'a != false' to 'a == true' + */ + public void normalize() { + if (getOp() == IfOp.NE && getArg(1).isFalse()) { + changeCondition(IfOp.EQ, getArg(0), LiteralArg.litTrue()); + } + } + public void changeCondition(IfOp op, InsnArg arg1, InsnArg arg2) { this.op = op; setArg(0, arg1); 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 d3038756a..610a66aca 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 @@ -16,9 +16,11 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jadx.api.DecompilationMode; import jadx.api.ICodeCache; import jadx.api.ICodeInfo; import jadx.api.ICodeWriter; +import jadx.api.JadxArgs; import jadx.api.plugins.input.data.IClassData; import jadx.api.plugins.input.data.IFieldData; import jadx.api.plugins.input.data.IMethodData; @@ -304,6 +306,26 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN return decompile(true); } + /** + * WARNING: Slow operation! Use with caution! + */ + public ICodeInfo decompileWithMode(DecompilationMode mode) { + DecompilationMode baseMode = root.getArgs().getDecompilationMode(); + if (mode == baseMode) { + return decompile(true); + } + JadxArgs args = root.getArgs(); + try { + unload(); + args.setDecompilationMode(mode); + ProcessClass process = new ProcessClass(args); + process.initPasses(root); + return process.generateCode(this); + } finally { + args.setDecompilationMode(baseMode); + } + } + public ICodeInfo getCode() { return decompile(true); } @@ -355,7 +377,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN return code; } } - ICodeInfo codeInfo = ProcessClass.generateCode(this); + ICodeInfo codeInfo = root.getProcessClasses().generateCode(this); codeCache.add(clsRawName, codeInfo); return codeInfo; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index a682cb813..6c7924379 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -336,6 +336,14 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return exitBlock.getPredecessors(); } + public boolean isPreExitBlocks(BlockNode block) { + List successors = block.getSuccessors(); + if (successors.size() == 1) { + return successors.get(0).equals(exitBlock); + } + return exitBlock.getPredecessors().contains(block); + } + public void registerLoop(LoopInfo loop) { if (loops.isEmpty()) { loops = new ArrayList<>(5); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java index bb1f3453f..3f3af548d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java @@ -22,6 +22,7 @@ import jadx.api.data.ICodeData; import jadx.api.plugins.input.data.IClassData; import jadx.api.plugins.input.data.ILoadResult; import jadx.core.Jadx; +import jadx.core.ProcessClass; import jadx.core.clsp.ClspGraph; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ConstStorage; @@ -51,9 +52,9 @@ public class RootNode { private final JadxArgs args; private final List preDecompilePasses; - private final List passes; private final List codeDataUpdateListeners = new ArrayList<>(); + private final ProcessClass processClasses; private final ErrorsCounter errorsCounter = new ErrorsCounter(); private final StringUtils stringUtils; private final ConstStorage constValues; @@ -76,7 +77,7 @@ public class RootNode { public RootNode(JadxArgs args) { this.args = args; this.preDecompilePasses = Jadx.getPreDecompilePassesList(); - this.passes = Jadx.getPassesList(args); + this.processClasses = new ProcessClass(this.getArgs()); this.stringUtils = new StringUtils(args); this.constValues = new ConstStorage(args); this.typeUpdate = new TypeUpdate(this); @@ -460,18 +461,16 @@ public class RootNode { return null; } + public ProcessClass getProcessClasses() { + return processClasses; + } + public List getPasses() { - return passes; + return processClasses.getPasses(); } public void initPasses() { - for (IDexTreeVisitor pass : passes) { - try { - pass.init(this); - } catch (Exception e) { - LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e); - } - } + processClasses.initPasses(this); } public ICodeWriter makeCodeWriter() { diff --git a/jadx-core/src/main/java/jadx/core/dex/regions/conditions/Compare.java b/jadx-core/src/main/java/jadx/core/dex/regions/conditions/Compare.java index 03861a9dd..6faa8668f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/regions/conditions/Compare.java +++ b/jadx-core/src/main/java/jadx/core/dex/regions/conditions/Compare.java @@ -4,7 +4,6 @@ import jadx.core.dex.attributes.AFlag; import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.IfOp; import jadx.core.dex.instructions.args.InsnArg; -import jadx.core.dex.instructions.args.LiteralArg; public final class Compare { private final IfNode insn; @@ -35,13 +34,8 @@ public final class Compare { return this; } - /** - * Change 'a != false' to 'a == true' - */ public void normalize() { - if (getOp() == IfOp.NE && getB().isFalse()) { - insn.changeCondition(IfOp.EQ, getA(), LiteralArg.litTrue()); - } + insn.normalize(); } @Override diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodVisitor.java new file mode 100644 index 000000000..7c15d0afc --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodVisitor.java @@ -0,0 +1,31 @@ +package jadx.core.dex.visitors; + +import java.util.function.Consumer; + +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.utils.exceptions.JadxException; + +public class MethodVisitor implements IDexTreeVisitor { + + private final Consumer visitor; + + public MethodVisitor(Consumer visitor) { + this.visitor = visitor; + } + + @Override + public void visit(MethodNode mth) throws JadxException { + visitor.accept(mth); + } + + @Override + public void init(RootNode root) throws JadxException { + } + + @Override + public boolean visit(ClassNode cls) throws JadxException { + return true; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockExceptionHandler.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockExceptionHandler.java index 8e7b5fbca..0ebba28d8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockExceptionHandler.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockExceptionHandler.java @@ -149,12 +149,7 @@ public class BlockExceptionHandler { continue; } firstInsn.remove(AType.EXC_HANDLER); - TmpEdgeAttr tmpEdgeAttr = block.get(AType.TMP_EDGE); - if (tmpEdgeAttr != null) { - // remove temp connection - BlockSplitter.removeConnection(tmpEdgeAttr.getBlock(), block); - block.remove(AType.TMP_EDGE); - } + removeTmpConnection(block); ExceptionHandler excHandler = excHandlerAttr.getHandler(); if (block.getPredecessors().isEmpty()) { @@ -176,6 +171,19 @@ public class BlockExceptionHandler { } } + protected static void removeTmpConnections(MethodNode mth) { + mth.getBasicBlocks().forEach(BlockExceptionHandler::removeTmpConnection); + } + + private static void removeTmpConnection(BlockNode block) { + TmpEdgeAttr tmpEdgeAttr = block.get(AType.TMP_EDGE); + if (tmpEdgeAttr != null) { + // remove temp connection + BlockSplitter.removeConnection(tmpEdgeAttr.getBlock(), block); + block.remove(AType.TMP_EDGE); + } + } + private static List prepareTryBlocks(MethodNode mth) { Map> blocksByHandler = new HashMap<>(); for (BlockNode block : mth.getBasicBlocks()) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockProcessor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockProcessor.java index 95b9e1aca..f54b78a78 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockProcessor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockProcessor.java @@ -77,7 +77,9 @@ public class BlockProcessor extends AbstractVisitor { processNestedLoops(mth); updateCleanSuccessors(mth); - mth.finishBasicBlocks(); + if (!mth.contains(AFlag.DISABLE_BLOCKS_LOCK)) { + mth.finishBasicBlocks(); + } } static void updateCleanSuccessors(MethodNode mth) { @@ -686,7 +688,7 @@ public class BlockProcessor extends AbstractVisitor { return false; } - static void removeMarkedBlocks(MethodNode mth) { + public static void removeMarkedBlocks(MethodNode mth) { mth.getBasicBlocks().removeIf(block -> { if (block.contains(AFlag.REMOVE)) { if (!block.getPredecessors().isEmpty() || !block.getSuccessors().isEmpty()) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockSplitter.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockSplitter.java index 23259e8ba..a0916ce66 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockSplitter.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockSplitter.java @@ -142,7 +142,7 @@ public class BlockSplitter extends AbstractVisitor { return block; } - static void connect(BlockNode from, BlockNode to) { + public static void connect(BlockNode from, BlockNode to) { if (!from.getSuccessors().contains(to)) { from.getSuccessors().add(to); } @@ -151,19 +151,19 @@ public class BlockSplitter extends AbstractVisitor { } } - static void removeConnection(BlockNode from, BlockNode to) { + public static void removeConnection(BlockNode from, BlockNode to) { from.getSuccessors().remove(to); to.getPredecessors().remove(from); } - static void removePredecessors(BlockNode block) { + public static void removePredecessors(BlockNode block) { for (BlockNode pred : block.getPredecessors()) { pred.getSuccessors().remove(block); } block.getPredecessors().clear(); } - static void replaceConnection(BlockNode source, BlockNode oldDest, BlockNode newDest) { + public static void replaceConnection(BlockNode source, BlockNode oldDest, BlockNode newDest) { removeConnection(source, oldDest); connect(source, newDest); replaceTarget(source, oldDest, newDest); diff --git a/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java b/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java index 26482939b..3a296ee85 100644 --- a/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java @@ -453,6 +453,31 @@ public class BlockUtils { } } + public static void dfsVisit(MethodNode mth, Consumer visitor) { + BitSet visited = newBlocksBitSet(mth); + Deque queue = new ArrayDeque<>(); + BlockNode enterBlock = mth.getEnterBlock(); + queue.addLast(enterBlock); + visited.set(mth.getEnterBlock().getId()); + while (true) { + BlockNode current = queue.pollLast(); + if (current == null) { + return; + } + visitor.accept(current); + List successors = current.getSuccessors(); + int count = successors.size(); + for (int i = count - 1; i >= 0; i--) { // to preserve order in queue + BlockNode next = successors.get(i); + int nextId = next.getId(); + if (!visited.get(nextId)) { + queue.addLast(next); + visited.set(nextId); + } + } + } + } + public static List collectPredecessors(MethodNode mth, BlockNode start, Collection stopBlocks) { BitSet bs = newBlocksBitSet(mth); if (!stopBlocks.isEmpty()) { diff --git a/jadx-core/src/main/java/jadx/core/utils/CodeGenUtils.java b/jadx-core/src/main/java/jadx/core/utils/CodeGenUtils.java index dabf7560e..bdc6df386 100644 --- a/jadx-core/src/main/java/jadx/core/utils/CodeGenUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/CodeGenUtils.java @@ -35,18 +35,21 @@ public class CodeGenUtils { List errors = node.getAll(AType.JADX_ERROR); if (!errors.isEmpty()) { errors.stream().distinct().sorted().forEach(err -> { - code.startLine("/* JADX ERROR: ").add(err.getError()); - Throwable cause = err.getCause(); - if (cause != null) { - code.incIndent(); - Utils.appendStackTrace(code, cause); - code.decIndent(); - } - code.add("*/"); + addError(code, err.getError(), err.getCause()); }); } } + public static void addError(ICodeWriter code, String errMsg, Throwable cause) { + code.startLine("/* JADX ERROR: ").add(errMsg); + if (cause != null) { + code.incIndent(); + Utils.appendStackTrace(code, cause); + code.decIndent(); + } + code.add("*/"); + } + public static void addComments(ICodeWriter code, NotificationAttrNode node) { JadxCommentsAttr commentsAttr = node.get(AType.JADX_COMMENTS); if (commentsAttr != null) { diff --git a/jadx-core/src/test/java/jadx/tests/integration/fallback/TestFallbackMode.java b/jadx-core/src/test/java/jadx/tests/integration/fallback/TestFallbackMode.java index e3313e8af..ca050599d 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/fallback/TestFallbackMode.java +++ b/jadx-core/src/test/java/jadx/tests/integration/fallback/TestFallbackMode.java @@ -33,8 +33,8 @@ public class TestFallbackMode extends IntegrationTest { assertThat(code, containsString("public int test(int r2) {")); assertThat(code, containsOne("r1 = this;")); - assertThat(code, containsOne("L_0x0000:")); - assertThat(code, containsOne("L_0x0007:")); + assertThat(code, containsOne("L0:")); + assertThat(code, containsOne("L7:")); assertThat(code, containsOne("int r2 = r2 + 1")); assertThat(code, not(containsString("throw new UnsupportedOperationException"))); } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index 1d741e53a..625e36824 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory; import com.beust.jcommander.Parameter; import jadx.api.CommentsLevel; +import jadx.api.DecompilationMode; import jadx.api.JadxArgs; import jadx.api.args.DeobfuscationMapFileMode; import jadx.cli.JadxCLIArgs; @@ -44,7 +45,7 @@ public class JadxSettings extends JadxCLIArgs { private static final Path USER_HOME = Paths.get(System.getProperty("user.home")); private static final int RECENT_PROJECTS_COUNT = 15; - private static final int CURRENT_SETTINGS_VERSION = 16; + private static final int CURRENT_SETTINGS_VERSION = 17; private static final Font DEFAULT_FONT = new RSyntaxTextArea().getFont(); @@ -291,6 +292,10 @@ public class JadxSettings extends JadxCLIArgs { this.skipSources = skipSources; } + public void setDecompilationMode(DecompilationMode decompilationMode) { + this.decompilationMode = decompilationMode; + } + public void setShowInconsistentCode(boolean showInconsistentCode) { this.showInconsistentCode = showInconsistentCode; } @@ -672,6 +677,14 @@ public class JadxSettings extends JadxCLIArgs { } fromVersion++; } + if (fromVersion == 16) { + if (fallbackMode) { + decompilationMode = DecompilationMode.FALLBACK; + } else { + decompilationMode = DecompilationMode.AUTO; + } + fromVersion++; + } if (fromVersion != CURRENT_SETTINGS_VERSION) { throw new JadxRuntimeException("Incorrect settings upgrade"); } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java index 3168083e1..556e22334 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -59,6 +59,7 @@ import com.google.gson.JsonObject; import say.swing.JFontChooser; import jadx.api.CommentsLevel; +import jadx.api.DecompilationMode; import jadx.api.JadxArgs; import jadx.api.JadxArgs.UseKotlinMethodsForVarNames; import jadx.api.args.DeobfuscationMapFileMode; @@ -419,13 +420,6 @@ public class JadxSettingsWindow extends JDialog { } private SettingsGroup makeDecompilationGroup() { - JCheckBox fallback = new JCheckBox(); - fallback.setSelected(settings.isFallbackMode()); - fallback.addItemListener(e -> { - settings.setFallbackMode(e.getStateChange() == ItemEvent.SELECTED); - needReload(); - }); - JCheckBox useDx = new JCheckBox(); useDx.setSelected(settings.isUseDx()); useDx.addItemListener(e -> { @@ -433,6 +427,13 @@ public class JadxSettingsWindow extends JDialog { needReload(); }); + JComboBox decompilationModeComboBox = new JComboBox<>(DecompilationMode.values()); + decompilationModeComboBox.setSelectedItem(settings.getDecompilationMode()); + decompilationModeComboBox.addActionListener(e -> { + settings.setDecompilationMode((DecompilationMode) decompilationModeComboBox.getSelectedItem()); + needReload(); + }); + JCheckBox showInconsistentCode = new JCheckBox(); showInconsistentCode.setSelected(settings.isShowInconsistentCode()); showInconsistentCode.addItemListener(e -> { @@ -543,6 +544,7 @@ public class JadxSettingsWindow extends JDialog { other.addRow(NLS.str("preferences.excludedPackages"), NLS.str("preferences.excludedPackages.tooltip"), editExcludedPackages); other.addRow(NLS.str("preferences.start_jobs"), autoStartJobs); + other.addRow(NLS.str("preferences.decompilationMode"), decompilationModeComboBox); other.addRow(NLS.str("preferences.showInconsistentCode"), showInconsistentCode); other.addRow(NLS.str("preferences.escapeUnicode"), escapeUnicode); other.addRow(NLS.str("preferences.replaceConsts"), replaceConsts); @@ -551,7 +553,6 @@ public class JadxSettingsWindow extends JDialog { other.addRow(NLS.str("preferences.inlineAnonymous"), inlineAnonymous); other.addRow(NLS.str("preferences.inlineMethods"), inlineMethods); other.addRow(NLS.str("preferences.fsCaseSensitive"), fsCaseSensitive); - other.addRow(NLS.str("preferences.fallback"), fallback); other.addRow(NLS.str("preferences.useDx"), useDx); other.addRow(NLS.str("preferences.skipResourcesDecode"), resourceDecode); other.addRow(NLS.str("preferences.useKotlinMethodsForVarNames"), kotlinRenameVars); diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties index 6325b555a..86f9d2de0 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -125,8 +125,8 @@ preferences.other=Andere preferences.language=Sprache preferences.lineNumbersMode=Editor Zeilennummern-Modus preferences.check_for_updates=Nach Updates beim Start suchen -preferences.fallback=Zwischencode ausgeben (einfacher Speicherauszug) #preferences.useDx=Use dx/d8 to convert java bytecode +#preferences.decompilationMode=Decompilation mode preferences.showInconsistentCode=Inkonsistenten Code anzeigen preferences.escapeUnicode=Unicodezeichen escapen preferences.replaceConsts=Konstanten ersetzen diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index c3ad9b081..a36302302 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -125,8 +125,8 @@ preferences.other=Other preferences.language=Language preferences.lineNumbersMode=Editor line numbers mode preferences.check_for_updates=Check for updates on startup -preferences.fallback=Fallback mode (simple dump) preferences.useDx=Use dx/d8 to convert java bytecode +preferences.decompilationMode=Decompilation mode preferences.showInconsistentCode=Show inconsistent code preferences.escapeUnicode=Escape unicode preferences.replaceConsts=Replace constants diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties index bb48d31ab..2bf28b281 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -125,8 +125,8 @@ preferences.other=Otros preferences.language=Idioma #preferences.lineNumbersMode=Editor line numbers mode preferences.check_for_updates=Buscar actualizaciones al iniciar -preferences.fallback=Modo fallback (simple dump) #preferences.useDx=Use dx/d8 to convert java bytecode +#preferences.decompilationMode=Decompilation mode preferences.showInconsistentCode=Mostrar código inconsistente preferences.escapeUnicode=Escape unicode preferences.replaceConsts=Reemplazar constantes diff --git a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties index 57037f48c..53b95983e 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -125,8 +125,8 @@ preferences.other=기타 preferences.language=언어 preferences.lineNumbersMode=편집기 줄 번호 모드 preferences.check_for_updates=시작시 업데이트 확인 -preferences.fallback=대체 모드 (단순 덤프) #preferences.useDx=Use dx/d8 to convert java bytecode +#preferences.decompilationMode=Decompilation mode preferences.showInconsistentCode=디컴파일 안된 코드 표시 preferences.escapeUnicode=유니코드 이스케이프 preferences.replaceConsts=상수 바꾸기 diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties index 6ef3d6fce..b14cdbfe3 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -125,8 +125,8 @@ preferences.other=其他 preferences.language=语言 preferences.lineNumbersMode=编辑器行号模式 preferences.check_for_updates=启动时检查更新 -preferences.fallback=输出中间代码 #preferences.useDx=Use dx/d8 to convert java bytecode +#preferences.decompilationMode=Decompilation mode preferences.showInconsistentCode=显示不一致的代码 preferences.escapeUnicode=将 Unicode 字符转义 preferences.replaceConsts=替换常量 diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties index ef0f522b6..4b3993791 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -125,8 +125,8 @@ preferences.other=其他 preferences.language=語言 preferences.lineNumbersMode=編輯器行號模式 preferences.check_for_updates=啟動時檢查更新 -preferences.fallback=後援模式 (簡易傾印) preferences.useDx=使用 dx/d8 來轉換 Java 位元組碼 +#preferences.decompilationMode=Decompilation mode preferences.showInconsistentCode=顯示不一致的程式碼 preferences.escapeUnicode=Unicode 逸出 preferences.replaceConsts=替換常數