diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index df7ff1cfd..b90e889fd 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -168,6 +168,11 @@ public class JadxArgs implements Closeable { */ private boolean skipFilesSave = false; + /** + * Run additional expensive checks to verify internal invariants and info integrity + */ + private boolean runDebugChecks = false; + private Map pluginOptions = new HashMap<>(); private JadxPluginLoader pluginLoader = new JadxBasePluginLoader(); @@ -685,6 +690,14 @@ public class JadxArgs implements Closeable { this.skipFilesSave = skipFilesSave; } + public boolean isRunDebugChecks() { + return runDebugChecks; + } + + public void setRunDebugChecks(boolean runDebugChecks) { + this.runDebugChecks = runDebugChecks; + } + public Map getPluginOptions() { return pluginOptions; } diff --git a/jadx-core/src/main/java/jadx/core/ProcessClass.java b/jadx-core/src/main/java/jadx/core/ProcessClass.java index 74d781ea5..4b3965b03 100644 --- a/jadx-core/src/main/java/jadx/core/ProcessClass.java +++ b/jadx-core/src/main/java/jadx/core/ProcessClass.java @@ -8,7 +8,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.api.ICodeInfo; -import jadx.api.JadxArgs; import jadx.api.impl.SimpleCodeInfo; import jadx.core.codegen.CodeGen; import jadx.core.dex.attributes.AFlag; @@ -32,8 +31,8 @@ public class ProcessClass { private final List passes; - public ProcessClass(JadxArgs args) { - this.passes = Jadx.getPassesList(args); + public ProcessClass(List passesList) { + this.passes = passesList; } @Nullable 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 c6f6d92e0..2e2e0b272 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 @@ -37,6 +37,7 @@ import jadx.api.plugins.input.data.attributes.types.SourceFileAttr; import jadx.api.plugins.input.data.impl.ListConsumer; import jadx.api.usage.IUsageInfoData; import jadx.core.Consts; +import jadx.core.Jadx; import jadx.core.ProcessClass; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; @@ -324,7 +325,7 @@ public class ClassNode extends NotificationAttrNode try { unload(); args.setDecompilationMode(mode); - ProcessClass process = new ProcessClass(args); + ProcessClass process = new ProcessClass(Jadx.getPassesList(args)); process.initPasses(root); return process.generateCode(this); } finally { 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 45570d963..a08026bbe 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 @@ -48,6 +48,7 @@ import jadx.core.dex.visitors.typeinference.TypeCompare; import jadx.core.dex.visitors.typeinference.TypeUpdate; import jadx.core.export.GradleInfoStorage; import jadx.core.utils.CacheStorage; +import jadx.core.utils.DebugChecks; import jadx.core.utils.ErrorsCounter; import jadx.core.utils.PassMerge; import jadx.core.utils.StringUtils; @@ -64,10 +65,6 @@ public class RootNode { private static final Logger LOG = LoggerFactory.getLogger(RootNode.class); private final JadxArgs args; - private final List preDecompilePasses; - private final List codeDataUpdateListeners = new ArrayList<>(); - - private final ProcessClass processClasses; private final ErrorsCounter errorsCounter = new ErrorsCounter(); private final StringUtils stringUtils; private final ConstStorage constValues; @@ -78,6 +75,7 @@ public class RootNode { private final TypeUtils typeUtils; private final AttributeStorage attributes = new AttributeStorage(); + private final List codeDataUpdateListeners = new ArrayList<>(); private final GradleInfoStorage gradleInfoStorage = new GradleInfoStorage(); private final Map clsMap = new HashMap<>(); @@ -87,11 +85,12 @@ public class RootNode { private final Map pkgMap = new HashMap<>(); private final List packages = new ArrayList<>(); + private List preDecompilePasses; + private ProcessClass processClasses; + private ClspGraph clsp; - @Nullable - private String appPackage; - @Nullable - private ClassNode appResClass; + private @Nullable String appPackage; + private @Nullable ClassNode appResClass; /** * Optional decompiler reference @@ -101,7 +100,7 @@ public class RootNode { public RootNode(JadxArgs args) { this.args = args; this.preDecompilePasses = Jadx.getPreDecompilePassesList(); - this.processClasses = new ProcessClass(args); + this.processClasses = new ProcessClass(Jadx.getPassesList(args)); this.stringUtils = new StringUtils(args); this.constValues = new ConstStorage(args); this.typeUpdate = new TypeUpdate(this); @@ -320,6 +319,11 @@ public class RootNode { .merge(customPasses.get(JadxPreparePass.TYPE), p -> new PreparePassWrapper((JadxPreparePass) p)); new PassMerge(processClasses.getPasses()) .merge(customPasses.get(JadxDecompilePass.TYPE), p -> new DecompilePassWrapper((JadxDecompilePass) p)); + + if (args.isRunDebugChecks()) { + preDecompilePasses = DebugChecks.insertPasses(preDecompilePasses); + processClasses = new ProcessClass(DebugChecks.insertPasses(processClasses.getPasses())); + } } public void runPreDecompileStage() { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java index 6f9fe85ea..d6774b14d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java @@ -3,7 +3,6 @@ package jadx.core.dex.visitors; import jadx.core.dex.attributes.AType; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.MethodNode; -import jadx.core.utils.DebugChecks; public class DepthTraversal { @@ -24,9 +23,6 @@ public class DepthTraversal { return; } visitor.visit(mth); - if (DebugChecks.checksEnabled) { - DebugChecks.runChecksAfterVisitor(mth, visitor); - } } catch (StackOverflowError | Exception e) { mth.addError(e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java index 184ab3c2f..27d28a2a6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java @@ -57,6 +57,11 @@ public class DotGraphVisitor extends AbstractVisitor { this.rawInsn = rawInsn; } + @Override + public String getName() { + return "DotGraphVisitor"; + } + @Override public void visit(MethodNode mth) { if (mth.isNoCode()) { 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 7d3f41b85..9dc3c863e 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 @@ -61,6 +61,11 @@ import jadx.core.utils.exceptions.JadxException; ) public class PrepareForCodeGen extends AbstractVisitor { + @Override + public String getName() { + return "PrepareForCodeGen"; + } + @Override public boolean visit(ClassNode cls) throws JadxException { if (cls.root().getArgs().isDebugInfo()) { diff --git a/jadx-core/src/main/java/jadx/core/utils/DebugChecks.java b/jadx-core/src/main/java/jadx/core/utils/DebugChecks.java index 53f6ffca2..2a223283c 100644 --- a/jadx-core/src/main/java/jadx/core/utils/DebugChecks.java +++ b/jadx-core/src/main/java/jadx/core/utils/DebugChecks.java @@ -1,11 +1,14 @@ package jadx.core.utils; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.nodes.PhiListAttr; +import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.PhiInsn; import jadx.core.dex.instructions.args.InsnArg; @@ -17,26 +20,37 @@ import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.visitors.IDexTreeVisitor; -import jadx.core.dex.visitors.PrepareForCodeGen; -import jadx.core.dex.visitors.rename.RenameVisitor; import jadx.core.utils.exceptions.JadxRuntimeException; /** - * Check invariants and information consistency for registers and SSA variables + * Check invariants and information consistency for blocks, instructions, registers, SSA variables. + * These checks are very expensive and executed only in tests. */ public class DebugChecks { - public static boolean /* not final! */ checksEnabled = false; + private static final Set IGNORE_CHECKS = new HashSet<>(List.of( + "PrepareForCodeGen", + "RenameVisitor", + "DotGraphVisitor")); - public static void runChecksAfterVisitor(MethodNode mth, IDexTreeVisitor visitor) { - Class visitorCls = visitor.getClass(); - if (visitorCls == PrepareForCodeGen.class || visitorCls == RenameVisitor.class) { - return; + public static List insertPasses(List passes) { + int size = passes.size(); + List list = new ArrayList<>(size * 2); + for (IDexTreeVisitor pass : passes) { + list.add(pass); + String name = pass.getName(); + if (!IGNORE_CHECKS.contains(name)) { + list.add(new DebugChecksPass(name)); + } } + return list; + } + + public static void runChecksAfterVisitor(MethodNode mth, String visitor) { try { checkMethod(mth); } catch (Exception e) { - throw new JadxRuntimeException("Debug check failed after visitor: " + visitorCls.getSimpleName(), e); + throw new JadxRuntimeException("Debug check failed after visitor: " + visitor, e); } } @@ -71,6 +85,16 @@ public class DebugChecks { for (RegisterArg arg : ternaryInsn.getCondition().getRegisterArgs()) { checkVar(mth, insn, arg); } + } else if (insn instanceof IfNode) { + IfNode ifNode = (IfNode) insn; + checkBlock(mth, ifNode.getThenBlock()); + checkBlock(mth, ifNode.getElseBlock()); + } + } + + private static void checkBlock(MethodNode mth, BlockNode block) { + if (!mth.getBasicBlocks().contains(block)) { + throw new JadxRuntimeException("Block not registered in method: " + block); } } diff --git a/jadx-core/src/main/java/jadx/core/utils/DebugChecksPass.java b/jadx-core/src/main/java/jadx/core/utils/DebugChecksPass.java new file mode 100644 index 000000000..0b75b2784 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/utils/DebugChecksPass.java @@ -0,0 +1,31 @@ +package jadx.core.utils; + +import jadx.core.dex.attributes.AType; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.visitors.AbstractVisitor; +import jadx.core.utils.exceptions.JadxException; + +public class DebugChecksPass extends AbstractVisitor { + + private final String visitorName; + + public DebugChecksPass(String visitorName) { + this.visitorName = visitorName; + } + + @Override + public String getName() { + return "Checks-for-" + visitorName; + } + + @Override + public void visit(MethodNode mth) throws JadxException { + if (!mth.contains(AType.JADX_ERROR)) { + try { + DebugChecks.runChecksAfterVisitor(mth, visitorName); + } catch (Throwable e) { + mth.addError("Check error", e); + } + } + } +} 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 0918468ed..a311b494c 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -51,7 +51,6 @@ import jadx.core.dex.attributes.AFlag; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; -import jadx.core.utils.DebugChecks; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.files.FileUtils; @@ -62,9 +61,9 @@ import jadx.tests.api.compiler.JavaUtils; import jadx.tests.api.compiler.TestCompiler; import jadx.tests.api.utils.TestUtils; -import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; import static org.apache.commons.lang3.StringUtils.leftPad; import static org.apache.commons.lang3.StringUtils.rightPad; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; public abstract class IntegrationTest extends TestUtils { @@ -114,11 +113,6 @@ public abstract class IntegrationTest extends TestUtils { */ private boolean forceDecompiledCheck = false; - static { - // enable debug checks - DebugChecks.checksEnabled = true; - } - protected JadxDecompiler jadxDecompiler; @BeforeEach @@ -137,6 +131,7 @@ public abstract class IntegrationTest extends TestUtils { args.setCommentsLevel(CommentsLevel.DEBUG); args.setDeobfuscationOn(false); args.setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode.IGNORE); + args.setRunDebugChecks(true); // use the same values on all systems args.setFsCaseSensitive(false); 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 80e25be23..e1095406e 100644 --- a/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java +++ b/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java @@ -19,7 +19,6 @@ import jadx.api.metadata.annotations.NodeDeclareRef; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; -import jadx.core.utils.DebugChecks; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.tests.api.utils.TestUtils; @@ -37,7 +36,6 @@ public abstract class BaseExternalTest extends TestUtils { } protected JadxArgs prepare(File input) { - DebugChecks.checksEnabled = false; JadxArgs args = new JadxArgs(); args.getInputFiles().add(input); args.setOutDir(new File("../jadx-external-tests-tmp"));