refactor(tests): add debug checks switch to jadx args

This commit is contained in:
Skylot 2024-08-13 22:38:42 +01:00
parent c94201be4a
commit 0be5b2cea9
No known key found for this signature in database
GPG Key ID: 47A4975761262B6A
11 changed files with 106 additions and 35 deletions

View File

@ -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<String, String> 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<String, String> getPluginOptions() {
return pluginOptions;
}

View File

@ -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<IDexTreeVisitor> passes;
public ProcessClass(JadxArgs args) {
this.passes = Jadx.getPassesList(args);
public ProcessClass(List<IDexTreeVisitor> passesList) {
this.passes = passesList;
}
@Nullable

View File

@ -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 {

View File

@ -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<IDexTreeVisitor> preDecompilePasses;
private final List<ICodeDataUpdateListener> 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<ICodeDataUpdateListener> codeDataUpdateListeners = new ArrayList<>();
private final GradleInfoStorage gradleInfoStorage = new GradleInfoStorage();
private final Map<ClassInfo, ClassNode> clsMap = new HashMap<>();
@ -87,11 +85,12 @@ public class RootNode {
private final Map<String, PackageNode> pkgMap = new HashMap<>();
private final List<PackageNode> packages = new ArrayList<>();
private List<IDexTreeVisitor> 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() {

View File

@ -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);
}

View File

@ -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()) {

View File

@ -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()) {

View File

@ -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<String> IGNORE_CHECKS = new HashSet<>(List.of(
"PrepareForCodeGen",
"RenameVisitor",
"DotGraphVisitor"));
public static void runChecksAfterVisitor(MethodNode mth, IDexTreeVisitor visitor) {
Class<? extends IDexTreeVisitor> visitorCls = visitor.getClass();
if (visitorCls == PrepareForCodeGen.class || visitorCls == RenameVisitor.class) {
return;
public static List<IDexTreeVisitor> insertPasses(List<IDexTreeVisitor> passes) {
int size = passes.size();
List<IDexTreeVisitor> 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);
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);

View File

@ -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"));