Skip to content

Commit

Permalink
feat: output decompilation results in json format (#676)
Browse files Browse the repository at this point in the history
  • Loading branch information
skylot authored Jun 18, 2019
1 parent 554e119 commit ed385e8
Show file tree
Hide file tree
Showing 39 changed files with 1,085 additions and 139 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ options:
-j, --threads-count - processing threads count
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
--single-class - decompile a single class
--output-format - can be 'java' or 'json' (default: java)
-e, --export-gradle - save as android gradle project
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-imports - disable use of imports, always write entire package name
Expand Down
10 changes: 10 additions & 0 deletions jadx-cli/src/main/java/jadx/cli/JCommanderWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,16 @@ private void addDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
// ignore
}
}
if (fieldType == String.class) {
try {
String val = (String) f.get(args);
if (val != null) {
opt.append(" (default: ").append(val).append(')');
}
} catch (Exception e) {
// ignore
}
}
}

private static void addSpaces(StringBuilder str, int count) {
Expand Down
28 changes: 16 additions & 12 deletions jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public class JadxCLIArgs {
@Parameter(names = { "--single-class" }, description = "decompile a single class")
protected String singleClass = null;

@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
protected String outputFormat = "java";

@Parameter(names = { "-e", "--export-gradle" }, description = "save as android gradle project")
protected boolean exportAsGradleProject = false;

Expand Down Expand Up @@ -86,7 +89,18 @@ public class JadxCLIArgs {
protected boolean deobfuscationForceSave = false;

@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
protected boolean deobfuscationUseSourceNameAsAlias = true;
protected boolean deobfuscationUseSourceNameAsAlias = false;

@Parameter(
names = { "--rename-flags" },
description = "what to rename, comma-separated,"
+ " 'case' for system case sensitivity,"
+ " 'valid' for java identifiers,"
+ " 'printable' characters,"
+ " 'none' or 'all' (default)",
converter = RenameConverter.class
)
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);

@Parameter(names = { "--fs-case-sensitive" }, description = "treat filesystem as case sensitive, false by default")
protected boolean fsCaseSensitive = false;
Expand All @@ -100,17 +114,6 @@ public class JadxCLIArgs {
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false;

@Parameter(
names = { "--rename-flags" },
description = "what to rename, comma-separated,"
+ " 'case' for system case sensitivity,"
+ " 'valid' for java identifiers,"
+ " 'printable' characters,"
+ " 'none' or 'all' (default)",
converter = RenameConverter.class
)
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);

@Parameter(names = { "-v", "--verbose" }, description = "verbose output")
protected boolean verbose = false;

Expand Down Expand Up @@ -178,6 +181,7 @@ public JadxArgs toJadxArgs() {
args.setOutDir(FileUtils.toFile(outDir));
args.setOutDirSrc(FileUtils.toFile(outDirSrc));
args.setOutDirRes(FileUtils.toFile(outDirRes));
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
args.setThreadsCount(threadsCount);
args.setSkipSources(skipSources);
if (singleClass != null) {
Expand Down
1 change: 1 addition & 0 deletions jadx-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dependencies {
compile 'org.ow2.asm:asm:7.1'
compile 'org.jetbrains:annotations:17.0.0'
compile 'uk.com.robust-it:cloning:1.9.12'
compile 'com.google.code.gson:gson:2.8.5'

compile 'org.smali:baksmali:2.2.7'
compile('org.smali:smali:2.2.7') {
Expand Down
10 changes: 9 additions & 1 deletion jadx-core/src/main/java/jadx/api/CodePosition.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ public int hashCode() {

@Override
public String toString() {
return line + ':' + offset + (node != null ? " " + node : "");
StringBuilder sb = new StringBuilder();
sb.append(line);
if (offset != 0) {
sb.append(':').append(offset);
}
if (node != null) {
sb.append(' ').append(node);
}
return sb.toString();
}
}
19 changes: 19 additions & 0 deletions jadx-core/src/main/java/jadx/api/JadxArgs.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ public enum RenameEnum {

private Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);

public enum OutputFormatEnum {
JAVA, JSON
}

private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;

public JadxArgs() {
// use default options
}
Expand Down Expand Up @@ -308,6 +314,18 @@ private void updateRenameFlag(boolean enabled, RenameEnum flag) {
}
}

public OutputFormatEnum getOutputFormat() {
return outputFormat;
}

public boolean isJsonOutput() {
return outputFormat == OutputFormatEnum.JSON;
}

public void setOutputFormat(OutputFormatEnum outputFormat) {
this.outputFormat = outputFormat;
}

@Override
public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles
Expand All @@ -333,6 +351,7 @@ public String toString() {
+ ", exportAsGradleProject=" + exportAsGradleProject
+ ", fsCaseSensitive=" + fsCaseSensitive
+ ", renameFlags=" + renameFlags
+ ", outputFormat=" + outputFormat
+ '}';
}
}
2 changes: 1 addition & 1 deletion jadx-core/src/main/java/jadx/api/JadxDecompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ private void appendSourcesSave(ExecutorService executor, File outDir) {
executor.execute(() -> {
try {
cls.decompile();
SaveCode.save(outDir, args, cls.getClassNode());
SaveCode.save(outDir, cls.getClassNode());
} catch (Exception e) {
LOG.error("Error saving class: {}", cls.getFullName(), e);
}
Expand Down
91 changes: 37 additions & 54 deletions jadx-core/src/main/java/jadx/core/codegen/ClassGen.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
Expand Down Expand Up @@ -128,8 +127,8 @@ public void addClassDeclaration(CodeWriter clsCode) {
}

annotationGen.addForClass(clsCode);
insertSourceFileInfo(clsCode, cls);
insertRenameInfo(clsCode, cls);
CodeGenUtils.addSourceFileInfo(clsCode, cls);
clsCode.startLine(af.makeString());
if (af.isInterface()) {
if (af.isAnnotation()) {
Expand Down Expand Up @@ -290,29 +289,21 @@ private boolean isMethodsPresents() {
return false;
}

private void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
public void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
CodeGenUtils.addComments(code, mth);
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code);
if (cls.getAccessFlags().isAnnotation()) {
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
if (def != null) {
code.add(" default ");
annotationGen.encodeValue(code, def);
}
}
code.add(';');
} else {
CodeGenUtils.addComments(code, mth);
insertDecompilationProblems(code, mth);
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
if (badCode && showInconsistentCode) {
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
mth.remove(AFlag.INCONSISTENT_CODE);
badCode = false;
}
MethodGen mthGen;
if (badCode || mth.contains(AType.JADX_ERROR) || fallback) {
if (badCode || fallback || mth.contains(AType.JADX_ERROR) || mth.getRegion() == null) {
mthGen = MethodGen.getFallbackMethodGen(mth);
} else {
mthGen = new MethodGen(this, mth);
Expand All @@ -322,12 +313,7 @@ private void addMethod(CodeWriter code, MethodNode mth) throws CodegenException
}
code.add('{');
code.incIndent();
insertSourceFileInfo(code, mth);
if (fallback) {
mthGen.addFallbackMethodCode(code);
} else {
mthGen.addInstructions(code);
}
mthGen.addInstructions(code);
code.decIndent();
code.startLine('}');
}
Expand Down Expand Up @@ -357,37 +343,41 @@ public void insertDecompilationProblems(CodeWriter code, AttrNode node) {
private void addFields(CodeWriter code) throws CodegenException {
addEnumFields(code);
for (FieldNode f : cls.getFields()) {
if (f.contains(AFlag.DONT_GENERATE)) {
continue;
}
CodeGenUtils.addComments(code, f);
annotationGen.addForField(code, f);
addField(code, f);
}
}

if (f.getFieldInfo().isRenamed()) {
code.newLine();
CodeGenUtils.addRenamedComment(code, f, f.getName());
}
code.startLine(f.getAccessFlags().makeString());
useType(code, f.getType());
code.add(' ');
code.attachDefinition(f);
code.add(f.getAlias());
FieldInitAttr fv = f.get(AType.FIELD_INIT);
if (fv != null) {
code.add(" = ");
if (fv.getValue() == null) {
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
} else {
if (fv.getValueType() == InitType.CONST) {
annotationGen.encodeValue(code, fv.getValue());
} else if (fv.getValueType() == InitType.INSN) {
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
addInsnBody(insnGen, code, fv.getInsn());
}
public void addField(CodeWriter code, FieldNode f) {
if (f.contains(AFlag.DONT_GENERATE)) {
return;
}
CodeGenUtils.addComments(code, f);
annotationGen.addForField(code, f);

if (f.getFieldInfo().isRenamed()) {
code.newLine();
CodeGenUtils.addRenamedComment(code, f, f.getName());
}
code.startLine(f.getAccessFlags().makeString());
useType(code, f.getType());
code.add(' ');
code.attachDefinition(f);
code.add(f.getAlias());
FieldInitAttr fv = f.get(AType.FIELD_INIT);
if (fv != null) {
code.add(" = ");
if (fv.getValue() == null) {
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
} else {
if (fv.getValueType() == InitType.CONST) {
annotationGen.encodeValue(code, fv.getValue());
} else if (fv.getValueType() == InitType.INSN) {
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
addInsnBody(insnGen, code, fv.getInsn());
}
}
code.add(';');
}
code.add(';');
}

private boolean isFieldsPresents() {
Expand Down Expand Up @@ -569,7 +559,7 @@ private void addImport(ClassInfo classInfo) {
}
}

private Set<ClassInfo> getImports() {
public Set<ClassInfo> getImports() {
if (parentGen != null) {
return parentGen.getImports();
} else {
Expand Down Expand Up @@ -615,13 +605,6 @@ private static boolean searchCollision(DexNode dex, ClassInfo useCls, ClassInfo
return searchCollision(dex, useCls.getParentClass(), searchCls);
}

private void insertSourceFileInfo(CodeWriter code, AttrNode node) {
SourceFileAttr sourceFileAttr = node.get(AType.SOURCE_FILE);
if (sourceFileAttr != null) {
code.startLine("/* compiled from: ").add(sourceFileAttr.getFileName()).add(" */");
}
}

private void insertRenameInfo(CodeWriter code, ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
if (classInfo.hasAlias()) {
Expand Down
55 changes: 42 additions & 13 deletions jadx-core/src/main/java/jadx/core/codegen/CodeGen.java
Original file line number Diff line number Diff line change
@@ -1,29 +1,58 @@
package jadx.core.codegen;

import java.util.concurrent.Callable;

import jadx.api.JadxArgs;
import jadx.core.codegen.json.JsonCodeGen;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;

public class CodeGen {

public static void generate(ClassNode cls) throws CodegenException {
public static void generate(ClassNode cls) {
if (cls.contains(AFlag.DONT_GENERATE)) {
cls.setCode(CodeWriter.EMPTY);
} else {
ClassGen clsGen = new ClassGen(cls, cls.root().getArgs());
CodeWriter code;
try {
code = clsGen.makeClass();
} catch (Exception e) {
if (cls.contains(AFlag.RESTART_CODEGEN)) {
cls.remove(AFlag.RESTART_CODEGEN);
code = clsGen.makeClass();
} else {
throw new JadxRuntimeException("Code generation error", e);
JadxArgs args = cls.root().getArgs();
switch (args.getOutputFormat()) {
case JAVA:
generateJavaCode(cls, args);
break;

case JSON:
generateJson(cls);
break;
}
}
}

private static void generateJavaCode(ClassNode cls, JadxArgs args) {
ClassGen clsGen = new ClassGen(cls, args);
CodeWriter code = wrapCodeGen(cls, clsGen::makeClass);
cls.setCode(code);
}

private static void generateJson(ClassNode cls) {
JsonCodeGen codeGen = new JsonCodeGen(cls);
String clsJson = wrapCodeGen(cls, codeGen::process);
cls.setCode(new CodeWriter(clsJson));
}

private static <R> R wrapCodeGen(ClassNode cls, Callable<R> codeGenFunc) {
try {
return codeGenFunc.call();
} catch (Exception e) {
if (cls.contains(AFlag.RESTART_CODEGEN)) {
cls.remove(AFlag.RESTART_CODEGEN);
try {
return codeGenFunc.call();
} catch (Exception ex) {
throw new JadxRuntimeException("Code generation error after restart", ex);
}
} else {
throw new JadxRuntimeException("Code generation error", e);
}
cls.setCode(code);
}
}

Expand Down
Loading

0 comments on commit ed385e8

Please sign in to comment.