mirror of
https://github.com/skylot/jadx.git
synced 2024-11-26 22:20:50 +00:00
core: allow to disable constant dereference (#106)
This commit is contained in:
parent
7cba2c3f81
commit
5f302238ad
11
README.md
11
README.md
@ -44,19 +44,20 @@ jadx[-gui] [options] <input file> (.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
|
||||
|
@ -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<Boolean> {
|
||||
@Override
|
||||
public Boolean convert(String value) {
|
||||
return "false".equals(value);
|
||||
}
|
||||
}
|
||||
|
||||
public List<File> getInput() {
|
||||
return input;
|
||||
}
|
||||
@ -264,4 +276,9 @@ public class JadxCLIArgs implements IJadxArgs {
|
||||
public boolean escapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReplaceConsts() {
|
||||
return replaceConsts;
|
||||
}
|
||||
}
|
||||
|
22
jadx-cli/src/test/java/jadx/cli/JadxCLIArgsTest.java
Normal file
22
jadx-cli/src/test/java/jadx/cli/JadxCLIArgsTest.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -32,4 +32,9 @@ public interface IJadxArgs {
|
||||
boolean useSourceNameAsClassAlias();
|
||||
|
||||
boolean escapeUnicode();
|
||||
|
||||
/**
|
||||
* Replace constant values with static final fields with same value
|
||||
*/
|
||||
boolean isReplaceConsts();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +167,7 @@ public final class ClassInfo {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return fullName.hashCode();
|
||||
return type.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
187
jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java
Normal file
187
jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java
Normal file
@ -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<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;
|
||||
|
||||
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<MethodNode> methods;
|
||||
private final List<FieldNode> fields;
|
||||
private Map<Object, FieldNode> constFields = Collections.emptyMap();
|
||||
private List<ClassNode> 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<Object, FieldNode>(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();
|
||||
|
@ -39,8 +39,6 @@ public class DexNode implements IDexNode {
|
||||
private final List<ClassNode> classes = new ArrayList<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();
|
||||
|
||||
public DexNode(RootNode root, DexFile input) {
|
||||
@ -155,10 +153,6 @@ public class DexNode implements IDexNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map<Object, FieldNode> getConstFields() {
|
||||
return constFields;
|
||||
}
|
||||
|
||||
public InfoStorage getInfoStorage() {
|
||||
return infoStorage;
|
||||
}
|
||||
|
@ -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<DexNode> dexNodes;
|
||||
private Map<Integer, String> resourcesNames = new HashMap<Integer, String>();
|
||||
@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<InputFile> 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<Integer, String> 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;
|
||||
}
|
||||
}
|
||||
|
@ -367,7 +367,8 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
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();
|
||||
if (exitBlock.getPredecessors().isEmpty()) {
|
||||
mth.getBasicBlocks().remove(exitBlock);
|
||||
|
@ -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<Object, FieldNode> 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<Object, FieldNode> constFields = constStorage.getGlobalConstFields();
|
||||
for (Map.Entry<Object, FieldNode> 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();
|
||||
|
@ -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<Integer, String> 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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;")));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user