Commit 5f302238 authored by Skylot's avatar Skylot

core: allow to disable constant dereference (#106)

parent 7cba2c3f
...@@ -44,19 +44,20 @@ jadx[-gui] [options] <input file> (.dex, .apk, .jar or .class) ...@@ -44,19 +44,20 @@ jadx[-gui] [options] <input file> (.dex, .apk, .jar or .class)
options: options:
-d, --output-dir - output directory -d, --output-dir - output directory
-j, --threads-count - processing threads count -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 -r, --no-res - do not decode resources
-s, --no-src - do not decompile source code -s, --no-src - do not decompile source code
--show-bad-code - show inconsistent code (incorrectly decompiled) --show-bad-code - show inconsistent code (incorrectly decompiled)
--cfg - save methods control flow graph to dot file --no-replace-consts - don't replace constant value with matching constant field
--raw-cfg - save methods control flow graph (use raw instructions) --escape-unicode - escape non latin characters in strings (with \u)
-v, --verbose - verbose output
--deobf - activate deobfuscation --deobf - activate deobfuscation
--deobf-min - min length of name --deobf-min - min length of name
--deobf-max - max length of name --deobf-max - max length of name
--deobf-rewrite-cfg - force to save deobfuscation map --deobf-rewrite-cfg - force to save deobfuscation map
--deobf-use-sourcename - use source file name as class name alias --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 -h, --help - print this help
Example: Example:
jadx -d out classes.dex jadx -d out classes.dex
......
...@@ -17,6 +17,7 @@ import java.util.Map; ...@@ -17,6 +17,7 @@ import java.util.Map;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.JCommander; import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterDescription; import com.beust.jcommander.ParameterDescription;
...@@ -33,9 +34,6 @@ public class JadxCLIArgs implements IJadxArgs { ...@@ -33,9 +34,6 @@ public class JadxCLIArgs implements IJadxArgs {
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count") @Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
protected int threadsCount = Math.max(1, Runtime.getRuntime().availableProcessors() / 2); 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") @Parameter(names = {"-r", "--no-res"}, description = "do not decode resources")
protected boolean skipResources = false; protected boolean skipResources = false;
...@@ -45,14 +43,12 @@ public class JadxCLIArgs implements IJadxArgs { ...@@ -45,14 +43,12 @@ public class JadxCLIArgs implements IJadxArgs {
@Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)") @Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)")
protected boolean showInconsistentCode = false; protected boolean showInconsistentCode = false;
@Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file") @Parameter(names = "--no-replace-consts", converter = InvertedBooleanConverter.class,
protected boolean cfgOutput = false; 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") @Parameter(names = {"--escape-unicode"}, description = "escape non latin characters in strings (with \\u)")
protected boolean verbose = false; protected boolean escapeUnicode = false;
@Parameter(names = {"--deobf"}, description = "activate deobfuscation") @Parameter(names = {"--deobf"}, description = "activate deobfuscation")
protected boolean deobfuscationOn = false; protected boolean deobfuscationOn = false;
...@@ -69,8 +65,17 @@ public class JadxCLIArgs implements IJadxArgs { ...@@ -69,8 +65,17 @@ public class JadxCLIArgs implements IJadxArgs {
@Parameter(names = {"--deobf-use-sourcename"}, description = "use source file name as class name alias") @Parameter(names = {"--deobf-use-sourcename"}, description = "use source file name as class name alias")
protected boolean deobfuscationUseSourceNameAsAlias = false; protected boolean deobfuscationUseSourceNameAsAlias = false;
@Parameter(names = {"--escape-unicode"}, description = "escape non latin characters in strings (with \\u)") @Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file")
protected boolean escapeUnicode = false; 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) @Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
protected boolean printHelp = false; protected boolean printHelp = false;
...@@ -178,6 +183,13 @@ public class JadxCLIArgs implements IJadxArgs { ...@@ -178,6 +183,13 @@ public class JadxCLIArgs implements IJadxArgs {
} }
} }
public static class InvertedBooleanConverter implements IStringConverter<Boolean> {
@Override
public Boolean convert(String value) {
return "false".equals(value);
}
}
public List<File> getInput() { public List<File> getInput() {
return input; return input;
} }
...@@ -264,4 +276,9 @@ public class JadxCLIArgs implements IJadxArgs { ...@@ -264,4 +276,9 @@ public class JadxCLIArgs implements IJadxArgs {
public boolean escapeUnicode() { public boolean escapeUnicode() {
return escapeUnicode; return escapeUnicode;
} }
@Override
public boolean isReplaceConsts() {
return replaceConsts;
}
} }
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;
}
}
...@@ -32,4 +32,9 @@ public interface IJadxArgs { ...@@ -32,4 +32,9 @@ public interface IJadxArgs {
boolean useSourceNameAsClassAlias(); boolean useSourceNameAsClassAlias();
boolean escapeUnicode(); boolean escapeUnicode();
/**
* Replace constant values with static final fields with same value
*/
boolean isReplaceConsts();
} }
...@@ -25,6 +25,7 @@ public class JadxArgs implements IJadxArgs { ...@@ -25,6 +25,7 @@ public class JadxArgs implements IJadxArgs {
private int deobfuscationMaxLength = Integer.MAX_VALUE; private int deobfuscationMaxLength = Integer.MAX_VALUE;
private boolean escapeUnicode = false; private boolean escapeUnicode = false;
private boolean replaceConsts = true;
@Override @Override
public File getOutDir() { public File getOutDir() {
...@@ -160,4 +161,13 @@ public class JadxArgs implements IJadxArgs { ...@@ -160,4 +161,13 @@ public class JadxArgs implements IJadxArgs {
public void setEscapeUnicode(boolean escapeUnicode) { public void setEscapeUnicode(boolean escapeUnicode) {
this.escapeUnicode = escapeUnicode; this.escapeUnicode = escapeUnicode;
} }
@Override
public boolean isReplaceConsts() {
return replaceConsts;
}
public void setReplaceConsts(boolean replaceConsts) {
this.replaceConsts = replaceConsts;
}
} }
...@@ -167,7 +167,7 @@ public final class ClassInfo { ...@@ -167,7 +167,7 @@ public final class ClassInfo {
@Override @Override
public int hashCode() { public int hashCode() {
return fullName.hashCode(); return type.hashCode();
} }
@Override @Override
......
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<Object, FieldNode> values = new HashMap<Object, FieldNode>();
private final Set<Object> duplicates = new HashSet<Object>();
public Map<Object, FieldNode> 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<ClassNode, Values> classes = new HashMap<ClassNode, Values>();
private Map<Integer, String> resourcesNames = new HashMap<Integer, String>();
public ConstStorage(IJadxArgs args) {
this.replaceEnabled = args.isReplaceConsts();
}
public void processConstFields(ClassNode cls, List<FieldNode> 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<Integer, String> resourcesNames) {
this.resourcesNames = resourcesNames;
}
public Map<Integer, String> getResourcesNames() {
return resourcesNames;
}
public Map<Object, FieldNode> getGlobalConstFields() {
return globalValues.getValues();
}
public boolean isReplaceEnabled() {
return replaceEnabled;
}
}
...@@ -2,7 +2,6 @@ package jadx.core.dex.nodes; ...@@ -2,7 +2,6 @@ package jadx.core.dex.nodes;
import jadx.core.Consts; import jadx.core.Consts;
import jadx.core.codegen.CodeWriter; import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.annotations.Annotation; import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.nodes.JadxErrorAttr; import jadx.core.dex.attributes.nodes.JadxErrorAttr;
import jadx.core.dex.attributes.nodes.LineAttrNode; import jadx.core.dex.attributes.nodes.LineAttrNode;
...@@ -14,10 +13,8 @@ import jadx.core.dex.info.FieldInfo; ...@@ -14,10 +13,8 @@ import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.LiteralArg; 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.AnnotationsParser;
import jadx.core.dex.nodes.parser.FieldInitAttr; 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.SignatureParser;
import jadx.core.dex.nodes.parser.StaticValuesParser; import jadx.core.dex.nodes.parser.StaticValuesParser;
import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.DecodeException;
...@@ -27,7 +24,6 @@ import java.util.ArrayList; ...@@ -27,7 +24,6 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
...@@ -41,6 +37,7 @@ import com.android.dex.ClassData; ...@@ -41,6 +37,7 @@ import com.android.dex.ClassData;
import com.android.dex.ClassData.Field; import com.android.dex.ClassData.Field;
import com.android.dex.ClassData.Method; import com.android.dex.ClassData.Method;
import com.android.dex.ClassDef; import com.android.dex.ClassDef;
import com.android.dex.Dex;
import com.android.dx.rop.code.AccessFlags; import com.android.dx.rop.code.AccessFlags;
public class ClassNode extends LineAttrNode implements ILoadable, IDexNode { public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
...@@ -55,7 +52,6 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode { ...@@ -55,7 +52,6 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
private final List<MethodNode> methods; private final List<MethodNode> methods;
private final List<FieldNode> fields; private final List<FieldNode> fields;
private Map<Object, FieldNode> constFields = Collections.emptyMap();
private List<ClassNode> innerClasses = Collections.emptyList(); private List<ClassNode> innerClasses = Collections.emptyList();
// store decompiled code // store decompiled code
...@@ -168,24 +164,12 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode { ...@@ -168,24 +164,12 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
if (offset == 0) { if (offset == 0) {
return; return;
} }
StaticValuesParser parser = new StaticValuesParser(dex, dex.openSection(offset)); Dex.Section section = dex.openSection(offset);
int count = parser.processFields(staticFields); StaticValuesParser parser = new StaticValuesParser(dex, section);
if (count == 0) { parser.processFields(staticFields);
return;
} // process const fields
constFields = new LinkedHashMap<Object, FieldNode>(count); root().getConstValues().processConstFields(this, staticFields);
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);
}
}
}
} }
private void parseClassSignature() { private void parseClassSignature() {
...@@ -315,61 +299,14 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode { ...@@ -315,61 +299,14 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
return getConstField(obj, true); return getConstField(obj, true);
} }
@Nullable
public FieldNode getConstField(Object obj, boolean searchGlobal) { public FieldNode getConstField(Object obj, boolean searchGlobal) {
ClassNode cn = this; return root().getConstValues().getConstField(this, obj, searchGlobal);
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;
} }
@Nullable
public FieldNode getConstFieldByLiteralArg(LiteralArg arg) { public FieldNode getConstFieldByLiteralArg(LiteralArg arg) {
PrimitiveType type = arg.getType().getPrimitiveType(); return root().getConstValues().getConstFieldByLiteralArg(this, arg);
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;
} }
public FieldNode searchFieldById(int id) { public FieldNode searchFieldById(int id) {
...@@ -533,6 +470,24 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode { ...@@ -533,6 +470,24 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
} }
@Override @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() { public String toString() {
return clsInfo.getFullName(); return clsInfo.getFullName();
} }
......
...@@ -39,8 +39,6 @@ public class DexNode implements IDexNode { ...@@ -39,8 +39,6 @@ public class DexNode implements IDexNode {
private final List<ClassNode> classes = new ArrayList<ClassNode>(); private final List<ClassNode> classes = new ArrayList<ClassNode>();
private final Map<ClassInfo, ClassNode> clsMap = new HashMap<ClassInfo, ClassNode>(); private final Map<ClassInfo, ClassNode> clsMap = new HashMap<ClassInfo, ClassNode>();
private final Map<Object, FieldNode> constFields = new HashMap<Object, FieldNode>();
private final InfoStorage infoStorage = new InfoStorage(); private final InfoStorage infoStorage = new InfoStorage();
public DexNode(RootNode root, DexFile input) { public DexNode(RootNode root, DexFile input) {
...@@ -155,10 +153,6 @@ public class DexNode implements IDexNode { ...@@ -155,10 +153,6 @@ public class DexNode implements IDexNode {
return null; return null;
} }
public Map<Object, FieldNode> getConstFields() {
return constFields;
}
public InfoStorage getInfoStorage() { public InfoStorage getInfoStorage() {
return infoStorage; return infoStorage;
} }
......
...@@ -6,6 +6,7 @@ import jadx.api.ResourceType; ...@@ -6,6 +6,7 @@ import jadx.api.ResourceType;
import jadx.api.ResourcesLoader; import jadx.api.ResourcesLoader;
import jadx.core.clsp.ClspGraph; import jadx.core.clsp.ClspGraph;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.ConstStorage;
import jadx.core.utils.ErrorsCounter; import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.StringUtils; import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.DecodeException;
...@@ -19,9 +20,7 @@ import jadx.core.xmlgen.ResourceStorage; ...@@ -19,9 +20,7 @@ import jadx.core.xmlgen.ResourceStorage;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
...@@ -33,9 +32,9 @@ public class RootNode { ...@@ -33,9 +32,9 @@ public class RootNode {
private final ErrorsCounter errorsCounter = new ErrorsCounter(); private final ErrorsCounter errorsCounter = new ErrorsCounter();
private final IJadxArgs args; private final IJadxArgs args;
private final StringUtils stringUtils; private final StringUtils stringUtils;
private final ConstStorage constValues;
private List<DexNode> dexNodes; private List<DexNode> dexNodes;
private Map<Integer, String> resourcesNames = new HashMap<Integer, String>();
@Nullable @Nullable
private String appPackage; private String appPackage;
private ClassNode appResClass; private ClassNode appResClass;
...@@ -44,6 +43,7 @@ public class RootNode { ...@@ -44,6 +43,7 @@ public class RootNode {
public RootNode(IJadxArgs args) { public RootNode(IJadxArgs args) {
this.args = args; this.args = args;
this.stringUtils = new StringUtils(args); this.stringUtils = new StringUtils(args);
this.constValues = new ConstStorage(args);
} }
public void load(List<InputFile> inputFiles) throws DecodeException { public void load(List<InputFile> inputFiles) throws DecodeException {
...@@ -92,7 +92,7 @@ public class RootNode { ...@@ -92,7 +92,7 @@ public class RootNode {
} }
ResourceStorage resStorage = parser.getResStorage(); ResourceStorage resStorage = parser.getResStorage();
resourcesNames = resStorage.getResourcesNames(); constValues.setResourcesNames(resStorage.getResourcesNames());
appPackage = resStorage.getAppPackage(); appPackage = resStorage.getAppPackage();
} }
...@@ -181,10 +181,6 @@ public class RootNode { ...@@ -181,10 +181,6 @@ public class RootNode {
return errorsCounter; return errorsCounter;
} }
public Map<Integer, String> getResourcesNames() {
return resourcesNames;
}
@Nullable @Nullable
public String getAppPackage() { public String getAppPackage() {
return appPackage; return appPackage;
...@@ -201,4 +197,8 @@ public class RootNode { ...@@ -201,4 +197,8 @@ public class RootNode {
public StringUtils getStringUtils() { public StringUtils getStringUtils() {
return stringUtils; return stringUtils;
} }
public ConstStorage getConstValues() {
return constValues;
}
} }
...@@ -367,7 +367,8 @@ public class BlockProcessor extends AbstractVisitor { ...@@ -367,7 +367,8 @@ public class BlockProcessor extends AbstractVisitor {
} }
private static void cleanExitNodes(MethodNode mth) { private static void cleanExitNodes(MethodNode mth) {
for (Iterator<BlockNode> iterator = mth.getExitBlocks().iterator(); iterator.hasNext(); ) { Iterator<BlockNode> iterator = mth.getExitBlocks().iterator();
while (iterator.hasNext()) {
BlockNode exitBlock = iterator.next(); BlockNode exitBlock = iterator.next();
if (exitBlock.getPredecessors().isEmpty()) { if (exitBlock.getPredecessors().isEmpty()) {
mth.getBasicBlocks().remove(exitBlock); mth.getBasicBlocks().remove(exitBlock);
......
...@@ -2,8 +2,8 @@ package jadx.core.xmlgen; ...@@ -2,8 +2,8 @@ package jadx.core.xmlgen;
import jadx.api.ResourcesLoader; import jadx.api.ResourcesLoader;
import jadx.core.codegen.CodeWriter; import jadx.core.codegen.CodeWriter;
import jadx.core.dex.info.ConstStorage;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.StringUtils; import jadx.core.utils.StringUtils;
...@@ -64,17 +64,16 @@ public class BinaryXMLParser extends CommonBinaryParser { ...@@ -64,17 +64,16 @@ public class BinaryXMLParser extends CommonBinaryParser {
LOG.error("R class loading failed", th); LOG.error("R class loading failed", th);
} }
// add application constants // add application constants
for (DexNode dexNode : root.getDexNodes()) { ConstStorage constStorage = root.getConstValues();
for (Map.Entry<Object, FieldNode> entry : dexNode.getConstFields().entrySet()) { Map<Object, FieldNode> constFields = constStorage.getGlobalConstFields();
Object key = entry.getKey(); for (Map.Entry<Object, FieldNode> entry : constFields.entrySet()) {
FieldNode field = entry.getValue(); Object key = entry.getKey();
if (field.getType().equals(ArgType.INT) && key instanceof Integer) { FieldNode field = entry.getValue();
localStyleMap.put((Integer) key, field); if (field.getType().equals(ArgType.INT) && key instanceof Integer) {
} localStyleMap.put((Integer) key, field);
} }
} }
resNames = constStorage.getResourcesNames();
resNames = root.getResourcesNames();
attributes = new ManifestAttributes(); attributes = new ManifestAttributes();
attributes.parseAll(); attributes.parseAll();
......
package jadx.tests.api; package jadx.tests.api;
import jadx.api.IJadxArgs;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler; import jadx.api.JadxDecompiler;
import jadx.api.JadxInternalAccess; import jadx.api.JadxInternalAccess;
...@@ -51,8 +50,8 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -51,8 +50,8 @@ public abstract class IntegrationTest extends TestUtils {
private static final String TEST_DIRECTORY = "src/test/java"; private static final String TEST_DIRECTORY = "src/test/java";
private static final String TEST_DIRECTORY2 = "jadx-core/" + TEST_DIRECTORY; private static final String TEST_DIRECTORY2 = "jadx-core/" + TEST_DIRECTORY;
protected boolean outputCFG = false; private JadxArgs args;
protected boolean isFallback = false;
protected boolean deleteTmpFiles = true; protected boolean deleteTmpFiles = true;
protected boolean withDebugInfo = true; protected boolean withDebugInfo = true;
protected boolean unloadCls = true; protected boolean unloadCls = true;
...@@ -64,6 +63,13 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -64,6 +63,13 @@ public abstract class IntegrationTest extends TestUtils {
protected boolean compile = true; protected boolean compile = true;
private DynamicCompiler dynamicCompiler; private DynamicCompiler dynamicCompiler;
public IntegrationTest() {
args = new JadxArgs();
args.setShowInconsistentCode(true);
args.setThreadsCount(1);
args.setSkipResources(true);
}
public ClassNode getClassNode(Class<?> clazz) { public ClassNode getClassNode(Class<?> clazz) {
try { try {
File jar = getJarForClass(clazz); File jar = getJarForClass(clazz);
...@@ -76,7 +82,7 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -76,7 +82,7 @@ public abstract class IntegrationTest extends TestUtils {
} }
public ClassNode getClassNodeFromFile(File file, String clsName) { public ClassNode getClassNodeFromFile(File file, String clsName) {
JadxDecompiler d = new JadxDecompiler(getArgs()); JadxDecompiler d = new JadxDecompiler(args);
try { try {
d.loadFile(file); d.loadFile(file);
} catch (JadxException e) { } catch (JadxException e) {
...@@ -84,7 +90,7 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -84,7 +90,7 @@ public abstract class IntegrationTest extends TestUtils {
fail(e.getMessage()); fail(e.getMessage());
} }
RootNode root = JadxInternalAccess.getRoot(d); RootNode root = JadxInternalAccess.getRoot(d);
root.getResourcesNames().putAll(resMap); root.getConstValues().getResourcesNames().putAll(resMap);
ClassNode cls = root.searchClassByName(clsName); ClassNode cls = root.searchClassByName(clsName);
assertThat("Class not found: " + clsName, cls, notNullValue()); assertThat("Class not found: " + clsName, cls, notNullValue());
...@@ -136,17 +142,6 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -136,17 +142,6 @@ public abstract class IntegrationTest extends TestUtils {
assertThat(cls.getCode().toString(), not(containsString("inconsistent"))); 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) { private void runAutoCheck(String clsName) {
try { try {
// run 'check' method from original class // run 'check' method from original class
...@@ -228,12 +223,12 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -228,12 +223,12 @@ public abstract class IntegrationTest extends TestUtils {
return invoke(method, new Class<?>[0]); 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); Method mth = getReflectMethod(method, types);
return invoke(mth, args); return invoke(mth, args);
} }
public Method getReflectMethod(String method, Class... types) { public Method getReflectMethod(String method, Class<?>... types) {
assertNotNull("dynamicCompiler not ready", dynamicCompiler); assertNotNull("dynamicCompiler not ready", dynamicCompiler);
try { try {
return dynamicCompiler.getMethod(method, types); return dynamicCompiler.getMethod(method, types);
...@@ -352,6 +347,14 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -352,6 +347,14 @@ public abstract class IntegrationTest extends TestUtils {
return files; return files;
} }
public JadxArgs getArgs() {
return args;
}
public void setArgs(JadxArgs args) {
this.args = args;
}
public void setResMap(Map<Integer, String> resMap) { public void setResMap(Map<Integer, String> resMap) {
this.resMap = resMap; this.resMap = resMap;
} }
...@@ -361,7 +364,7 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -361,7 +364,7 @@ public abstract class IntegrationTest extends TestUtils {
} }
protected void setFallback() { protected void setFallback() {
this.isFallback = true; this.args.setFallbackMode(true);
} }
protected void disableCompilation() { protected void disableCompilation() {
...@@ -375,7 +378,8 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -375,7 +378,8 @@ public abstract class IntegrationTest extends TestUtils {
// Use only for debug purpose // Use only for debug purpose
@Deprecated @Deprecated
protected void setOutputCFG() { protected void setOutputCFG() {
this.outputCFG = true; this.args.setCfgOutput(true);
this.args.setRawCFGOutput(true);
} }
// Use only for debug purpose // Use only for debug purpose
......
...@@ -57,7 +57,7 @@ public class DynamicCompiler { ...@@ -57,7 +57,7 @@ public class DynamicCompiler {
return instance; return instance;
} }
public Method getMethod(String method, Class[] types) throws Exception { public Method getMethod(String method, Class<?>[] types) throws Exception {
for (Class<?> type : types) { for (Class<?> type : types) {
checkType(type); checkType(type);
} }
......
...@@ -6,6 +6,7 @@ import jadx.tests.api.IntegrationTest; ...@@ -6,6 +6,7 @@ import jadx.tests.api.IntegrationTest;
import org.junit.Test; import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
public class TestSwitchLabels extends IntegrationTest { public class TestSwitchLabels extends IntegrationTest {
...@@ -42,7 +43,23 @@ public class TestSwitchLabels extends IntegrationTest { ...@@ -42,7 +43,23 @@ public class TestSwitchLabels extends IntegrationTest {
assertThat(code, containsString("return CONST_CDE;")); assertThat(code, containsString("return CONST_CDE;"));
cls.addInnerClass(getClassNode(TestCls.Inner.class)); 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;")); 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;")));
}
} }
...@@ -168,6 +168,10 @@ public class JadxSettings extends JadxCLIArgs { ...@@ -168,6 +168,10 @@ public class JadxSettings extends JadxCLIArgs {
this.escapeUnicode = escapeUnicode; this.escapeUnicode = escapeUnicode;
} }
public void setReplaceConsts(boolean replaceConsts) {
this.replaceConsts = replaceConsts;
}
public boolean isAutoStartJobs() { public boolean isAutoStartJobs() {
return autoStartJobs; return autoStartJobs;
} }
......
...@@ -257,11 +257,21 @@ public class JadxSettingsWindow extends JDialog { ...@@ -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")); SettingsGroup other = new SettingsGroup(NLS.str("preferences.decompile"));
other.addRow(NLS.str("preferences.threads"), threadsCount); other.addRow(NLS.str("preferences.threads"), threadsCount);
other.addRow(NLS.str("preferences.start_jobs"), autoStartJobs); other.addRow(NLS.str("preferences.start_jobs"), autoStartJobs);
other.addRow(NLS.str("preferences.showInconsistentCode"), showInconsistentCode); other.addRow(NLS.str("preferences.showInconsistentCode"), showInconsistentCode);
other.addRow(NLS.str("preferences.escapeUnicode"), escapeUnicode); 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.fallback"), fallback);
other.addRow(NLS.str("preferences.skipResourcesDecode"), resourceDecode); other.addRow(NLS.str("preferences.skipResourcesDecode"), resourceDecode);
return other; return other;
......
...@@ -59,6 +59,7 @@ preferences.check_for_updates=Check for updates on startup ...@@ -59,6 +59,7 @@ preferences.check_for_updates=Check for updates on startup
preferences.fallback=Fallback mode (simple dump) preferences.fallback=Fallback mode (simple dump)
preferences.showInconsistentCode=Show inconsistent code preferences.showInconsistentCode=Show inconsistent code
preferences.escapeUnicode=Escape unicode preferences.escapeUnicode=Escape unicode
preferences.replaceConsts=Replace constants
preferences.skipResourcesDecode=Don't decode resources preferences.skipResourcesDecode=Don't decode resources
preferences.threads=Processing threads count preferences.threads=Processing threads count
preferences.cfg=Generate methods CFG graphs (in 'dot' format) preferences.cfg=Generate methods CFG graphs (in 'dot' format)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment