Commit a532287d authored by Skylot's avatar Skylot

core: refactor deobfuscator

parent 7844e554
......@@ -56,12 +56,12 @@ public class DefaultJadxArgs implements IJadxArgs {
@Override
public int getDeobfuscationMinLength() {
return Integer.MIN_VALUE+1;
return Integer.MIN_VALUE + 1;
}
@Override
public int getDeobfuscationMaxLength() {
return Integer.MAX_VALUE-1;
return Integer.MAX_VALUE - 1;
}
@Override
......
......@@ -4,8 +4,6 @@ import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.codegen.CodeGen;
import jadx.core.codegen.CodeWriter;
import jadx.core.deobf.DefaultDeobfuscator;
import jadx.core.deobf.Deobfuscator;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
......@@ -93,6 +91,8 @@ public final class JadxDecompiler {
resources = null;
xmlParser = null;
root = null;
passes = null;
codeGen = null;
}
public static String getVersion() {
......@@ -254,56 +254,27 @@ public final class JadxDecompiler {
void parse() throws DecodeException {
reset();
root = new RootNode();
init();
root = new RootNode(args);
LOG.info("loading ...");
root.load(inputFiles);
if (args.isDeobfuscationOn()) {
final String firstInputFileName = inputFiles.get(0).getFile().getAbsolutePath();
final String inputPath = org.apache.commons.io.FilenameUtils.getFullPathNoEndSeparator(
firstInputFileName);
final String inputName = org.apache.commons.io.FilenameUtils.getBaseName(firstInputFileName);
final File deobfuscationMapFile = new File(inputPath, inputName + ".jobf");
DefaultDeobfuscator deobfuscator = new DefaultDeobfuscator();
if (deobfuscationMapFile.exists()) {
try {
deobfuscator.load(deobfuscationMapFile);
} catch (IOException e) {
LOG.error("Failed to load deobfuscation map file '{}'",
deobfuscationMapFile.getAbsolutePath());
}
}
deobfuscator.setInputData(root.getDexNodes());
deobfuscator.setMinNameLength(args.getDeobfuscationMinLength());
deobfuscator.setMaxNameLength(args.getDeobfuscationMaxLength());
root.initClassPath();
root.loadResources(getResources());
root.initAppResClass();
deobfuscator.process();
initVisitors();
}
private void initVisitors() {
for (IDexTreeVisitor pass : passes) {
try {
if (deobfuscationMapFile.exists()) {
if (args.isDeobfuscationForceSave()) {
deobfuscator.save(deobfuscationMapFile);
} else {
LOG.warn("Deobfuscation map file '{}' exists. Use command line option '--deobf=rewrite-cfg'" +
" to rewrite it", deobfuscationMapFile.getAbsolutePath());
}
} else {
deobfuscator.save(deobfuscationMapFile);
}
} catch (IOException e) {
LOG.error("Failed to load deobfuscation map file '{}'",
deobfuscationMapFile.getAbsolutePath());
pass.init(root);
} catch (Exception e) {
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
}
Deobfuscator.setDeobfuscator(deobfuscator);
}
root.loadResources(getResources());
root.initAppResClass();
}
void processClass(ClassNode cls) {
......
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.deobf.Deobfuscator;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.AccessInfo;
......@@ -116,6 +115,9 @@ public final class JavaClass implements JavaNode {
public CodePosition getDefinitionPosition(int line, int offset) {
Map<CodePosition, Object> map = getCodeAnnotations();
if (map.isEmpty()) {
return null;
}
Object obj = map.get(new CodePosition(line, offset));
if (!(obj instanceof LineAttrNode)) {
return null;
......@@ -151,16 +153,16 @@ public final class JavaClass implements JavaNode {
@Override
public String getName() {
return Deobfuscator.instance().getClassShortName(cls);
return cls.getShortName();
}
@Override
public String getFullName() {
return Deobfuscator.instance().getClassFullName(cls);
return cls.getFullName();
}
public String getPackage() {
return Deobfuscator.instance().getPackageName(cls.getPackage());
return cls.getPackage();
}
@Override
......
package jadx.api;
import jadx.core.deobf.Deobfuscator;
import java.util.List;
import org.jetbrains.annotations.NotNull;
......@@ -11,7 +9,7 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
private final List<JavaClass> classes;
JavaPackage(String name, List<JavaClass> classes) {
this.name = Deobfuscator.instance().getPackageName(name);
this.name = name;
this.classes = classes;
}
......
......@@ -14,6 +14,7 @@ import jadx.core.dex.visitors.MethodInlineVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.ReSugarCode;
import jadx.core.dex.visitors.RenameVisitor;
import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler;
import jadx.core.dex.visitors.blocksmaker.BlockFinallyExtract;
......@@ -106,6 +107,10 @@ public class Jadx {
passes.add(new ProcessVariables());
passes.add(new DependencyCollector());
if (args.isDeobfuscationOn()) {
passes.add(new RenameVisitor());
}
}
return passes;
}
......
......@@ -33,9 +33,7 @@ public final class ProcessClass {
for (IDexTreeVisitor visitor : passes) {
DepthTraversal.visit(visitor, cls);
}
for (ClassNode clsNode : cls.getDependencies()) {
process(clsNode, passes, null);
}
processDependencies(cls, passes);
cls.setState(PROCESSED);
}
if (cls.getState() == PROCESSED && codeGen != null) {
......@@ -52,4 +50,14 @@ public final class ProcessClass {
}
}
}
static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
for (ClassNode depCls : cls.getDependencies()) {
if (cls.getTopParentClass() == cls) {
// ignore inner classes of this class
continue;
}
process(depCls, passes, null);
}
}
}
package jadx.core.clsp;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
......@@ -77,15 +77,15 @@ public class ClsSet {
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
List<NClass> parents = new ArrayList<NClass>(1 + cls.getInterfaces().size());
ClassInfo superClass = cls.getSuperClass();
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
NClass c = getCls(superClass.getRawName(), names);
NClass c = getCls(superClass.getObject(), names);
if (c != null) {
parents.add(c);
}
}
for (ClassInfo iface : cls.getInterfaces()) {
NClass c = getCls(iface.getRawName(), names);
for (ArgType iface : cls.getInterfaces()) {
NClass c = getCls(iface.getObject(), names);
if (c != null) {
parents.add(c);
}
......
......@@ -45,16 +45,10 @@ public class ClspGraph {
throw new JadxRuntimeException("Classpath must be loaded first");
}
int size = classes.size();
for (ClassNode cls : classes) {
size += cls.getInnerClasses().size();
}
NClass[] nClasses = new NClass[size];
int k = 0;
for (ClassNode cls : classes) {
nClasses[k++] = addClass(cls);
for (ClassNode inner : cls.getInnerClasses()) {
nClasses[k++] = addClass(inner);
}
}
for (int i = 0; i < size; i++) {
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
......@@ -62,8 +56,9 @@ public class ClspGraph {
}
private NClass addClass(ClassNode cls) {
NClass nClass = new NClass(cls.getRawName(), -1);
nameMap.put(cls.getRawName(), nClass);
String rawName = cls.getRawName();
NClass nClass = new NClass(rawName, -1);
nameMap.put(rawName, nClass);
return nClass;
}
......
package jadx.core.clsp;
import jadx.api.DefaultJadxArgs;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
......@@ -42,7 +43,7 @@ public class ConvertToClsSet {
LOG.info("Loaded: {}", inputFile.getFile());
}
RootNode root = new RootNode();
RootNode root = new RootNode(new DefaultJadxArgs());
root.load(inputFiles);
ClsSet set = new ClsSet();
......
package jadx.core.codegen;
import jadx.api.IJadxArgs;
import jadx.core.Consts;
import jadx.core.deobf.Deobfuscator;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
......@@ -81,14 +79,14 @@ public class ClassGen {
CodeWriter clsCode = new CodeWriter();
if (!"".equals(cls.getPackage())) {
clsCode.add("package ").add(Deobfuscator.instance().getPackageName(cls.getPackage())).add(';');
clsCode.add("package ").add(cls.getPackage()).add(';');
clsCode.newLine();
}
int importsCount = imports.size();
if (importsCount != 0) {
List<String> sortImports = new ArrayList<String>(importsCount);
for (ClassInfo ic : imports) {
sortImports.add(Deobfuscator.instance().getClassFullName(ic));
sortImports.add(ic.getAlias().getFullName());
}
Collections.sort(sortImports);
......@@ -126,7 +124,7 @@ public class ClassGen {
}
// 'static' modifier not allowed for top classes (not inner)
if (!cls.getClassInfo().isInner()) {
if (!cls.getAlias().isInner()) {
af = af.remove(AccessFlags.ACC_STATIC);
}
......@@ -143,15 +141,15 @@ public class ClassGen {
} else {
clsCode.add("class ");
}
clsCode.add(Deobfuscator.instance().getClassShortName(cls));
clsCode.add(cls.getShortName());
addGenericMap(clsCode, cls.getGenericMap());
clsCode.add(' ');
ClassInfo sup = cls.getSuperClass();
ArgType sup = cls.getSuperClass();
if (sup != null
&& !sup.getFullName().equals(Consts.CLASS_OBJECT)
&& !sup.getFullName().equals(Consts.CLASS_ENUM)) {
&& !sup.equals(ArgType.OBJECT)
&& !sup.getObject().equals(ArgType.ENUM.getObject())) {
clsCode.add("extends ");
useClass(clsCode, sup);
clsCode.add(' ');
......@@ -163,8 +161,8 @@ public class ClassGen {
} else {
clsCode.add("implements ");
}
for (Iterator<ClassInfo> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
ClassInfo interf = it.next();
for (Iterator<ArgType> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
ArgType interf = it.next();
useClass(clsCode, interf);
if (it.hasNext()) {
clsCode.add(", ");
......@@ -192,7 +190,7 @@ public class ClassGen {
if (type.isGenericType()) {
code.add(type.getObject());
} else {
useClass(code, ClassInfo.fromType(cls.dex(), type));
useClass(code, type);
}
if (list != null && !list.isEmpty()) {
code.add(" extends ");
......@@ -201,7 +199,7 @@ public class ClassGen {
if (g.isGenericType()) {
code.add(g.getObject());
} else {
useClass(code, ClassInfo.fromType(cls.dex(), g));
useClass(code, g);
}
if (it.hasNext()) {
code.add(" & ");
......@@ -407,7 +405,7 @@ public class ClassGen {
if (type.isGenericType()) {
code.add(type.getObject());
} else {
useClass(code, ClassInfo.fromType(cls.dex(), type));
useClass(code, type);
}
} else if (stype == PrimitiveType.ARRAY) {
useType(code, type.getArrayElement());
......@@ -417,14 +415,9 @@ public class ClassGen {
}
}
public void useClass(CodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.dex().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
String baseClass = useClassInternal(cls.getClassInfo(), classInfo);
code.add(baseClass);
ArgType[] generics = classInfo.getType().getGenericTypes();
public void useClass(CodeWriter code, ArgType type) {
useClass(code, ClassInfo.extCls(cls.dex(), type));
ArgType[] generics = type.getGenericTypes();
if (generics != null) {
code.add('<');
int len = generics.length;
......@@ -449,54 +442,63 @@ public class ClassGen {
}
}
private String useClassInternal(ClassInfo useCls, ClassInfo classInfo) {
String fullName = classInfo.getFullName();
public void useClass(CodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.dex().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
String baseClass = useClassInternal(cls.getAlias(), classInfo.getAlias());
code.add(baseClass);
}
private String useClassInternal(ClassInfo useCls, ClassInfo extClsInfo) {
String fullName = extClsInfo.getFullName();
if (fallback) {
return fullName;
}
fullName = Deobfuscator.instance().getClassFullName(classInfo);
String shortName = Deobfuscator.instance().getClassShortName(classInfo);
if (classInfo.getPackage().equals("java.lang") && classInfo.getParentClass() == null) {
fullName = extClsInfo.getFullName();
String shortName = extClsInfo.getShortName();
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
return shortName;
} else {
// don't add import if this class inner for current class
if (isClassInnerFor(classInfo, useCls)) {
if (isClassInnerFor(extClsInfo, useCls)) {
return shortName;
}
// don't add import if this class from same package
if (classInfo.getPackage().equals(useCls.getPackage()) && !classInfo.isInner()) {
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
return shortName;
}
// don't add import if class not public (must be accessed using inheritance)
ClassNode classNode = cls.dex().resolveClass(classInfo);
ClassNode classNode = cls.dex().resolveClass(extClsInfo);
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
return shortName;
}
if (searchCollision(cls.dex(), useCls, classInfo)) {
if (searchCollision(cls.dex(), useCls, extClsInfo)) {
return fullName;
}
if (classInfo.getPackage().equals(useCls.getPackage())) {
fullName = Deobfuscator.instance().getClassName(classInfo);
if (extClsInfo.getPackage().equals(useCls.getPackage())) {
fullName = extClsInfo.getNameWithoutPackage();
}
for (ClassInfo importCls : getImports()) {
if (!importCls.equals(classInfo)
if (!importCls.equals(extClsInfo)
&& importCls.getShortName().equals(shortName)) {
if (classInfo.isInner()) {
String parent = useClassInternal(useCls, classInfo.getParentClass());
if (extClsInfo.isInner()) {
String parent = useClassInternal(useCls, extClsInfo.getParentClass().getAlias());
return parent + "." + shortName;
} else {
return fullName;
}
}
}
addImport(classInfo);
addImport(extClsInfo);
return shortName;
}
}
private void addImport(ClassInfo classInfo) {
if (parentGen != null) {
parentGen.addImport(classInfo);
parentGen.addImport(classInfo.getAlias());
} else {
imports.add(classInfo);
}
......@@ -530,7 +532,7 @@ public class ClassGen {
if (classNode != null) {
for (ClassNode inner : classNode.getInnerClasses()) {
if (inner.getShortName().equals(shortName)
&& !inner.getClassInfo().equals(searchCls)) {
&& !inner.getAlias().equals(searchCls)) {
return true;
}
}
......
......@@ -162,14 +162,14 @@ public class InsnGen {
public static void makeStaticFieldAccess(CodeWriter code, FieldInfo field, ClassGen clsGen) {
ClassInfo declClass = field.getDeclClass();
boolean fieldFromThisClass = clsGen.getClassNode().getFullName().startsWith(declClass.getFullName());
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
if (!fieldFromThisClass) {
// Android specific resources class handler
ClassInfo parentClass = declClass.getParentClass();
if (parentClass != null && parentClass.getShortName().equals("R")) {
clsGen.useClass(code, parentClass);
code.add('.');
code.add(declClass.getShortName());
code.add(declClass.getAlias().getShortName());
} else {
clsGen.useClass(code, declClass);
}
......@@ -186,6 +186,10 @@ public class InsnGen {
makeStaticFieldAccess(code, field, mgen.getClassGen());
}
public void useClass(CodeWriter code, ArgType type) {
mgen.getClassGen().useClass(code, type);
}
public void useClass(CodeWriter code, ClassInfo cls) {
mgen.getClassGen().useClass(code, cls);
}
......@@ -200,9 +204,6 @@ public class InsnGen {
protected boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
try {
if (insn.getType() == InsnType.NOP) {
return false;
}
Set<Flags> state = EnumSet.noneOf(Flags.class);
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
state.add(flag);
......@@ -531,7 +532,7 @@ public class InsnGen {
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous() && !fallback) {
// anonymous class construction
ClassInfo parent;
ArgType parent;
if (cls.getInterfaces().size() == 1) {
parent = cls.getInterfaces().get(0);
} else {
......@@ -600,7 +601,7 @@ public class InsnGen {
break;
case STATIC:
ClassInfo insnCls = mth.getParentClass().getClassInfo();
ClassInfo insnCls = mth.getParentClass().getAlias();
ClassInfo declClass = callMth.getDeclClass();
if (!insnCls.equals(declClass)) {
useClass(code, declClass);
......
package jadx.core.codegen;
import jadx.core.deobf.Deobfuscator;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
......@@ -86,7 +86,7 @@ public class MethodGen {
code.add(' ');
}
if (mth.getAccessFlags().isConstructor()) {
code.add(Deobfuscator.instance().getClassShortName(classGen.getClassNode())); // constructor
code.add(classGen.getClassNode().getShortName()); // constructor
} else {
classGen.useType(code, mth.getReturnType());
code.add(' ');
......@@ -209,7 +209,7 @@ public class MethodGen {
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) {
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
for (InsnNode insn : insnArr) {
if (insn == null) {
if (insn == null || insn.getType() == InsnType.NOP) {
continue;
}
if (addLabels && (insn.contains(AType.JUMP) || insn.contains(AType.EXC_HANDLER))) {
......
......@@ -158,8 +158,8 @@ public class NameGen {
if (alias != null) {
return alias;
}
ClassInfo clsInfo = ClassInfo.fromType(mth.dex(), type);
String shortName = clsInfo.getShortName();
ClassInfo extClsInfo = ClassInfo.extCls(mth.dex(), type);
String shortName = extClsInfo.getShortName();
String vName = fromName(shortName);
if (vName != null) {
return vName;
......@@ -223,12 +223,12 @@ public class NameGen {
return null;
}
private static String makeNameFromInvoke(MethodInfo callMth) {
private String makeNameFromInvoke(MethodInfo callMth) {
String name = callMth.getName();
if (name.startsWith("get") || name.startsWith("set")) {
return fromName(name.substring(3));
}
ArgType declType = callMth.getDeclClass().getType();
ArgType declType = callMth.getDeclClass().getAlias().getType();
if ("iterator".equals(name)) {
return "it";
}
......
package jadx.core.deobf;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.nodes.ClassNode;
public interface IDeobfuscator {
public String getPackageName(String packageName);
public String getClassShortName(ClassNode cls);
public String getClassShortName(ClassInfo clsInfo);
public String getClassName(ClassNode cls);
public String getClassName(ClassInfo clsInfo);
public String getClassFullName(ClassNode cls);
public String getClassFullName(ClassInfo clsInfo);
public String getClassFullPath(ClassInfo clsInfo);
}
......@@ -14,7 +14,7 @@ public class PackageNode {
private List<PackageNode> innerPackages = Collections.emptyList();
public static final char separatorChar = '.';
private String packageName;
private String packageAlias;
......@@ -51,14 +51,14 @@ public class PackageNode {
if (packageAlias != null) {
return packageAlias;
}
return packageName;
}
public void setAlias(String alias) {
packageAlias = alias;
}
public boolean hasAlias() {
return (packageAlias != null);
}
......@@ -108,9 +108,8 @@ public class PackageNode {
/**
* Gets inner package node by name
*
*
* @param name inner package name
*
* @return package node or {@code null}
*/
public PackageNode getInnerPackageByName(String name) {
......@@ -127,7 +126,7 @@ public class PackageNode {
/**
* Fills stack with parent packages exclude root node
*
*
* @return stack with parent packages
*/
private Stack<PackageNode> getParentPackages() {
......
package jadx.core.deobf;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.nodes.ClassNode;
public class StubDeobfuscator implements IDeobfuscator {
@Override
public String getPackageName(String packageName) {
return packageName;
}
@Override
public String getClassShortName(ClassNode cls) {
return cls.getShortName();
}
@Override
public String getClassShortName(ClassInfo clsInfo) {
return clsInfo.getShortName();
}
@Override
public String getClassName(ClassNode cls) {
return cls.getClassInfo().getNameWithoutPackage();
}
@Override
public String getClassName(ClassInfo clsInfo) {
return clsInfo.getNameWithoutPackage();
}
@Override
public String getClassFullName(ClassNode cls) {
return cls.getFullName();
}
@Override
public String getClassFullName(ClassInfo clsInfo) {
return clsInfo.getFullName();
}
@Override
public String getClassFullPath(ClassInfo clsInfo) {
return clsInfo.getFullPath();
}
}
......@@ -16,17 +16,27 @@ public final class ClassInfo {
private String fullName;
// for inner class not equals null
private ClassInfo parentClass;
// class info after rename (deobfuscation)
private ClassInfo alias;
private ClassInfo(DexNode dex, ArgType type) {
if (!type.isObject()) {
this(dex, type, true);
}
private ClassInfo(DexNode dex, ArgType type, boolean inner) {
if (!type.isObject() || type.isGeneric()) {
throw new JadxRuntimeException("Not class type: " + type);
}
this.type = type;
this.alias = this;
splitNames(dex, true);
splitNames(dex, inner);
}
public static ClassInfo fromType(DexNode dex, ArgType type) {
if (type.isArray()) {
type = ArgType.OBJECT;
}
ClassInfo cls = dex.getInfoStorage().getCls(type);
if (cls != null) {
return cls;
......@@ -39,26 +49,32 @@ public final class ClassInfo {
if (clsIndex == DexNode.NO_INDEX) {
return null;
}
ArgType type = dex.getType(clsIndex);
if (type.isArray()) {
type = ArgType.OBJECT;
}
return fromType(dex, type);
return fromType(dex, dex.getType(clsIndex));
}
public static ClassInfo fromName(DexNode dex, String clsName) {
return fromType(dex, ArgType.object(clsName));
}
public static ClassInfo extCls(DexNode dex, ArgType type) {
ClassInfo classInfo = fromName(dex, type.getObject());
return classInfo.alias;
}
public void rename(DexNode dex, String fullName) {
this.alias = new ClassInfo(dex, ArgType.object(fullName), isInner());
}
public ClassInfo getAlias() {
return alias;
}
private void splitNames(DexNode dex, boolean canBeInner) {
String fullObjectName = type.getObject();
assert fullObjectName.indexOf('/') == -1 : "Raw type: " + type;
String clsName;
int dot = fullObjectName.lastIndexOf('.');
if (dot == -1) {
// rename default package if it used from class with package (often for obfuscated apps),
pkg = Consts.DEFAULT_PACKAGE_NAME;
pkg = "";
clsName = fullObjectName;
} else {
pkg = fullObjectName.substring(0, dot);
......@@ -83,8 +99,12 @@ public final class ClassInfo {
if (NameMapper.isReserved(clsName)) {
clsName += "_";
}
this.fullName = (parentClass != null ? parentClass.getFullName() : pkg) + "." + clsName;
this.name = clsName;
if (parentClass != null) {
this.fullName = parentClass.fullName + "." + clsName;
} else {
this.fullName = pkg.isEmpty() ? clsName : pkg + "." + clsName;
}
}
public String getFullPath() {
......@@ -97,28 +117,23 @@ public final class ClassInfo {
return fullName;
}
public boolean isObject() {
return fullName.equals(Consts.CLASS_OBJECT);
}
public String getShortName() {
return name;
}
public String getRawName() {
return type.getObject();
}
public String getPackage() {
return pkg;
}
public boolean isPackageDefault() {
return pkg.isEmpty() || pkg.equals(Consts.DEFAULT_PACKAGE_NAME);
public String getRawName() {
return type.getObject();
}
public String getNameWithoutPackage() {
return (parentClass != null ? parentClass.getNameWithoutPackage() + "." : "") + name;
if (parentClass == null) {
return name;
}
return parentClass.getNameWithoutPackage() + "." + name;
}
public ClassInfo getParentClass() {
......@@ -154,7 +169,7 @@ public final class ClassInfo {
}
if (obj instanceof ClassInfo) {
ClassInfo other = (ClassInfo) obj;
return this.getFullName().equals(other.getFullName());
return this.type.equals(other.type);
}
return false;
}
......
......@@ -5,7 +5,7 @@ import jadx.core.dex.nodes.DexNode;
import com.android.dex.FieldId;
public class FieldInfo {
public final class FieldInfo {
private final ClassInfo declClass;
private final String name;
......
......@@ -621,12 +621,10 @@ public class InsnDecoder {
regs[i] = InsnArg.reg(regNum, elType, typeImmutable);
}
}
InsnNode node = new FilledNewArrayNode(elType, regs == null ? 0 : regs.length);
InsnNode node = new FilledNewArrayNode(elType, regs.length);
node.setResult(resReg == -1 ? null : InsnArg.reg(resReg, arrType));
if (regs != null) {
for (InsnArg arg : regs) {
node.addArg(arg);
}
for (InsnArg arg : regs) {
node.addArg(arg);
}
return node;
}
......
......@@ -26,6 +26,7 @@ public abstract class ArgType {
public static final ArgType OBJECT = object(Consts.CLASS_OBJECT);
public static final ArgType CLASS = object(Consts.CLASS_CLASS);
public static final ArgType STRING = object(Consts.CLASS_STRING);
public static final ArgType ENUM = object(Consts.CLASS_ENUM);
public static final ArgType THROWABLE = object(Consts.CLASS_THROWABLE);
public static final ArgType UNKNOWN = unknown(PrimitiveType.values());
......
......@@ -30,6 +30,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -46,8 +47,8 @@ public class ClassNode extends LineAttrNode implements ILoadable {
private final DexNode dex;
private final ClassInfo clsInfo;
private final AccessInfo accessFlags;
private ClassInfo superClass;
private List<ClassInfo> interfaces;
private ArgType superClass;
private List<ArgType> interfaces;
private Map<ArgType, List<ArgType>> genericMap;
private final List<MethodNode> methods;
......@@ -70,11 +71,11 @@ public class ClassNode extends LineAttrNode implements ILoadable {
if (cls.getSupertypeIndex() == DexNode.NO_INDEX) {
this.superClass = null;
} else {
this.superClass = ClassInfo.fromDex(dex, cls.getSupertypeIndex());
this.superClass = dex.getType(cls.getSupertypeIndex());
}
this.interfaces = new ArrayList<ClassInfo>(cls.getInterfaces().length);
this.interfaces = new ArrayList<ArgType>(cls.getInterfaces().length);
for (short interfaceIdx : cls.getInterfaces()) {
this.interfaces.add(ClassInfo.fromDex(dex, interfaceIdx));
this.interfaces.add(dex.getType(interfaceIdx));
}
if (cls.getClassDataOffset() != 0) {
ClassData clsData = dex.readClassData(cls);
......@@ -111,7 +112,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
int sfIdx = cls.getSourceFileIndex();
if (sfIdx != DexNode.NO_INDEX) {
String fileName = dex.getString(sfIdx);
if (!this.getFullName().contains(fileName.replace(".java", ""))
if (!clsInfo.getFullName().contains(fileName.replace(".java", ""))
&& !fileName.equals("SourceFile")) {
this.addAttr(new SourceFileAttr(fileName));
LOG.debug("Class '{}' compiled from '{}'", this, fileName);
......@@ -129,7 +130,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS);
} catch (Exception e) {
throw new DecodeException("Error decode class: " + getFullName(), e);
throw new DecodeException("Error decode class: " + clsInfo, e);
}
}
......@@ -191,12 +192,12 @@ public class ClassNode extends LineAttrNode implements ILoadable {
// parse class generic map
genericMap = sp.consumeGenericMap();
// parse super class signature
superClass = ClassInfo.fromType(dex, sp.consumeType());
superClass = sp.consumeType();
// parse interfaces signatures
for (int i = 0; i < interfaces.size(); i++) {
ArgType type = sp.consumeType();
if (type != null) {
interfaces.set(i, ClassInfo.fromType(dex, type));
interfaces.set(i, type);
} else {
break;
}
......@@ -247,11 +248,12 @@ public class ClassNode extends LineAttrNode implements ILoadable {
}
}
public ClassInfo getSuperClass() {
@Nullable
public ArgType getSuperClass() {
return superClass;
}
public List<ClassInfo> getInterfaces() {
public List<ArgType> getInterfaces() {
return interfaces;
}
......@@ -403,12 +405,14 @@ public class ClassNode extends LineAttrNode implements ILoadable {
}
public boolean isEnum() {
return getAccessFlags().isEnum() && getSuperClass().getFullName().equals(Consts.CLASS_ENUM);
return getAccessFlags().isEnum()
&& getSuperClass() != null
&& getSuperClass().getObject().equals(ArgType.ENUM.getObject());
}
public boolean isAnonymous() {
return clsInfo.isInner()
&& getShortName().startsWith(Consts.ANONYMOUS_CLASS_PREFIX)
&& clsInfo.getShortName().startsWith(Consts.ANONYMOUS_CLASS_PREFIX)
&& getDefaultConstructor() != null;
}
......@@ -429,24 +433,34 @@ public class ClassNode extends LineAttrNode implements ILoadable {
return dex;
}
public String getRawName() {
return clsInfo.getRawName();
}
/**
* Internal class info (don't use in code generation and external api).
*/
public ClassInfo getClassInfo() {
return clsInfo;
}
/**
* Class info for external usage (code generation and external api).
*/
public ClassInfo getAlias() {
return clsInfo.getAlias();
}
public String getShortName() {
return clsInfo.getShortName();
return clsInfo.getAlias().getShortName();
}
public String getFullName() {
return clsInfo.getFullName();
return clsInfo.getAlias().getFullName();
}
public String getPackage() {
return clsInfo.getPackage();
}
public String getRawName() {
return clsInfo.getRawName();
return clsInfo.getAlias().getPackage();
}
public void setCode(CodeWriter code) {
......@@ -471,6 +485,6 @@ public class ClassNode extends LineAttrNode implements ILoadable {
@Override
public String toString() {
return getFullName();
return clsInfo.getFullName();
}
}
......@@ -33,7 +33,10 @@ public class DexNode {
private final RootNode root;
private final Dex dexBuf;
private final InputFile file;
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>();
......@@ -41,12 +44,36 @@ public class DexNode {
public DexNode(RootNode root, InputFile input) {
this.root = root;
this.file = input;
this.dexBuf = input.getDexBuffer();
}
public void loadClasses() throws DecodeException {
for (ClassDef cls : dexBuf.classDefs()) {
classes.add(new ClassNode(this, cls));
ClassNode clsNode = new ClassNode(this, cls);
classes.add(clsNode);
clsMap.put(clsNode.getClassInfo(), clsNode);
}
}
void initInnerClasses() {
// move inner classes
List<ClassNode> inner = new ArrayList<ClassNode>();
for (ClassNode cls : classes) {
if (cls.getClassInfo().isInner()) {
inner.add(cls);
}
}
for (ClassNode cls : inner) {
ClassInfo clsInfo = cls.getClassInfo();
ClassNode parent = resolveClass(clsInfo.getParentClass());
if (parent == null) {
clsMap.remove(clsInfo);
clsInfo.notInner(cls.dex());
clsMap.put(clsInfo, cls);
} else {
parent.addInnerClass(cls);
}
}
}
......@@ -56,7 +83,7 @@ public class DexNode {
@Nullable
public ClassNode resolveClass(ClassInfo clsInfo) {
return root.resolveClass(clsInfo);
return clsMap.get(clsInfo);
}
@Nullable
......@@ -85,6 +112,10 @@ public class DexNode {
return infoStorage;
}
public InputFile getInputFile() {
return file;
}
// DexBuffer wrappers
public String getString(int index) {
......
......@@ -597,7 +597,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
@Override
public String toString() {
return parentClass.getFullName() + "." + mthInfo.getName()
return parentClass + "." + mthInfo.getName()
+ "(" + Utils.listToString(mthInfo.getArgumentsTypes()) + "):"
+ retType;
}
......
package jadx.core.dex.nodes;
import jadx.api.IJadxArgs;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.api.ResourcesLoader;
......@@ -27,19 +28,19 @@ import org.slf4j.LoggerFactory;
public class RootNode {
private static final Logger LOG = LoggerFactory.getLogger(RootNode.class);
private final Map<String, ClassNode> names = new HashMap<String, ClassNode>();
private final ErrorsCounter errorsCounter = new ErrorsCounter();
private final IJadxArgs args;
private List<DexNode> dexNodes;
/**
* Resources *
*/
private Map<Integer, String> resourcesNames = new HashMap<Integer, String>();
@Nullable
private String appPackage;
private ClassNode appResClass;
public RootNode(IJadxArgs args) {
this.args = args;
}
public void load(List<InputFile> dexFiles) throws DecodeException {
dexNodes = new ArrayList<DexNode>(dexFiles.size());
for (InputFile dex : dexFiles) {
......@@ -54,21 +55,7 @@ public class RootNode {
for (DexNode dexNode : dexNodes) {
dexNode.loadClasses();
}
List<ClassNode> classes = new ArrayList<ClassNode>();
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
names.put(cls.getFullName(), cls);
}
classes.addAll(dexNode.getClasses());
}
try {
initClassPath(classes);
} catch (IOException e) {
throw new DecodeException("Error loading classpath", e);
}
initInnerClasses(classes);
initInnerClasses();
}
public void loadResources(List<ResourceFile> resources) {
......@@ -99,55 +86,52 @@ public class RootNode {
ResourceStorage resStorage = parser.getResStorage();
resourcesNames = resStorage.getResourcesNames();
appPackage = resStorage.getAppPackage();
}
public void initAppResClass() {
ClassNode resCls = null;
if (appPackage != null) {
resCls = searchClassByName(appPackage + ".R");
} else {
for (ClassNode cls : names.values()) {
if (cls.getShortName().equals("R")) {
resCls = cls;
break;
}
}
ClassNode resCls;
if (appPackage == null) {
appResClass = makeClass("R");
return;
}
String fullName = appPackage + ".R";
resCls = searchClassByName(fullName);
if (resCls != null) {
appResClass = resCls;
return;
} else {
appResClass = makeClass(fullName);
}
}
private ClassNode makeClass(String clsName) {
DexNode firstDex = dexNodes.get(0);
appResClass = new ClassNode(firstDex, ClassInfo.fromName(firstDex, "R"));
ClassInfo r = ClassInfo.fromName(firstDex, clsName);
return new ClassNode(firstDex, r);
}
private static void initClassPath(List<ClassNode> classes) throws IOException, DecodeException {
if (!ArgType.isClspSet()) {
ClspGraph clsp = new ClspGraph();
clsp.load();
clsp.addApp(classes);
public void initClassPath() throws DecodeException {
try {
if (!ArgType.isClspSet()) {
ClspGraph clsp = new ClspGraph();
clsp.load();
ArgType.setClsp(clsp);
}
}
List<ClassNode> classes = new ArrayList<ClassNode>();
for (DexNode dexNode : dexNodes) {
classes.addAll(dexNode.getClasses());
}
clsp.addApp(classes);
private void initInnerClasses(List<ClassNode> classes) {
// move inner classes
List<ClassNode> inner = new ArrayList<ClassNode>();
for (ClassNode cls : classes) {
if (cls.getClassInfo().isInner()) {
inner.add(cls);
ArgType.setClsp(clsp);
}
} catch (IOException e) {
throw new DecodeException("Error loading classpath", e);
}
for (ClassNode cls : inner) {
ClassNode parent = resolveClass(cls.getClassInfo().getParentClass());
if (parent == null) {
names.remove(cls.getFullName());
cls.getClassInfo().notInner(cls.dex());
names.put(cls.getFullName(), cls);
} else {
parent.addInnerClass(cls);
}
}
private void initInnerClasses() {
for (DexNode dexNode : dexNodes) {
dexNode.initInnerClasses();
}
}
......@@ -168,12 +152,14 @@ public class RootNode {
}
public ClassNode searchClassByName(String fullName) {
return names.get(fullName);
}
public ClassNode resolveClass(ClassInfo cls) {
String fullName = cls.getFullName();
return searchClassByName(fullName);
for (DexNode dexNode : dexNodes) {
ClassInfo clsInfo = ClassInfo.fromName(dexNode, fullName);
ClassNode cls = dexNode.resolveClass(clsInfo);
if (cls != null) {
return cls;
}
}
return null;
}
public List<DexNode> getDexNodes() {
......@@ -196,4 +182,8 @@ public class RootNode {
public ClassNode getAppResClass() {
return appResClass;
}
public IJadxArgs getArgs() {
return args;
}
}
......@@ -2,11 +2,16 @@ package jadx.core.dex.visitors;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxException;
public class AbstractVisitor implements IDexTreeVisitor {
@Override
public void init(RootNode root) throws JadxException {
}
@Override
public boolean visit(ClassNode cls) throws JadxException {
return true;
}
......
......@@ -199,7 +199,7 @@ public class ClassModifier extends AbstractVisitor {
*/
private static void processStaticFieldAssign(ClassNode cls, IndexInsnNode insn) {
FieldInfo field = (FieldInfo) insn.getIndex();
String thisClass = cls.getFullName();
String thisClass = cls.getClassInfo().getFullName();
if (field.getDeclClass().getFullName().equals(thisClass)) {
FieldNode fn = cls.searchField(field);
if (fn != null && fn.getAccessFlags().isFinal()) {
......
......@@ -31,8 +31,8 @@ public class DependencyCollector extends AbstractVisitor {
private static void processClass(ClassNode cls, DexNode dex, Set<ClassNode> depList) {
addDep(dex, depList, cls.getSuperClass());
for (ClassInfo clsInfo : cls.getInterfaces()) {
addDep(dex, depList, clsInfo);
for (ArgType iType : cls.getInterfaces()) {
addDep(dex, depList, iType);
}
for (FieldNode fieldNode : cls.getFields()) {
addDep(dex, depList, fieldNode.getType());
......@@ -77,7 +77,7 @@ public class DependencyCollector extends AbstractVisitor {
private static void addDep(DexNode dex, Set<ClassNode> depList, ArgType type) {
if (type != null) {
if (type.isObject()) {
addDep(dex, depList, ClassInfo.fromName(type.getObject()));
addDep(dex, depList, ClassInfo.fromName(dex, type.getObject()));
ArgType[] genericTypes = type.getGenericTypes();
if (type.isGeneric() && genericTypes != null) {
for (ArgType argType : genericTypes) {
......
......@@ -2,7 +2,6 @@ package jadx.core.dex.visitors;
import jadx.core.codegen.CodeWriter;
import jadx.core.codegen.MethodGen;
import jadx.core.deobf.Deobfuscator;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.InsnType;
......@@ -68,7 +67,7 @@ public class DotGraphVisitor extends AbstractVisitor {
public void process(MethodNode mth) {
dot.startLine("digraph \"CFG for");
dot.add(escape(mth.getParentClass().getFullName() + "." + mth.getMethodInfo().getShortId()));
dot.add(escape(mth.getParentClass() + "." + mth.getMethodInfo().getShortId()));
dot.add("\" {");
if (useRegions) {
......@@ -85,7 +84,7 @@ public class DotGraphVisitor extends AbstractVisitor {
dot.startLine("MethodNode[shape=record,label=\"{");
dot.add(escape(mth.getAccessFlags().makeString()));
dot.add(escape(mth.getReturnType() + " "
+ mth.getParentClass().getFullName() + "." + mth.getName()
+ mth.getParentClass() + "." + mth.getName()
+ "(" + Utils.listToString(mth.getArguments(true)) + ") "));
String attrs = attributesString(mth);
......@@ -105,7 +104,7 @@ public class DotGraphVisitor extends AbstractVisitor {
+ (useRegions ? ".regions" : "")
+ (rawInsn ? ".raw" : "")
+ ".dot";
dot.save(dir, Deobfuscator.instance().getClassFullPath(mth.getParentClass().getClassInfo()) + "_graphs", fileName);
dot.save(dir, mth.getParentClass().getClassInfo().getFullPath() + "_graphs", fileName);
}
private void processMethodRegion(MethodNode mth) {
......
......@@ -2,6 +2,7 @@ package jadx.core.dex.visitors;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxException;
/**
......@@ -10,6 +11,11 @@ import jadx.core.utils.exceptions.JadxException;
public interface IDexTreeVisitor {
/**
* Called after loading dex tree, but before visitor traversal.
*/
void init(RootNode root) throws JadxException;
/**
* Visit class
*
* @return false for disable child methods and inner classes traversal
......
package jadx.core.dex.visitors;
import jadx.core.deobf.Deobfuscator;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
import org.apache.commons.io.FilenameUtils;
public class RenameVisitor extends AbstractVisitor {
private Deobfuscator deobfuscator;
@Override
public void init(RootNode root) {
String firstInputFileName = root.getDexNodes().get(0).getInputFile().getFile().getAbsolutePath();
String inputPath = FilenameUtils.getFullPathNoEndSeparator(firstInputFileName);
String inputName = FilenameUtils.getBaseName(firstInputFileName);
File deobfMapFile = new File(inputPath, inputName + ".jobf");
deobfuscator = new Deobfuscator(root.getArgs(), root.getDexNodes(), deobfMapFile);
// TODO: check classes for case sensitive names (issue #24)
// TODO: sometimes can be used source file name from 'SourceFileAttr'
deobfuscator.execute();
}
@Override
public boolean visit(ClassNode cls) throws JadxException {
// TODO: rename fields and methods
return false;
}
}
......@@ -2,7 +2,6 @@ package jadx.core.dex.visitors;
import jadx.api.IJadxArgs;
import jadx.core.codegen.CodeWriter;
import jadx.core.deobf.Deobfuscator;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.utils.exceptions.CodegenException;
......@@ -25,7 +24,7 @@ public class SaveCode extends AbstractVisitor {
public static void save(File dir, IJadxArgs args, ClassNode cls) {
CodeWriter clsCode = cls.getCode();
String fileName = Deobfuscator.instance().getClassFullPath(cls.getClassInfo()) + ".java";
String fileName = cls.getClassInfo().getFullPath() + ".java";
if (args.isFallbackMode()) {
fileName += ".jadx";
}
......
......@@ -145,70 +145,70 @@ public class SimplifyVisitor extends AbstractVisitor {
}
private static InsnNode convertInvoke(MethodNode mth, InsnNode insn) {
MethodInfo callMth = ((InvokeNode) insn).getCallMth();
MethodInfo callMth = ((InvokeNode) insn).getCallMth();
// If this is a 'new StringBuilder(xxx).append(yyy).append(zzz).toString(),
// convert it to STRING_CONCAT pseudo instruction.
if (callMth.getDeclClass().getFullName().equals(Consts.CLASS_STRING_BUILDER)
&& callMth.getShortId().equals(Consts.MTH_TOSTRING_SIGNATURE)
&& insn.getArg(0).isInsnWrap()) {
try {
List<InsnNode> chain = flattenInsnChain(insn);
int constrIndex = -1; //RAF
// Case where new StringBuilder() is called with NO args (the entire
// string is created using .append() calls:
if (chain.size()>1 && chain.get(0).getType()==InsnType.CONSTRUCTOR) {
constrIndex = 0;
} else if (chain.size()>2 && chain.get(1).getType()==InsnType.CONSTRUCTOR) {
//RAF Case where the first string element is String arg to the
// new StringBuilder("xxx") constructor
constrIndex = 1;
} else if (chain.size()>3 && chain.get(2).getType()==InsnType.CONSTRUCTOR) {
//RAF Case where the first string element is String.valueOf() arg
// to the new StringBuilder(String.valueOf(zzz)) constructor
constrIndex = 2;
}
// If this is a 'new StringBuilder(xxx).append(yyy).append(zzz).toString(),
// convert it to STRING_CONCAT pseudo instruction.
if (callMth.getDeclClass().getFullName().equals(Consts.CLASS_STRING_BUILDER)
&& callMth.getShortId().equals(Consts.MTH_TOSTRING_SIGNATURE)
&& insn.getArg(0).isInsnWrap()) {
try {
List<InsnNode> chain = flattenInsnChain(insn);
int constrIndex = -1; //RAF
// Case where new StringBuilder() is called with NO args (the entire
// string is created using .append() calls:
if (chain.size() > 1 && chain.get(0).getType() == InsnType.CONSTRUCTOR) {
constrIndex = 0;
} else if (chain.size() > 2 && chain.get(1).getType() == InsnType.CONSTRUCTOR) {
//RAF Case where the first string element is String arg to the
// new StringBuilder("xxx") constructor
constrIndex = 1;
} else if (chain.size() > 3 && chain.get(2).getType() == InsnType.CONSTRUCTOR) {
//RAF Case where the first string element is String.valueOf() arg
// to the new StringBuilder(String.valueOf(zzz)) constructor
constrIndex = 2;
}
if (constrIndex != -1) { // If we found a CONSTRUCTOR, is it a StringBuilder?
ConstructorInsn constr = (ConstructorInsn) chain.get(constrIndex);
if (constr.getClassType().getFullName().equals(Consts.CLASS_STRING_BUILDER)) {
int len = chain.size(), argInd = 1;
InsnNode concatInsn = new InsnNode(InsnType.STR_CONCAT, len-1);
InsnNode argInsn;
if (constrIndex > 0) { // There was an arg to the StringBuilder constr
InsnWrapArg iwa;
if (constrIndex==2
&& (argInsn = chain.get(1)).getType()==InsnType.INVOKE
&& ((InvokeNode)argInsn).getCallMth().getName().compareTo("valueOf")==0) {
// The argument of new StringBuilder() is a String.valueOf(chainElement0)
iwa = (InsnWrapArg)argInsn.getArg(0);
argInd = 3; // Cause for loop below to skip to after the constructor
} else {
InsnNode firstNode = chain.get(0);
if (firstNode instanceof ConstStringNode) {
ConstStringNode csn = (ConstStringNode) firstNode;
iwa = new InsnWrapArg(csn);
argInd = 2; // Cause for loop below to skip to after the constructor
} else {
return null;
}
}
concatInsn.addArg(iwa);
}
if (constrIndex != -1) { // If we found a CONSTRUCTOR, is it a StringBuilder?
ConstructorInsn constr = (ConstructorInsn) chain.get(constrIndex);
if (constr.getClassType().getFullName().equals(Consts.CLASS_STRING_BUILDER)) {
int len = chain.size(), argInd = 1;
InsnNode concatInsn = new InsnNode(InsnType.STR_CONCAT, len - 1);
InsnNode argInsn;
if (constrIndex > 0) { // There was an arg to the StringBuilder constr
InsnWrapArg iwa;
if (constrIndex == 2
&& (argInsn = chain.get(1)).getType() == InsnType.INVOKE
&& ((InvokeNode) argInsn).getCallMth().getName().compareTo("valueOf") == 0) {
// The argument of new StringBuilder() is a String.valueOf(chainElement0)
iwa = (InsnWrapArg) argInsn.getArg(0);
argInd = 3; // Cause for loop below to skip to after the constructor
} else {
InsnNode firstNode = chain.get(0);
if (firstNode instanceof ConstStringNode) {
ConstStringNode csn = (ConstStringNode) firstNode;
iwa = new InsnWrapArg(csn);
argInd = 2; // Cause for loop below to skip to after the constructor
} else {
return null;
}
}
concatInsn.addArg(iwa);
}
for (; argInd < len; argInd++) { // Add the .append(xxx) arg string to concat
concatInsn.addArg(chain.get(argInd).getArg(1));
}
concatInsn.setResult(insn.getResult());
return concatInsn;
} // end of if constructor is for StringBuilder
} // end of if we found a constructor early in the chain
for (; argInd < len; argInd++) { // Add the .append(xxx) arg string to concat
concatInsn.addArg(chain.get(argInd).getArg(1));
}
concatInsn.setResult(insn.getResult());
return concatInsn;
} // end of if constructor is for StringBuilder
} // end of if we found a constructor early in the chain
} catch (Throwable e) {
LOG.debug("Can't convert string concatenation: {} insn: {}", mth, insn, e);
}
}
return null;
} catch (Throwable e) {
LOG.debug("Can't convert string concatenation: {} insn: {}", mth, insn, e);
}
}
return null;
}
private static InsnNode simplifyArith(InsnNode insn) {
......
......@@ -4,6 +4,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import org.jetbrains.annotations.NotNull;
public class ParserStream {
protected static final Charset STRING_CHARSET_UTF16 = Charset.forName("UTF-16LE");
......@@ -15,7 +17,7 @@ public class ParserStream {
private final InputStream input;
private long readPos = 0;
public ParserStream(InputStream inputStream) {
public ParserStream(@NotNull InputStream inputStream) {
this.input = inputStream;
}
......
package jadx.tests.api;
import jadx.core.Consts;
import jadx.core.dex.nodes.ClassNode;
import java.io.File;
......@@ -21,8 +20,7 @@ public class SmaliTest extends IntegrationTest {
File smaliFile = getSmaliFile(clsName);
File outDex = createTempFile(".dex");
compileSmali(smaliFile, outDex);
String fullClsName = Consts.DEFAULT_PACKAGE_NAME + "." + clsName;
return getClassNodeFromFile(outDex, fullClsName);
return getClassNodeFromFile(outDex, clsName);
}
private static File getSmaliFile(String clsName) {
......
......@@ -12,42 +12,42 @@ import static org.junit.Assert.assertThat;
public class TestSwitchReturnFromCase extends IntegrationTest {
public static class TestCls {
public void test(int a) {
String s = null;
if (a > 1000) {
return;
}
switch (a % 4) {
case 1:
s = "1";
break;
case 2:
s = "2";
break;
case 3:
case 4:
s = "4";
break;
case 5:
return;
}
s = "5";
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsString("switch (a % 4) {"));
assertEquals(5, count(code, "case "));
assertEquals(3, count(code, "break;"));
assertThat(code, containsOne("s = \"1\";"));
assertThat(code, containsOne("s = \"2\";"));
assertThat(code, containsOne("s = \"4\";"));
assertThat(code, containsOne("s = \"5\";"));
}
public static class TestCls {
public void test(int a) {
String s = null;
if (a > 1000) {
return;
}
switch (a % 4) {
case 1:
s = "1";
break;
case 2:
s = "2";
break;
case 3:
case 4:
s = "4";
break;
case 5:
return;
}
s = "5";
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsString("switch (a % 4) {"));
assertEquals(5, count(code, "case "));
assertEquals(3, count(code, "break;"));
assertThat(code, containsOne("s = \"1\";"));
assertThat(code, containsOne("s = \"2\";"));
assertThat(code, containsOne("s = \"4\";"));
assertThat(code, containsOne("s = \"5\";"));
}
}
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