Unverified Commit ed385e8c authored by skylot's avatar skylot Committed by GitHub

feat: output decompilation results in json format (#676)

parent 554e119e
...@@ -66,6 +66,8 @@ options: ...@@ -66,6 +66,8 @@ options:
-j, --threads-count - processing threads count -j, --threads-count - processing threads count
-r, --no-res - do not decode resources -r, --no-res - do not decode resources
-s, --no-src - do not decompile source code -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 -e, --export-gradle - save as android gradle project
--show-bad-code - show inconsistent code (incorrectly decompiled) --show-bad-code - show inconsistent code (incorrectly decompiled)
--no-imports - disable use of imports, always write entire package name --no-imports - disable use of imports, always write entire package name
......
...@@ -112,6 +112,16 @@ public class JCommanderWrapper<T> { ...@@ -112,6 +112,16 @@ public class JCommanderWrapper<T> {
// ignore // 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) { private static void addSpaces(StringBuilder str, int count) {
......
...@@ -46,6 +46,9 @@ public class JadxCLIArgs { ...@@ -46,6 +46,9 @@ public class JadxCLIArgs {
@Parameter(names = { "--single-class" }, description = "decompile a single class") @Parameter(names = { "--single-class" }, description = "decompile a single class")
protected String singleClass = null; 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") @Parameter(names = { "-e", "--export-gradle" }, description = "save as android gradle project")
protected boolean exportAsGradleProject = false; protected boolean exportAsGradleProject = false;
...@@ -86,7 +89,18 @@ public class JadxCLIArgs { ...@@ -86,7 +89,18 @@ public class JadxCLIArgs {
protected boolean deobfuscationForceSave = false; protected boolean deobfuscationForceSave = false;
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias") @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") @Parameter(names = { "--fs-case-sensitive" }, description = "treat filesystem as case sensitive, false by default")
protected boolean fsCaseSensitive = false; protected boolean fsCaseSensitive = false;
...@@ -100,17 +114,6 @@ public class JadxCLIArgs { ...@@ -100,17 +114,6 @@ public class JadxCLIArgs {
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)") @Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false; 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") @Parameter(names = { "-v", "--verbose" }, description = "verbose output")
protected boolean verbose = false; protected boolean verbose = false;
...@@ -178,6 +181,7 @@ public class JadxCLIArgs { ...@@ -178,6 +181,7 @@ public class JadxCLIArgs {
args.setOutDir(FileUtils.toFile(outDir)); args.setOutDir(FileUtils.toFile(outDir));
args.setOutDirSrc(FileUtils.toFile(outDirSrc)); args.setOutDirSrc(FileUtils.toFile(outDirSrc));
args.setOutDirRes(FileUtils.toFile(outDirRes)); args.setOutDirRes(FileUtils.toFile(outDirRes));
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
args.setThreadsCount(threadsCount); args.setThreadsCount(threadsCount);
args.setSkipSources(skipSources); args.setSkipSources(skipSources);
if (singleClass != null) { if (singleClass != null) {
......
...@@ -8,6 +8,7 @@ dependencies { ...@@ -8,6 +8,7 @@ dependencies {
compile 'org.ow2.asm:asm:7.1' compile 'org.ow2.asm:asm:7.1'
compile 'org.jetbrains:annotations:17.0.0' compile 'org.jetbrains:annotations:17.0.0'
compile 'uk.com.robust-it:cloning:1.9.12' 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:baksmali:2.2.7'
compile('org.smali:smali:2.2.7') { compile('org.smali:smali:2.2.7') {
......
...@@ -57,6 +57,14 @@ public final class CodePosition { ...@@ -57,6 +57,14 @@ public final class CodePosition {
@Override @Override
public String toString() { 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();
} }
} }
...@@ -62,6 +62,12 @@ public class JadxArgs { ...@@ -62,6 +62,12 @@ public class JadxArgs {
private Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class); private Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
public enum OutputFormatEnum {
JAVA, JSON
}
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
public JadxArgs() { public JadxArgs() {
// use default options // use default options
} }
...@@ -308,6 +314,18 @@ public class JadxArgs { ...@@ -308,6 +314,18 @@ public class JadxArgs {
} }
} }
public OutputFormatEnum getOutputFormat() {
return outputFormat;
}
public boolean isJsonOutput() {
return outputFormat == OutputFormatEnum.JSON;
}
public void setOutputFormat(OutputFormatEnum outputFormat) {
this.outputFormat = outputFormat;
}
@Override @Override
public String toString() { public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles return "JadxArgs{" + "inputFiles=" + inputFiles
...@@ -333,6 +351,7 @@ public class JadxArgs { ...@@ -333,6 +351,7 @@ public class JadxArgs {
+ ", exportAsGradleProject=" + exportAsGradleProject + ", exportAsGradleProject=" + exportAsGradleProject
+ ", fsCaseSensitive=" + fsCaseSensitive + ", fsCaseSensitive=" + fsCaseSensitive
+ ", renameFlags=" + renameFlags + ", renameFlags=" + renameFlags
+ ", outputFormat=" + outputFormat
+ '}'; + '}';
} }
} }
...@@ -215,7 +215,7 @@ public final class JadxDecompiler { ...@@ -215,7 +215,7 @@ public final class JadxDecompiler {
executor.execute(() -> { executor.execute(() -> {
try { try {
cls.decompile(); cls.decompile();
SaveCode.save(outDir, args, cls.getClassNode()); SaveCode.save(outDir, cls.getClassNode());
} catch (Exception e) { } catch (Exception e) {
LOG.error("Error saving class: {}", cls.getFullName(), e); LOG.error("Error saving class: {}", cls.getFullName(), e);
} }
......
...@@ -19,7 +19,6 @@ import jadx.core.dex.attributes.nodes.EnumClassAttr; ...@@ -19,7 +19,6 @@ import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField; import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.JadxError; import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.LineAttrNode; 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.AccessInfo;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
...@@ -128,8 +127,8 @@ public class ClassGen { ...@@ -128,8 +127,8 @@ public class ClassGen {
} }
annotationGen.addForClass(clsCode); annotationGen.addForClass(clsCode);
insertSourceFileInfo(clsCode, cls);
insertRenameInfo(clsCode, cls); insertRenameInfo(clsCode, cls);
CodeGenUtils.addSourceFileInfo(clsCode, cls);
clsCode.startLine(af.makeString()); clsCode.startLine(af.makeString());
if (af.isInterface()) { if (af.isInterface()) {
if (af.isAnnotation()) { if (af.isAnnotation()) {
...@@ -290,29 +289,21 @@ public class ClassGen { ...@@ -290,29 +289,21 @@ public class ClassGen {
return false; 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()) { if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
MethodGen mthGen = new MethodGen(this, mth); MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code); 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(';'); code.add(';');
} else { } else {
CodeGenUtils.addComments(code, mth);
insertDecompilationProblems(code, mth); insertDecompilationProblems(code, mth);
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE); boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
if (badCode && showInconsistentCode) { if (badCode && showInconsistentCode) {
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
mth.remove(AFlag.INCONSISTENT_CODE); mth.remove(AFlag.INCONSISTENT_CODE);
badCode = false; badCode = false;
} }
MethodGen mthGen; 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); mthGen = MethodGen.getFallbackMethodGen(mth);
} else { } else {
mthGen = new MethodGen(this, mth); mthGen = new MethodGen(this, mth);
...@@ -322,12 +313,7 @@ public class ClassGen { ...@@ -322,12 +313,7 @@ public class ClassGen {
} }
code.add('{'); code.add('{');
code.incIndent(); code.incIndent();
insertSourceFileInfo(code, mth); mthGen.addInstructions(code);
if (fallback) {
mthGen.addFallbackMethodCode(code);
} else {
mthGen.addInstructions(code);
}
code.decIndent(); code.decIndent();
code.startLine('}'); code.startLine('}');
} }
...@@ -357,37 +343,41 @@ public class ClassGen { ...@@ -357,37 +343,41 @@ public class ClassGen {
private void addFields(CodeWriter code) throws CodegenException { private void addFields(CodeWriter code) throws CodegenException {
addEnumFields(code); addEnumFields(code);
for (FieldNode f : cls.getFields()) { for (FieldNode f : cls.getFields()) {
if (f.contains(AFlag.DONT_GENERATE)) { addField(code, f);
continue; }
} }
CodeGenUtils.addComments(code, f);
annotationGen.addForField(code, f);
if (f.getFieldInfo().isRenamed()) { public void addField(CodeWriter code, FieldNode f) {
code.newLine(); if (f.contains(AFlag.DONT_GENERATE)) {
CodeGenUtils.addRenamedComment(code, f, f.getName()); return;
} }
code.startLine(f.getAccessFlags().makeString()); CodeGenUtils.addComments(code, f);
useType(code, f.getType()); annotationGen.addForField(code, f);
code.add(' ');
code.attachDefinition(f); if (f.getFieldInfo().isRenamed()) {
code.add(f.getAlias()); code.newLine();
FieldInitAttr fv = f.get(AType.FIELD_INIT); CodeGenUtils.addRenamedComment(code, f, f.getName());
if (fv != null) { }
code.add(" = "); code.startLine(f.getAccessFlags().makeString());
if (fv.getValue() == null) { useType(code, f.getType());
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback)); code.add(' ');
} else { code.attachDefinition(f);
if (fv.getValueType() == InitType.CONST) { code.add(f.getAlias());
annotationGen.encodeValue(code, fv.getValue()); FieldInitAttr fv = f.get(AType.FIELD_INIT);
} else if (fv.getValueType() == InitType.INSN) { if (fv != null) {
InsnGen insnGen = makeInsnGen(fv.getInsnMth()); code.add(" = ");
addInsnBody(insnGen, code, fv.getInsn()); 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() { private boolean isFieldsPresents() {
...@@ -569,7 +559,7 @@ public class ClassGen { ...@@ -569,7 +559,7 @@ public class ClassGen {
} }
} }
private Set<ClassInfo> getImports() { public Set<ClassInfo> getImports() {
if (parentGen != null) { if (parentGen != null) {
return parentGen.getImports(); return parentGen.getImports();
} else { } else {
...@@ -615,13 +605,6 @@ public class ClassGen { ...@@ -615,13 +605,6 @@ public class ClassGen {
return searchCollision(dex, useCls.getParentClass(), searchCls); 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) { private void insertRenameInfo(CodeWriter code, ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo(); ClassInfo classInfo = cls.getClassInfo();
if (classInfo.hasAlias()) { if (classInfo.hasAlias()) {
......
package jadx.core.codegen; 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.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
public class CodeGen { public class CodeGen {
public static void generate(ClassNode cls) throws CodegenException { public static void generate(ClassNode cls) {
if (cls.contains(AFlag.DONT_GENERATE)) { if (cls.contains(AFlag.DONT_GENERATE)) {
cls.setCode(CodeWriter.EMPTY); cls.setCode(CodeWriter.EMPTY);
} else { } else {
ClassGen clsGen = new ClassGen(cls, cls.root().getArgs()); JadxArgs args = cls.root().getArgs();
CodeWriter code; switch (args.getOutputFormat()) {
try { case JAVA:
code = clsGen.makeClass(); generateJavaCode(cls, args);
} catch (Exception e) { break;
if (cls.contains(AFlag.RESTART_CODEGEN)) {
cls.remove(AFlag.RESTART_CODEGEN); case JSON:
code = clsGen.makeClass(); generateJson(cls);
} else { break;
throw new JadxRuntimeException("Code generation error", e); }
}
}
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);
} }
} }
......
...@@ -4,7 +4,6 @@ import java.io.File; ...@@ -4,7 +4,6 @@ import java.io.File;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
...@@ -37,7 +36,7 @@ public class CodeWriter { ...@@ -37,7 +36,7 @@ public class CodeWriter {
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR, INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
}; };
private StringBuilder buf = new StringBuilder(); private StringBuilder buf;
@Nullable @Nullable
private String code; private String code;
private String indentStr; private String indentStr;
...@@ -49,6 +48,7 @@ public class CodeWriter { ...@@ -49,6 +48,7 @@ public class CodeWriter {
private Map<Integer, Integer> lineMap = Collections.emptyMap(); private Map<Integer, Integer> lineMap = Collections.emptyMap();
public CodeWriter() { public CodeWriter() {
this.buf = new StringBuilder();
this.indent = 0; this.indent = 0;
this.indentStr = ""; this.indentStr = "";
if (ADD_LINE_NUMBERS) { if (ADD_LINE_NUMBERS) {
...@@ -56,6 +56,12 @@ public class CodeWriter { ...@@ -56,6 +56,12 @@ public class CodeWriter {
} }
} }
// create filled instance (just string wrapper)
public CodeWriter(String code) {
this.buf = null;
this.code = code;
}
public CodeWriter startLine() { public CodeWriter startLine() {
addLine(); addLine();
addLineIndent(); addLineIndent();
...@@ -225,6 +231,10 @@ public class CodeWriter { ...@@ -225,6 +231,10 @@ public class CodeWriter {
attachAnnotation(obj, new CodePosition(line, offset + 1)); attachAnnotation(obj, new CodePosition(line, offset + 1));
} }
public void attachLineAnnotation(Object obj) {
attachAnnotation(obj, new CodePosition(line, 0));
}
private Object attachAnnotation(Object obj, CodePosition pos) { private Object attachAnnotation(Object obj, CodePosition pos) {
if (annotations.isEmpty()) { if (annotations.isEmpty()) {
annotations = new HashMap<>(); annotations = new HashMap<>();
...@@ -260,16 +270,15 @@ public class CodeWriter { ...@@ -260,16 +270,15 @@ public class CodeWriter {
code = buf.toString(); code = buf.toString();
buf = null; buf = null;
Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator(); annotations.entrySet().removeIf(entry -> {
while (it.hasNext()) {
Map.Entry<CodePosition, Object> entry = it.next();
Object v = entry.getValue(); Object v = entry.getValue();
if (v instanceof DefinitionWrapper) { if (v instanceof DefinitionWrapper) {
LineAttrNode l = ((DefinitionWrapper) v).getNode(); LineAttrNode l = ((DefinitionWrapper) v).getNode();
l.setDecompiledLine(entry.getKey().getLine()); l.setDecompiledLine(entry.getKey().getLine());
it.remove(); return true;
} }
} return false;
});
return this; return this;
} }
......
...@@ -63,6 +63,7 @@ public class InsnGen { ...@@ -63,6 +63,7 @@ public class InsnGen {
protected final MethodNode mth; protected final MethodNode mth;
protected final RootNode root; protected final RootNode root;
protected final boolean fallback; protected final boolean fallback;
protected final boolean attachInsns;
protected enum Flags { protected enum Flags {
BODY_ONLY, BODY_ONLY,
...@@ -73,8 +74,9 @@ public class InsnGen { ...@@ -73,8 +74,9 @@ public class InsnGen {
public InsnGen(MethodGen mgen, boolean fallback) { public InsnGen(MethodGen mgen, boolean fallback) {
this.mgen = mgen; this.mgen = mgen;
this.mth = mgen.getMethodNode(); this.mth = mgen.getMethodNode();
this.root = mth.dex().root(); this.root = mth.root();
this.fallback = fallback; this.fallback = fallback;
this.attachInsns = root.getArgs().isJsonOutput();
} }
private boolean isFallback() { private boolean isFallback() {
...@@ -222,6 +224,9 @@ public class InsnGen { ...@@ -222,6 +224,9 @@ public class InsnGen {
} else { } else {
if (flag != Flags.INLINE) { if (flag != Flags.INLINE) {
code.startLineWithNum(insn.getSourceLine()); code.startLineWithNum(insn.getSourceLine());
if (attachInsns) {
code.attachLineAnnotation(insn);
}
} }
if (insn.getResult() != null) { if (insn.getResult() != null) {
SSAVar var = insn.getResult().getSVar(); SSAVar var = insn.getResult().getSVar();
......
...@@ -85,9 +85,14 @@ public class MethodGen { ...@@ -85,9 +85,14 @@ public class MethodGen {
ai = ai.remove(AccessFlags.ACC_PUBLIC); ai = ai.remove(AccessFlags.ACC_PUBLIC);
} }
if (mth.getMethodInfo().isRenamed() && !ai.isConstructor()) { if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) {
CodeGenUtils.addRenamedComment(code, mth, mth.getName()); CodeGenUtils.addRenamedComment(code, mth, mth.getName());
} }
CodeGenUtils.addSourceFileInfo(code, mth);
if (mth.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
}
code.startLineWithNum(mth.getSourceLine()); code.startLineWithNum(mth.getSourceLine());
code.add(ai.makeString()); code.add(ai.makeString());
if (Consts.DEBUG) { if (Consts.DEBUG) {
...@@ -125,6 +130,15 @@ public class MethodGen { ...@@ -125,6 +130,15 @@ public class MethodGen {
code.add(')'); code.add(')');
annotationGen.addThrows(mth, code); annotationGen.addThrows(mth, code);
// add default value if in annotation class
if (mth.getParentClass().getAccessFlags().isAnnotation()) {
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
if (def != null) {
code.add(" default ");
annotationGen.encodeValue(code, def);
}
}
return true; return true;
} }
...@@ -181,41 +195,49 @@ public class MethodGen { ...@@ -181,41 +195,49 @@ public class MethodGen {
} }
public void addInstructions(CodeWriter code) throws CodegenException { public void addInstructions(CodeWriter code) throws CodegenException {
if (mth.contains(AType.JADX_ERROR) if (mth.root().getArgs().isFallbackMode()) {
|| mth.contains(AFlag.INCONSISTENT_CODE)
|| mth.getRegion() == null) {
code.startLine("/*");
addFallbackMethodCode(code); addFallbackMethodCode(code);
code.startLine("*/"); } else if (classGen.isFallbackMode()) {
dumpInstructions(code);
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
.add(mth.getParentClass().getClassInfo().getAliasFullName())
.add('.')
.add(mth.getAlias())
.add('(')
.add(Utils.listToString(mth.getMethodInfo().getArgumentsTypes()))
.add("):")
.add(mth.getMethodInfo().getReturnType().toString())
.add("\");");
} else { } else {
try { addRegionInsns(code);
RegionGen regionGen = new RegionGen(this); }
regionGen.makeRegion(code, mth.getRegion()); }
} catch (StackOverflowError | BootstrapMethodError e) {
mth.addError("Method code generation error", new JadxOverflowException("StackOverflow")); public void addRegionInsns(CodeWriter code) throws CodegenException {
classGen.insertDecompilationProblems(code, mth); try {
addInstructions(code); RegionGen regionGen = new RegionGen(this);
} catch (Exception e) { regionGen.makeRegion(code, mth.getRegion());
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) { } catch (StackOverflowError | BootstrapMethodError e) {
throw e; mth.addError("Method code generation error", new JadxOverflowException("StackOverflow"));
} classGen.insertDecompilationProblems(code, mth);
mth.addError("Method code generation error", e); dumpInstructions(code);
classGen.insertDecompilationProblems(code, mth); } catch (Exception e) {
addInstructions(code); if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
throw e;
} }
mth.addError("Method code generation error", e);
classGen.insertDecompilationProblems(code, mth);
dumpInstructions(code);
} }
} }
public void dumpInstructions(CodeWriter code) {
code.startLine("/*");
addFallbackMethodCode(code);
code.startLine("*/");
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
.add(mth.getParentClass().getClassInfo().getAliasFullName())
.add('.')
.add(mth.getAlias())
.add('(')
.add(Utils.listToString(mth.getMethodInfo().getArgumentsTypes()))
.add("):")
.add(mth.getMethodInfo().getReturnType().toString())
.add("\");");
}
public void addFallbackMethodCode(CodeWriter code) { public void addFallbackMethodCode(CodeWriter code) {
if (mth.getInstructions() == null) { if (mth.getInstructions() == null) {
// load original instructions // load original instructions
...@@ -244,6 +266,7 @@ public class MethodGen { ...@@ -244,6 +266,7 @@ public class MethodGen {
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) { public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) {
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true); InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
boolean attachInsns = mth.root().getArgs().isJsonOutput();
InsnNode prevInsn = null; InsnNode prevInsn = null;
for (InsnNode insn : insnArr) { for (InsnNode insn : insnArr) {
if (insn == null) { if (insn == null) {
...@@ -259,6 +282,9 @@ public class MethodGen { ...@@ -259,6 +282,9 @@ public class MethodGen {
} }
try { try {
code.startLine(); code.startLine();
if (attachInsns) {
code.attachLineAnnotation(insn);
}
RegisterArg resArg = insn.getResult(); RegisterArg resArg = insn.getResult();
if (resArg != null) { if (resArg != null) {
ArgType varType = resArg.getInitType(); ArgType varType = resArg.getInitType();
...@@ -304,7 +330,7 @@ public class MethodGen { ...@@ -304,7 +330,7 @@ public class MethodGen {
* Return fallback variant of method codegen * Return fallback variant of method codegen
*/ */
public static MethodGen getFallbackMethodGen(MethodNode mth) { public static MethodGen getFallbackMethodGen(MethodNode mth) {
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true, true, true); ClassGen clsGen = new ClassGen(mth.getParentClass(), null, false, true, true);
return new MethodGen(clsGen, mth); return new MethodGen(clsGen, mth);
} }
......
...@@ -121,6 +121,17 @@ public class RegionGen extends InsnGen { ...@@ -121,6 +121,17 @@ public class RegionGen extends InsnGen {
} else { } else {
code.attachSourceLine(region.getSourceLine()); code.attachSourceLine(region.getSourceLine());
} }
if (attachInsns) {
List<BlockNode> conditionBlocks = region.getConditionBlocks();
if (!conditionBlocks.isEmpty()) {
BlockNode blockNode = conditionBlocks.get(0);
InsnNode lastInsn = BlockUtils.getLastInsn(blockNode);
if (lastInsn != null) {
code.attachLineAnnotation(lastInsn);
}
}
}
code.add("if ("); code.add("if (");
new ConditionGen(this).add(code, region.getCondition()); new ConditionGen(this).add(code, region.getCondition());
code.add(") {"); code.add(") {");
...@@ -128,7 +139,7 @@ public class RegionGen extends InsnGen { ...@@ -128,7 +139,7 @@ public class RegionGen extends InsnGen {
code.startLine('}'); code.startLine('}');
IContainer els = region.getElseRegion(); IContainer els = region.getElseRegion();
if (els != null && RegionUtils.notEmpty(els)) { if (RegionUtils.notEmpty(els)) {
code.add(" else "); code.add(" else ");
if (connectElseIf(code, els)) { if (connectElseIf(code, els)) {
return; return;
......
package jadx.core.codegen.json;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import jadx.api.CodePosition;
import jadx.api.JadxArgs;
import jadx.core.codegen.ClassGen;
import jadx.core.codegen.CodeWriter;
import jadx.core.codegen.MethodGen;
import jadx.core.codegen.json.cls.JsonClass;
import jadx.core.codegen.json.cls.JsonCodeLine;
import jadx.core.codegen.json.cls.JsonField;
import jadx.core.codegen.json.cls.JsonMethod;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class JsonCodeGen {
private static final Gson GSON = new GsonBuilder()
.setPrettyPrinting()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES)
.disableHtmlEscaping()
.create();
private final ClassNode cls;
private final JadxArgs args;
private final RootNode root;
public JsonCodeGen(ClassNode cls) {
this.cls = cls;
this.root = cls.root();
this.args = root.getArgs();
}
public String process() {
JsonClass jsonCls = processCls(cls, null);
return GSON.toJson(jsonCls);
}
private JsonClass processCls(ClassNode cls, @Nullable ClassGen parentCodeGen) {
ClassGen classGen;
if (parentCodeGen == null) {
classGen = new ClassGen(cls, args);
} else {
classGen = new ClassGen(cls, parentCodeGen);
}
ClassInfo classInfo = cls.getClassInfo();
JsonClass jsonCls = new JsonClass();
jsonCls.setPkg(classInfo.getAliasPkg());
jsonCls.setDex(cls.dex().getDexFile().getName());
jsonCls.setName(classInfo.getFullName());
if (classInfo.hasAlias()) {
jsonCls.setAlias(classInfo.getAliasFullName());
}
jsonCls.setType(getClassTypeStr(cls));
jsonCls.setAccessFlags(cls.getAccessFlags().rawValue());
if (!Objects.equals(cls.getSuperClass(), ArgType.OBJECT)) {
jsonCls.setSuperClass(getTypeAlias(cls.getSuperClass()));
}
if (!cls.getInterfaces().isEmpty()) {
jsonCls.setInterfaces(Utils.collectionMap(cls.getInterfaces(), this::getTypeAlias));
}
CodeWriter cw = new CodeWriter();
CodeGenUtils.addComments(cw, cls);
classGen.insertDecompilationProblems(cw, cls);
classGen.addClassDeclaration(cw);
jsonCls.setDeclaration(cw.finish().toString());
addFields(cls, jsonCls, classGen);
addMethods(cls, jsonCls, classGen);
addInnerClasses(cls, jsonCls, classGen);
if (!cls.getClassInfo().isInner()) {
List<String> imports = Utils.collectionMap(classGen.getImports(), ClassInfo::getAliasFullName);
Collections.sort(imports);
jsonCls.setImports(imports);
}
return jsonCls;
}
private void addInnerClasses(ClassNode cls, JsonClass jsonCls, ClassGen classGen) {
List<ClassNode> innerClasses = cls.getInnerClasses();
if (innerClasses.isEmpty()) {
return;
}
jsonCls.setInnerClasses(new ArrayList<>(innerClasses.size()));
for (ClassNode innerCls : innerClasses) {
if (innerCls.contains(AFlag.DONT_GENERATE)) {
continue;
}
JsonClass innerJsonCls = processCls(innerCls, classGen);
jsonCls.getInnerClasses().add(innerJsonCls);
}
}
private void addFields(ClassNode cls, JsonClass jsonCls, ClassGen classGen) {
jsonCls.setFields(new ArrayList<>());
for (FieldNode field : cls.getFields()) {
if (field.contains(AFlag.DONT_GENERATE)) {
continue;
}
JsonField jsonField = new JsonField();
jsonField.setName(field.getName());
if (field.getFieldInfo().hasAlias()) {
jsonField.setAlias(field.getAlias());
}
CodeWriter cw = new CodeWriter();
classGen.addField(cw, field);
jsonField.setDeclaration(cw.finish().toString());
jsonField.setAccessFlags(field.getAccessFlags().rawValue());
jsonCls.getFields().add(jsonField);
}
}
private void addMethods(ClassNode cls, JsonClass jsonCls, ClassGen classGen) {
jsonCls.setMethods(new ArrayList<>());
for (MethodNode mth : cls.getMethods()) {
if (mth.contains(AFlag.DONT_GENERATE)) {
continue;
}
JsonMethod jsonMth = new JsonMethod();
jsonMth.setName(mth.getName());
if (mth.getMethodInfo().hasAlias()) {
jsonMth.setAlias(mth.getAlias());
}
jsonMth.setSignature(mth.getMethodInfo().getShortId());
jsonMth.setReturnType(getTypeAlias(mth.getReturnType()));
jsonMth.setArguments(Utils.collectionMap(mth.getMethodInfo().getArgumentsTypes(), this::getTypeAlias));
MethodGen mthGen = new MethodGen(classGen, mth);
CodeWriter cw = new CodeWriter();
mthGen.addDefinition(cw);
jsonMth.setDeclaration(cw.finish().toString());
jsonMth.setAccessFlags(mth.getAccessFlags().rawValue());
jsonMth.setLines(fillMthCode(mth, mthGen));
jsonMth.setOffset("0x" + Long.toHexString(mth.getMethodCodeOffset()));
jsonCls.getMethods().add(jsonMth);
}
}
private List<JsonCodeLine> fillMthCode(MethodNode mth, MethodGen mthGen) {
if (mth.isNoCode()) {
return Collections.emptyList();
}
CodeWriter code = new CodeWriter();
try {
mthGen.addInstructions(code);
} catch (Exception e) {
throw new JadxRuntimeException("Method generation error", e);
}
code.finish();
String codeStr = code.toString();
if (codeStr.isEmpty()) {
return Collections.emptyList();
}
String[] lines = codeStr.split(CodeWriter.NL);
Map<Integer, Integer> lineMapping = code.getLineMapping();
Map<CodePosition, Object> annotations = code.getAnnotations();
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
int linesCount = lines.length;
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
for (int i = 0; i < linesCount; i++) {
String codeLine = lines[i];
int line = i + 2;
JsonCodeLine jsonCodeLine = new JsonCodeLine();
jsonCodeLine.setCode(codeLine);
jsonCodeLine.setSourceLine(lineMapping.get(line));
Object obj = annotations.get(new CodePosition(line, 0));
if (obj instanceof InsnNode) {
int offset = ((InsnNode) obj).getOffset();
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
}
codeLines.add(jsonCodeLine);
}
return codeLines;
}
private String getTypeAlias(ArgType clsType) {
if (Objects.equals(clsType, ArgType.OBJECT)) {
return ArgType.OBJECT.getObject();
}
if (clsType.isObject()) {
ClassInfo classInfo = ClassInfo.fromType(root, clsType);
return classInfo.getAliasFullName();
}
return clsType.toString();
}
private String getClassTypeStr(ClassNode cls) {
if (cls.isEnum()) {
return "enum";
}
if (cls.getAccessFlags().isInterface()) {
return "interface";
}
return "class";
}
}
package jadx.core.codegen.json;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import jadx.api.JadxArgs;
import jadx.core.codegen.json.mapping.JsonClsMapping;
import jadx.core.codegen.json.mapping.JsonFieldMapping;
import jadx.core.codegen.json.mapping.JsonMapping;
import jadx.core.codegen.json.mapping.JsonMthMapping;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
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.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
public class JsonMappingGen {
private static final Logger LOG = LoggerFactory.getLogger(JsonMappingGen.class);
private static final Gson GSON = new GsonBuilder()
.setPrettyPrinting()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES)
.disableHtmlEscaping()
.create();
public static void dump(RootNode root) {
JsonMapping mapping = new JsonMapping();
fillMapping(mapping, root);
JadxArgs args = root.getArgs();
File outDirSrc = args.getOutDirSrc().getAbsoluteFile();
File mappingFile = new File(outDirSrc, "mapping.json");
FileUtils.makeDirsForFile(mappingFile);
try (Writer writer = new FileWriter(mappingFile)) {
GSON.toJson(mapping, writer);
LOG.info("Save mappings to {}", mappingFile.getAbsolutePath());
} catch (Exception e) {
throw new JadxRuntimeException("Failed to save mapping json", e);
}
}
private static void fillMapping(JsonMapping mapping, RootNode root) {
List<ClassNode> classes = root.getClasses(true);
mapping.setClasses(new ArrayList<>(classes.size()));
for (ClassNode cls : classes) {
ClassInfo classInfo = cls.getClassInfo();
JsonClsMapping jsonCls = new JsonClsMapping();
jsonCls.setName(classInfo.getRawName());
jsonCls.setAlias(classInfo.getAliasFullName());
jsonCls.setInner(classInfo.isInner());
jsonCls.setJson(cls.getTopParentClass().getClassInfo().getAliasFullPath() + ".json");
if (classInfo.isInner()) {
jsonCls.setTopClass(cls.getTopParentClass().getClassInfo().getFullName());
}
addFields(cls, jsonCls);
addMethods(cls, jsonCls);
mapping.getClasses().add(jsonCls);
}
}
private static void addMethods(ClassNode cls, JsonClsMapping jsonCls) {
List<MethodNode> methods = cls.getMethods();
if (methods.isEmpty()) {
return;
}
jsonCls.setMethods(new ArrayList<>(methods.size()));
for (MethodNode method : methods) {
JsonMthMapping jsonMethod = new JsonMthMapping();
MethodInfo methodInfo = method.getMethodInfo();
jsonMethod.setSignature(methodInfo.getShortId());
jsonMethod.setName(methodInfo.getName());
jsonMethod.setAlias(methodInfo.getAlias());
jsonMethod.setOffset("0x" + Long.toHexString(method.getMethodCodeOffset()));
jsonCls.getMethods().add(jsonMethod);
}
}
private static void addFields(ClassNode cls, JsonClsMapping jsonCls) {
List<FieldNode> fields = cls.getFields();
if (fields.isEmpty()) {
return;
}
jsonCls.setFields(new ArrayList<>(fields.size()));
for (FieldNode field : fields) {
JsonFieldMapping jsonField = new JsonFieldMapping();
jsonField.setName(field.getName());
jsonField.setAlias(field.getAlias());
jsonCls.getFields().add(jsonField);
}
}
private JsonMappingGen() {
}
}
package jadx.core.codegen.json.cls;
import java.util.List;
import com.google.gson.annotations.SerializedName;
public class JsonClass extends JsonNode {
@SerializedName("package")
private String pkg;
private String type; // class, interface, enum
@SerializedName("extends")
private String superClass;
@SerializedName("implements")
private List<String> interfaces;
private String dex;
private List<JsonField> fields;
private List<JsonMethod> methods;
private List<JsonClass> innerClasses;
private List<String> imports;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getSuperClass() {
return superClass;
}
public void setSuperClass(String superClass) {
this.superClass = superClass;
}
public List<String> getInterfaces() {
return interfaces;
}
public void setInterfaces(List<String> interfaces) {
this.interfaces = interfaces;
}
public List<JsonField> getFields() {
return fields;
}
public void setFields(List<JsonField> fields) {
this.fields = fields;
}
public List<JsonMethod> getMethods() {
return methods;
}
public void setMethods(List<JsonMethod> methods) {
this.methods = methods;
}
public List<JsonClass> getInnerClasses() {
return innerClasses;
}
public void setInnerClasses(List<JsonClass> innerClasses) {
this.innerClasses = innerClasses;
}
public String getPkg() {
return pkg;
}
public void setPkg(String pkg) {
this.pkg = pkg;
}
public String getDex() {
return dex;
}
public void setDex(String dex) {
this.dex = dex;
}
public List<String> getImports() {
return imports;
}
public void setImports(List<String> imports) {
this.imports = imports;
}
}
package jadx.core.codegen.json.cls;
import org.jetbrains.annotations.Nullable;
public class JsonCodeLine {
private String code;
private String offset;
private Integer sourceLine;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getOffset() {
return offset;
}
public void setOffset(String offset) {
this.offset = offset;
}
public Integer getSourceLine() {
return sourceLine;
}
public void setSourceLine(@Nullable Integer sourceLine) {
this.sourceLine = sourceLine;
}
}
package jadx.core.codegen.json.cls;
public class JsonField extends JsonNode {
String type;
}
package jadx.core.codegen.json.cls;
import java.util.List;
public class JsonMethod extends JsonNode {
private String signature;
private String returnType;
private List<String> arguments;
private List<JsonCodeLine> lines;
private String offset;
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
public String getReturnType() {
return returnType;
}
public void setReturnType(String returnType) {
this.returnType = returnType;
}
public List<String> getArguments() {
return arguments;
}
public void setArguments(List<String> arguments) {
this.arguments = arguments;
}
public List<JsonCodeLine> getLines() {
return lines;
}
public void setLines(List<JsonCodeLine> lines) {
this.lines = lines;
}
public String getOffset() {
return offset;
}
public void setOffset(String offset) {
this.offset = offset;
}
}
package jadx.core.codegen.json.cls;
public class JsonNode {
private String name;
private String alias;
private String declaration;
private int accessFlags;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getDeclaration() {
return declaration;
}
public void setDeclaration(String declaration) {
this.declaration = declaration;
}
public int getAccessFlags() {
return accessFlags;
}
public void setAccessFlags(int accessFlags) {
this.accessFlags = accessFlags;
}
}
package jadx.core.codegen.json.mapping;
import java.util.List;
public class JsonClsMapping {
private String name;
private String alias;
private String json;
private boolean inner;
private String topClass;
private List<JsonFieldMapping> fields;
private List<JsonMthMapping> methods;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getJson() {
return json;
}
public void setJson(String json) {
this.json = json;
}
public boolean isInner() {
return inner;
}
public void setInner(boolean inner) {
this.inner = inner;
}
public String getTopClass() {
return topClass;
}
public void setTopClass(String topClass) {
this.topClass = topClass;
}
public List<JsonFieldMapping> getFields() {
return fields;
}
public void setFields(List<JsonFieldMapping> fields) {
this.fields = fields;
}
public List<JsonMthMapping> getMethods() {
return methods;
}
public void setMethods(List<JsonMthMapping> methods) {
this.methods = methods;
}
}
package jadx.core.codegen.json.mapping;
public class JsonFieldMapping {
private String name;
private String alias;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
}
package jadx.core.codegen.json.mapping;
import java.util.List;
public class JsonMapping {
private List<JsonClsMapping> classes;
public List<JsonClsMapping> getClasses() {
return classes;
}
public void setClasses(List<JsonClsMapping> classes) {
this.classes = classes;
}
}
package jadx.core.codegen.json.mapping;
public class JsonMthMapping {
private String signature;
private String name;
private String alias;
private String offset;
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getOffset() {
return offset;
}
public void setOffset(String offset) {
this.offset = offset;
}
}
...@@ -142,7 +142,7 @@ public class Deobfuscator { ...@@ -142,7 +142,7 @@ public class Deobfuscator {
} }
for (MethodInfo mth : o.getMethods()) { for (MethodInfo mth : o.getMethods()) {
if (aliasToUse == null) { if (aliasToUse == null) {
if (mth.isRenamed() && !mth.isAliasFromPreset()) { if (mth.hasAlias() && !mth.isAliasFromPreset()) {
mth.setAlias(String.format("mo%d%s", id, prepareNamePart(mth.getName()))); mth.setAlias(String.format("mo%d%s", id, prepareNamePart(mth.getName())));
} }
aliasToUse = mth.getAlias(); aliasToUse = mth.getAlias();
......
...@@ -201,6 +201,10 @@ public class AccessInfo { ...@@ -201,6 +201,10 @@ public class AccessInfo {
} }
} }
public int rawValue() {
return accFlags;
}
@Override @Override
public String toString() { public String toString() {
return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + rawString() + ')'; return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + rawString() + ')';
......
package jadx.core.dex.info; package jadx.core.dex.info;
import java.util.Objects;
import com.android.dex.FieldId; import com.android.dex.FieldId;
import jadx.core.codegen.TypeGen; import jadx.core.codegen.TypeGen;
...@@ -53,6 +55,10 @@ public final class FieldInfo { ...@@ -53,6 +55,10 @@ public final class FieldInfo {
this.alias = alias; this.alias = alias;
} }
public boolean hasAlias() {
return !Objects.equals(name, alias);
}
public String getFullId() { public String getFullId() {
return declClass.getFullName() + '.' + name + ':' + TypeGen.signature(type); return declClass.getFullName() + '.' + name + ':' + TypeGen.signature(type);
} }
......
...@@ -130,7 +130,7 @@ public final class MethodInfo { ...@@ -130,7 +130,7 @@ public final class MethodInfo {
this.alias = alias; this.alias = alias;
} }
public boolean isRenamed() { public boolean hasAlias() {
return !name.equals(alias); return !name.equals(alias);
} }
......
...@@ -667,6 +667,10 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode { ...@@ -667,6 +667,10 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
return mthInfo; return mthInfo;
} }
public long getMethodCodeOffset() {
return noCode ? 0 : methodData.getCodeOffset();
}
/** /**
* Stat method. * Stat method.
* Calculate instructions count as a measure of method size * Calculate instructions count as a measure of method size
......
package jadx.core.dex.visitors; package jadx.core.dex.visitors;
import jadx.core.codegen.json.JsonMappingGen;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
public class FallbackModeVisitor extends AbstractVisitor { public class FallbackModeVisitor extends AbstractVisitor {
@Override @Override
public void init(RootNode root) {
if (root.getArgs().isJsonOutput()) {
JsonMappingGen.dump(root);
}
}
@Override
public void visit(MethodNode mth) throws JadxException { public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode()) { if (mth.isNoCode()) {
return; return;
......
...@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable; ...@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.core.Consts; import jadx.core.Consts;
import jadx.core.codegen.json.JsonMappingGen;
import jadx.core.deobf.Deobfuscator; import jadx.core.deobf.Deobfuscator;
import jadx.core.deobf.NameMapper; import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
...@@ -51,6 +52,9 @@ public class RenameVisitor extends AbstractVisitor { ...@@ -51,6 +52,9 @@ public class RenameVisitor extends AbstractVisitor {
deobfuscator.savePresets(); deobfuscator.savePresets();
deobfuscator.clear(); deobfuscator.clear();
} }
if (args.isJsonOutput()) {
JsonMappingGen.dump(root);
}
} }
private static void checkClasses(Deobfuscator deobfuscator, RootNode root, JadxArgs args) { private static void checkClasses(Deobfuscator deobfuscator, RootNode root, JadxArgs args) {
......
...@@ -13,7 +13,7 @@ public class SaveCode { ...@@ -13,7 +13,7 @@ public class SaveCode {
private SaveCode() { private SaveCode() {
} }
public static void save(File dir, JadxArgs args, ClassNode cls) { public static void save(File dir, ClassNode cls) {
if (cls.contains(AFlag.DONT_GENERATE)) { if (cls.contains(AFlag.DONT_GENERATE)) {
return; return;
} }
...@@ -21,10 +21,24 @@ public class SaveCode { ...@@ -21,10 +21,24 @@ public class SaveCode {
if (clsCode == null) { if (clsCode == null) {
throw new JadxRuntimeException("Code not generated for class " + cls.getFullName()); throw new JadxRuntimeException("Code not generated for class " + cls.getFullName());
} }
String fileName = cls.getClassInfo().getAliasFullPath() + ".java"; if (clsCode == CodeWriter.EMPTY) {
if (args.isFallbackMode()) { return;
fileName += ".jadx";
} }
String fileName = cls.getClassInfo().getAliasFullPath() + getFileExtension(cls);
clsCode.save(dir, fileName); clsCode.save(dir, fileName);
} }
private static String getFileExtension(ClassNode cls) {
JadxArgs.OutputFormatEnum outputFormat = cls.root().getArgs().getOutputFormat();
switch (outputFormat) {
case JAVA:
return ".java";
case JSON:
return ".json";
default:
throw new JadxRuntimeException("Unknown output format: " + outputFormat);
}
}
} }
...@@ -6,6 +6,7 @@ import jadx.core.codegen.CodeWriter; ...@@ -6,6 +6,7 @@ import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode; import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.nodes.RenameReasonAttr; import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
public class CodeGenUtils { public class CodeGenUtils {
...@@ -27,6 +28,13 @@ public class CodeGenUtils { ...@@ -27,6 +28,13 @@ public class CodeGenUtils {
code.add(" */"); code.add(" */");
} }
public static void addSourceFileInfo(CodeWriter code, AttrNode node) {
SourceFileAttr sourceFileAttr = node.get(AType.SOURCE_FILE);
if (sourceFileAttr != null) {
code.startLine("/* compiled from: ").add(sourceFileAttr.getFileName()).add(" */");
}
}
private CodeGenUtils() { private CodeGenUtils() {
} }
} }
...@@ -210,11 +210,26 @@ public final class ImmutableList<E> implements List<E>, RandomAccess { ...@@ -210,11 +210,26 @@ public final class ImmutableList<E> implements List<E>, RandomAccess {
if (this == o) { if (this == o) {
return true; return true;
} }
if (o == null || getClass() != o.getClass()) { if (o instanceof ImmutableList) {
return false; ImmutableList<?> other = (ImmutableList<?>) o;
return Arrays.equals(arr, other.arr);
} }
ImmutableList<?> that = (ImmutableList<?>) o; if (o instanceof List) {
return Arrays.equals(arr, that.arr); List<?> other = (List<?>) o;
int size = size();
if (size != other.size()) {
return false;
}
for (int i = 0; i < size; i++) {
E e1 = arr[i];
Object e2 = other.get(i);
if (!Objects.equals(e1, e2)) {
return false;
}
}
return true;
}
return false;
} }
@Override @Override
......
...@@ -3,7 +3,9 @@ package jadx.core.utils; ...@@ -3,7 +3,9 @@ package jadx.core.utils;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
...@@ -154,6 +156,17 @@ public class Utils { ...@@ -154,6 +156,17 @@ public class Utils {
} }
} }
public static <T, R> List<R> collectionMap(Collection<T> list, Function<T, R> mapFunc) {
if (list == null || list.isEmpty()) {
return Collections.emptyList();
}
List<R> result = new ArrayList<>(list.size());
for (T t : list) {
result.add(mapFunc.apply(t));
}
return result;
}
public static <T> List<T> lockList(List<T> list) { public static <T> List<T> lockList(List<T> list) {
if (list.isEmpty()) { if (list.isEmpty()) {
return Collections.emptyList(); return Collections.emptyList();
......
...@@ -97,7 +97,7 @@ public class InputFile { ...@@ -97,7 +97,7 @@ public class InputFile {
} }
private void addDexFile(Path path) throws IOException { private void addDexFile(Path path) throws IOException {
addDexFile("", path); addDexFile(path.getFileName().toString(), path);
} }
private void addDexFile(String fileName, Path path) throws IOException { private void addDexFile(String fileName, Path path) throws IOException {
......
package jadx.tests.integration.others;
import java.util.List;
import org.junit.jupiter.api.Test;
import jadx.api.JadxArgs;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
public class TestJsonOutput extends IntegrationTest {
public static class TestCls {
private final String prefix = "list: ";
static {
System.out.println("test");
}
public void test(boolean b, List<String> list) {
if (b) {
System.out.println(prefix + list);
}
}
public static class Inner implements Runnable {
@Override
public void run() {
System.out.println("run");
}
}
}
@Test
public void test() {
disableCompilation();
args.setOutputFormat(JadxArgs.OutputFormatEnum.JSON);
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsString("\"offset\": \"0x"));
assertThat(code, containsOne("public static class Inner implements Runnable"));
}
@Test
public void testFallback() {
disableCompilation();
setFallback();
args.setOutputFormat(JadxArgs.OutputFormatEnum.JSON);
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsString("\"offset\": \"0x"));
assertThat(code, containsOne("public static class Inner implements java.lang.Runnable"));
}
}
...@@ -15,7 +15,6 @@ dependencies { ...@@ -15,7 +15,6 @@ dependencies {
compile(project(":jadx-cli")) compile(project(":jadx-cli"))
compile 'com.fifesoft:rsyntaxtextarea:3.0.2' compile 'com.fifesoft:rsyntaxtextarea:3.0.2'
compile 'com.google.code.gson:gson:2.8.5'
compile files('libs/jfontchooser-1.0.5.jar') compile files('libs/jfontchooser-1.0.5.jar')
compile 'hu.kazocsaba:image-viewer:1.2.3' compile 'hu.kazocsaba:image-viewer:1.2.3'
......
package jadx.gui.settings; package jadx.gui.settings;
import java.awt.Font; import java.awt.*;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Window;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -15,7 +12,7 @@ import java.util.Map; ...@@ -15,7 +12,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import javax.swing.JFrame; import javax.swing.*;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
...@@ -42,7 +39,9 @@ public class JadxSettings extends JadxCLIArgs { ...@@ -42,7 +39,9 @@ public class JadxSettings extends JadxCLIArgs {
private static final Font DEFAULT_FONT = new RSyntaxTextArea().getFont(); private static final Font DEFAULT_FONT = new RSyntaxTextArea().getFont();
static final Set<String> SKIP_FIELDS = new HashSet<>(Arrays.asList( static final Set<String> SKIP_FIELDS = new HashSet<>(Arrays.asList(
"files", "input", "outDir", "outDirSrc", "outDirRes", "verbose", "printVersion", "printHelp")); "files", "input", "outDir", "outDirSrc", "outDirRes", "outputFormat",
"verbose", "printVersion", "printHelp"));
private Path lastSaveProjectPath = USER_HOME; private Path lastSaveProjectPath = USER_HOME;
private Path lastOpenFilePath = USER_HOME; private Path lastOpenFilePath = USER_HOME;
private Path lastSaveFilePath = USER_HOME; private Path lastSaveFilePath = USER_HOME;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment