diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml new file mode 100644 index 000000000..ebed36431 --- /dev/null +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -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 diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLI.java b/jadx-cli/src/main/java/jadx/cli/JadxCLI.java index dd7c3031a..4691991a7 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLI.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLI.java @@ -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); } } 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 ba8f9ce50..4a83d5c35 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -52,6 +52,8 @@ public class ClassGen { private final Set 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; + } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java index f3def9baf..92219107a 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -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); diff --git a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java index 54b3c1fa8..4596d8c48 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java @@ -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 keys = sw.getKeys().get(i); - IContainer c = sw.getCases().get(i); + for (CaseInfo caseInfo : sw.getCases()) { + List keys = caseInfo.getKeys(); + IContainer c = caseInfo.getContainer(); for (Object k : keys) { code.startLine("case "); if (k instanceof FieldNode) { diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java index 4079c71a6..26dc41b71 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java @@ -118,6 +118,7 @@ public abstract class AttrNode implements IAttributeNode { return storage.toString(); } + @Override public boolean isAttrStorageEmpty() { return storage.isEmpty(); } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/IAttributeNode.java b/jadx-core/src/main/java/jadx/core/dex/attributes/IAttributeNode.java index 50d70577f..b6435d5b1 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/IAttributeNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/IAttributeNode.java @@ -35,4 +35,6 @@ public interface IAttributeNode { List getAttributesStringsList(); String getAttributesString(); + + boolean isAttrStorageEmpty(); } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java index d599d83bc..cbb30286a 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java @@ -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 diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java index fc6249d2f..641b45ac0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java @@ -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(); } } 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 62d693d29..ab81f0cfd 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 @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/regions/SwitchRegion.java b/jadx-core/src/main/java/jadx/core/dex/regions/SwitchRegion.java index 2ac0a44df..bbdfbcb91 100644 --- a/jadx-core/src/main/java/jadx/core/dex/regions/SwitchRegion.java +++ b/jadx-core/src/main/java/jadx/core/dex/regions/SwitchRegion.java @@ -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> keys; - private final List cases; + private final List 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 keys; + private final IContainer container; + + public CaseInfo(List keys, IContainer container) { + this.keys = keys; + this.container = container; + } + + public List getKeys() { + return keys; + } + + public IContainer getContainer() { + return container; + } + } + public BlockNode getHeader() { return header; } public void addCase(List 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> getKeys() { - return keys; + public List getCases() { + return cases; } - public List getCases() { - return cases; + public List getCaseContainers() { + return Utils.collectionMap(cases, caseInfo -> caseInfo.container); } @Override public List getSubBlocks() { List 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 getBranches() { List 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(); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java index 1a36d71bc..a7e854060 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java @@ -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 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) { diff --git a/jadx-core/src/main/java/jadx/core/export/TemplateFile.java b/jadx-core/src/main/java/jadx/core/export/TemplateFile.java index e93a02a6c..79d56cbe4 100644 --- a/jadx-core/src/main/java/jadx/core/export/TemplateFile.java +++ b/jadx-core/src/main/java/jadx/core/export/TemplateFile.java @@ -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); } } diff --git a/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java b/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java index 9cb673986..16d8dbcb0 100644 --- a/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java @@ -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 insnStrings = Arrays.stream(codeStr.split(CodeWriter.NL)) + .filter(StringUtils::notBlank) + .map(s -> "|> " + s) + .collect(Collectors.toList()); + Iterator 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 attrStrings = Arrays.stream(str.split(CodeWriter.NL)) + .filter(StringUtils::notBlank) + .collect(Collectors.toList()); + Iterator it = attrStrings.iterator(); + if (!it.hasNext()) { + return; + } + cw.startLine(indent).add(it.next()); + while (it.hasNext()) { + cw.startLine(indent).add("|+ ").add(it.next()); } } } diff --git a/jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java b/jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java index c3a454849..aea2a162f 100644 --- a/jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java @@ -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)); } } diff --git a/jadx-core/src/main/java/jadx/core/utils/StringUtils.java b/jadx-core/src/main/java/jadx/core/utils/StringUtils.java index f5643871c..896eb446f 100644 --- a/jadx-core/src/main/java/jadx/core/utils/StringUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/StringUtils.java @@ -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; diff --git a/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java b/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java index 5b03934cb..ae4c82a57 100644 --- a/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java @@ -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 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) { diff --git a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java index 288108f55..ada2ea51c 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -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(); diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore3.java b/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore3.java new file mode 100644 index 000000000..7fbca415b --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore3.java @@ -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 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)"); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestReplaceConstsInAnnotations.java b/jadx-core/src/test/java/jadx/tests/integration/inner/TestReplaceConstsInAnnotations.java new file mode 100644 index 000000000..3009283fd --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/inner/TestReplaceConstsInAnnotations.java @@ -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"); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchFallThrough.java b/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchFallThrough.java new file mode 100644 index 000000000..7e2a0fe9d --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchFallThrough.java @@ -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 + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 6ed0bba4d..e9aecfa23 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -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); } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/SearchBar.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/SearchBar.java index 40a69fe9d..7c96b210a 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/SearchBar.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/SearchBar.java @@ -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); diff --git a/jadx-gui/src/main/java/jadx/gui/utils/SystemInfo.java b/jadx-gui/src/main/java/jadx/gui/utils/SystemInfo.java index f2eca96e5..e7c24e43f 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/SystemInfo.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/SystemInfo.java @@ -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() { + } }