feat: add options to JadxArgs to change code new line and indent (#1945, #1948)

This commit is contained in:
Skylot
2024-04-08 21:51:24 +01:00
parent 41d6b0018e
commit 6e8affcbdc
36 changed files with 194 additions and 172 deletions
@@ -8,8 +8,6 @@ import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
public interface ICodeWriter {
String NL = System.getProperty("line.separator");
String INDENT_STR = " ";
boolean isMetadataSupported();
@@ -42,6 +42,9 @@ public class JadxArgs implements Closeable {
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
public static final String DEFAULT_NEW_LINE_STR = System.lineSeparator();
public static final String DEFAULT_INDENT_STR = " ";
public static final String DEFAULT_OUT_DIR = "jadx-output";
public static final String DEFAULT_SRC_DIR = "sources";
public static final String DEFAULT_RES_DIR = "resources";
@@ -144,6 +147,10 @@ public class JadxArgs implements Closeable {
private ICodeData codeData;
private String codeNewLineStr = DEFAULT_NEW_LINE_STR;
private String codeIndentStr = DEFAULT_INDENT_STR;
private CommentsLevel commentsLevel = CommentsLevel.INFO;
private IntegerFormat integerFormat = IntegerFormat.AUTO;
@@ -622,6 +629,22 @@ public class JadxArgs implements Closeable {
this.codeData = codeData;
}
public String getCodeIndentStr() {
return codeIndentStr;
}
public void setCodeIndentStr(String codeIndentStr) {
this.codeIndentStr = codeIndentStr;
}
public String getCodeNewLineStr() {
return codeNewLineStr;
}
public void setCodeNewLineStr(String codeNewLineStr) {
this.codeNewLineStr = codeNewLineStr;
}
public CommentsLevel getCommentsLevel() {
return commentsLevel;
}
@@ -21,9 +21,6 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
private Map<Integer, ICodeAnnotation> annotations = Collections.emptyMap();
private Map<Integer, Integer> lineMap = Collections.emptyMap();
public AnnotatedCodeWriter() {
}
public AnnotatedCodeWriter(JadxArgs args) {
super(args);
}
@@ -35,9 +32,9 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
@Override
public AnnotatedCodeWriter addMultiLine(String str) {
if (str.contains(NL)) {
buf.append(str.replace(NL, NL + indentStr));
line += StringUtils.countMatches(str, NL);
if (str.contains(newLineStr)) {
buf.append(str.replace(newLineStr, newLineStr + indentStr));
line += StringUtils.countMatches(str, newLineStr);
offset = 0;
} else {
buf.append(str);
@@ -84,7 +81,7 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
@Override
protected void addLine() {
buf.append(NL);
buf.append(newLineStr);
line++;
offset = 0;
}
@@ -14,38 +14,39 @@ import jadx.api.metadata.ICodeNodeRef;
import jadx.core.utils.Utils;
/**
* CodeWriter implementation without meta information support (only strings builder)
* CodeWriter implementation without meta information support
*/
public class SimpleCodeWriter implements ICodeWriter {
private static final Logger LOG = LoggerFactory.getLogger(SimpleCodeWriter.class);
private static final String[] INDENT_CACHE = {
"",
INDENT_STR,
INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
};
protected StringBuilder buf = new StringBuilder();
protected String indentStr = "";
protected int indent = 0;
private final boolean insertLineNumbers;
public SimpleCodeWriter() {
this.insertLineNumbers = false;
}
protected final boolean insertLineNumbers;
protected final String singleIndentStr;
protected final String newLineStr;
public SimpleCodeWriter(JadxArgs args) {
this.insertLineNumbers = args.isInsertDebugLines();
this.singleIndentStr = args.getCodeIndentStr();
this.newLineStr = args.getCodeNewLineStr();
if (insertLineNumbers) {
incIndent(3);
add(indentStr);
}
}
/**
* Constructor with JadxArgs should be used.
*/
@Deprecated
public SimpleCodeWriter() {
this.insertLineNumbers = false;
this.singleIndentStr = JadxArgs.DEFAULT_INDENT_STR;
this.newLineStr = JadxArgs.DEFAULT_INDENT_STR;
}
@Override
public boolean isMetadataSupported() {
return false;
@@ -96,8 +97,8 @@ public class SimpleCodeWriter implements ICodeWriter {
@Override
public SimpleCodeWriter addMultiLine(String str) {
if (str.contains(NL)) {
buf.append(str.replace(NL, NL + indentStr));
if (str.contains(newLineStr)) {
buf.append(str.replace(newLineStr, newLineStr + indentStr));
} else {
buf.append(str);
}
@@ -130,12 +131,12 @@ public class SimpleCodeWriter implements ICodeWriter {
@Override
public SimpleCodeWriter addIndent() {
add(INDENT_STR);
add(singleIndentStr);
return this;
}
protected void addLine() {
buf.append(NL);
buf.append(newLineStr);
}
protected SimpleCodeWriter addLineIndent() {
@@ -144,12 +145,7 @@ public class SimpleCodeWriter implements ICodeWriter {
}
private void updateIndent() {
int curIndent = indent;
if (curIndent < INDENT_CACHE.length) {
this.indentStr = INDENT_CACHE[curIndent];
} else {
this.indentStr = Utils.strRepeat(INDENT_STR, curIndent);
}
this.indentStr = Utils.strRepeat(singleIndentStr, indent);
}
@Override
@@ -219,17 +215,17 @@ public class SimpleCodeWriter implements ICodeWriter {
@Override
public ICodeInfo finish() {
removeFirstEmptyLine();
String code = buf.toString();
String code = getStringWithoutFirstEmptyLine();
buf = null;
return new SimpleCodeInfo(code);
}
protected void removeFirstEmptyLine() {
int len = NL.length();
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
buf.delete(0, len);
private String getStringWithoutFirstEmptyLine() {
int len = newLineStr.length();
if (buf.length() > len && buf.substring(0, len).equals(newLineStr)) {
return buf.substring(len);
}
return buf.toString();
}
@Override
@@ -1,7 +1,5 @@
package jadx.api.utils;
import jadx.api.ICodeWriter;
public class CodeUtils {
public static String getLineForPos(String code, int pos) {
@@ -11,18 +9,32 @@ public class CodeUtils {
}
public static int getLineStartForPos(String code, int pos) {
String newLine = ICodeWriter.NL;
int start = code.lastIndexOf(newLine, pos);
return start == -1 ? 0 : start + newLine.length();
int start = getNewLinePosBefore(code, pos);
return start == -1 ? 0 : start + 1;
}
public static int getLineEndForPos(String code, int pos) {
int end = code.indexOf(ICodeWriter.NL, pos);
int end = getNewLinePosAfter(code, pos);
return end == -1 ? code.length() : end;
}
public static int getLineNumForPos(String code, int pos) {
String newLine = ICodeWriter.NL;
public static int getNewLinePosAfter(String code, int startPos) {
int pos = code.indexOf('\n', startPos);
if (pos != -1) {
// check for '\r\n'
int prev = pos - 1;
if (code.charAt(prev) == '\r') {
return prev;
}
}
return pos;
}
public static int getNewLinePosBefore(String code, int startPos) {
return code.lastIndexOf('\n', startPos);
}
public static int getLineNumForPos(String code, int pos, String newLine) {
int newLineLen = newLine.length();
int line = 1;
int prev = 0;
@@ -86,7 +86,7 @@ public class JsonCodeGen {
jsonCls.setInterfaces(Utils.collectionMap(cls.getInterfaces(), clsType -> getTypeAlias(classGen, clsType)));
}
ICodeWriter cw = new SimpleCodeWriter();
ICodeWriter cw = new SimpleCodeWriter(args);
CodeGenUtils.addErrorsAndComments(cw, cls);
classGen.addClassDeclaration(cw);
jsonCls.setDeclaration(cw.getCodeStr());
@@ -130,7 +130,7 @@ public class JsonCodeGen {
jsonField.setAlias(field.getAlias());
}
ICodeWriter cw = new SimpleCodeWriter();
ICodeWriter cw = new SimpleCodeWriter(args);
classGen.addField(cw, field);
jsonField.setDeclaration(cw.getCodeStr());
jsonField.setAccessFlags(field.getAccessFlags().rawValue());
@@ -154,7 +154,7 @@ public class JsonCodeGen {
jsonMth.setArguments(Utils.collectionMap(mth.getMethodInfo().getArgumentsTypes(), clsType -> getTypeAlias(classGen, clsType)));
MethodGen mthGen = new MethodGen(classGen, mth);
ICodeWriter cw = new AnnotatedCodeWriter();
ICodeWriter cw = new AnnotatedCodeWriter(args);
mthGen.addDefinition(cw);
jsonMth.setDeclaration(cw.getCodeStr());
jsonMth.setAccessFlags(mth.getAccessFlags().rawValue());
@@ -181,7 +181,7 @@ public class JsonCodeGen {
return Collections.emptyList();
}
String[] lines = codeStr.split(ICodeWriter.NL);
String[] lines = codeStr.split(args.getCodeNewLineStr());
Map<Integer, Integer> lineMapping = code.getCodeMetadata().getLineMapping();
ICodeMetadata metadata = code.getCodeMetadata();
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
@@ -189,7 +189,7 @@ public class JsonCodeGen {
int linesCount = lines.length;
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
int lineStartPos = 0;
int newLineLen = ICodeWriter.NL.length();
int newLineLen = args.getCodeNewLineStr().length();
for (int i = 0; i < linesCount; i++) {
String codeLine = lines[i];
int line = i + 2;
@@ -208,7 +208,7 @@ public class JsonCodeGen {
}
private String getTypeAlias(ClassGen classGen, ArgType clsType) {
ICodeWriter code = new SimpleCodeWriter();
ICodeWriter code = new SimpleCodeWriter(args);
classGen.useType(code, clsType);
return code.getCodeStr();
}
@@ -4,7 +4,6 @@ import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import jadx.api.ICodeWriter;
import jadx.core.utils.Utils;
public class JadxError implements Comparable<JadxError> {
@@ -55,7 +54,7 @@ public class JadxError implements Comparable<JadxError> {
str.append(cause.getClass());
str.append(':');
str.append(cause.getMessage());
str.append(ICodeWriter.NL);
str.append('\n');
str.append(Utils.getStackTrace(cause));
}
return str.toString();
@@ -2,7 +2,6 @@ package jadx.core.dex.attributes.nodes;
import java.util.List;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.data.ILocalVar;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
@@ -26,6 +25,6 @@ public class LocalVarsDebugInfoAttr implements IJadxAttribute {
@Override
public String toString() {
return "Debug Info:" + ICodeWriter.NL + " " + Utils.listToString(localVars, ICodeWriter.NL + " ");
return "Debug Info:\n " + Utils.listToString(localVars, "\n ");
}
}
@@ -1,7 +1,6 @@
package jadx.core.dex.attributes.nodes;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.data.CommentStyle;
import jadx.core.codegen.utils.CodeComment;
import jadx.core.dex.attributes.AFlag;
@@ -39,7 +38,7 @@ public abstract class NotificationAttrNode extends LineAttrNode implements ICode
}
public void addWarnComment(String warn, Throwable exc) {
String commentStr = warn + ICodeWriter.NL + Utils.getStackTrace(exc);
String commentStr = warn + root().getArgs().getCodeNewLineStr() + Utils.getStackTrace(exc);
JadxCommentsAttr.add(this, CommentsLevel.WARN, commentStr);
}
@@ -3,7 +3,6 @@ package jadx.core.dex.attributes.nodes;
import java.util.ArrayList;
import java.util.List;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.PhiInsn;
@@ -33,7 +32,7 @@ public class PhiListAttr implements IJadxAttribute {
}
}
for (PhiInsn phiInsn : list) {
sb.append(ICodeWriter.NL).append(" ").append(phiInsn);
sb.append('\n').append(" ").append(phiInsn);
}
return sb.toString();
}
@@ -5,7 +5,6 @@ import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeWriter;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
@@ -111,20 +110,20 @@ public class SwitchInsn extends TargetInsnNode {
int[] keys = switchData.getKeys();
if (targetBlocks != null) {
for (int i = 0; i < size; i++) {
sb.append(ICodeWriter.NL);
sb.append('\n');
sb.append(" case ").append(keys[i]).append(": goto ").append(targetBlocks[i]);
}
if (def != -1) {
sb.append(ICodeWriter.NL).append(" default: goto ").append(defTargetBlock);
sb.append('\n').append(" default: goto ").append(defTargetBlock);
}
} else {
int[] targets = switchData.getTargets();
for (int i = 0; i < size; i++) {
sb.append(ICodeWriter.NL);
sb.append('\n');
sb.append(" case ").append(keys[i]).append(": goto ").append(InsnUtils.formatOffset(targets[i]));
}
if (def != -1) {
sb.append(ICodeWriter.NL);
sb.append('\n');
sb.append(" default: goto ").append(InsnUtils.formatOffset(def));
}
}
@@ -18,10 +18,10 @@ import org.slf4j.LoggerFactory;
import jadx.api.DecompilationMode;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.JavaClass;
import jadx.api.impl.SimpleCodeInfo;
import jadx.api.impl.SimpleCodeWriter;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.api.plugins.input.data.IClassData;
@@ -831,33 +831,29 @@ public class ClassNode extends NotificationAttrNode
public String getDisassembledCode() {
if (smali == null) {
StringBuilder sb = new StringBuilder();
getDisassembledCode(sb);
sb.append(ICodeWriter.NL);
SimpleCodeWriter code = new SimpleCodeWriter(root.getArgs());
getDisassembledCode(code);
Set<ClassNode> allInlinedClasses = new LinkedHashSet<>();
getInnerAndInlinedClassesRecursive(allInlinedClasses);
for (ClassNode innerClass : allInlinedClasses) {
innerClass.getDisassembledCode(sb);
sb.append(ICodeWriter.NL);
innerClass.getDisassembledCode(code);
}
smali = sb.toString();
smali = code.finish().getCodeStr();
}
return smali;
}
protected void getDisassembledCode(StringBuilder sb) {
protected void getDisassembledCode(SimpleCodeWriter code) {
if (clsData == null) {
sb.append(String.format("###### Class %s is created by jadx", getFullName()));
code.startLine(String.format("###### Class %s is created by jadx", getFullName()));
return;
}
sb.append(String.format("###### Class %s (%s)", getFullName(), getRawName()));
sb.append(ICodeWriter.NL);
code.startLine(String.format("###### Class %s (%s)", getFullName(), getRawName()));
try {
sb.append(clsData.getDisassembledCode());
code.startLine(clsData.getDisassembledCode());
} catch (Throwable e) {
sb.append("Failed to disassemble class:");
sb.append(ICodeWriter.NL);
sb.append(Utils.getStackTrace(e));
code.startLine("Failed to disassemble class:");
code.startLine(Utils.getStackTrace(e));
}
}
@@ -10,7 +10,6 @@ import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.insns.InsnData;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
@@ -563,9 +562,9 @@ public class InsnNode extends LineAttrNode {
return false;
}
// wrap args
String separator = ICodeWriter.NL + " ";
String separator = "\n ";
sb.append(separator).append(Utils.listToString(arguments, separator));
sb.append(ICodeWriter.NL);
sb.append('\n');
return true;
}
@@ -96,7 +96,7 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
for (CaseInfo caseInfo : cases) {
List<String> keyStrings = Utils.collectionMap(caseInfo.getKeys(),
k -> k == DEFAULT_CASE_KEY ? "default" : k.toString());
sb.append(ICodeWriter.NL).append(" case ")
sb.append("\n case ")
.append(Utils.listToString(keyStrings))
.append(" -> ").append(caseInfo.getContainer());
}
@@ -319,8 +319,7 @@ public class DotGraphVisitor extends AbstractVisitor {
.replace("\"", "\\\"")
.replace("-", "\\-")
.replace("|", "\\|")
.replace(ICodeWriter.NL, NL)
.replace("\n", NL);
.replaceAll("\\R", NL);
}
}
}
@@ -8,7 +8,6 @@ import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeWriter;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.MethodInfo;
@@ -290,10 +289,10 @@ public class MethodInvokeVisitor extends AbstractVisitor {
if (Consts.DEBUG_OVERLOADED_CASTS) {
// TODO: try to minimize casts count
parentMth.addDebugComment("Failed to find minimal casts for resolve overloaded methods, cast all args instead"
+ ICodeWriter.NL + " method: " + mthDetails
+ ICodeWriter.NL + " arg types: " + compilerVarTypes
+ ICodeWriter.NL + " candidates:"
+ ICodeWriter.NL + " " + Utils.listToString(overloadedMethods, ICodeWriter.NL + " "));
+ "\n method: " + mthDetails
+ "\n arg types: " + compilerVarTypes
+ "\n candidates:"
+ "\n " + Utils.listToString(overloadedMethods, "\n "));
}
// not resolved -> cast all args
return mthDetails.getArgTypes();
@@ -3,7 +3,6 @@ package jadx.core.utils;
import java.util.ArrayList;
import java.util.List;
import jadx.api.ICodeWriter;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.PhiListAttr;
@@ -96,13 +95,13 @@ public class DebugChecks {
if (resArg == reg) {
if (sVar.getAssignInsn() != insn) {
throw new JadxRuntimeException("Incorrect assign in ssa var: " + sVar
+ ICodeWriter.NL + " expected: " + sVar.getAssignInsn()
+ ICodeWriter.NL + " got: " + insn);
+ "\n expected: " + sVar.getAssignInsn()
+ "\n got: " + insn);
}
} else {
if (!Utils.containsInListByRef(useList, reg)) {
throw new JadxRuntimeException("Incorrect use list in ssa var: " + sVar + ", register not listed."
+ ICodeWriter.NL + " insn: " + insn);
+ "\n insn: " + insn);
}
}
for (RegisterArg useArg : useList) {
@@ -177,7 +176,7 @@ public class DebugChecks {
BlockNode parentInsnBlock = BlockUtils.getBlockByInsn(mth, parentInsn);
if (parentInsnBlock == null) {
throw new JadxRuntimeException("Parent insn not found in blocks tree for: " + reg
+ ICodeWriter.NL + " insn: " + parentInsn);
+ "\n insn: " + parentInsn);
}
}
}
@@ -136,7 +136,7 @@ public class DebugUtils {
ICodeWriter cw = new SimpleCodeWriter();
cw.startLine('|').add(mth.toString());
printRegion(mth, region, cw, "| ", printInsns);
LOG.debug("{}{}", ICodeWriter.NL, cw.finish().getCodeStr());
LOG.debug("{}{}", '\n', cw.finish().getCodeStr());
}
private static void printRegion(MethodNode mth, IRegion region, ICodeWriter cw, String indent, boolean printInsns) {
@@ -182,7 +182,7 @@ public class DebugUtils {
ig.makeInsn(insn, code);
String codeStr = code.getCodeStr();
List<String> insnStrings = Stream.of(codeStr.split(ICodeWriter.NL))
List<String> insnStrings = Stream.of(codeStr.split("\\R"))
.filter(StringUtils::notBlank)
.map(s -> "|> " + s)
.collect(Collectors.toList());
@@ -204,7 +204,7 @@ public class DebugUtils {
private static void printWithAttributes(ICodeWriter cw, String indent, String codeStr, IAttributeNode attrNode) {
String str = attrNode.isAttrStorageEmpty() ? codeStr : codeStr + ' ' + attrNode.getAttributesString();
List<String> attrStrings = Stream.of(str.split(ICodeWriter.NL))
List<String> attrStrings = Stream.of(str.split("\\R"))
.filter(StringUtils::notBlank)
.collect(Collectors.toList());
Iterator<String> it = attrStrings.iterator();
@@ -7,7 +7,6 @@ import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeWriter;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.InsnType;
@@ -147,9 +146,9 @@ public class InsnRemover {
return;
}
throw new JadxRuntimeException("Can't remove SSA var: " + ssaVar + ", still in use, count: " + useCount
+ ", list:" + ICodeWriter.NL + " " + ssaVar.getUseList().stream()
+ ", list:\n " + ssaVar.getUseList().stream()
.map(arg -> arg + " from " + arg.getParentInsn())
.collect(Collectors.joining(ICodeWriter.NL + " ")));
.collect(Collectors.joining("\n ")));
}
public static void unbindArgUsage(@Nullable MethodNode mth, InsnArg arg) {
@@ -183,9 +182,9 @@ public class InsnRemover {
}
if (!found && Consts.DEBUG_WITH_ERRORS) {
throw new JadxRuntimeException("Can't remove insn:"
+ ICodeWriter.NL + " " + rem
+ ICodeWriter.NL + " not found in list:"
+ ICodeWriter.NL + " " + Utils.listToString(insns, ICodeWriter.NL + " "));
+ "\n " + rem
+ "\n not found in list:"
+ "\n " + Utils.listToString(insns, "\n "));
}
}
}
@@ -55,7 +55,14 @@ public class Utils {
return 'L' + obj.replace('.', '/') + ';';
}
@SuppressWarnings("StringRepeatCanBeUsed")
public static String strRepeat(String str, int count) {
if (count < 1) {
return "";
}
if (count == 1) {
return str;
}
StringBuilder sb = new StringBuilder(str.length() * count);
for (int i = 0; i < count; i++) {
sb.append(str);
@@ -31,7 +31,7 @@ public class ResProtoParser extends CommonProtoParser implements IResParser {
ValuesParser vp = new ValuesParser(new BinaryXMLStrings(), resStorage.getResourcesNames());
ResXmlGen resGen = new ResXmlGen(resStorage, vp);
ICodeInfo content = XmlGenUtils.makeXmlDump(root.makeCodeWriter(), resStorage);
List<ResContainer> xmlFiles = resGen.makeResourcesXml();
List<ResContainer> xmlFiles = resGen.makeResourcesXml(root.getArgs());
return ResContainer.resourceTable("res", xmlFiles, content);
}
@@ -103,7 +103,7 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
ResXmlGen resGen = new ResXmlGen(resStorage, vp);
ICodeInfo content = XmlGenUtils.makeXmlDump(root.makeCodeWriter(), resStorage);
List<ResContainer> xmlFiles = resGen.makeResourcesXml();
List<ResContainer> xmlFiles = resGen.makeResourcesXml(root.getArgs());
return ResContainer.resourceTable("res", xmlFiles, content);
}
@@ -11,6 +11,7 @@ import java.util.Set;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.impl.SimpleCodeWriter;
import jadx.core.utils.StringUtils;
import jadx.core.xmlgen.entry.ProtoValue;
@@ -48,7 +49,7 @@ public class ResXmlGen {
this.vp = vp;
}
public List<ResContainer> makeResourcesXml() {
public List<ResContainer> makeResourcesXml(JadxArgs args) {
Map<String, ICodeWriter> contMap = new HashMap<>();
for (ResourceEntry ri : resStorage.getResources()) {
if (SKIP_RES_TYPES.contains(ri.getTypeName())) {
@@ -57,7 +58,7 @@ public class ResXmlGen {
String fn = getFileName(ri);
ICodeWriter cw = contMap.get(fn);
if (cw == null) {
cw = new SimpleCodeWriter();
cw = new SimpleCodeWriter(args);
cw.add("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
cw.startLine("<resources>");
cw.incIndent();
@@ -4,8 +4,10 @@ import java.util.ArrayList;
import java.util.List;
import org.assertj.core.util.Lists;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jadx.api.JadxArgs;
import jadx.core.xmlgen.entry.RawNamedValue;
import jadx.core.xmlgen.entry.RawValue;
import jadx.core.xmlgen.entry.ResourceEntry;
@@ -14,6 +16,12 @@ import jadx.core.xmlgen.entry.ValuesParser;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ResXmlGenTest {
private final JadxArgs args = new JadxArgs();
@BeforeEach
void init() {
args.setCodeNewLineStr("\n");
}
@Test
void testSimpleAttr() {
@@ -24,15 +32,16 @@ class ResXmlGenTest {
ValuesParser vp = new ValuesParser(null, resStorage.getResourcesNames());
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
List<ResContainer> files = resXmlGen.makeResourcesXml();
List<ResContainer> files = resXmlGen.makeResourcesXml(args);
assertEquals(1, files.size());
assertEquals("res/values/attrs.xml", files.get(0).getName());
String input = files.get(0).getText().toString();
assertEquals("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<resources>\n"
+ " <attr name=\"size\" format=\"dimension\">\n"
+ " </attr>\n"
+ "</resources>", adaptLineEndings(files.get(0).getText().toString()));
+ "</resources>", input);
}
@Test
@@ -45,16 +54,17 @@ class ResXmlGenTest {
ValuesParser vp = new ValuesParser(null, resStorage.getResourcesNames());
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
List<ResContainer> files = resXmlGen.makeResourcesXml();
List<ResContainer> files = resXmlGen.makeResourcesXml(args);
assertEquals(1, files.size());
assertEquals("res/values/attrs.xml", files.get(0).getName());
String input = files.get(0).getText().toString();
assertEquals("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<resources>\n"
+ " <attr name=\"size\">\n"
+ " <enum name=\"android:string.aerr_wait\" value=\"1\" />\n"
+ " </attr>\n"
+ "</resources>", adaptLineEndings(files.get(0).getText().toString()));
+ "</resources>", input);
}
@Test
@@ -67,16 +77,17 @@ class ResXmlGenTest {
ValuesParser vp = new ValuesParser(null, resStorage.getResourcesNames());
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
List<ResContainer> files = resXmlGen.makeResourcesXml();
List<ResContainer> files = resXmlGen.makeResourcesXml(args);
assertEquals(1, files.size());
assertEquals("res/values/attrs.xml", files.get(0).getName());
String input = files.get(0).getText().toString();
assertEquals("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<resources>\n"
+ " <attr name=\"size\">\n"
+ " <flag name=\"android:string.aerr_wait\" value=\"1\" />\n"
+ " </attr>\n"
+ "</resources>", adaptLineEndings(files.get(0).getText().toString()));
+ "</resources>", input);
}
@Test
@@ -89,15 +100,16 @@ class ResXmlGenTest {
ValuesParser vp = new ValuesParser(null, resStorage.getResourcesNames());
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
List<ResContainer> files = resXmlGen.makeResourcesXml();
List<ResContainer> files = resXmlGen.makeResourcesXml(args);
assertEquals(1, files.size());
assertEquals("res/values/attrs.xml", files.get(0).getName());
String input = files.get(0).getText().toString();
assertEquals("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<resources>\n"
+ " <attr name=\"size\" format=\"integer\" min=\"1\">\n"
+ " </attr>\n"
+ "</resources>", adaptLineEndings(files.get(0).getText().toString()));
+ "</resources>", input);
}
@Test
@@ -113,10 +125,11 @@ class ResXmlGenTest {
resStorage.add(re);
ValuesParser vp = new ValuesParser(null, resStorage.getResourcesNames());
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
List<ResContainer> files = resXmlGen.makeResourcesXml();
List<ResContainer> files = resXmlGen.makeResourcesXml(args);
assertEquals(1, files.size());
assertEquals("res/values/styles.xml", files.get(0).getName());
String input = files.get(0).getText().toString();
assertEquals("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<resources>\n"
+ " <style name=\"JadxGui\" parent=\"\">\n"
@@ -124,7 +137,7 @@ class ResXmlGenTest {
+ " </style>\n"
+ " <style name=\"JadxGui.Dialog\" parent=\"@style/JadxGui\">\n"
+ " </style>\n"
+ "</resources>", adaptLineEndings(files.get(0).getText().toString()));
+ "</resources>", input);
}
@Test
@@ -139,14 +152,15 @@ class ResXmlGenTest {
strings.put(0, "Jadx Decompiler App");
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
List<ResContainer> files = resXmlGen.makeResourcesXml();
List<ResContainer> files = resXmlGen.makeResourcesXml(args);
assertEquals(1, files.size());
assertEquals("res/values/strings.xml", files.get(0).getName());
String input = files.get(0).getText().toString();
assertEquals("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<resources>\n"
+ " <string name=\"app_name\">Jadx Decompiler App</string>\n"
+ "</resources>", adaptLineEndings(files.get(0).getText().toString()));
+ "</resources>", input);
}
@Test
@@ -161,14 +175,15 @@ class ResXmlGenTest {
strings.put(0, "%s at %s");
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
List<ResContainer> files = resXmlGen.makeResourcesXml();
List<ResContainer> files = resXmlGen.makeResourcesXml(args);
assertEquals(1, files.size());
assertEquals("res/values/strings.xml", files.get(0).getName());
String input = files.get(0).getText().toString();
assertEquals("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<resources>\n"
+ " <string name=\"app_name\" formatted=\"false\">%s at %s</string>\n"
+ "</resources>", adaptLineEndings(files.get(0).getText().toString()));
+ "</resources>", input);
}
@Test
@@ -183,22 +198,16 @@ class ResXmlGenTest {
strings.put(0, "Let's go");
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
List<ResContainer> files = resXmlGen.makeResourcesXml();
List<ResContainer> files = resXmlGen.makeResourcesXml(args);
assertEquals(1, files.size());
assertEquals("res/values/arrays.xml", files.get(0).getName());
String input = files.get(0).getText().toString();
assertEquals("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<resources>\n"
+ " <array name=\"single_quote_escape_sample\">\n"
+ " <item>Let\\'s go</item>\n"
+ " </array>\n"
+ "</resources>", adaptLineEndings(files.get(0).getText().toString()));
}
private static String adaptLineEndings(String input) {
if (System.lineSeparator().equals("\n")) {
return input; // no adaption necessary
}
return input.replaceAll(System.lineSeparator(), "\n");
+ "</resources>", input);
}
}
@@ -36,7 +36,6 @@ import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.DecompilationMode;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.JadxInternalAccess;
@@ -141,10 +140,14 @@ public abstract class IntegrationTest extends TestUtils {
args.setShowInconsistentCode(true);
args.setThreadsCount(1);
args.setSkipResources(true);
args.setFsCaseSensitive(false); // use same value on all systems
args.setCommentsLevel(CommentsLevel.DEBUG);
args.setDeobfuscationOn(false);
args.setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode.IGNORE);
// use the same values on all systems
args.setFsCaseSensitive(false);
args.setCodeNewLineStr("\n");
args.setCodeIndentStr(JadxArgs.DEFAULT_INDENT_STR);
}
@AfterEach
@@ -315,7 +318,7 @@ public abstract class IntegrationTest extends TestUtils {
private void printCodeWithLineNumbers(ICodeInfo code) {
String codeStr = code.getCodeStr();
Map<Integer, Integer> lineMapping = code.getCodeMetadata().getLineMapping();
String[] lines = codeStr.split(ICodeWriter.NL);
String[] lines = codeStr.split("\\R");
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
int curLine = i + 1;
@@ -332,8 +335,9 @@ public abstract class IntegrationTest extends TestUtils {
String codeStr = code.getCodeStr();
ICodeMetadata metadata = code.getCodeMetadata();
int lineStartPos = 0;
int newLineLen = ICodeWriter.NL.length();
for (String line : codeStr.split(ICodeWriter.NL)) {
String newLineStr = args.getCodeNewLineStr();
int newLineLen = newLineStr.length();
for (String line : codeStr.split(newLineStr)) {
Object ann = metadata.getAt(lineStartPos);
String offsetStr = "";
if (ann instanceof InsnCodeOffset) {
@@ -2,8 +2,6 @@ package jadx.tests.api.utils;
import org.hamcrest.Matcher;
import jadx.api.ICodeWriter;
public class JadxMatchers {
public static Matcher<String> countString(int count, String substring) {
@@ -17,7 +15,7 @@ public class JadxMatchers {
public static Matcher<String> containsLines(String... lines) {
StringBuilder sb = new StringBuilder();
for (String line : lines) {
sb.append(line).append(ICodeWriter.NL);
sb.append(line).append('\n');
}
return countString(1, sb.toString());
}
@@ -30,7 +28,7 @@ public class JadxMatchers {
sb.append(indent);
sb.append(line);
}
sb.append(ICodeWriter.NL);
sb.append('\n');
}
return countString(1, sb.toString());
}
@@ -4,7 +4,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import jadx.NotYetImplementedExtension;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttributeNode;
@@ -23,18 +23,14 @@ import static org.junit.jupiter.api.Assertions.fail;
public class TestUtils {
public static String indent() {
return ICodeWriter.INDENT_STR;
return JadxArgs.DEFAULT_INDENT_STR;
}
public static String indent(int indent) {
if (indent == 1) {
return ICodeWriter.INDENT_STR;
return JadxArgs.DEFAULT_INDENT_STR;
}
StringBuilder sb = new StringBuilder(indent * ICodeWriter.INDENT_STR.length());
for (int i = 0; i < indent; i++) {
sb.append(ICodeWriter.INDENT_STR);
}
return sb.toString();
return Utils.strRepeat(JadxArgs.DEFAULT_INDENT_STR, indent);
}
public static int count(String string, String substring) {
@@ -8,7 +8,6 @@ import java.util.stream.Collectors;
import org.assertj.core.api.AbstractStringAssert;
import jadx.api.ICodeWriter;
import jadx.tests.api.utils.TestUtils;
public class JadxCodeAssertions extends AbstractStringAssert<JadxCodeAssertions> {
@@ -53,7 +52,7 @@ public class JadxCodeAssertions extends AbstractStringAssert<JadxCodeAssertions>
String indent = TestUtils.indent(commonIndent);
StringBuilder sb = new StringBuilder();
for (String line : lines) {
sb.append(ICodeWriter.NL);
sb.append('\n');
if (line.isEmpty()) {
// don't add common indent to empty lines
continue;
@@ -63,7 +62,7 @@ public class JadxCodeAssertions extends AbstractStringAssert<JadxCodeAssertions>
// check every line for easier debugging
contains(searchLine);
}
return containsOnlyOnce(sb.substring(ICodeWriter.NL.length()));
return containsOnlyOnce(sb.substring(1));
}
public JadxCodeAssertions removeBlockComments() {
@@ -10,7 +10,6 @@ import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.JadxInternalAccess;
@@ -186,7 +185,7 @@ public abstract class BaseExternalTest extends TestUtils {
}
protected int getCommentStartPos(ICodeInfo codeInfo, int pos) {
String emptyLine = ICodeWriter.NL + ICodeWriter.NL;
String emptyLine = "\n\n";
int emptyLinePos = codeInfo.getCodeStr().lastIndexOf(emptyLine, pos);
return emptyLinePos == -1 ? pos : emptyLinePos + emptyLine.length();
}
@@ -2,7 +2,6 @@ package jadx.tests.integration.conditions;
import org.junit.jupiter.api.Test;
import jadx.api.ICodeWriter;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
@@ -32,6 +31,6 @@ public class TestElseIfCodeStyle extends IntegrationTest {
assertThat(getClassNode(TestCls.class))
.code()
.doesNotContain("!\"c\".equals(str)")
.doesNotContain("{" + ICodeWriter.NL + indent(2) + "} else {"); // no empty `then` block
.doesNotContain("{\n" + indent(2) + "} else {"); // no empty `then` block
}
}
@@ -3,7 +3,6 @@ package jadx.tests.integration.debuginfo;
import org.junit.jupiter.api.Test;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.utils.CodeUtils;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.nodes.ClassNode;
@@ -55,7 +54,7 @@ public class TestReturnSourceLine extends IntegrationTest {
ClassNode cls = getClassNode(TestCls.class);
ICodeInfo codeInfo = cls.getCode();
String[] lines = codeInfo.getCodeStr().split(ICodeWriter.NL);
String[] lines = codeInfo.getCodeStr().split("\\R");
MethodNode test1 = cls.searchMethodByShortId("test1(Z)I");
checkLine(lines, codeInfo, test1, 3, "return 1;");
@@ -72,7 +71,7 @@ public class TestReturnSourceLine extends IntegrationTest {
}
private static void checkLine(String[] lines, ICodeInfo cw, LineAttrNode node, int offset, String str) {
int nodeDefLine = CodeUtils.getLineNumForPos(cw.getCodeStr(), node.getDefPosition());
int nodeDefLine = CodeUtils.getLineNumForPos(cw.getCodeStr(), node.getDefPosition(), "\n");
int decompiledLine = nodeDefLine + offset;
assertThat(lines[decompiledLine - 1], containsOne(str));
Integer sourceLine = cw.getCodeMetadata().getLineMapping().get(decompiledLine);
@@ -17,6 +17,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo;
import jadx.api.JadxArgs;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.AccessFlagsScope;
import jadx.api.plugins.input.data.ICatch;
@@ -787,7 +788,7 @@ public class Smali {
int lineStart = getInsnColStart();
lineStart += CODE_OFFSET_COLUMN_WIDTH + 1 + 1; // plus 1s for space and the ':'
String basicIndent = new String(new byte[lineStart]).replace("\0", " ");
String indent = SmaliWriter.INDENT_STR + basicIndent;
String indent = JadxArgs.DEFAULT_INDENT_STR + basicIndent;
int[] keys = payload.getKeys();
int[] targets = payload.getTargets();
Integer switchOffset = line.payloadOffsetMap.get(insn.getOffset());
@@ -7,11 +7,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeCache;
import jadx.api.ICodeWriter;
import jadx.api.JavaClass;
import jadx.api.JavaNode;
import jadx.api.metadata.ICodeMetadata;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.utils.CodeUtils;
import jadx.gui.JadxWrapper;
import jadx.gui.jobs.Cancelable;
import jadx.gui.search.SearchSettings;
@@ -68,8 +68,8 @@ public final class CodeSearchProvider extends BaseSearchProvider {
if (newPos == -1) {
return null;
}
int lineStart = 1 + clsCode.lastIndexOf(ICodeWriter.NL, newPos);
int lineEnd = clsCode.indexOf(ICodeWriter.NL, newPos);
int lineStart = 1 + CodeUtils.getNewLinePosBefore(clsCode, newPos);
int lineEnd = CodeUtils.getNewLinePosAfter(clsCode, newPos);
int end = lineEnd == -1 ? clsCode.length() : lineEnd;
String line = clsCode.substring(lineStart, end);
this.pos = end;
@@ -13,10 +13,10 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeWriter;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.api.plugins.utils.CommonFileUtils;
import jadx.api.utils.CodeUtils;
import jadx.gui.jobs.Cancelable;
import jadx.gui.search.ISearchProvider;
import jadx.gui.search.SearchSettings;
@@ -95,8 +95,8 @@ public class ResourceSearchProvider implements ISearchProvider {
if (newPos == -1) {
return null;
}
int lineStart = content.lastIndexOf(ICodeWriter.NL, newPos) + ICodeWriter.NL.length();
int lineEnd = content.indexOf(ICodeWriter.NL, newPos + searchString.length());
int lineStart = 1 + CodeUtils.getNewLinePosBefore(content, newPos);
int lineEnd = CodeUtils.getNewLinePosAfter(content, newPos);
int end = lineEnd == -1 ? content.length() : lineEnd;
String line = content.substring(lineStart, end);
this.pos = end;
@@ -12,7 +12,6 @@ import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.api.ResourcesLoader;
@@ -189,7 +188,7 @@ public class JResource extends JLoadableNode {
return ResourcesLoader.loadToCodeWriter(is);
});
} catch (Exception e) {
return new SimpleCodeInfo("Failed to load resource file:" + ICodeWriter.NL + Utils.getStackTrace(e));
return new SimpleCodeInfo("Failed to load resource file:\n" + Utils.getStackTrace(e));
}
case DECODED_DATA:
@@ -10,7 +10,6 @@ import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import hu.kazocsaba.imageviewer.ImageViewer;
import jadx.api.ICodeWriter;
import jadx.api.ResourceFile;
import jadx.api.ResourcesLoader;
import jadx.core.utils.Utils;
@@ -32,7 +31,7 @@ public class ImagePanel extends ContentPanel {
add(imageViewer.getComponent());
} catch (Exception e) {
RSyntaxTextArea textArea = AbstractCodeArea.getDefaultArea(panel.getMainWindow());
textArea.setText("Image load error:" + ICodeWriter.NL + Utils.getStackTrace(e));
textArea.setText("Image load error:\n" + Utils.getStackTrace(e));
add(textArea);
}
}