提交 ab970840 编写于 作者: S Skylot

refactor: move passes list to root node

上级 0911b2dc
......@@ -27,14 +27,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.export.ExportGradleProject;
import jadx.core.utils.Utils;
......@@ -72,12 +70,9 @@ public final class JadxDecompiler {
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
private JadxArgs args;
private final List<InputFile> inputFiles = new ArrayList<>();
private List<InputFile> inputFiles;
private RootNode root;
private List<IDexTreeVisitor> passes;
private List<JavaClass> classes;
private List<ResourceFile> resources;
......@@ -98,48 +93,45 @@ public final class JadxDecompiler {
public void load() {
reset();
JadxArgsValidator.validate(args);
init();
LOG.info("loading ...");
loadFiles(args.getInputFiles());
inputFiles = loadFiles(args.getInputFiles());
root = new RootNode(args);
root.load(inputFiles);
root.initClassPath();
root.loadResources(getResources());
initVisitors();
}
void init() {
this.passes = Jadx.getPassesList(args);
root.initPasses();
}
void reset() {
private void reset() {
root = null;
classes = null;
resources = null;
xmlParser = null;
root = null;
passes = null;
classesMap.clear();
methodsMap.clear();
fieldsMap.clear();
}
public static String getVersion() {
return Jadx.getVersion();
}
private void loadFiles(List<File> files) {
private List<InputFile> loadFiles(List<File> files) {
if (files.isEmpty()) {
throw new JadxRuntimeException("Empty file list");
}
inputFiles.clear();
List<InputFile> filesList = new ArrayList<>();
for (File file : files) {
try {
InputFile.addFilesFrom(file, inputFiles, args.isSkipSources());
InputFile.addFilesFrom(file, filesList, args.isSkipSources());
} catch (Exception e) {
throw new JadxRuntimeException("Error load file: " + file, e);
}
}
return filesList;
}
public void save() {
......@@ -299,20 +291,6 @@ public final class JadxDecompiler {
root.getErrorsCounter().printReport();
}
private void initVisitors() {
for (IDexTreeVisitor pass : passes) {
try {
pass.init(root);
} catch (Exception e) {
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
}
}
}
void processClass(ClassNode cls) {
ProcessClass.process(cls, passes, true);
}
void generateSmali(ClassNode cls) {
Path path = cls.dex().getDexFile().getPath();
String className = Utils.makeQualifiedObjectName(cls.getClassInfo().getType().getObject());
......@@ -341,10 +319,6 @@ public final class JadxDecompiler {
return root;
}
List<IDexTreeVisitor> getPasses() {
return passes;
}
synchronized BinaryXMLParser getXmlParser() {
if (xmlParser == null) {
xmlParser = new BinaryXMLParser(root);
......
......@@ -9,7 +9,6 @@ import java.util.Map;
import org.jetbrains.annotations.Nullable;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.AccessInfo;
......@@ -43,7 +42,7 @@ public final class JavaClass implements JavaNode {
}
public String getCode() {
CodeWriter code = cls.getCode();
ICodeInfo code = cls.getCode();
if (code == null) {
decompile();
code = cls.getCode();
......@@ -59,7 +58,7 @@ public final class JavaClass implements JavaNode {
return;
}
if (cls.getCode() == null) {
decompiler.processClass(cls);
cls.decompile();
load();
}
}
......@@ -135,7 +134,7 @@ public final class JavaClass implements JavaNode {
private Map<CodePosition, Object> getCodeAnnotations() {
decompile();
CodeWriter code = cls.getCode();
ICodeInfo code = cls.getCode();
if (code == null) {
return Collections.emptyMap();
}
......
package jadx.core;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import jadx.api.ICodeInfo;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.ProcessState;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.nodes.ProcessState.LOADED;
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
import static jadx.core.dex.nodes.ProcessState.PROCESSED;
import static jadx.core.dex.nodes.ProcessState.STARTED;
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED;
public final class ProcessClass {
private ProcessClass() {
}
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, boolean generateCode) {
if (!generateCode && cls.getState() == PROCESSED) {
return;
public static void process(ClassNode cls) {
process(cls, false);
}
@NotNull
public static ICodeInfo generateCode(ClassNode cls) {
ICodeInfo codeInfo = process(cls, true);
if (codeInfo == null) {
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName());
}
return codeInfo;
}
private static ICodeInfo process(ClassNode cls, boolean generateCode) {
ClassNode topParentClass = cls.getTopParentClass();
if (topParentClass != cls) {
return process(topParentClass, generateCode);
}
if (!generateCode && cls.getState() == PROCESS_COMPLETE) {
// nothing to do
return null;
}
synchronized (getSyncObj(cls)) {
try {
if (cls.getState() == NOT_LOADED) {
cls.load();
cls.setState(STARTED);
for (IDexTreeVisitor visitor : passes) {
}
if (cls.getState() == LOADED) {
cls.setState(PROCESS_STARTED);
for (IDexTreeVisitor visitor : cls.root().getPasses()) {
DepthTraversal.visit(visitor, cls);
}
cls.setState(PROCESSED);
cls.setState(PROCESS_COMPLETE);
}
if (cls.getState() == PROCESSED && generateCode) {
processDependencies(cls, passes);
CodeGen.generate(cls);
if (generateCode && cls.getState() == PROCESS_COMPLETE) {
processDependencies(cls);
ICodeInfo code = CodeGen.generate(cls);
cls.setState(ProcessState.GENERATED);
// TODO: unload class (need to build dependency tree or allow to load class several times)
return code;
}
} catch (Exception e) {
} catch (Throwable e) {
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
}
}
return null;
}
public static Object getSyncObj(ClassNode cls) {
private static Object getSyncObj(ClassNode cls) {
return cls.getClassInfo();
}
private static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
cls.getDependencies().forEach(depCls -> process(depCls, passes, false));
private static void processDependencies(ClassNode cls) {
for (ClassNode depCls : cls.getDependencies()) {
process(depCls, false);
}
}
}
......@@ -2,6 +2,7 @@ package jadx.core.codegen;
import java.util.concurrent.Callable;
import jadx.api.ICodeInfo;
import jadx.api.JadxArgs;
import jadx.core.codegen.json.JsonCodeGen;
import jadx.core.dex.attributes.AFlag;
......@@ -10,33 +11,32 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
public class CodeGen {
public static void generate(ClassNode cls) {
public static ICodeInfo generate(ClassNode cls) {
if (cls.contains(AFlag.DONT_GENERATE)) {
cls.setCode(CodeWriter.EMPTY);
} else {
JadxArgs args = cls.root().getArgs();
switch (args.getOutputFormat()) {
case JAVA:
generateJavaCode(cls, args);
break;
case JSON:
generateJson(cls);
break;
}
return CodeWriter.EMPTY;
}
JadxArgs args = cls.root().getArgs();
switch (args.getOutputFormat()) {
case JAVA:
return generateJavaCode(cls, args);
case JSON:
return generateJson(cls);
default:
throw new JadxRuntimeException("Unknown output format");
}
}
private static void generateJavaCode(ClassNode cls, JadxArgs args) {
private static ICodeInfo generateJavaCode(ClassNode cls, JadxArgs args) {
ClassGen clsGen = new ClassGen(cls, args);
CodeWriter code = wrapCodeGen(cls, clsGen::makeClass);
cls.setCode(code);
return wrapCodeGen(cls, clsGen::makeClass);
}
private static void generateJson(ClassNode cls) {
private static ICodeInfo generateJson(ClassNode cls) {
JsonCodeGen codeGen = new JsonCodeGen(cls);
String clsJson = wrapCodeGen(cls, codeGen::process);
cls.setCode(new CodeWriter(clsJson));
return new CodeWriter(clsJson);
}
private static <R> R wrapCodeGen(ClassNode cls, Callable<R> codeGenFunc) {
......
......@@ -24,8 +24,6 @@ public class CodeWriter implements ICodeInfo {
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 = {
......@@ -37,6 +35,8 @@ public class CodeWriter implements ICodeInfo {
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
};
public static final CodeWriter EMPTY = new CodeWriter().finish();
private StringBuilder buf;
@Nullable
private String code;
......@@ -53,7 +53,8 @@ public class CodeWriter implements ICodeInfo {
this.indent = 0;
this.indentStr = "";
if (ADD_LINE_NUMBERS) {
incIndent(2);
incIndent(3);
add(indentStr);
}
}
......
......@@ -788,8 +788,13 @@ public class InsnGen {
* Add additional cast for overloaded method argument.
*/
private boolean processOverloadedArg(CodeWriter code, MethodNode callMth, InsnArg arg, int origPos) {
ArgType origType;
List<RegisterArg> arguments = callMth.getArguments(false);
if (arguments == null || arguments.isEmpty()) {
// try to load class
callMth.getParentClass().loadAndProcess();
arguments = callMth.getArguments(false);
}
ArgType origType;
if (arguments == null || arguments.isEmpty()) {
mth.addComment("JADX INFO: used method not loaded: " + callMth + ", types can be incorrect");
origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
......
......@@ -16,8 +16,9 @@ import com.android.dex.ClassData.Method;
import com.android.dex.ClassDef;
import com.android.dex.Dex;
import jadx.api.ICodeInfo;
import jadx.core.Consts;
import jadx.core.codegen.CodeWriter;
import jadx.core.ProcessClass;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.nodes.LineAttrNode;
......@@ -36,6 +37,7 @@ import jadx.core.dex.nodes.parser.StaticValuesParser;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.nodes.ProcessState.LOADED;
import static jadx.core.dex.nodes.ProcessState.UNLOADED;
public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
......@@ -53,13 +55,13 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
private List<ClassNode> innerClasses = new ArrayList<>();
// store decompiled code
private CodeWriter code;
private ICodeInfo code;
// store smali
private String smali;
// store parent for inner classes or 'this' otherwise
private ClassNode parentClass;
private ProcessState state = ProcessState.NOT_LOADED;
private volatile ProcessState state = ProcessState.NOT_LOADED;
private List<ClassNode> dependencies = Collections.emptyList();
// cache maps
......@@ -234,14 +236,28 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
&& fileName.endsWith('$' + name)) {
return;
}
ClassInfo parentClass = clsInfo.getTopParentClass();
if (parentClass != null && fileName.equals(parentClass.getShortName())) {
ClassInfo parentCls = clsInfo.getTopParentClass();
if (parentCls != null && fileName.equals(parentCls.getShortName())) {
return;
}
}
this.addAttr(new SourceFileAttr(fileName));
}
public void loadAndProcess() {
ProcessClass.process(this);
}
public ICodeInfo decompile() {
if (code != null) {
return code;
}
ICodeInfo codeInfo = ProcessClass.generateCode(this);
// TODO: don't store code in class node
setCode(codeInfo);
return codeInfo;
}
@Override
public void load() {
for (MethodNode mth : getMethods()) {
......@@ -254,6 +270,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
for (ClassNode innerCls : getInnerClasses()) {
innerCls.load();
}
setState(LOADED);
}
@Override
......@@ -471,11 +488,11 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
return clsInfo.getAliasPkg();
}
public void setCode(CodeWriter code) {
public void setCode(ICodeInfo code) {
this.code = code;
}
public CodeWriter getCode() {
public ICodeInfo getCode() {
return code;
}
......
......@@ -2,8 +2,9 @@ package jadx.core.dex.nodes;
public enum ProcessState {
NOT_LOADED,
STARTED,
PROCESSED,
LOADED,
PROCESS_STARTED,
PROCESS_COMPLETE,
GENERATED,
UNLOADED
}
......@@ -12,6 +12,7 @@ import jadx.api.JadxArgs;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.api.ResourcesLoader;
import jadx.core.Jadx;
import jadx.core.clsp.ClspGraph;
import jadx.core.clsp.NMethod;
import jadx.core.dex.info.ClassInfo;
......@@ -20,6 +21,7 @@ import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.InfoStorage;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.typeinference.TypeUpdate;
import jadx.core.utils.CacheStorage;
import jadx.core.utils.ErrorsCounter;
......@@ -35,8 +37,10 @@ import jadx.core.xmlgen.ResourceStorage;
public class RootNode {
private static final Logger LOG = LoggerFactory.getLogger(RootNode.class);
private final ErrorsCounter errorsCounter = new ErrorsCounter();
private final JadxArgs args;
private final List<IDexTreeVisitor> passes;
private final ErrorsCounter errorsCounter = new ErrorsCounter();
private final StringUtils stringUtils;
private final ConstStorage constValues;
private final InfoStorage infoStorage = new InfoStorage();
......@@ -52,6 +56,7 @@ public class RootNode {
public RootNode(JadxArgs args) {
this.args = args;
this.passes = Jadx.getPassesList(args);
this.stringUtils = new StringUtils(args);
this.constValues = new ConstStorage(args);
this.typeUpdate = new TypeUpdate(this);
......@@ -208,6 +213,20 @@ public class RootNode {
return cls.dex().deepResolveField(cls, field);
}
public List<IDexTreeVisitor> getPasses() {
return passes;
}
public void initPasses() {
for (IDexTreeVisitor pass : passes) {
try {
pass.init(this);
} catch (Exception e) {
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
}
}
}
@Nullable
public ArgType getMethodGenericReturnType(MethodInfo callMth) {
MethodNode methodNode = deepResolveMethod(callMth);
......
......@@ -4,6 +4,7 @@ import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.JadxOverflowException;
public class DepthTraversal {
......@@ -13,6 +14,8 @@ public class DepthTraversal {
cls.getInnerClasses().forEach(inCls -> visit(visitor, inCls));
cls.getMethods().forEach(mth -> visit(visitor, mth));
}
} catch (StackOverflowError e) {
ErrorsCounter.classError(cls, "StackOverflow in pass: " + visitor.getClass().getSimpleName(), new JadxOverflowException(""));
} catch (Exception e) {
ErrorsCounter.classError(cls,
e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e);
......@@ -25,6 +28,8 @@ public class DepthTraversal {
}
try {
visitor.visit(mth);
} catch (StackOverflowError e) {
ErrorsCounter.methodError(mth, "StackOverflow in pass: " + visitor.getClass().getSimpleName(), new JadxOverflowException(""));
} catch (Exception e) {
ErrorsCounter.methodError(mth,
e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e);
......
......@@ -275,6 +275,7 @@ public class ModVisitor extends AbstractVisitor {
if (!mth.getParentClass().getInnerClasses().contains(classNode)) {
return;
}
classNode.loadAndProcess();
Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(callMthNode, co);
if (argsMap.isEmpty() && !callMthNode.getArguments(true).isEmpty()) {
return;
......
......@@ -2,6 +2,7 @@ package jadx.core.dex.visitors;
import java.io.File;
import jadx.api.ICodeInfo;
import jadx.api.JadxArgs;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AFlag;
......@@ -17,13 +18,20 @@ public class SaveCode {
if (cls.contains(AFlag.DONT_GENERATE)) {
return;
}
CodeWriter clsCode = cls.getCode();
if (clsCode == null) {
ICodeInfo code = cls.getCode();
if (code == null) {
throw new JadxRuntimeException("Code not generated for class " + cls.getFullName());
}
if (clsCode == CodeWriter.EMPTY) {
if (code == CodeWriter.EMPTY) {
return;
}
CodeWriter clsCode;
if (code instanceof CodeWriter) {
clsCode = (CodeWriter) code;
} else {
// TODO: move 'save' method from CodeWriter
clsCode = new CodeWriter(code.getCodeStr());
}
String fileName = cls.getClassInfo().getAliasFullPath() + getFileExtension(cls);
clsCode.save(dir, fileName);
}
......
......@@ -140,7 +140,7 @@ public class DebugUtils {
}
}
}
checkPHI(mth);
// checkPHI(mth);
}
private static void checkSSAVar(MethodNode mth, InsnNode insn, RegisterArg reg) {
......
......@@ -88,7 +88,7 @@ public class AndroidResourcesUtils {
}
ClassNode rCls = new ClassNode(dexNodes.get(0), clsName, AccessFlags.ACC_PUBLIC | AccessFlags.ACC_FINAL);
rCls.addAttr(AType.COMMENTS, "This class is generated by JADX");
rCls.setState(ProcessState.PROCESSED);
rCls.setState(ProcessState.PROCESS_COMPLETE);
return rCls;
}
......
......@@ -139,10 +139,10 @@ public class InputFile {
Files.copy(inputStream, jarFile, StandardCopyOption.REPLACE_EXISTING);
InputFile tempFile = new InputFile(jarFile.toFile());
tempFile.loadFromZip(ext);
List<DexFile> dexFiles = tempFile.getDexFiles();
if (!dexFiles.isEmpty()) {
index += dexFiles.size();
this.dexFiles.addAll(dexFiles);
List<DexFile> files = tempFile.getDexFiles();
if (!files.isEmpty()) {
index += files.size();
this.dexFiles.addAll(files);
}
}
}
......
......@@ -2,22 +2,39 @@ package jadx.api;
import java.io.File;
import org.junit.jupiter.api.Disabled;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.InputFileTest;
import static org.hamcrest.MatcherAssert.assertThat;
public class JadxDecompilerTest {
@Test
@Disabled
public void testExampleUsage() {
File sampleApk = InputFileTest.getFileFromSampleDir("app-with-fake-dex.apk");
File outDir = FileUtils.createTempDir("jadx-usage-example").toFile();
// test simple apk loading
JadxArgs args = new JadxArgs();
args.getInputFiles().add(new File("test.apk"));
args.setOutDir(new File("jadx-test-output"));
args.getInputFiles().add(sampleApk);
args.setOutDir(outDir);
JadxDecompiler jadx = new JadxDecompiler(args);
jadx.load();
jadx.save();
jadx.printErrorsReport();
// test class print
for (JavaClass cls : jadx.getClasses()) {
System.out.println(cls.getCode());
}
assertThat(jadx.getClasses(), Matchers.hasSize(3));
assertThat(jadx.getErrorsCount(), Matchers.is(0));
}
// TODO make more tests
// TODO add more tests
}
package jadx.api;
import java.util.List;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
public class JadxInternalAccess {
public static RootNode getRoot(JadxDecompiler d) {
return d.getRoot();
}
public static List<IDexTreeVisitor> getPassList(JadxDecompiler d) {
return d.getPasses();
}
}
......@@ -14,7 +14,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue;
class InputFileTest {
public class InputFileTest {
private static final String TEST_SAMPLES_DIR = "test-samples/";
@Test
......@@ -28,7 +28,7 @@ class InputFileTest {
assertThat(inputFile.getDexFiles(), hasSize(1));
}
private static File getFileFromSampleDir(String fileName) {
public static File getFileFromSampleDir(String fileName) {
URL resource = InputFileTest.class.getClassLoader().getResource(TEST_SAMPLES_DIR + fileName);
assertThat(resource, notNullValue());
String pathStr = resource.getFile();
......
......@@ -22,11 +22,12 @@ import java.util.jar.JarOutputStream;
import org.junit.jupiter.api.BeforeEach;
import jadx.api.ICodeInfo;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.JadxInternalAccess;
import jadx.core.ProcessClass;
import jadx.core.codegen.CodeGen;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrList;
......@@ -34,8 +35,6 @@ import jadx.core.dex.attributes.IAttributeNode;
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.files.FileUtils;
import jadx.core.xmlgen.ResourceStorage;
import jadx.core.xmlgen.entry.ResourceEntry;
......@@ -44,6 +43,8 @@ import jadx.tests.api.compiler.StaticCompiler;
import jadx.tests.api.utils.TestUtils;
import static jadx.core.utils.files.FileUtils.addFileToJar;
import static org.apache.commons.lang3.StringUtils.leftPad;
import static org.apache.commons.lang3.StringUtils.rightPad;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
......@@ -82,6 +83,7 @@ public abstract class IntegrationTest extends TestUtils {
protected Map<Integer, String> resMap = Collections.emptyMap();
private boolean allowWarnInCode;
private boolean printLineNumbers;
private DynamicCompiler dynamicCompiler;
......@@ -161,14 +163,18 @@ public abstract class IntegrationTest extends TestUtils {
protected void decompileAndCheck(JadxDecompiler d, List<ClassNode> clsList) {
if (unloadCls) {
clsList.forEach(cls -> decompile(d, cls));
clsList.forEach(cls -> cls.decompile());
} else {
clsList.forEach(cls -> decompileWithoutUnload(d, cls));
}
for (ClassNode cls : clsList) {
System.out.println("-----------------------------------------------------------");
System.out.println(cls.getCode());
if (printLineNumbers) {
printCodeWithLineNumbers(cls.getCode());
} else {
System.out.println(cls.getCode());
}
}
System.out.println("-----------------------------------------------------------");
......@@ -177,6 +183,22 @@ public abstract class IntegrationTest extends TestUtils {
clsList.forEach(this::runAutoCheck);
}
private void printCodeWithLineNumbers(ICodeInfo code) {
String codeStr = code.getCodeStr();
Map<Integer, Integer> lineMapping = code.getLineMapping();
String[] lines = codeStr.split(CodeWriter.NL);
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
int curLine = i + 1;
String lineNumStr = "/* " + leftPad(String.valueOf(curLine), 3) + " */";
Integer sourceLine = lineMapping.get(curLine);
if (sourceLine != null) {
lineNumStr += " /* " + sourceLine + " */";
}
System.out.println(rightPad(lineNumStr, 20) + line);
}
}
private void insertResources(RootNode root) {
if (resMap.isEmpty()) {
return;
......@@ -191,23 +213,15 @@ public abstract class IntegrationTest extends TestUtils {
root.processResources(resStorage);
}
protected void decompile(JadxDecompiler jadx, ClassNode cls) {
List<IDexTreeVisitor> passes = JadxInternalAccess.getPassList(jadx);
ProcessClass.process(cls, passes, true);
}
protected void decompileWithoutUnload(JadxDecompiler jadx, ClassNode cls) {
cls.load();
for (IDexTreeVisitor visitor : JadxInternalAccess.getPassList(jadx)) {
DepthTraversal.visit(visitor, cls);
}
cls.loadAndProcess();
generateClsCode(cls);
// don't unload class
}
protected void generateClsCode(ClassNode cls) {
try {
CodeGen.generate(cls);
cls.setCode(CodeGen.generate(cls));
} catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
......@@ -452,6 +466,10 @@ public abstract class IntegrationTest extends TestUtils {
allowWarnInCode = true;
}
protected void printLineNumbers() {
printLineNumbers = true;
}
// Use only for debug purpose
@Deprecated
protected void outputCFG() {
......
......@@ -2,7 +2,6 @@ package jadx.tests.external;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
......@@ -15,12 +14,10 @@ import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.JadxInternalAccess;
import jadx.api.JavaClass;
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.IDexTreeVisitor;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.tests.api.IntegrationTest;
......@@ -68,13 +65,12 @@ public abstract class BaseExternalTest extends IntegrationTest {
}
private void processByPatterns(JadxDecompiler jadx, String clsPattern, @Nullable String mthPattern) {
List<IDexTreeVisitor> passes = JadxInternalAccess.getPassList(jadx);
RootNode root = JadxInternalAccess.getRoot(jadx);
int processed = 0;
for (ClassNode classNode : root.getClasses(true)) {
String clsFullName = classNode.getClassInfo().getFullName();
if (clsFullName.equals(clsPattern)) {
if (processCls(mthPattern, passes, classNode)) {
if (processCls(mthPattern, classNode)) {
processed++;
}
}
......@@ -82,7 +78,7 @@ public abstract class BaseExternalTest extends IntegrationTest {
assertThat("No classes processed", processed, greaterThan(0));
}
private boolean processCls(@Nullable String mthPattern, List<IDexTreeVisitor> passes, ClassNode classNode) {
private boolean processCls(@Nullable String mthPattern, ClassNode classNode) {
classNode.load();
boolean decompile = false;
if (mthPattern == null) {
......@@ -99,7 +95,7 @@ public abstract class BaseExternalTest extends IntegrationTest {
return false;
}
try {
ProcessClass.process(classNode, passes, true);
classNode.decompile();
} catch (Exception e) {
throw new JadxRuntimeException("Class process failed", e);
}
......
......@@ -5,7 +5,6 @@ import java.util.Map;
import org.junit.jupiter.api.Test;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
......@@ -16,6 +15,7 @@ public class TestLineNumbers2 extends IntegrationTest {
public static class TestCls {
private WeakReference<TestCls> f;
// keep at line 18
public TestCls(TestCls s) {
}
......@@ -35,10 +35,10 @@ public class TestLineNumbers2 extends IntegrationTest {
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
CodeWriter codeWriter = cls.getCode();
printLineNumbers();
Map<Integer, Integer> lineMapping = codeWriter.getLineMapping();
ClassNode cls = getClassNode(TestCls.class);
Map<Integer, Integer> 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}",
lineMapping.toString());
}
......
......@@ -3,6 +3,7 @@ package jadx.tests.integration.debuginfo;
import org.junit.jupiter.api.Test;
import jadx.NotYetImplemented;
import jadx.api.ICodeInfo;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.nodes.ClassNode;
......@@ -51,30 +52,30 @@ public class TestReturnSourceLine extends IntegrationTest {
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
CodeWriter codeWriter = cls.getCode();
String code = codeWriter.toString();
ICodeInfo codeInfo = cls.getCode();
String code = codeInfo.toString();
String[] lines = code.split(CodeWriter.NL);
MethodNode test1 = cls.searchMethodByShortId("test1(Z)I");
checkLine(lines, codeWriter, test1, 3, "return 1;");
checkLine(lines, codeInfo, test1, 3, "return 1;");
MethodNode test2 = cls.searchMethodByShortId("test2(I)I");
checkLine(lines, codeWriter, test2, 3, "return v - 1;");
checkLine(lines, codeInfo, test2, 3, "return v - 1;");
}
@Test
@NotYetImplemented
public void test2() {
ClassNode cls = getClassNode(TestCls.class);
CodeWriter codeWriter = cls.getCode();
String code = codeWriter.toString();
ICodeInfo codeInfo = cls.getCode();
String code = codeInfo.toString();
String[] lines = code.split(CodeWriter.NL);
MethodNode test3 = cls.searchMethodByShortId("test3(I)I");
checkLine(lines, codeWriter, test3, 3, "return v;");
checkLine(lines, codeInfo, test3, 3, "return v;");
}
private static void checkLine(String[] lines, CodeWriter cw, LineAttrNode node, int offset, String str) {
private static void checkLine(String[] lines, ICodeInfo cw, LineAttrNode node, int offset, String str) {
int decompiledLine = node.getDecompiledLine() + offset;
assertThat(lines[decompiledLine - 1], containsOne(str));
Integer sourceLine = cw.getLineMapping().get(decompiledLine);
......
package jadx.tests.integration.loops;
import org.junit.jupiter.api.Test;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
public class TestComplexWhileLoop extends IntegrationTest {
public static class TestCls {
public static String test(String[] arr) {
int index = 0;
int length = arr.length;
String str;
while ((str = arr[index]) != null) {
if (str.length() == 1) {
return str.trim();
}
if (++index >= length) {
index = 0;
}
}
System.out.println("loop end");
return "";
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, not(containsString("for (int at = 0; at < len; at = endAt) {")));
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册