From 7562ec9e1a3c6525321383586a1cb2c4fadf3877 Mon Sep 17 00:00:00 2001 From: Skylot Date: Sat, 26 May 2018 19:58:35 +0300 Subject: [PATCH] tests: add base test class for simplified apk debugging --- .gitignore | 2 - jadx-core/build.gradle | 2 + .../java/jadx/tests/api/IntegrationTest.java | 12 +- .../test/java/jadx/tests/external/.gitignore | 1 + .../jadx/tests/external/BaseExternalTest.java | 141 ++++++++++++++++++ 5 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/external/.gitignore create mode 100644 jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java diff --git a/.gitignore b/.gitignore index e87f7d17f..ead4e1c4f 100644 --- a/.gitignore +++ b/.gitignore @@ -28,5 +28,3 @@ jadx-output/ *.dump *.log *.cfg - -jadx-core/src/test/java/jadx/tests/external/ diff --git a/jadx-core/build.gradle b/jadx-core/build.gradle index b4aced120..54c5dc951 100644 --- a/jadx-core/build.gradle +++ b/jadx-core/build.gradle @@ -11,5 +11,7 @@ dependencies { testCompile 'org.smali:smali:2.2.2' testCompile 'org.smali:baksmali:2.2.2' + + testCompile 'org.apache.commons:commons-lang3:3.7' } 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 37f607c34..bbaea4aae 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -48,6 +48,14 @@ public abstract class IntegrationTest extends TestUtils { private static final String TEST_DIRECTORY = "src/test/java"; private static final String TEST_DIRECTORY2 = "jadx-core/" + TEST_DIRECTORY; + /** + * Run auto check method if defined: + *
+	 *     public static void check()
+	 * 
+ */ + public static final String CHECK_METHOD_NAME = "check"; + protected JadxArgs args; protected boolean deleteTmpFiles = true; @@ -159,7 +167,7 @@ public abstract class IntegrationTest extends TestUtils { } Method checkMth; try { - checkMth = origCls.getMethod("check"); + checkMth = origCls.getMethod(CHECK_METHOD_NAME); } catch (NoSuchMethodException e) { // ignore return; @@ -173,7 +181,7 @@ public abstract class IntegrationTest extends TestUtils { try { checkMth.invoke(origCls.newInstance()); } catch (InvocationTargetException ie) { - rethrow("Java check failed", ie); + rethrow("Original check failed", ie); } // run 'check' method from decompiled class try { diff --git a/jadx-core/src/test/java/jadx/tests/external/.gitignore b/jadx-core/src/test/java/jadx/tests/external/.gitignore new file mode 100644 index 000000000..810fe2873 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/external/.gitignore @@ -0,0 +1 @@ +/ExternalTests.java diff --git a/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java b/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java new file mode 100644 index 000000000..e1d59882f --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java @@ -0,0 +1,141 @@ +package jadx.tests.external; + +import java.io.File; +import java.util.List; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.JadxArgs; +import jadx.api.JadxDecompiler; +import jadx.api.JadxInternalAccess; +import jadx.api.JavaClass; +import jadx.core.Jadx; +import jadx.core.codegen.CodeGen; +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.DepthTraversal; +import jadx.core.dex.visitors.IDexTreeVisitor; +import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.tests.api.IntegrationTest; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +public abstract class BaseExternalTest extends IntegrationTest { + private static final Logger LOG = LoggerFactory.getLogger(BaseExternalTest.class); + + protected abstract String getSamplesDir(); + + protected JadxArgs prepare(String inputFile) { + JadxArgs args = new JadxArgs(); + args.getInputFiles().add(new File(getSamplesDir(), inputFile)); + args.setOutDir(new File("../jadx-external-tests-tmp")); + return args; + } + + protected void decompile(JadxArgs jadxArgs) { + decompile(jadxArgs, null, null); + } + + protected void decompile(JadxArgs jadxArgs, String clsPatternStr) { + decompile(jadxArgs, clsPatternStr, null); + } + + protected void decompile(JadxArgs jadxArgs, @Nullable String clsPatternStr, @Nullable String mthPatternStr) { + JadxDecompiler jadx = new JadxDecompiler(jadxArgs); + jadx.load(); + + if (clsPatternStr == null) { + processAll(jadx); +// jadx.saveSources(); + } else { + Pattern clsPtrn = Pattern.compile(clsPatternStr); + Pattern mthPtrn = mthPatternStr == null ? null : Pattern.compile(mthPatternStr); + processMthByPatterns(jadx, clsPtrn, mthPtrn); + } + printErrorReport(jadx); + } + + private void processAll(JadxDecompiler jadx) { + for (JavaClass javaClass : jadx.getClasses()) { + javaClass.decompile(); + } + } + + private void processMthByPatterns(JadxDecompiler jadx, Pattern clsPattern, @Nullable Pattern mthPattern) { + List passes = Jadx.getPassesList(jadx.getArgs()); + RootNode root = JadxInternalAccess.getRoot(jadx); + for (ClassNode classNode : root.getClasses(true)) { + String clsFullName = classNode.getClassInfo().getFullName(); + if (clsPattern.matcher(clsFullName).matches()) { + classNode.load(); + boolean decompile = false; + if (mthPattern == null) { + decompile = true; + } else { + for (MethodNode mth : classNode.getMethods()) { + if (mthPattern.matcher(mth.getName()).matches()) { + decompile = true; + break; + } + } + } + if (decompile) { + for (IDexTreeVisitor visitor : passes) { + DepthTraversal.visit(visitor, classNode); + } + try { + new CodeGen().visit(classNode); + } catch (Exception e) { + throw new JadxRuntimeException("Codegen failed", e); + } + LOG.warn("\n Print class: {}, {}", classNode.getFullName(), classNode.dex()); + if (mthPattern != null) { + printMethods(classNode, mthPattern); + } else { + LOG.info("Code: \n{}", classNode.getCode()); + } + checkCode(classNode); +// SaveCode.save(jadx.getArgs().getOutDirSrc(), jadx.getArgs(), classNode); + } + } + } + } + + private void printMethods(ClassNode classNode, @NotNull Pattern mthPattern) { + String code = classNode.getCode().getCodeStr(); + if (code == null) { + return; + } + String[] lines = code.split(CodeWriter.NL); + for (MethodNode mth : classNode.getMethods()) { + if (mthPattern.matcher(mth.getName()).matches()) { + int decompiledLine = mth.getDecompiledLine(); + StringBuilder mthCode = new StringBuilder(); + int brackets = 0; + for (int i = decompiledLine - 1; i > 0 && i < lines.length; i++) { + String line = lines[i]; + mthCode.append(line).append(CodeWriter.NL); + brackets += StringUtils.countMatches(line, '{'); + brackets -= StringUtils.countMatches(line, '}'); + if (brackets <= 0) { + break; + } + } + LOG.info("\n{}", mthCode); + } + } + } + + private void printErrorReport(JadxDecompiler jadx) { + jadx.printErrorsReport(); + assertThat(jadx.getErrorsCount(), is(0)); + } +}