Merge branch 'master' into rename
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
name: "Validate Gradle Wrapper"
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
@@ -7,6 +7,7 @@ import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.impl.NoOpCodeCache;
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
public class JadxCLI {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
|
||||
@@ -22,6 +23,7 @@ public class JadxCLI {
|
||||
LOG.error("jadx error: {}", e.getMessage(), e);
|
||||
result = 1;
|
||||
} finally {
|
||||
FileUtils.deleteTempRootDir();
|
||||
System.exit(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ public class ClassGen {
|
||||
private final Set<ClassInfo> imports = new HashSet<>();
|
||||
private int clsDeclLine;
|
||||
|
||||
private boolean bodyGenStarted;
|
||||
|
||||
public ClassGen(ClassNode cls, JadxArgs jadxArgs) {
|
||||
this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
|
||||
}
|
||||
@@ -222,6 +224,7 @@ public class ClassGen {
|
||||
|
||||
public void addClassBody(CodeWriter clsCode) throws CodegenException {
|
||||
clsCode.add('{');
|
||||
setBodyGenStarted(true);
|
||||
clsDeclLine = clsCode.getLine();
|
||||
clsCode.incIndent();
|
||||
addFields(clsCode);
|
||||
@@ -656,4 +659,12 @@ public class ClassGen {
|
||||
public boolean isFallbackMode() {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
public boolean isBodyGenStarted() {
|
||||
return bodyGenStarted;
|
||||
}
|
||||
|
||||
public void setBodyGenStarted(boolean bodyGenStarted) {
|
||||
this.bodyGenStarted = bodyGenStarted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ public class InsnGen {
|
||||
ClassInfo declClass = field.getDeclClass();
|
||||
// TODO
|
||||
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
|
||||
if (!fieldFromThisClass) {
|
||||
if (!fieldFromThisClass || !clsGen.isBodyGenStarted()) {
|
||||
// Android specific resources class handler
|
||||
if (!handleAppResField(code, clsGen, declClass)) {
|
||||
clsGen.useClass(code, declClass);
|
||||
|
||||
@@ -28,6 +28,7 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.regions.SwitchRegion;
|
||||
import jadx.core.dex.regions.SwitchRegion.CaseInfo;
|
||||
import jadx.core.dex.regions.SynchronizedRegion;
|
||||
import jadx.core.dex.regions.TryCatchRegion;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
@@ -270,10 +271,9 @@ public class RegionGen extends InsnGen {
|
||||
code.add(") {");
|
||||
code.incIndent();
|
||||
|
||||
int size = sw.getKeys().size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
List<Object> keys = sw.getKeys().get(i);
|
||||
IContainer c = sw.getCases().get(i);
|
||||
for (CaseInfo caseInfo : sw.getCases()) {
|
||||
List<Object> keys = caseInfo.getKeys();
|
||||
IContainer c = caseInfo.getContainer();
|
||||
for (Object k : keys) {
|
||||
code.startLine("case ");
|
||||
if (k instanceof FieldNode) {
|
||||
|
||||
@@ -118,6 +118,7 @@ public abstract class AttrNode implements IAttributeNode {
|
||||
return storage.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAttrStorageEmpty() {
|
||||
return storage.isEmpty();
|
||||
}
|
||||
|
||||
@@ -35,4 +35,6 @@ public interface IAttributeNode {
|
||||
List<String> getAttributesStringsList();
|
||||
|
||||
String getAttributesString();
|
||||
|
||||
boolean isAttrStorageEmpty();
|
||||
}
|
||||
|
||||
@@ -110,12 +110,7 @@ public class ConstStorage {
|
||||
}
|
||||
|
||||
private ValueStorage getClsValues(ClassNode cls) {
|
||||
ValueStorage classValues = classes.get(cls);
|
||||
if (classValues == null) {
|
||||
classValues = new ValueStorage();
|
||||
classes.put(cls, classValues);
|
||||
}
|
||||
return classValues;
|
||||
return classes.computeIfAbsent(cls, c -> new ValueStorage());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -3,6 +3,7 @@ package jadx.core.dex.instructions;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
@@ -110,15 +111,13 @@ public class SwitchNode extends TargetInsnNode {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder targ = new StringBuilder();
|
||||
targ.append('[');
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(super.toString());
|
||||
for (int i = 0; i < targets.length; i++) {
|
||||
targ.append(InsnUtils.formatOffset(targets[i]));
|
||||
if (i < targets.length - 1) {
|
||||
targ.append(", ");
|
||||
}
|
||||
sb.append(" case ").append(keys[i])
|
||||
.append(": goto ").append(InsnUtils.formatOffset(targets[i]));
|
||||
sb.append(CodeWriter.NL);
|
||||
}
|
||||
targ.append(']');
|
||||
return super.toString() + " k:" + Arrays.toString(keys) + " t:" + targ;
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -555,11 +556,24 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
|
||||
public String getSmali() {
|
||||
if (smali == null) {
|
||||
smali = SmaliUtils.getSmaliCode(dex, clsDefOffset);
|
||||
StringWriter stringWriter = new StringWriter(4096);
|
||||
getSmali(this, stringWriter);
|
||||
stringWriter.append(System.lineSeparator());
|
||||
for (ClassNode innerClass : innerClasses) {
|
||||
getSmali(innerClass, stringWriter);
|
||||
stringWriter.append(System.lineSeparator());
|
||||
}
|
||||
smali = stringWriter.toString();
|
||||
}
|
||||
return smali;
|
||||
}
|
||||
|
||||
protected static boolean getSmali(ClassNode classNode, StringWriter stringWriter) {
|
||||
stringWriter.append(String.format("###### Class %s (%s)", classNode.getFullName(), classNode.getRawName()));
|
||||
stringWriter.append(System.lineSeparator());
|
||||
return SmaliUtils.getSmaliCode(classNode.dex, classNode.clsDefOffset, stringWriter);
|
||||
}
|
||||
|
||||
public ProcessState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -4,33 +4,50 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IBranchRegion;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public final class SwitchRegion extends AbstractRegion implements IBranchRegion {
|
||||
|
||||
private final BlockNode header;
|
||||
|
||||
private final List<List<Object>> keys;
|
||||
private final List<IContainer> cases;
|
||||
private final List<CaseInfo> cases;
|
||||
private IContainer defCase;
|
||||
|
||||
public SwitchRegion(IRegion parent, BlockNode header) {
|
||||
super(parent);
|
||||
this.header = header;
|
||||
this.keys = new ArrayList<>();
|
||||
this.cases = new ArrayList<>();
|
||||
}
|
||||
|
||||
public static final class CaseInfo {
|
||||
private final List<Object> keys;
|
||||
private final IContainer container;
|
||||
|
||||
public CaseInfo(List<Object> keys, IContainer container) {
|
||||
this.keys = keys;
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
public List<Object> getKeys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
public IContainer getContainer() {
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
public BlockNode getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public void addCase(List<Object> keysList, IContainer c) {
|
||||
keys.add(keysList);
|
||||
cases.add(c);
|
||||
cases.add(new CaseInfo(keysList, c));
|
||||
}
|
||||
|
||||
public void setDefaultCase(IContainer block) {
|
||||
@@ -41,19 +58,19 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
|
||||
return defCase;
|
||||
}
|
||||
|
||||
public List<List<Object>> getKeys() {
|
||||
return keys;
|
||||
public List<CaseInfo> getCases() {
|
||||
return cases;
|
||||
}
|
||||
|
||||
public List<IContainer> getCases() {
|
||||
return cases;
|
||||
public List<IContainer> getCaseContainers() {
|
||||
return Utils.collectionMap(cases, caseInfo -> caseInfo.container);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IContainer> getSubBlocks() {
|
||||
List<IContainer> all = new ArrayList<>(cases.size() + 2);
|
||||
all.add(header);
|
||||
all.addAll(cases);
|
||||
all.addAll(getCaseContainers());
|
||||
if (defCase != null) {
|
||||
all.add(defCase);
|
||||
}
|
||||
@@ -63,7 +80,7 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
|
||||
@Override
|
||||
public List<IContainer> getBranches() {
|
||||
List<IContainer> branches = new ArrayList<>(cases.size() + 1);
|
||||
branches.addAll(cases);
|
||||
branches.addAll(getCaseContainers());
|
||||
if (defCase != null) {
|
||||
branches.add(defCase);
|
||||
}
|
||||
@@ -77,6 +94,16 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Switch: " + cases.size() + ", default: " + defCase;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Switch: ").append(cases.size());
|
||||
for (CaseInfo caseInfo : cases) {
|
||||
sb.append(CodeWriter.NL).append(" case ")
|
||||
.append(Utils.listToString(caseInfo.getKeys()))
|
||||
.append(" -> ").append(caseInfo.getContainer());
|
||||
}
|
||||
if (defCase != null) {
|
||||
sb.append(CodeWriter.NL).append(" default -> ").append(defCase);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
@@ -46,6 +49,7 @@ import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.InsnRemover;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.utils.BlockUtils.replaceInsn;
|
||||
@@ -68,6 +72,12 @@ public class ModVisitor extends AbstractVisitor {
|
||||
private static final long DOUBLE_TO_BITS = Double.doubleToLongBits(1);
|
||||
private static final long FLOAT_TO_BITS = Float.floatToIntBits(1);
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
replaceConstInAnnotations(cls);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) {
|
||||
if (mth.isNoCode()) {
|
||||
@@ -171,6 +181,33 @@ public class ModVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private void replaceConstInAnnotations(ClassNode cls) {
|
||||
if (cls.root().getArgs().isReplaceConsts()) {
|
||||
replaceConstsInAnnotationForAttrNode(cls, cls);
|
||||
cls.getFields().forEach(f -> replaceConstsInAnnotationForAttrNode(cls, f));
|
||||
cls.getMethods().forEach(m -> replaceConstsInAnnotationForAttrNode(cls, m));
|
||||
}
|
||||
}
|
||||
|
||||
private void replaceConstsInAnnotationForAttrNode(ClassNode parentCls, AttrNode attrNode) {
|
||||
AnnotationsList annotationsList = attrNode.get(AType.ANNOTATION_LIST);
|
||||
if (annotationsList == null) {
|
||||
return;
|
||||
}
|
||||
for (Annotation annotation : annotationsList.getAll()) {
|
||||
if (annotation.getVisibility() == Annotation.Visibility.SYSTEM) {
|
||||
continue;
|
||||
}
|
||||
for (Map.Entry<String, Object> entry : annotation.getValues().entrySet()) {
|
||||
Object value = entry.getValue();
|
||||
FieldNode constField = parentCls.getConstField(value);
|
||||
if (constField != null) {
|
||||
entry.setValue(constField.getFieldInfo());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void replaceConst(MethodNode mth, ClassNode parentClass, BlockNode block, int i, InsnNode insn) {
|
||||
FieldNode f;
|
||||
if (insn.getType() == InsnType.CONST_STR) {
|
||||
|
||||
@@ -16,8 +16,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.utils.files.FileUtils.close;
|
||||
|
||||
/**
|
||||
* Simple template engine
|
||||
* Syntax for replace variable with value: '{{variable}}'
|
||||
@@ -56,21 +54,15 @@ public class TemplateFile {
|
||||
}
|
||||
|
||||
public String build() throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
try {
|
||||
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
process(out);
|
||||
} finally {
|
||||
close(out);
|
||||
return out.toString();
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
public void save(File outFile) throws IOException {
|
||||
OutputStream out = new FileOutputStream(outFile);
|
||||
try {
|
||||
try (OutputStream out = new FileOutputStream(outFile)) {
|
||||
process(out);
|
||||
} finally {
|
||||
close(out);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
import org.slf4j.Logger;
|
||||
@@ -12,6 +15,7 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.codegen.InsnGen;
|
||||
import jadx.core.codegen.MethodGen;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
@@ -85,51 +89,74 @@ public class DebugUtils {
|
||||
printRegions(mth, false);
|
||||
}
|
||||
|
||||
public static void printRegion(MethodNode mth, IRegion region, boolean printInsn) {
|
||||
printRegion(mth, region, "", printInsn);
|
||||
}
|
||||
|
||||
public static void printRegions(MethodNode mth, boolean printInsns) {
|
||||
LOG.debug("|{}", mth);
|
||||
printRegion(mth, mth.getRegion(), "| ", printInsns);
|
||||
printRegion(mth, mth.getRegion(), printInsns);
|
||||
}
|
||||
|
||||
private static void printRegion(MethodNode mth, IRegion region, String indent, boolean printInsns) {
|
||||
LOG.debug("{}{} {}", indent, region, region.getAttributesString());
|
||||
public static void printRegion(MethodNode mth, IRegion region, boolean printInsns) {
|
||||
CodeWriter cw = new CodeWriter();
|
||||
cw.startLine('|').add(mth.toString());
|
||||
printRegion(mth, region, cw, "| ", printInsns);
|
||||
LOG.debug("\n{}", cw.finish().getCodeStr());
|
||||
}
|
||||
|
||||
private static void printRegion(MethodNode mth, IRegion region, CodeWriter cw, String indent, boolean printInsns) {
|
||||
printWithAttributes(cw, indent, region.toString(), region);
|
||||
indent += "| ";
|
||||
for (IContainer container : region.getSubBlocks()) {
|
||||
if (container instanceof IRegion) {
|
||||
printRegion(mth, (IRegion) container, indent, printInsns);
|
||||
printRegion(mth, (IRegion) container, cw, indent, printInsns);
|
||||
} else {
|
||||
LOG.debug("{}{} {}", indent, container, container.getAttributesString());
|
||||
printWithAttributes(cw, indent, container.toString(), container);
|
||||
if (printInsns && container instanceof IBlock) {
|
||||
IBlock block = (IBlock) container;
|
||||
printInsns(mth, indent, block);
|
||||
printInsns(mth, cw, indent, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void printInsns(MethodNode mth, String indent, IBlock block) {
|
||||
private static void printInsns(MethodNode mth, CodeWriter cw, String indent, IBlock block) {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
try {
|
||||
MethodGen mg = MethodGen.getFallbackMethodGen(mth);
|
||||
InsnGen ig = new InsnGen(mg, true);
|
||||
CodeWriter code = new CodeWriter();
|
||||
ig.makeInsn(insn, code);
|
||||
String insnStr = code.toString().substring(CodeWriter.NL.length());
|
||||
String attrStr = insn.isAttrStorageEmpty() ? "" : '\t' + insn.getAttributesString();
|
||||
LOG.debug("{}|> {}{}", indent, insnStr, attrStr);
|
||||
String codeStr = code.finish().getCodeStr();
|
||||
|
||||
List<String> insnStrings = Arrays.stream(codeStr.split(CodeWriter.NL))
|
||||
.filter(StringUtils::notBlank)
|
||||
.map(s -> "|> " + s)
|
||||
.collect(Collectors.toList());
|
||||
Iterator<String> it = insnStrings.iterator();
|
||||
while (true) {
|
||||
String insnStr = it.next();
|
||||
if (it.hasNext()) {
|
||||
cw.startLine(indent).add(insnStr);
|
||||
} else {
|
||||
printWithAttributes(cw, indent, insnStr, insn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (CodegenException e) {
|
||||
LOG.debug("{}|>!! {}", indent, insn);
|
||||
cw.startLine(indent).add(">!! ").add(insn.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void printMap(String desc, Map<?, ?> map) {
|
||||
LOG.debug("Map of {}, size: {}", desc, map.size());
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
LOG.debug(" {} : {}", entry.getKey(), entry.getValue());
|
||||
private static void printWithAttributes(CodeWriter cw, String indent, String codeStr, IAttributeNode attrNode) {
|
||||
String str = attrNode.isAttrStorageEmpty() ? codeStr : codeStr + ' ' + attrNode.getAttributesString();
|
||||
List<String> attrStrings = Arrays.stream(str.split(CodeWriter.NL))
|
||||
.filter(StringUtils::notBlank)
|
||||
.collect(Collectors.toList());
|
||||
Iterator<String> it = attrStrings.iterator();
|
||||
if (!it.hasNext()) {
|
||||
return;
|
||||
}
|
||||
cw.startLine(indent).add(it.next());
|
||||
while (it.hasNext()) {
|
||||
cw.startLine(indent).add("|+ ").add(it.next());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jf.baksmali.Adaptors.ClassDefinition;
|
||||
import org.jf.baksmali.BaksmaliOptions;
|
||||
import org.jf.dexlib2.DexFileFactory;
|
||||
@@ -34,24 +33,25 @@ public class SmaliUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getSmaliCode(DexNode dex, int clsDefOffset) {
|
||||
public static boolean getSmaliCode(DexNode dex, int clsDefOffset, StringWriter stringWriter) {
|
||||
try {
|
||||
Path path = dex.getDexFile().getPath();
|
||||
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(path.toFile(), null);
|
||||
DexBackedClassDef dexBackedClassDef = new DexBackedClassDef(dexFile, clsDefOffset);
|
||||
return getSmaliCode(dexBackedClassDef);
|
||||
getSmaliCode(dexBackedClassDef, stringWriter);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error generating smali", e);
|
||||
return "Error generating smali code: " + e.getMessage()
|
||||
+ '\n' + Utils.getStackTrace(e);
|
||||
stringWriter.append("Error generating smali code: ");
|
||||
stringWriter.append(e.getMessage());
|
||||
stringWriter.append(System.lineSeparator());
|
||||
stringWriter.append(Utils.getStackTrace(e));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getSmaliCode(DexBackedClassDef classDef) throws IOException {
|
||||
private static void getSmaliCode(DexBackedClassDef classDef, StringWriter stringWriter) throws IOException {
|
||||
ClassDefinition classDefinition = new ClassDefinition(new BaksmaliOptions(), classDef);
|
||||
StringWriter sw = new StringWriter();
|
||||
classDefinition.writeTo(new IndentingWriter(sw));
|
||||
return sw.toString();
|
||||
classDefinition.writeTo(new IndentingWriter(stringWriter));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,6 +211,10 @@ public class StringUtils {
|
||||
return str == null || str.isEmpty();
|
||||
}
|
||||
|
||||
public static boolean notBlank(String str) {
|
||||
return notEmpty(str) && !str.trim().isEmpty();
|
||||
}
|
||||
|
||||
public static int countMatches(String str, String subStr) {
|
||||
if (str == null || str.isEmpty() || subStr == null || subStr.isEmpty()) {
|
||||
return 0;
|
||||
|
||||
@@ -10,12 +10,15 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
@@ -32,6 +35,9 @@ public class FileUtils {
|
||||
public static final int READ_BUFFER_SIZE = 8 * 1024;
|
||||
private static final int MAX_FILENAME_LENGTH = 128;
|
||||
|
||||
public static final String JADX_TMP_INSTANCE_PREFIX = "jadx-instance-";
|
||||
public static final String JADX_TMP_PREFIX = "jadx-tmp-";
|
||||
|
||||
private FileUtils() {
|
||||
}
|
||||
|
||||
@@ -70,6 +76,12 @@ public class FileUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void makeDirs(@Nullable Path dir) {
|
||||
if (dir != null) {
|
||||
makeDirs(dir.toFile());
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean deleteDir(File dir) {
|
||||
File[] content = dir.listFiles();
|
||||
if (content != null) {
|
||||
@@ -80,11 +92,27 @@ public class FileUtils {
|
||||
return dir.delete();
|
||||
}
|
||||
|
||||
public static void deleteDir(Path dir) {
|
||||
try (Stream<Path> pathStream = Files.walk(dir)) {
|
||||
pathStream.sorted(Comparator.reverseOrder())
|
||||
.map(Path::toFile)
|
||||
.forEach(File::delete);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to delete directory " + dir, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Path TEMP_ROOT_DIR = createTempRootDir();
|
||||
|
||||
private static Path createTempRootDir() {
|
||||
try {
|
||||
Path dir = Files.createTempDirectory("jadx-instance-");
|
||||
String jadxTmpDir = System.getenv("JADX_TMP_DIR");
|
||||
Path dir;
|
||||
if (jadxTmpDir != null) {
|
||||
dir = Files.createTempDirectory(Paths.get(jadxTmpDir), "jadx-instance-");
|
||||
} else {
|
||||
dir = Files.createTempDirectory(JADX_TMP_INSTANCE_PREFIX);
|
||||
}
|
||||
dir.toFile().deleteOnExit();
|
||||
return dir;
|
||||
} catch (Exception e) {
|
||||
@@ -92,6 +120,15 @@ public class FileUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteTempRootDir() {
|
||||
deleteDir(TEMP_ROOT_DIR);
|
||||
}
|
||||
|
||||
public static void clearTempRootDir() {
|
||||
deleteDir(TEMP_ROOT_DIR);
|
||||
makeDirs(TEMP_ROOT_DIR);
|
||||
}
|
||||
|
||||
public static Path createTempDir(String prefix) {
|
||||
try {
|
||||
Path dir = Files.createTempDirectory(TEMP_ROOT_DIR, prefix);
|
||||
@@ -104,7 +141,7 @@ public class FileUtils {
|
||||
|
||||
public static Path createTempFile(String suffix) {
|
||||
try {
|
||||
Path path = Files.createTempFile(TEMP_ROOT_DIR, "jadx-tmp-", suffix);
|
||||
Path path = Files.createTempFile(TEMP_ROOT_DIR, JADX_TMP_PREFIX, suffix);
|
||||
path.toFile().deleteOnExit();
|
||||
return path;
|
||||
} catch (Exception e) {
|
||||
@@ -112,6 +149,14 @@ public class FileUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static Path createTempFileNoDelete(String suffix) {
|
||||
try {
|
||||
return Files.createTempFile(TEMP_ROOT_DIR, JADX_TMP_PREFIX, suffix);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to create temp file with suffix: " + suffix, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void copyStream(InputStream input, OutputStream output) throws IOException {
|
||||
byte[] buffer = new byte[READ_BUFFER_SIZE];
|
||||
while (true) {
|
||||
|
||||
@@ -6,7 +6,6 @@ import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -21,6 +20,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.jar.JarOutputStream;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
@@ -119,6 +119,11 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
args.setFsCaseSensitive(false); // use same value on all systems
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void after() {
|
||||
FileUtils.clearTempRootDir();
|
||||
}
|
||||
|
||||
public String getTestName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
@@ -414,7 +419,7 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
temp = FileUtils.createTempFile(suffix);
|
||||
} else {
|
||||
// don't delete on exit
|
||||
temp = Files.createTempFile("jadx", suffix);
|
||||
temp = FileUtils.createTempFileNoDelete(suffix);
|
||||
System.out.println("Temporary file saved: " + temp.toAbsolutePath());
|
||||
}
|
||||
return temp.toFile();
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package jadx.tests.integration.inner;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestRFieldRestore3 extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
|
||||
@T(2131230730)
|
||||
public static class A {
|
||||
@F(2131230730)
|
||||
private int f;
|
||||
|
||||
@M(bind = 2137373737)
|
||||
private void mth() {
|
||||
}
|
||||
|
||||
@T(2137373737)
|
||||
private class D {
|
||||
}
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@interface T {
|
||||
int value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.FIELD })
|
||||
@interface F {
|
||||
int value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD })
|
||||
@interface M {
|
||||
int bind();
|
||||
}
|
||||
|
||||
public static class R {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
Map<Integer, String> map = new HashMap<>();
|
||||
map.put(2131230730, "id.Button");
|
||||
map.put(2137373737, "id.MyId");
|
||||
setResMap(map);
|
||||
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOnlyOnce("@T(R.id.Button)")
|
||||
.containsOnlyOnce("@T(R.id.MyId)")
|
||||
.containsOnlyOnce("@F(R.id.Button)")
|
||||
.containsOnlyOnce("@M(bind = R.id.MyId)");
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package jadx.tests.integration.inner;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestReplaceConstsInAnnotations extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface A {
|
||||
int i();
|
||||
|
||||
float f();
|
||||
}
|
||||
|
||||
@A(i = -1, f = C.FLOAT_CONST)
|
||||
public static class C {
|
||||
public static final float FLOAT_CONST = 3.14f;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOnlyOnce("f = C.FLOAT_CONST");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package jadx.tests.integration.switches;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.NotYetImplemented;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestSwitchFallThrough extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
public int r;
|
||||
|
||||
public void test(int a) {
|
||||
int i = 10;
|
||||
switch (a) {
|
||||
case 1:
|
||||
i = 1000;
|
||||
// fallthrough
|
||||
case 2:
|
||||
r = i;
|
||||
break;
|
||||
|
||||
default:
|
||||
r = -1;
|
||||
break;
|
||||
}
|
||||
r *= 2;
|
||||
System.out.println("in: " + a + ", out: " + r);
|
||||
}
|
||||
|
||||
public int testWrap(int a) {
|
||||
r = 0;
|
||||
test(a);
|
||||
return r;
|
||||
}
|
||||
|
||||
public void check() {
|
||||
assertThat(testWrap(1)).isEqualTo(2000);
|
||||
assertThat(testWrap(2)).isEqualTo(20);
|
||||
assertThat(testWrap(0)).isEqualTo(-2);
|
||||
}
|
||||
}
|
||||
|
||||
@NotYetImplemented("switch fallthrough")
|
||||
@Test
|
||||
public void test() {
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOnlyOnce("switch");
|
||||
// code correctness checks done in 'check' method
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,7 @@ import jadx.api.JadxArgs;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.jobs.BackgroundExecutor;
|
||||
import jadx.gui.jobs.BackgroundWorker;
|
||||
@@ -1109,6 +1110,8 @@ public class MainWindow extends JFrame {
|
||||
settings.setMainWindowExtendedState(getExtendedState());
|
||||
cancelBackgroundJobs();
|
||||
dispose();
|
||||
|
||||
FileUtils.deleteTempRootDir();
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ class SearchBar extends JToolBar {
|
||||
|
||||
private final JCheckBox wholeWordCB;
|
||||
private final JCheckBox matchCaseCB;
|
||||
private ActionListener forwardListener = e -> search(0);
|
||||
|
||||
public SearchBar(RSyntaxTextArea textArea) {
|
||||
rTextArea = textArea;
|
||||
@@ -81,6 +80,8 @@ class SearchBar extends JToolBar {
|
||||
nextButton.setBorderPainted(false);
|
||||
add(nextButton);
|
||||
|
||||
ActionListener forwardListener = e -> search(0);
|
||||
|
||||
markAllCB = new JCheckBox(NLS.str("search.mark_all"));
|
||||
markAllCB.addActionListener(forwardListener);
|
||||
add(markAllCB);
|
||||
|
||||
@@ -17,4 +17,7 @@ public class SystemInfo {
|
||||
public static final boolean IS_WINDOWS = LOWER_OS_NAME.startsWith("windows");
|
||||
public static final boolean IS_MAC = LOWER_OS_NAME.startsWith("mac");
|
||||
public static final boolean IS_LINUX = LOWER_OS_NAME.startsWith("linux");
|
||||
|
||||
private SystemInfo() {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user