core: allow to disable constant dereference (#106)

This commit is contained in:
Skylot 2016-03-13 12:43:24 +03:00
parent 7cba2c3f81
commit 5f302238ad
18 changed files with 366 additions and 139 deletions

View File

@ -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

View File

@ -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;
}
}

View 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;
}
}

View File

@ -32,4 +32,9 @@ public interface IJadxArgs {
boolean useSourceNameAsClassAlias();
boolean escapeUnicode();
/**
* Replace constant values with static final fields with same value
*/
boolean isReplaceConsts();
}

View File

@ -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;
}
}

View File

@ -167,7 +167,7 @@ public final class ClassInfo {
@Override
public int hashCode() {
return fullName.hashCode();
return type.hashCode();
}
@Override

View 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;
}
}

View File

@ -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();

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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();

View File

@ -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

View File

@ -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);
}

View File

@ -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;")));
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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)