diff --git a/jadx-core/src/main/java/jadx/api/ICodeInfo.java b/jadx-core/src/main/java/jadx/api/ICodeInfo.java index 50ac1aa14..9f8b1a9cb 100644 --- a/jadx-core/src/main/java/jadx/api/ICodeInfo.java +++ b/jadx-core/src/main/java/jadx/api/ICodeInfo.java @@ -2,7 +2,12 @@ package jadx.api; import java.util.Map; +import jadx.api.impl.SimpleCodeInfo; + public interface ICodeInfo { + + ICodeInfo EMPTY = new SimpleCodeInfo(""); + String getCodeStr(); Map getLineMapping(); diff --git a/jadx-core/src/main/java/jadx/api/ResourceFileContent.java b/jadx-core/src/main/java/jadx/api/ResourceFileContent.java index 194c91b20..d5fefbf1b 100644 --- a/jadx-core/src/main/java/jadx/api/ResourceFileContent.java +++ b/jadx-core/src/main/java/jadx/api/ResourceFileContent.java @@ -1,12 +1,11 @@ package jadx.api; -import jadx.core.codegen.CodeWriter; import jadx.core.xmlgen.ResContainer; public class ResourceFileContent extends ResourceFile { - private final CodeWriter content; + private final ICodeInfo content; - public ResourceFileContent(String name, ResourceType type, CodeWriter content) { + public ResourceFileContent(String name, ResourceType type, ICodeInfo content) { super(null, name, type); this.content = content; } diff --git a/jadx-core/src/main/java/jadx/api/ResourcesLoader.java b/jadx-core/src/main/java/jadx/api/ResourcesLoader.java index 6aa99fc4c..0312342ba 100644 --- a/jadx-core/src/main/java/jadx/api/ResourcesLoader.java +++ b/jadx-core/src/main/java/jadx/api/ResourcesLoader.java @@ -16,6 +16,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.api.ResourceFile.ZipRef; +import jadx.api.impl.SimpleCodeInfo; import jadx.core.codegen.CodeWriter; import jadx.core.utils.Utils; import jadx.core.utils.android.Res9patchStreamDecoder; @@ -85,7 +86,7 @@ public final class ResourcesLoader { CodeWriter cw = new CodeWriter(); cw.add("Error decode ").add(rf.getType().toString().toLowerCase()); Utils.appendStackTrace(cw, e.getCause()); - return ResContainer.textResource(rf.getName(), cw); + return ResContainer.textResource(rf.getName(), cw.finish()); } } @@ -94,7 +95,7 @@ public final class ResourcesLoader { switch (rf.getType()) { case MANIFEST: case XML: - CodeWriter content = jadxRef.getXmlParser().parse(inputStream); + ICodeInfo content = jadxRef.getXmlParser().parse(inputStream); return ResContainer.textResource(rf.getName(), content); case ARSC: @@ -162,9 +163,9 @@ public final class ResourcesLoader { } } - public static CodeWriter loadToCodeWriter(InputStream is) throws IOException { + public static ICodeInfo loadToCodeWriter(InputStream is) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE); copyStream(is, baos); - return new CodeWriter(baos.toString("UTF-8")); + return new SimpleCodeInfo(baos.toString("UTF-8")); } } diff --git a/jadx-core/src/main/java/jadx/api/impl/SimpleCodeInfo.java b/jadx-core/src/main/java/jadx/api/impl/SimpleCodeInfo.java new file mode 100644 index 000000000..e6822a872 --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/impl/SimpleCodeInfo.java @@ -0,0 +1,48 @@ +package jadx.api.impl; + +import java.util.Collections; +import java.util.Map; + +import jadx.api.CodePosition; +import jadx.api.ICodeInfo; + +public class SimpleCodeInfo implements ICodeInfo { + + private final String code; + private final Map lineMapping; + private final Map annotations; + + public SimpleCodeInfo(String code) { + this(code, Collections.emptyMap(), Collections.emptyMap()); + } + + public SimpleCodeInfo(ICodeInfo codeInfo) { + this(codeInfo.getCodeStr(), codeInfo.getLineMapping(), codeInfo.getAnnotations()); + } + + public SimpleCodeInfo(String code, Map lineMapping, Map annotations) { + this.code = code; + this.lineMapping = lineMapping; + this.annotations = annotations; + } + + @Override + public String getCodeStr() { + return code; + } + + @Override + public Map getLineMapping() { + return lineMapping; + } + + @Override + public Map getAnnotations() { + return annotations; + } + + @Override + public String toString() { + return code; + } +} diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index 13141bc2d..ba8f9ce50 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -12,6 +12,7 @@ import java.util.Set; import com.android.dx.rop.code.AccessFlags; import com.google.common.collect.Streams; +import jadx.api.ICodeInfo; import jadx.api.JadxArgs; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; @@ -73,7 +74,7 @@ public class ClassGen { return cls; } - public CodeWriter makeClass() throws CodegenException { + public ICodeInfo makeClass() throws CodegenException { CodeWriter clsBody = new CodeWriter(); addClassCode(clsBody); diff --git a/jadx-core/src/main/java/jadx/core/codegen/CodeGen.java b/jadx-core/src/main/java/jadx/core/codegen/CodeGen.java index ebe4c8a1f..d055ee0ab 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/CodeGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/CodeGen.java @@ -4,6 +4,7 @@ import java.util.concurrent.Callable; import jadx.api.ICodeInfo; import jadx.api.JadxArgs; +import jadx.api.impl.SimpleCodeInfo; import jadx.core.codegen.json.JsonCodeGen; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.nodes.ClassNode; @@ -13,7 +14,7 @@ public class CodeGen { public static ICodeInfo generate(ClassNode cls) { if (cls.contains(AFlag.DONT_GENERATE)) { - return CodeWriter.EMPTY; + return ICodeInfo.EMPTY; } JadxArgs args = cls.root().getArgs(); switch (args.getOutputFormat()) { @@ -36,7 +37,7 @@ public class CodeGen { private static ICodeInfo generateJson(ClassNode cls) { JsonCodeGen codeGen = new JsonCodeGen(cls); String clsJson = wrapCodeGen(cls, codeGen::process); - return new CodeWriter(clsJson); + return new SimpleCodeInfo(clsJson); } private static R wrapCodeGen(ClassNode cls, Callable codeGenFunc) { diff --git a/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java b/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java index 42a2f8f64..5bca30bfa 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java +++ b/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java @@ -1,7 +1,5 @@ package jadx.core.codegen; -import java.io.File; -import java.io.PrintWriter; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -13,12 +11,11 @@ import org.slf4j.LoggerFactory; import jadx.api.CodePosition; import jadx.api.ICodeInfo; +import jadx.api.impl.SimpleCodeInfo; import jadx.core.dex.attributes.nodes.LineAttrNode; import jadx.core.utils.StringUtils; -import jadx.core.utils.files.FileUtils; -import jadx.core.utils.files.ZipSecurity; -public class CodeWriter implements ICodeInfo { +public class CodeWriter { private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class); public static final String NL = System.getProperty("line.separator"); @@ -35,8 +32,6 @@ 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; @@ -58,12 +53,6 @@ public class CodeWriter implements ICodeInfo { } } - // create filled instance (just string wrapper) - public CodeWriter(String code) { - this.buf = null; - this.code = code; - } - public CodeWriter startLine() { addLine(); addLineIndent(); @@ -244,11 +233,6 @@ public class CodeWriter implements ICodeInfo { return annotations.put(pos, obj); } - @Override - public Map getAnnotations() { - return annotations; - } - public void attachSourceLine(int sourceLine) { if (sourceLine == 0) { return; @@ -263,27 +247,12 @@ public class CodeWriter implements ICodeInfo { lineMap.put(decompiledLine, sourceLine); } - @Override - public Map getLineMapping() { - return lineMap; - } - - public CodeWriter finish() { + public ICodeInfo finish() { removeFirstEmptyLine(); - buf.trimToSize(); + processDefinitionAnnotations(); code = buf.toString(); buf = null; - - annotations.entrySet().removeIf(entry -> { - Object v = entry.getValue(); - if (v instanceof DefinitionWrapper) { - LineAttrNode l = ((DefinitionWrapper) v).getNode(); - l.setDecompiledLine(entry.getKey().getLine()); - return true; - } - return false; - }); - return this; + return new SimpleCodeInfo(code, lineMap, annotations); } private void removeFirstEmptyLine() { @@ -293,46 +262,33 @@ public class CodeWriter implements ICodeInfo { } } + private void processDefinitionAnnotations() { + if (!annotations.isEmpty()) { + annotations.entrySet().removeIf(entry -> { + Object v = entry.getValue(); + if (v instanceof DefinitionWrapper) { + LineAttrNode l = ((DefinitionWrapper) v).getNode(); + l.setDecompiledLine(entry.getKey().getLine()); + return true; + } + return false; + }); + } + } + public int bufLength() { return buf.length(); } - @Override public String getCodeStr() { if (code == null) { - throw new NullPointerException("Code not set"); + finish(); } return code; } @Override public String toString() { - return buf == null ? code : buf.toString(); - } - - public void save(File dir, String subDir, String fileName) { - if (!ZipSecurity.isValidZipEntryName(subDir) || !ZipSecurity.isValidZipEntryName(fileName)) { - return; - } - save(dir, new File(subDir, fileName).getPath()); - } - - public void save(File dir, String fileName) { - if (!ZipSecurity.isValidZipEntryName(fileName)) { - return; - } - save(new File(dir, fileName)); - } - - public void save(File file) { - if (code == null) { - finish(); - } - File outFile = FileUtils.prepareFile(file); - try (PrintWriter out = new PrintWriter(outFile, "UTF-8")) { - out.println(code); - } catch (Exception e) { - LOG.error("Save file error", e); - } + return code != null ? code : buf.toString(); } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java b/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java index cc05de0a2..47c04be57 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java @@ -13,6 +13,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import jadx.api.CodePosition; +import jadx.api.ICodeInfo; import jadx.api.JadxArgs; import jadx.core.codegen.ClassGen; import jadx.core.codegen.CodeWriter; @@ -85,7 +86,7 @@ public class JsonCodeGen { CodeGenUtils.addComments(cw, cls); classGen.insertDecompilationProblems(cw, cls); classGen.addClassDeclaration(cw); - jsonCls.setDeclaration(cw.finish().toString()); + jsonCls.setDeclaration(cw.getCodeStr()); addFields(cls, jsonCls, classGen); addMethods(cls, jsonCls, classGen); @@ -128,7 +129,7 @@ public class JsonCodeGen { CodeWriter cw = new CodeWriter(); classGen.addField(cw, field); - jsonField.setDeclaration(cw.finish().toString()); + jsonField.setDeclaration(cw.getCodeStr()); jsonField.setAccessFlags(field.getAccessFlags().rawValue()); jsonCls.getFields().add(jsonField); @@ -153,7 +154,7 @@ public class JsonCodeGen { MethodGen mthGen = new MethodGen(classGen, mth); CodeWriter cw = new CodeWriter(); mthGen.addDefinition(cw); - jsonMth.setDeclaration(cw.finish().toString()); + jsonMth.setDeclaration(cw.getCodeStr()); jsonMth.setAccessFlags(mth.getAccessFlags().rawValue()); jsonMth.setLines(fillMthCode(mth, mthGen)); jsonMth.setOffset("0x" + Long.toHexString(mth.getMethodCodeOffset())); @@ -166,14 +167,14 @@ public class JsonCodeGen { return Collections.emptyList(); } - CodeWriter code = new CodeWriter(); + CodeWriter cw = new CodeWriter(); try { - mthGen.addInstructions(code); + mthGen.addInstructions(cw); } catch (Exception e) { throw new JadxRuntimeException("Method generation error", e); } - code.finish(); - String codeStr = code.toString(); + ICodeInfo code = cw.finish(); + String codeStr = code.getCodeStr(); if (codeStr.isEmpty()) { return Collections.emptyList(); } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 87f505c6f..fc554a8a5 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -280,7 +280,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { String clsRawName = topParentClass.getRawName(); if (searchInCache) { ICodeInfo code = codeCache.get(clsRawName); - if (code != null) { + if (code != null && code != ICodeInfo.EMPTY) { return code; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java index 5b866a8e9..e5a944e74 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java @@ -116,7 +116,11 @@ public class DotGraphVisitor extends AbstractVisitor { + (useRegions ? ".regions" : "") + (rawInsn ? ".raw" : "") + ".dot"; - dot.save(dir, mth.getParentClass().getClassInfo().getAliasFullPath() + "_graphs", fileName); + File file = dir.toPath() + .resolve(mth.getParentClass().getClassInfo().getAliasFullPath() + "_graphs") + .resolve(fileName) + .toFile(); + SaveCode.save(dot.finish(), file); } private void processMethodRegion(MethodNode mth) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/SaveCode.java b/jadx-core/src/main/java/jadx/core/dex/visitors/SaveCode.java index 0c0e02c62..be0c9340a 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/SaveCode.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/SaveCode.java @@ -1,15 +1,21 @@ package jadx.core.dex.visitors; import java.io.File; +import java.io.PrintWriter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import jadx.api.ICodeInfo; import jadx.api.JadxArgs; -import jadx.core.codegen.CodeWriter; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.nodes.ClassNode; import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.core.utils.files.FileUtils; +import jadx.core.utils.files.ZipSecurity; public class SaveCode { + private static final Logger LOG = LoggerFactory.getLogger(SaveCode.class); private SaveCode() { } @@ -21,18 +27,35 @@ public class SaveCode { if (code == null) { throw new JadxRuntimeException("Code not generated for class " + cls.getFullName()); } - if (code == CodeWriter.EMPTY) { + if (code == ICodeInfo.EMPTY) { return; } - CodeWriter clsCode; - if (code instanceof CodeWriter) { - clsCode = (CodeWriter) code; - } else { - // TODO: move 'save' method from CodeWriter - clsCode = new CodeWriter(code.getCodeStr()); + String codeStr = code.getCodeStr(); + if (codeStr.isEmpty()) { + return; } String fileName = cls.getClassInfo().getAliasFullPath() + getFileExtension(cls); - clsCode.save(dir, fileName); + save(codeStr, dir, fileName); + } + + public static void save(String code, File dir, String fileName) { + if (!ZipSecurity.isValidZipEntryName(fileName)) { + return; + } + save(code, new File(dir, fileName)); + } + + public static void save(ICodeInfo codeInfo, File file) { + save(codeInfo.getCodeStr(), file); + } + + public static void save(String code, File file) { + File outFile = FileUtils.prepareFile(file); + try (PrintWriter out = new PrintWriter(outFile, "UTF-8")) { + out.println(code); + } catch (Exception e) { + LOG.error("Save file error", e); + } } private static String getFileExtension(ClassNode cls) { diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java index 6987981bf..3a1adc0c5 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java @@ -12,6 +12,7 @@ import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jadx.api.ICodeInfo; import jadx.api.ResourcesLoader; import jadx.core.codegen.CodeWriter; import jadx.core.dex.info.ConstStorage; @@ -80,7 +81,7 @@ public class BinaryXMLParser extends CommonBinaryParser { } } - public synchronized CodeWriter parse(InputStream inputStream) throws IOException { + public synchronized ICodeInfo parse(InputStream inputStream) throws IOException { is = new ParserStream(inputStream); if (!isBinaryXml()) { return ResourcesLoader.loadToCodeWriter(inputStream); @@ -91,9 +92,8 @@ public class BinaryXMLParser extends CommonBinaryParser { writer.add(""); firstElement = true; decode(); - writer.finish(); nsMap = null; - return writer; + return writer.finish(); } private boolean isBinaryXml() throws IOException { diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResContainer.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResContainer.java index b3b70fb87..1c189904b 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResContainer.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResContainer.java @@ -7,6 +7,7 @@ import java.util.Objects; import org.jetbrains.annotations.NotNull; +import jadx.api.ICodeInfo; import jadx.api.ResourceFile; import jadx.core.codegen.CodeWriter; @@ -21,7 +22,7 @@ public class ResContainer implements Comparable { private final Object data; private final List subFiles; - public static ResContainer textResource(String name, CodeWriter content) { + public static ResContainer textResource(String name, ICodeInfo content) { return new ResContainer(name, Collections.emptyList(), content, DataType.TEXT); } @@ -60,8 +61,8 @@ public class ResContainer implements Comparable { return dataType; } - public CodeWriter getText() { - return (CodeWriter) data; + public ICodeInfo getText() { + return (ICodeInfo) data; } public byte[] getDecodedData() { diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResXmlGen.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResXmlGen.java index a9422d420..2ef3c6360 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResXmlGen.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResXmlGen.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import jadx.api.ICodeInfo; import jadx.core.codegen.CodeWriter; import jadx.core.utils.StringUtils; import jadx.core.xmlgen.entry.RawNamedValue; @@ -57,8 +58,8 @@ public class ResXmlGen { content.decIndent(); content.startLine(""); - content.finish(); - files.add(ResContainer.textResource(fileName, content)); + ICodeInfo codeInfo = content.finish(); + files.add(ResContainer.textResource(fileName, codeInfo)); } Collections.sort(files); return files; diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResourcesSaver.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResourcesSaver.java index ec2389b76..617b17300 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResourcesSaver.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResourcesSaver.java @@ -9,7 +9,7 @@ import org.slf4j.LoggerFactory; import jadx.api.ResourceFile; import jadx.api.ResourcesLoader; -import jadx.core.codegen.CodeWriter; +import jadx.core.dex.visitors.SaveCode; import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.files.FileUtils; @@ -58,8 +58,7 @@ public class ResourcesSaver implements Runnable { switch (rc.getDataType()) { case TEXT: case RES_TABLE: - CodeWriter cw = rc.getText(); - cw.save(outFile); + SaveCode.save(rc.getText(), outFile); return; case DECODED_DATA: diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java index 608517f20..291f0f58e 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java @@ -16,7 +16,7 @@ import jadx.api.ResourceFile; import jadx.api.ResourceFileContent; import jadx.api.ResourceType; import jadx.api.ResourcesLoader; -import jadx.core.codegen.CodeWriter; +import jadx.api.impl.SimpleCodeInfo; import jadx.core.xmlgen.ResContainer; import jadx.gui.utils.NLS; import jadx.gui.utils.OverlayIcon; @@ -146,17 +146,17 @@ public class JResource extends JLoadableNode implements Comparable { try { return ResourcesLoader.decodeStream(rc.getResLink(), (size, is) -> { if (size > 10 * 1024 * 1024L) { - return new CodeWriter("File too large for view"); + return new SimpleCodeInfo("File too large for view"); } return ResourcesLoader.loadToCodeWriter(is); }); } catch (Exception e) { - return new CodeWriter("Failed to load resource file: \n" + jadx.core.utils.Utils.getStackTrace(e)); + return new SimpleCodeInfo("Failed to load resource file: \n" + jadx.core.utils.Utils.getStackTrace(e)); } case DECODED_DATA: default: - return new CodeWriter("Unexpected resource type: " + rc); + return new SimpleCodeInfo("Unexpected resource type: " + rc); } } @@ -164,8 +164,8 @@ public class JResource extends JLoadableNode implements Comparable { String resName = rc.getName(); String[] path = resName.split("/"); String resShortName = path.length == 0 ? resName : path[path.length - 1]; - CodeWriter cw = rc.getText(); - ResourceFileContent fileContent = new ResourceFileContent(resShortName, ResourceType.XML, cw); + ICodeInfo code = rc.getText(); + ResourceFileContent fileContent = new ResourceFileContent(resShortName, ResourceType.XML, code); addPath(path, root, new JResource(fileContent, resName, resShortName, JResType.FILE)); for (ResContainer subFile : rc.getSubFiles()) {