diff --git a/README.md b/README.md index e408e9d88..99b8aa46a 100644 --- a/README.md +++ b/README.md @@ -44,19 +44,20 @@ jadx[-gui] [options] (.dex, .apk, .jar or .class) options: -d, --output-dir - output directory -j, --threads-count - processing threads count - -f, --fallback - make simple dump (using goto instead of 'if', 'for', etc) -r, --no-res - do not decode resources -s, --no-src - do not decompile source code --show-bad-code - show inconsistent code (incorrectly decompiled) - --cfg - save methods control flow graph to dot file - --raw-cfg - save methods control flow graph (use raw instructions) - -v, --verbose - verbose output + --no-replace-consts - don't replace constant value with matching constant field + --escape-unicode - escape non latin characters in strings (with \u) --deobf - activate deobfuscation --deobf-min - min length of name --deobf-max - max length of name --deobf-rewrite-cfg - force to save deobfuscation map --deobf-use-sourcename - use source file name as class name alias - --escape-unicode - escape non latin characters in strings (with \u) + --cfg - save methods control flow graph to dot file + --raw-cfg - save methods control flow graph (use raw instructions) + -f, --fallback - make simple dump (using goto instead of 'if', 'for', etc) + -v, --verbose - verbose output -h, --help - print this help Example: jadx -d out classes.dex diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index b1add245c..632adb33a 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -17,6 +17,7 @@ import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.beust.jcommander.IStringConverter; import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; import com.beust.jcommander.ParameterDescription; @@ -33,9 +34,6 @@ public class JadxCLIArgs implements IJadxArgs { @Parameter(names = {"-j", "--threads-count"}, description = "processing threads count") protected int threadsCount = Math.max(1, Runtime.getRuntime().availableProcessors() / 2); - @Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)") - protected boolean fallbackMode = false; - @Parameter(names = {"-r", "--no-res"}, description = "do not decode resources") protected boolean skipResources = false; @@ -45,14 +43,12 @@ public class JadxCLIArgs implements IJadxArgs { @Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)") protected boolean showInconsistentCode = false; - @Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file") - protected boolean cfgOutput = false; + @Parameter(names = "--no-replace-consts", converter = InvertedBooleanConverter.class, + description = "don't replace constant value with matching constant field") + protected boolean replaceConsts = true; - @Parameter(names = {"--raw-cfg"}, description = "save methods control flow graph (use raw instructions)") - protected boolean rawCfgOutput = false; - - @Parameter(names = {"-v", "--verbose"}, description = "verbose output") - protected boolean verbose = false; + @Parameter(names = {"--escape-unicode"}, description = "escape non latin characters in strings (with \\u)") + protected boolean escapeUnicode = false; @Parameter(names = {"--deobf"}, description = "activate deobfuscation") protected boolean deobfuscationOn = false; @@ -69,8 +65,17 @@ public class JadxCLIArgs implements IJadxArgs { @Parameter(names = {"--deobf-use-sourcename"}, description = "use source file name as class name alias") protected boolean deobfuscationUseSourceNameAsAlias = false; - @Parameter(names = {"--escape-unicode"}, description = "escape non latin characters in strings (with \\u)") - protected boolean escapeUnicode = false; + @Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file") + protected boolean cfgOutput = false; + + @Parameter(names = {"--raw-cfg"}, description = "save methods control flow graph (use raw instructions)") + protected boolean rawCfgOutput = false; + + @Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)") + protected boolean fallbackMode = false; + + @Parameter(names = {"-v", "--verbose"}, description = "verbose output") + protected boolean verbose = false; @Parameter(names = {"-h", "--help"}, description = "print this help", help = true) protected boolean printHelp = false; @@ -178,6 +183,13 @@ public class JadxCLIArgs implements IJadxArgs { } } + public static class InvertedBooleanConverter implements IStringConverter { + @Override + public Boolean convert(String value) { + return "false".equals(value); + } + } + public List getInput() { return input; } @@ -264,4 +276,9 @@ public class JadxCLIArgs implements IJadxArgs { public boolean escapeUnicode() { return escapeUnicode; } + + @Override + public boolean isReplaceConsts() { + return replaceConsts; + } } diff --git a/jadx-cli/src/test/java/jadx/cli/JadxCLIArgsTest.java b/jadx-cli/src/test/java/jadx/cli/JadxCLIArgsTest.java new file mode 100644 index 000000000..428efccfa --- /dev/null +++ b/jadx-cli/src/test/java/jadx/cli/JadxCLIArgsTest.java @@ -0,0 +1,22 @@ +package jadx.cli; + +import org.junit.Test; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +public class JadxCLIArgsTest { + + @Test + public void testInvertedBooleanOption() throws Exception { + assertThat(parse("--no-replace-consts").isReplaceConsts(), is(false)); + assertThat(parse("").isReplaceConsts(), is(true)); + } + + private JadxCLIArgs parse(String... args) { + JadxCLIArgs jadxArgs = new JadxCLIArgs(); + boolean res = jadxArgs.processArgs(args); + assertThat(res, is(true)); + return jadxArgs; + } +} diff --git a/jadx-core/src/main/java/jadx/api/IJadxArgs.java b/jadx-core/src/main/java/jadx/api/IJadxArgs.java index 2c5034d63..212ae6199 100644 --- a/jadx-core/src/main/java/jadx/api/IJadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/IJadxArgs.java @@ -32,4 +32,9 @@ public interface IJadxArgs { boolean useSourceNameAsClassAlias(); boolean escapeUnicode(); + + /** + * Replace constant values with static final fields with same value + */ + boolean isReplaceConsts(); } diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index 7d789f1ef..81c3746c4 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -25,6 +25,7 @@ public class JadxArgs implements IJadxArgs { private int deobfuscationMaxLength = Integer.MAX_VALUE; private boolean escapeUnicode = false; + private boolean replaceConsts = true; @Override public File getOutDir() { @@ -160,4 +161,13 @@ public class JadxArgs implements IJadxArgs { public void setEscapeUnicode(boolean escapeUnicode) { this.escapeUnicode = escapeUnicode; } + + @Override + public boolean isReplaceConsts() { + return replaceConsts; + } + + public void setReplaceConsts(boolean replaceConsts) { + this.replaceConsts = replaceConsts; + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java index 0f6a9e89e..53b04c03f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java @@ -167,7 +167,7 @@ public final class ClassInfo { @Override public int hashCode() { - return fullName.hashCode(); + return type.hashCode(); } @Override 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 new file mode 100644 index 000000000..9697c4248 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java @@ -0,0 +1,187 @@ +package jadx.core.dex.info; + +import jadx.api.IJadxArgs; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.instructions.args.LiteralArg; +import jadx.core.dex.instructions.args.PrimitiveType; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.DexNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.ResRefField; +import jadx.core.dex.nodes.parser.FieldInitAttr; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jetbrains.annotations.Nullable; + +public class ConstStorage { + + private static final class Values { + private final Map values = new HashMap(); + private final Set duplicates = new HashSet(); + + public Map getValues() { + return values; + } + + public FieldNode get(Object key) { + return values.get(key); + } + + /** + * @return true if this value is duplicated + */ + public boolean put(Object value, FieldNode fld) { + FieldNode prev = values.put(value, fld); + if (prev != null) { + values.remove(value); + duplicates.add(value); + return true; + } + if (duplicates.contains(value)) { + values.remove(value); + return true; + } + return false; + } + + public boolean contains(Object value) { + return duplicates.contains(value) || values.containsKey(value); + } + } + + private final boolean replaceEnabled; + private final Values globalValues = new Values(); + private final Map classes = new HashMap(); + + private Map resourcesNames = new HashMap(); + + public ConstStorage(IJadxArgs args) { + this.replaceEnabled = args.isReplaceConsts(); + } + + public void processConstFields(ClassNode cls, List staticFields) { + if (!replaceEnabled || staticFields.isEmpty()) { + return; + } + for (FieldNode f : staticFields) { + AccessInfo accFlags = f.getAccessFlags(); + if (accFlags.isStatic() && accFlags.isFinal()) { + FieldInitAttr fv = f.get(AType.FIELD_INIT); + if (fv != null + && fv.getValue() != null + && fv.getValueType() == FieldInitAttr.InitType.CONST + && fv != FieldInitAttr.NULL_VALUE) { + addConstField(cls, f, fv.getValue(), accFlags.isPublic()); + } + } + } + } + + private void addConstField(ClassNode cls, FieldNode fld, Object value, boolean isPublic) { + if (isPublic) { + globalValues.put(value, fld); + } else { + getClsValues(cls).put(value, fld); + } + } + + private Values getClsValues(ClassNode cls) { + Values classValues = classes.get(cls); + if (classValues == null) { + classValues = new Values(); + classes.put(cls, classValues); + } + return classValues; + } + + @Nullable + public FieldNode getConstField(ClassNode cls, Object value, boolean searchGlobal) { + DexNode dex = cls.dex(); + if (value instanceof Integer) { + String str = resourcesNames.get(value); + if (str != null) { + return new ResRefField(dex, str.replace('/', '.')); + } + } + if (!replaceEnabled) { + return null; + } + boolean foundInGlobal = globalValues.contains(value); + if (foundInGlobal && !searchGlobal) { + return null; + } + ClassNode current = cls; + while (current != null) { + Values classValues = classes.get(current); + if (classValues != null) { + FieldNode field = classValues.get(value); + if (field != null) { + if (foundInGlobal) { + return null; + } + return field; + } + } + ClassInfo parentClass = current.getClassInfo().getParentClass(); + if (parentClass == null) { + break; + } + current = dex.resolveClass(parentClass); + } + if (searchGlobal) { + return globalValues.get(value); + } + return null; + } + + @Nullable + public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) { + PrimitiveType type = arg.getType().getPrimitiveType(); + if (type == null) { + return null; + } + long literal = arg.getLiteral(); + switch (type) { + case BOOLEAN: + return getConstField(cls, literal == 1, false); + case CHAR: + return getConstField(cls, (char) literal, Math.abs(literal) > 10); + case BYTE: + return getConstField(cls, (byte) literal, Math.abs(literal) > 10); + case SHORT: + return getConstField(cls, (short) literal, Math.abs(literal) > 100); + case INT: + return getConstField(cls, (int) literal, Math.abs(literal) > 100); + case LONG: + return getConstField(cls, literal, Math.abs(literal) > 1000); + case FLOAT: + float f = Float.intBitsToFloat((int) literal); + return getConstField(cls, f, f != 0.0); + case DOUBLE: + double d = Double.longBitsToDouble(literal); + return getConstField(cls, d, d != 0); + } + return null; + } + + public void setResourcesNames(Map resourcesNames) { + this.resourcesNames = resourcesNames; + } + + public Map getResourcesNames() { + return resourcesNames; + } + + public Map getGlobalConstFields() { + return globalValues.getValues(); + } + + public boolean isReplaceEnabled() { + return replaceEnabled; + } +} 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 a5eb176fe..fcb162c53 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 @@ -2,7 +2,6 @@ package jadx.core.dex.nodes; import jadx.core.Consts; import jadx.core.codegen.CodeWriter; -import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.annotations.Annotation; import jadx.core.dex.attributes.nodes.JadxErrorAttr; import jadx.core.dex.attributes.nodes.LineAttrNode; @@ -14,10 +13,8 @@ import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.LiteralArg; -import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.nodes.parser.AnnotationsParser; import jadx.core.dex.nodes.parser.FieldInitAttr; -import jadx.core.dex.nodes.parser.FieldInitAttr.InitType; import jadx.core.dex.nodes.parser.SignatureParser; import jadx.core.dex.nodes.parser.StaticValuesParser; import jadx.core.utils.exceptions.DecodeException; @@ -27,7 +24,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -41,6 +37,7 @@ import com.android.dex.ClassData; import com.android.dex.ClassData.Field; import com.android.dex.ClassData.Method; import com.android.dex.ClassDef; +import com.android.dex.Dex; import com.android.dx.rop.code.AccessFlags; public class ClassNode extends LineAttrNode implements ILoadable, IDexNode { @@ -55,7 +52,6 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode { private final List methods; private final List fields; - private Map constFields = Collections.emptyMap(); private List innerClasses = Collections.emptyList(); // store decompiled code @@ -168,24 +164,12 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode { if (offset == 0) { return; } - StaticValuesParser parser = new StaticValuesParser(dex, dex.openSection(offset)); - int count = parser.processFields(staticFields); - if (count == 0) { - return; - } - constFields = new LinkedHashMap(count); - for (FieldNode f : staticFields) { - AccessInfo accFlags = f.getAccessFlags(); - if (accFlags.isStatic() && accFlags.isFinal()) { - FieldInitAttr fv = f.get(AType.FIELD_INIT); - if (fv != null && fv.getValue() != null && fv.getValueType() == InitType.CONST) { - if (accFlags.isPublic()) { - dex.getConstFields().put(fv.getValue(), f); - } - constFields.put(fv.getValue(), f); - } - } - } + Dex.Section section = dex.openSection(offset); + StaticValuesParser parser = new StaticValuesParser(dex, section); + parser.processFields(staticFields); + + // process const fields + root().getConstValues().processConstFields(this, staticFields); } private void parseClassSignature() { @@ -315,61 +299,14 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode { return getConstField(obj, true); } + @Nullable public FieldNode getConstField(Object obj, boolean searchGlobal) { - ClassNode cn = this; - FieldNode field; - do { - field = cn.constFields.get(obj); - } - while (field == null - && cn.clsInfo.getParentClass() != null - && (cn = dex.resolveClass(cn.clsInfo.getParentClass())) != null); - - if (field == null && searchGlobal) { - field = dex.getConstFields().get(obj); - } - if (obj instanceof Integer) { - String str = dex.root().getResourcesNames().get(obj); - if (str != null) { - ResRefField resField = new ResRefField(dex, str.replace('/', '.')); - if (field == null) { - return resField; - } - if (!field.getName().equals(resField.getName())) { - field = resField; - } - } - } - return field; + return root().getConstValues().getConstField(this, obj, searchGlobal); } + @Nullable public FieldNode getConstFieldByLiteralArg(LiteralArg arg) { - PrimitiveType type = arg.getType().getPrimitiveType(); - if (type == null) { - return null; - } - long literal = arg.getLiteral(); - switch (type) { - case BOOLEAN: - return getConstField(literal == 1, false); - case CHAR: - return getConstField((char) literal, Math.abs(literal) > 10); - case BYTE: - return getConstField((byte) literal, Math.abs(literal) > 10); - case SHORT: - return getConstField((short) literal, Math.abs(literal) > 100); - case INT: - return getConstField((int) literal, Math.abs(literal) > 100); - case LONG: - return getConstField(literal, Math.abs(literal) > 1000); - case FLOAT: - float f = Float.intBitsToFloat((int) literal); - return getConstField(f, f != 0.0); - case DOUBLE: - double d = Double.longBitsToDouble(literal); - return getConstField(d, d != 0); - } - return null; + return root().getConstValues().getConstFieldByLiteralArg(this, arg); } public FieldNode searchFieldById(int id) { @@ -532,6 +469,24 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode { return dependencies; } + @Override + public int hashCode() { + return clsInfo.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o instanceof ClassNode) { + ClassNode other = (ClassNode) o; + return clsInfo.equals(other.clsInfo); + } + return false; + + } + @Override public String toString() { return clsInfo.getFullName(); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java index a63941170..248f3c0c2 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java @@ -39,8 +39,6 @@ public class DexNode implements IDexNode { private final List classes = new ArrayList(); private final Map clsMap = new HashMap(); - private final Map constFields = new HashMap(); - private final InfoStorage infoStorage = new InfoStorage(); public DexNode(RootNode root, DexFile input) { @@ -155,10 +153,6 @@ public class DexNode implements IDexNode { return null; } - public Map getConstFields() { - return constFields; - } - public InfoStorage getInfoStorage() { return infoStorage; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java index 79118bed8..bcab66541 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java @@ -6,6 +6,7 @@ import jadx.api.ResourceType; import jadx.api.ResourcesLoader; import jadx.core.clsp.ClspGraph; import jadx.core.dex.info.ClassInfo; +import jadx.core.dex.info.ConstStorage; import jadx.core.utils.ErrorsCounter; import jadx.core.utils.StringUtils; import jadx.core.utils.exceptions.DecodeException; @@ -19,9 +20,7 @@ import jadx.core.xmlgen.ResourceStorage; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -33,9 +32,9 @@ public class RootNode { private final ErrorsCounter errorsCounter = new ErrorsCounter(); private final IJadxArgs args; private final StringUtils stringUtils; + private final ConstStorage constValues; private List dexNodes; - private Map resourcesNames = new HashMap(); @Nullable private String appPackage; private ClassNode appResClass; @@ -44,6 +43,7 @@ public class RootNode { public RootNode(IJadxArgs args) { this.args = args; this.stringUtils = new StringUtils(args); + this.constValues = new ConstStorage(args); } public void load(List inputFiles) throws DecodeException { @@ -92,7 +92,7 @@ public class RootNode { } ResourceStorage resStorage = parser.getResStorage(); - resourcesNames = resStorage.getResourcesNames(); + constValues.setResourcesNames(resStorage.getResourcesNames()); appPackage = resStorage.getAppPackage(); } @@ -181,10 +181,6 @@ public class RootNode { return errorsCounter; } - public Map getResourcesNames() { - return resourcesNames; - } - @Nullable public String getAppPackage() { return appPackage; @@ -201,4 +197,8 @@ public class RootNode { public StringUtils getStringUtils() { return stringUtils; } + + public ConstStorage getConstValues() { + return constValues; + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java index a9cc67b11..89f21bef4 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java @@ -367,7 +367,8 @@ public class BlockProcessor extends AbstractVisitor { } private static void cleanExitNodes(MethodNode mth) { - for (Iterator iterator = mth.getExitBlocks().iterator(); iterator.hasNext(); ) { + Iterator iterator = mth.getExitBlocks().iterator(); + while (iterator.hasNext()) { BlockNode exitBlock = iterator.next(); if (exitBlock.getPredecessors().isEmpty()) { mth.getBasicBlocks().remove(exitBlock); diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java index b334591bf..2e1e1baf2 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java @@ -2,8 +2,8 @@ package jadx.core.xmlgen; import jadx.api.ResourcesLoader; import jadx.core.codegen.CodeWriter; +import jadx.core.dex.info.ConstStorage; import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.StringUtils; @@ -64,17 +64,16 @@ public class BinaryXMLParser extends CommonBinaryParser { LOG.error("R class loading failed", th); } // add application constants - for (DexNode dexNode : root.getDexNodes()) { - for (Map.Entry entry : dexNode.getConstFields().entrySet()) { - Object key = entry.getKey(); - FieldNode field = entry.getValue(); - if (field.getType().equals(ArgType.INT) && key instanceof Integer) { - localStyleMap.put((Integer) key, field); - } + ConstStorage constStorage = root.getConstValues(); + Map constFields = constStorage.getGlobalConstFields(); + for (Map.Entry entry : constFields.entrySet()) { + Object key = entry.getKey(); + FieldNode field = entry.getValue(); + if (field.getType().equals(ArgType.INT) && key instanceof Integer) { + localStyleMap.put((Integer) key, field); } } - - resNames = root.getResourcesNames(); + resNames = constStorage.getResourcesNames(); attributes = new ManifestAttributes(); attributes.parseAll(); 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 c9a5321a0..0389d4f18 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -1,6 +1,5 @@ package jadx.tests.api; -import jadx.api.IJadxArgs; import jadx.api.JadxArgs; import jadx.api.JadxDecompiler; import jadx.api.JadxInternalAccess; @@ -51,8 +50,8 @@ public abstract class IntegrationTest extends TestUtils { private static final String TEST_DIRECTORY = "src/test/java"; private static final String TEST_DIRECTORY2 = "jadx-core/" + TEST_DIRECTORY; - protected boolean outputCFG = false; - protected boolean isFallback = false; + private JadxArgs args; + protected boolean deleteTmpFiles = true; protected boolean withDebugInfo = true; protected boolean unloadCls = true; @@ -64,6 +63,13 @@ public abstract class IntegrationTest extends TestUtils { protected boolean compile = true; private DynamicCompiler dynamicCompiler; + public IntegrationTest() { + args = new JadxArgs(); + args.setShowInconsistentCode(true); + args.setThreadsCount(1); + args.setSkipResources(true); + } + public ClassNode getClassNode(Class clazz) { try { File jar = getJarForClass(clazz); @@ -76,7 +82,7 @@ public abstract class IntegrationTest extends TestUtils { } public ClassNode getClassNodeFromFile(File file, String clsName) { - JadxDecompiler d = new JadxDecompiler(getArgs()); + JadxDecompiler d = new JadxDecompiler(args); try { d.loadFile(file); } catch (JadxException e) { @@ -84,7 +90,7 @@ public abstract class IntegrationTest extends TestUtils { fail(e.getMessage()); } RootNode root = JadxInternalAccess.getRoot(d); - root.getResourcesNames().putAll(resMap); + root.getConstValues().getResourcesNames().putAll(resMap); ClassNode cls = root.searchClassByName(clsName); assertThat("Class not found: " + clsName, cls, notNullValue()); @@ -136,17 +142,6 @@ public abstract class IntegrationTest extends TestUtils { assertThat(cls.getCode().toString(), not(containsString("inconsistent"))); } - private IJadxArgs getArgs() { - JadxArgs args = new JadxArgs(); - args.setCfgOutput(outputCFG); - args.setRawCFGOutput(outputCFG); - args.setFallbackMode(isFallback); - args.setShowInconsistentCode(true); - args.setThreadsCount(1); - args.setSkipResources(true); - return args; - } - private void runAutoCheck(String clsName) { try { // run 'check' method from original class @@ -228,12 +223,12 @@ public abstract class IntegrationTest extends TestUtils { return invoke(method, new Class[0]); } - public Object invoke(String method, Class[] types, Object... args) throws Exception { + public Object invoke(String method, Class[] types, Object... args) throws Exception { Method mth = getReflectMethod(method, types); return invoke(mth, args); } - public Method getReflectMethod(String method, Class... types) { + public Method getReflectMethod(String method, Class... types) { assertNotNull("dynamicCompiler not ready", dynamicCompiler); try { return dynamicCompiler.getMethod(method, types); @@ -352,6 +347,14 @@ public abstract class IntegrationTest extends TestUtils { return files; } + public JadxArgs getArgs() { + return args; + } + + public void setArgs(JadxArgs args) { + this.args = args; + } + public void setResMap(Map resMap) { this.resMap = resMap; } @@ -361,7 +364,7 @@ public abstract class IntegrationTest extends TestUtils { } protected void setFallback() { - this.isFallback = true; + this.args.setFallbackMode(true); } protected void disableCompilation() { @@ -375,7 +378,8 @@ public abstract class IntegrationTest extends TestUtils { // Use only for debug purpose @Deprecated protected void setOutputCFG() { - this.outputCFG = true; + this.args.setCfgOutput(true); + this.args.setRawCFGOutput(true); } // Use only for debug purpose diff --git a/jadx-core/src/test/java/jadx/tests/api/compiler/DynamicCompiler.java b/jadx-core/src/test/java/jadx/tests/api/compiler/DynamicCompiler.java index 427d8cb8d..b36d80786 100644 --- a/jadx-core/src/test/java/jadx/tests/api/compiler/DynamicCompiler.java +++ b/jadx-core/src/test/java/jadx/tests/api/compiler/DynamicCompiler.java @@ -57,7 +57,7 @@ public class DynamicCompiler { return instance; } - public Method getMethod(String method, Class[] types) throws Exception { + public Method getMethod(String method, Class[] types) throws Exception { for (Class type : types) { checkType(type); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchLabels.java b/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchLabels.java index 6ff6f48e0..983fb5d59 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchLabels.java +++ b/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchLabels.java @@ -6,6 +6,7 @@ import jadx.tests.api.IntegrationTest; import org.junit.Test; import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertThat; public class TestSwitchLabels extends IntegrationTest { @@ -42,7 +43,23 @@ public class TestSwitchLabels extends IntegrationTest { assertThat(code, containsString("return CONST_CDE;")); cls.addInnerClass(getClassNode(TestCls.Inner.class)); - assertThat(code, containsString("case CONST_CDE_PRIVATE")); + assertThat(code, not(containsString("case CONST_CDE_PRIVATE"))); assertThat(code, containsString(".CONST_ABC;")); } + + @Test + public void testWithDisabledConstReplace() { + getArgs().setReplaceConsts(false); + + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + assertThat(code, not(containsString("case CONST_ABC"))); + assertThat(code, containsString("case 2748")); + assertThat(code, not(containsString("return CONST_CDE;"))); + assertThat(code, containsString("return 3294;")); + + cls.addInnerClass(getClassNode(TestCls.Inner.class)); + assertThat(code, not(containsString("case CONST_CDE_PRIVATE"))); + assertThat(code, not(containsString(".CONST_ABC;"))); + } } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index 26455d152..02c7e90de 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -168,6 +168,10 @@ public class JadxSettings extends JadxCLIArgs { this.escapeUnicode = escapeUnicode; } + public void setReplaceConsts(boolean replaceConsts) { + this.replaceConsts = replaceConsts; + } + public boolean isAutoStartJobs() { return autoStartJobs; } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java index f0db23f39..417cad452 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -257,11 +257,21 @@ public class JadxSettingsWindow extends JDialog { } }); + JCheckBox replaceConsts = new JCheckBox(); + replaceConsts.setSelected(settings.isReplaceConsts()); + replaceConsts.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + settings.setReplaceConsts(e.getStateChange() == ItemEvent.SELECTED); + needReload(); + } + }); + SettingsGroup other = new SettingsGroup(NLS.str("preferences.decompile")); other.addRow(NLS.str("preferences.threads"), threadsCount); other.addRow(NLS.str("preferences.start_jobs"), autoStartJobs); other.addRow(NLS.str("preferences.showInconsistentCode"), showInconsistentCode); other.addRow(NLS.str("preferences.escapeUnicode"), escapeUnicode); + other.addRow(NLS.str("preferences.replaceConsts"), replaceConsts); other.addRow(NLS.str("preferences.fallback"), fallback); other.addRow(NLS.str("preferences.skipResourcesDecode"), resourceDecode); return other; diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index 453bec113..7401a46d1 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -59,6 +59,7 @@ preferences.check_for_updates=Check for updates on startup preferences.fallback=Fallback mode (simple dump) preferences.showInconsistentCode=Show inconsistent code preferences.escapeUnicode=Escape unicode +preferences.replaceConsts=Replace constants preferences.skipResourcesDecode=Don't decode resources preferences.threads=Processing threads count preferences.cfg=Generate methods CFG graphs (in 'dot' format)