refactor: enable class unloading after code generation

This commit is contained in:
Skylot
2019-08-01 20:59:46 +03:00
parent 7d07fb0b77
commit a17f9136dd
34 changed files with 446 additions and 164 deletions
@@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.impl.NoOpCodeCache;
import jadx.core.utils.exceptions.JadxArgsValidateException;
public class JadxCLI {
@@ -27,6 +28,7 @@ public class JadxCLI {
static int processAndSave(JadxCLIArgs inputArgs) {
JadxArgs args = inputArgs.toJadxArgs();
args.setCodeCache(new NoOpCodeCache());
JadxDecompiler jadx = new JadxDecompiler(args);
try {
jadx.load();
@@ -0,0 +1,11 @@
package jadx.api;
import org.jetbrains.annotations.Nullable;
public interface ICodeCache {
void add(String clsFullName, ICodeInfo codeInfo);
@Nullable
ICodeInfo get(String clsFullName);
}
@@ -8,6 +8,8 @@ import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import jadx.api.impl.InMemoryCodeCache;
public class JadxArgs {
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
@@ -22,6 +24,8 @@ public class JadxArgs {
private File outDirSrc;
private File outDirRes;
private ICodeCache codeCache = new InMemoryCodeCache();
private int threadsCount = DEFAULT_THREADS_COUNT;
private boolean cfgOutput = false;
@@ -326,6 +330,14 @@ public class JadxArgs {
this.outputFormat = outputFormat;
}
public ICodeCache getCodeCache() {
return codeCache;
}
public void setCodeCache(ICodeCache codeCache) {
this.codeCache = codeCache;
}
@Override
public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles
@@ -352,6 +364,7 @@ public class JadxArgs {
+ ", fsCaseSensitive=" + fsCaseSensitive
+ ", renameFlags=" + renameFlags
+ ", outputFormat=" + outputFormat
+ ", codeCache=" + codeCache
+ '}';
}
}
@@ -208,8 +208,8 @@ public final class JadxDecompiler {
}
executor.execute(() -> {
try {
cls.decompile();
SaveCode.save(outDir, cls.getClassNode());
ICodeInfo code = cls.getCodeInfo();
SaveCode.save(outDir, cls.getClassNode(), code);
} catch (Exception e) {
LOG.error("Error saving class: {}", cls.getFullName(), e);
}
+13 -19
View File
@@ -43,24 +43,19 @@ public final class JavaClass implements JavaNode {
}
public String getCode() {
ICodeInfo code = cls.getCode();
ICodeInfo code = getCodeInfo();
if (code == null) {
decompile();
code = cls.getCode();
if (code == null) {
return "";
}
return "";
}
return code.getCodeStr();
}
public synchronized void decompile() {
if (decompiler == null) {
return;
}
if (cls.getCode() == null) {
cls.decompile();
}
public ICodeInfo getCodeInfo() {
return cls.decompile();
}
public void decompile() {
cls.decompile();
}
public synchronized String getSmali() {
@@ -140,8 +135,7 @@ public final class JavaClass implements JavaNode {
}
private Map<CodePosition, Object> getCodeAnnotations() {
decompile();
ICodeInfo code = cls.getCode();
ICodeInfo code = getCodeInfo();
if (code == null) {
return Collections.emptyMap();
}
@@ -168,19 +162,19 @@ public final class JavaClass implements JavaNode {
}
@Nullable
@Deprecated
public JavaNode getJavaNodeAtPosition(int line, int offset) {
decompile();
return getRootDecompiler().getJavaNodeAtPosition(cls.getCode(), line, offset);
return getRootDecompiler().getJavaNodeAtPosition(getCodeInfo(), line, offset);
}
@Nullable
@Deprecated
public CodePosition getDefinitionPosition() {
return getRootDecompiler().getDefinitionPosition(this);
}
public Integer getSourceLine(int decompiledLine) {
decompile();
return cls.getCode().getLineMapping().get(decompiledLine);
return getCodeInfo().getLineMapping().get(decompiledLine);
}
@Override
@@ -2,13 +2,11 @@ package jadx.api;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.Utils;
public final class JavaMethod implements JavaNode {
private final MethodNode mth;
@@ -44,19 +42,16 @@ public final class JavaMethod implements JavaNode {
}
public List<ArgType> getArguments() {
if (mth.getMethodInfo().getArgumentsTypes().isEmpty()) {
List<ArgType> infoArgTypes = mth.getMethodInfo().getArgumentsTypes();
if (infoArgTypes.isEmpty()) {
return Collections.emptyList();
}
List<RegisterArg> arguments = mth.getArguments(false);
Stream<ArgType> argTypeStream;
if (arguments == null || arguments.isEmpty() || mth.isNoCode()) {
argTypeStream = mth.getMethodInfo().getArgumentsTypes().stream();
} else {
argTypeStream = arguments.stream().map(RegisterArg::getType);
List<ArgType> arguments = mth.getArgTypes();
if (arguments == null) {
arguments = infoArgTypes;
}
return argTypeStream
.map(type -> ArgType.tryToResolveClassAlias(mth.dex(), type))
.collect(Collectors.toList());
return Utils.collectionMap(arguments,
type -> ArgType.tryToResolveClassAlias(mth.dex(), type));
}
public ArgType getReturnType() {
@@ -0,0 +1,24 @@
package jadx.api.impl;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
public class InMemoryCodeCache implements ICodeCache {
private final Map<String, ICodeInfo> storage = new ConcurrentHashMap<>();
@Override
public void add(String clsFullName, ICodeInfo codeInfo) {
storage.put(clsFullName, codeInfo);
}
@Override
public @Nullable ICodeInfo get(String clsFullName) {
return storage.get(clsFullName);
}
}
@@ -0,0 +1,19 @@
package jadx.api.impl;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
public class NoOpCodeCache implements ICodeCache {
@Override
public void add(String clsFullName, ICodeInfo codeInfo) {
// do nothing
}
@Override
public @Nullable ICodeInfo get(String clsFullName) {
return null;
}
}
@@ -14,6 +14,7 @@ import static jadx.core.dex.nodes.ProcessState.LOADED;
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED;
import static jadx.core.dex.nodes.ProcessState.UNLOADED;
public final class ProcessClass {
@@ -26,7 +27,8 @@ public final class ProcessClass {
process(topParentClass);
return;
}
if (cls.getState() == PROCESS_COMPLETE) {
if (cls.getState() == PROCESS_COMPLETE
|| cls.getState() == UNLOADED) {
// nothing to do
return;
}
@@ -58,8 +60,9 @@ public final class ProcessClass {
process(cls);
cls.getDependencies().forEach(ProcessClass::process);
// TODO: unload class (need to build dependency tree or allow to load class several times)
return CodeGen.generate(cls);
ICodeInfo code = CodeGen.generate(cls);
cls.unload();
return code;
} catch (Throwable e) {
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
}
@@ -25,7 +25,6 @@ import org.slf4j.LoggerFactory;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.GenericInfo;
import jadx.core.dex.nodes.MethodNode;
@@ -113,7 +112,7 @@ public class ClsSet {
}
private void processMethodDetails(List<NMethod> methods, MethodNode mth, AccessInfo accessFlags) {
List<RegisterArg> args = mth.getArguments(false);
List<ArgType> args = mth.getArgTypes();
boolean genericArg = false;
ArgType[] genericArgs;
if (args.isEmpty()) {
@@ -122,8 +121,7 @@ public class ClsSet {
int argsCount = args.size();
genericArgs = new ArgType[argsCount];
for (int i = 0; i < argsCount; i++) {
RegisterArg arg = args.get(i);
ArgType argType = arg.getType();
ArgType argType = args.get(i);
if (argType.isGeneric() || argType.isGenericType()) {
genericArgs[i] = argType;
genericArg = true;
@@ -17,6 +17,7 @@ import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
@@ -756,8 +757,7 @@ public class InsnGen {
if (arg.contains(AFlag.SKIP_ARG)) {
continue;
}
RegisterArg callArg = getCallMthArg(callMth, i - startArgNum);
if (callArg != null && callArg.contains(AFlag.SKIP_ARG)) {
if (SkipMethodArgsAttr.isSkip(callMth, i - startArgNum)) {
continue;
}
if (!firstArg) {
@@ -778,7 +778,7 @@ public class InsnGen {
if (callMth == null) {
return null;
}
List<RegisterArg> args = callMth.getArguments(false);
List<RegisterArg> args = callMth.getArgRegs();
if (args != null && num < args.size()) {
return args.get(num);
}
@@ -789,18 +789,18 @@ public class InsnGen {
* Add additional cast for overloaded method argument.
*/
private boolean processOverloadedArg(CodeWriter code, MethodNode callMth, InsnArg arg, int origPos) {
List<RegisterArg> arguments = callMth.getArguments(false);
if (arguments == null || arguments.isEmpty()) {
List<ArgType> argTypes = callMth.getArgTypes();
if (argTypes == null) {
// try to load class
callMth.getParentClass().loadAndProcess();
arguments = callMth.getArguments(false);
argTypes = callMth.getArgTypes();
}
ArgType origType;
if (arguments == null || arguments.isEmpty()) {
if (argTypes == null) {
mth.addComment("JADX INFO: used method not loaded: " + callMth + ", types can be incorrect");
origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
} else {
origType = arguments.get(origPos).getInitType();
origType = argTypes.get(origPos);
if (origType.isGenericType() && !callMth.getParentClass().equals(mth.getParentClass())) {
// cancel cast
return false;
@@ -887,11 +887,10 @@ public class InsnGen {
} else {
// remap args
InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()];
List<RegisterArg> callArgs = callMthNode.getArguments(true);
for (int i = 0; i < callArgs.size(); i++) {
int[] regNums = mia.getArgsRegNums();
for (int i = 0; i < regNums.length; i++) {
InsnArg arg = insn.getArg(i);
RegisterArg callArg = callArgs.get(i);
regs[callArg.getRegNum()] = arg;
regs[regNums[i]] = arg;
}
// replace args
InsnNode inlCopy = inl.copy();
@@ -1,5 +1,6 @@
package jadx.core.codegen;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -88,7 +89,6 @@ public class MethodGen {
if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) {
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
}
CodeGenUtils.addSourceFileInfo(code, mth);
if (mth.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
}
@@ -113,11 +113,11 @@ public class MethodGen {
}
code.add('(');
List<RegisterArg> args = mth.getArguments(false);
List<RegisterArg> args = mth.getArgRegs();
if (mth.getMethodInfo().isConstructor()
&& mth.getParentClass().contains(AType.ENUM_CLASS)) {
if (args.size() == 2) {
args.clear();
args = Collections.emptyList();
} else if (args.size() > 2) {
args = args.subList(2, args.size());
} else {
@@ -1,5 +1,9 @@
package jadx.core.dex.attributes;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
@@ -18,6 +22,7 @@ import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.trycatch.CatchAttr;
@@ -32,35 +37,54 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
*/
public class AType<T extends IAttribute> {
public static final AType<AttrList<JumpInfo>> JUMP = new AType<>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
// class, method, field
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<>();
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
// class, method
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>(); // code failed to decompile completely
public static final AType<AttrList<String>> JADX_WARN = new AType<>(); // mark code as inconsistent (code can be viewed)
public static final AType<AttrList<String>> COMMENTS = new AType<>(); // any additional info about decompilation
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<>();
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
public static final AType<FieldInitAttr> FIELD_INIT = new AType<>();
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
// class
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>();
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<>();
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<>();
public static final AType<PhiListAttr> PHI_LIST = new AType<>();
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>();
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
// field
public static final AType<FieldInitAttr> FIELD_INIT = new AType<>();
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<>();
// method
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<>();
public static final AType<SkipMethodArgsAttr> SKIP_MTH_ARGS = new AType<>();
// registers
// region
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
// block
public static final AType<PhiListAttr> PHI_LIST = new AType<>();
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
// block or insn
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
// instruction
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
public static final AType<AttrList<JumpInfo>> JUMP = new AType<>();
// register
public static final AType<RegDebugInfoAttr> REG_DEBUG_INFO = new AType<>();
public static final Set<AType<?>> SKIP_ON_UNLOAD = new HashSet<>(Arrays.asList(
FIELD_REPLACE,
METHOD_INLINE,
SKIP_MTH_ARGS));
}
@@ -97,6 +97,17 @@ public abstract class AttrNode implements IAttributeNode {
unloadIfEmpty();
}
/**
* Remove all attribute with exceptions from {@link AType#SKIP_ON_UNLOAD}
*/
public void unloadAttributes() {
if (storage == EMPTY_ATTR_STORAGE) {
return;
}
storage.unloadAttributes();
unloadIfEmpty();
}
@Override
public List<String> getAttributesStringsList() {
return storage.getAttributeStrings();
@@ -20,11 +20,11 @@ import jadx.core.utils.Utils;
public class AttributeStorage {
private final Set<AFlag> flags;
private final Map<AType<?>, IAttribute> attributes;
private Map<AType<?>, IAttribute> attributes;
public AttributeStorage() {
flags = EnumSet.noneOf(AFlag.class);
attributes = new IdentityHashMap<>();
attributes = Collections.emptyMap();
}
public void add(AFlag flag) {
@@ -32,7 +32,7 @@ public class AttributeStorage {
}
public void add(IAttribute attr) {
attributes.put(attr.getType(), attr);
writeAttributes().put(attr.getType(), attr);
}
public <T> void add(AType<AttrList<T>> type, T obj) {
@@ -46,7 +46,7 @@ public class AttributeStorage {
public void addAll(AttributeStorage otherList) {
flags.addAll(otherList.flags);
attributes.putAll(otherList.attributes);
writeAttributes().putAll(otherList.attributes);
}
public boolean contains(AFlag flag) {
@@ -80,20 +80,41 @@ public class AttributeStorage {
}
public <T extends IAttribute> void remove(AType<T> type) {
attributes.remove(type);
}
public void remove(IAttribute attr) {
AType<? extends IAttribute> type = attr.getType();
IAttribute a = attributes.get(type);
if (a == attr) {
if (!attributes.isEmpty()) {
attributes.remove(type);
}
}
public void remove(IAttribute attr) {
if (!attributes.isEmpty()) {
AType<? extends IAttribute> type = attr.getType();
IAttribute a = attributes.get(type);
if (a == attr) {
attributes.remove(type);
}
}
}
private Map<AType<?>, IAttribute> writeAttributes() {
if (attributes.isEmpty()) {
attributes = new IdentityHashMap<>(5);
}
return attributes;
}
public void clear() {
flags.clear();
attributes.clear();
if (!attributes.isEmpty()) {
attributes.clear();
}
}
public synchronized void unloadAttributes() {
if (attributes.isEmpty()) {
return;
}
Set<AType<?>> skipOnUnload = AType.SKIP_ON_UNLOAD;
attributes.keySet().removeIf(attrType -> !skipOnUnload.contains(attrType));
}
public List<String> getAttributeStrings() {
@@ -1,21 +1,46 @@
package jadx.core.dex.attributes.nodes;
import java.util.List;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
public class MethodInlineAttr implements IAttribute {
public static void markForInline(MethodNode mth, InsnNode replaceInsn) {
List<RegisterArg> allArgRegs = mth.getAllArgRegs();
int argsCount = allArgRegs.size();
int[] regNums = new int[argsCount];
for (int i = 0; i < argsCount; i++) {
RegisterArg reg = allArgRegs.get(i);
regNums[i] = reg.getRegNum();
}
mth.addAttr(new MethodInlineAttr(replaceInsn, regNums));
}
private final InsnNode insn;
public MethodInlineAttr(InsnNode insn) {
/**
* Store method arguments register numbers to allow remap registers
*/
private final int[] argsRegNums;
private MethodInlineAttr(InsnNode insn, int[] argsRegNums) {
this.insn = insn;
this.argsRegNums = argsRegNums;
}
public InsnNode getInsn() {
return insn;
}
public int[] getArgsRegNums() {
return argsRegNums;
}
@Override
public AType<MethodInlineAttr> getType() {
return AType.METHOD_INLINE;
@@ -0,0 +1,67 @@
package jadx.core.dex.attributes.nodes;
import java.util.BitSet;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class SkipMethodArgsAttr implements IAttribute {
public static void skipArg(MethodNode mth, RegisterArg arg) {
int argNum = Utils.indexInList(mth.getArgRegs(), arg);
if (argNum == -1) {
throw new JadxRuntimeException("Arg not found: " + arg);
}
skipArg(mth, argNum);
}
public static void skipArg(MethodNode mth, int argNum) {
SkipMethodArgsAttr attr = mth.get(AType.SKIP_MTH_ARGS);
if (attr == null) {
attr = new SkipMethodArgsAttr(mth);
mth.addAttr(attr);
}
attr.skip(argNum);
}
public static boolean isSkip(@Nullable MethodNode mth, int argNum) {
if (mth == null) {
return false;
}
SkipMethodArgsAttr attr = mth.get(AType.SKIP_MTH_ARGS);
if (attr == null) {
return false;
}
return attr.isSkip(argNum);
}
private final BitSet skipArgs;
private SkipMethodArgsAttr(MethodNode mth) {
this.skipArgs = new BitSet(mth.getArgRegs().size());
}
public void skip(int argNum) {
skipArgs.set(argNum);
}
public boolean isSkip(int argNum) {
return skipArgs.get(argNum);
}
@Override
public AType<SkipMethodArgsAttr> getType() {
return AType.SKIP_MTH_ARGS;
}
@Override
public String toString() {
return "SKIP_MTH_ARGS: " + skipArgs;
}
}
@@ -16,6 +16,7 @@ import com.android.dex.ClassData.Method;
import com.android.dex.ClassDef;
import com.android.dex.Dex;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
import jadx.core.Consts;
import jadx.core.ProcessClass;
@@ -52,10 +53,8 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
private final List<MethodNode> methods;
private final List<FieldNode> fields;
private List<ClassNode> innerClasses = new ArrayList<>();
private List<ClassNode> innerClasses = Collections.emptyList();
// store decompiled code
private ICodeInfo code;
// store smali
private String smali;
// store parent for inner classes or 'this' otherwise
@@ -248,16 +247,23 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
ProcessClass.process(this);
}
public ICodeInfo decompile() {
public synchronized ICodeInfo decompile() {
ICodeCache codeCache = root().getCodeCache();
ClassNode topParentClass = getTopParentClass();
String clsRawName = topParentClass.getRawName();
ICodeInfo code = codeCache.get(clsRawName);
if (code != null) {
return code;
}
ICodeInfo codeInfo = ProcessClass.generateCode(this);
// TODO: don't store code in class node
setCode(codeInfo);
ICodeInfo codeInfo = ProcessClass.generateCode(topParentClass);
codeCache.add(clsRawName, codeInfo);
return codeInfo;
}
public ICodeInfo getCode() {
return decompile();
}
@Override
public void load() {
for (MethodNode mth : getMethods()) {
@@ -275,12 +281,10 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
@Override
public void unload() {
for (MethodNode mth : getMethods()) {
mth.unload();
}
for (ClassNode innerCls : getInnerClasses()) {
innerCls.unload();
}
methods.forEach(MethodNode::unload);
innerClasses.forEach(ClassNode::unload);
fields.forEach(FieldNode::unloadAttributes);
unloadAttributes();
setState(UNLOADED);
}
@@ -411,6 +415,9 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
}
public void addInnerClass(ClassNode cls) {
if (innerClasses.isEmpty()) {
innerClasses = new ArrayList<>(5);
}
innerClasses.add(cls);
cls.parentClass = this;
}
@@ -488,14 +495,6 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
return clsInfo.getAliasPkg();
}
public void setCode(ICodeInfo code) {
this.code = code;
}
public ICodeInfo getCode() {
return code;
}
public void setSmali(String smali) {
this.smali = smali;
}
@@ -58,23 +58,27 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
private boolean noCode;
private int regsCount;
private InsnNode[] instructions;
private int codeSize;
private int debugInfoOffset;
private boolean loaded;
// additional info available after load, keep on unload
private ArgType retType;
private RegisterArg thisArg;
private List<RegisterArg> argsList;
private List<SSAVar> sVars;
private List<ArgType> argTypes;
private List<GenericInfo> generics;
// decompilation data, reset on unload
private RegisterArg thisArg;
private List<RegisterArg> argsList;
private InsnNode[] instructions;
private List<BlockNode> blocks;
private BlockNode enterBlock;
private List<BlockNode> exitBlocks;
private Region region;
private List<SSAVar> sVars;
private List<ExceptionHandler> exceptionHandlers;
private List<LoopInfo> loops;
private Region region;
public MethodNode(ClassNode classNode, Method mthData, boolean isVirtual) {
this.mthInfo = MethodInfo.fromDex(classNode.dex(), mthData.getMethodIndex());
@@ -88,14 +92,14 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
@Override
public void unload() {
regsCount = -1;
loaded = false;
if (noCode) {
return;
}
// don't unload retType and argsList, will be used in jadx-gui after class unload
// don't unload retType, argTypes, generics
thisArg = null;
argsList = null;
sVars = Collections.emptyList();
generics = Collections.emptyList();
instructions = null;
blocks = null;
enterBlock = null;
@@ -103,15 +107,17 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
region = null;
exceptionHandlers = Collections.emptyList();
loops = Collections.emptyList();
unloadAttributes();
}
@Override
public void load() throws DecodeException {
if (regsCount != -1) {
if (loaded) {
// method already loaded
return;
}
try {
loaded = true;
if (noCode) {
regsCount = 0;
codeSize = 0;
@@ -135,6 +141,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
this.debugInfoOffset = mthCode.getDebugInfoOffset();
} catch (Exception e) {
if (!noCode) {
unload();
noCode = true;
// load without code
load();
@@ -166,30 +173,35 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
}
private void initMethodTypes() {
if (!parseSignature()) {
retType = mthInfo.getReturnType();
initArguments(mthInfo.getArgumentsTypes());
List<ArgType> types = parseSignature();
if (types == null) {
this.retType = mthInfo.getReturnType();
this.argTypes = mthInfo.getArgumentsTypes();
} else {
this.argTypes = types;
}
initArguments(this.argTypes);
}
private boolean parseSignature() {
@Nullable
private List<ArgType> parseSignature() {
SignatureParser sp = SignatureParser.fromNode(this);
if (sp == null) {
return false;
return null;
}
try {
generics = sp.consumeGenericMap();
this.generics = sp.consumeGenericMap();
List<ArgType> argsTypes = sp.consumeMethodArgs();
retType = sp.consumeType();
this.retType = sp.consumeType();
List<ArgType> mthArgs = mthInfo.getArgumentsTypes();
if (argsTypes.size() != mthArgs.size()) {
if (argsTypes.isEmpty()) {
return false;
return null;
}
if (!mthInfo.isConstructor()) {
LOG.warn("Wrong signature parse result: {} -> {}, not generic version: {}", sp, argsTypes, mthArgs);
return false;
return null;
} else if (getParentClass().getAccessFlags().isEnum()) {
// TODO:
argsTypes.add(0, mthArgs.get(0));
@@ -199,14 +211,13 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
argsTypes.add(0, mthArgs.get(0));
}
if (argsTypes.size() != mthArgs.size()) {
return false;
return null;
}
}
initArguments(argsTypes);
return true;
return argsTypes;
} catch (JadxRuntimeException e) {
LOG.error("Method signature parse error: {}", this, e);
return false;
return null;
}
}
@@ -242,18 +253,31 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
}
}
public List<RegisterArg> getArguments(boolean includeThis) {
if (includeThis && thisArg != null) {
List<RegisterArg> list = new ArrayList<>(argsList.size() + 1);
list.add(thisArg);
list.addAll(argsList);
return list;
/**
* Return null only if method not yet loaded
*/
@Nullable
public List<ArgType> getArgTypes() {
return argTypes;
}
public List<RegisterArg> getArgRegs() {
if (argsList == null) {
throw new JadxRuntimeException("Method args not loaded: " + this
+ ", class status: " + parentClass.getTopParentClass().getState());
}
return argsList;
}
public void skipFirstArgument() {
this.add(AFlag.SKIP_FIRST_ARG);
public List<RegisterArg> getAllArgRegs() {
List<RegisterArg> argRegs = getArgRegs();
if (thisArg != null) {
List<RegisterArg> list = new ArrayList<>(argRegs.size() + 1);
list.add(thisArg);
list.addAll(argRegs);
return list;
}
return argRegs;
}
@Nullable
@@ -261,6 +285,10 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
return thisArg;
}
public void skipFirstArgument() {
this.add(AFlag.SKIP_FIRST_ARG);
}
public ArgType getReturnType() {
return retType;
}
@@ -690,6 +718,10 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
return -1;
}
public boolean isLoaded() {
return loaded;
}
@Override
public int hashCode() {
return mthInfo.hashCode();
@@ -8,6 +8,7 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeCache;
import jadx.api.JadxArgs;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
@@ -46,6 +47,8 @@ public class RootNode {
private final CacheStorage cacheStorage = new CacheStorage();
private final TypeUpdate typeUpdate;
private final ICodeCache codeCache;
private ClspGraph clsp;
private List<DexNode> dexNodes;
@Nullable
@@ -59,6 +62,7 @@ public class RootNode {
this.stringUtils = new StringUtils(args);
this.constValues = new ConstStorage(args);
this.typeUpdate = new TypeUpdate(this);
this.codeCache = args.getCodeCache();
}
public void load(List<InputFile> inputFiles) {
@@ -287,4 +291,8 @@ public class RootNode {
public TypeUpdate getTypeUpdate() {
return typeUpdate;
}
public ICodeCache getCodeCache() {
return codeCache;
}
}
@@ -10,6 +10,7 @@ import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
@@ -103,7 +104,7 @@ public class ClassModifier extends AbstractVisitor {
if (mth.isNoCode() || !mth.getAccessFlags().isConstructor()) {
return false;
}
List<RegisterArg> args = mth.getArguments(false);
List<RegisterArg> args = mth.getArgRegs();
if (args.isEmpty() || mth.contains(AFlag.SKIP_FIRST_ARG)) {
return false;
}
@@ -157,7 +158,7 @@ public class ClassModifier extends AbstractVisitor {
}
// remove synthetic constructor for inner classes
if (af.isConstructor() && mth.getBasicBlocks().size() == 2) {
List<RegisterArg> args = mth.getArguments(false);
List<RegisterArg> args = mth.getArgRegs();
if (isRemovedClassInArgs(cls, args)) {
modifySyntheticMethod(cls, mth, args);
}
@@ -198,13 +199,15 @@ public class ClassModifier extends AbstractVisitor {
// remove first arg for non-static class (references to outer class)
RegisterArg firstArg = args.get(0);
if (firstArg.getType().equals(cls.getParentClass().getClassInfo().getType())) {
firstArg.add(AFlag.SKIP_ARG);
SkipMethodArgsAttr.skipArg(mth, 0);
}
// remove unused args
for (RegisterArg arg : args) {
int argsCount = args.size();
for (int i = 0; i < argsCount; i++) {
RegisterArg arg = args.get(i);
SSAVar sVar = arg.getSVar();
if (sVar != null && sVar.getUseCount() == 0) {
arg.add(AFlag.SKIP_ARG);
SkipMethodArgsAttr.skipArg(mth, i);
}
}
mth.add(AFlag.DONT_GENERATE);
@@ -306,7 +309,7 @@ public class ClassModifier extends AbstractVisitor {
// remove public empty constructors (static or default)
if (af.isConstructor()
&& (af.isPublic() || af.isStatic())
&& mth.getArguments(false).isEmpty()) {
&& mth.getArgRegs().isEmpty()) {
List<BlockNode> bb = mth.getBasicBlocks();
if (bb == null || bb.isEmpty() || BlockUtils.isAllBlocksEmpty(bb)) {
if (af.isStatic() && mth.getMethodInfo().isClassInit()) {
@@ -97,7 +97,7 @@ public class DotGraphVisitor extends AbstractVisitor {
dot.add(escape(mth.getAccessFlags().makeString()));
dot.add(escape(mth.getReturnType() + " "
+ mth.getParentClass() + '.' + mth.getName()
+ '(' + Utils.listToString(mth.getArguments(true)) + ") "));
+ '(' + Utils.listToString(mth.getAllArgRegs()) + ") "));
String attrs = attributesString(mth);
if (!attrs.isEmpty()) {
@@ -39,7 +39,11 @@ public class InitCodeVariables extends AbstractVisitor {
}
private static void initCodeVars(MethodNode mth) {
for (RegisterArg mthArg : mth.getArguments(true)) {
RegisterArg thisArg = mth.getThisArg();
if (thisArg != null) {
initCodeVar(thisArg.getSVar());
}
for (RegisterArg mthArg : mth.getArgRegs()) {
initCodeVar(mthArg.getSVar());
}
for (SSAVar ssaVar : mth.getSVars()) {
@@ -101,7 +101,7 @@ public class MethodInlineVisitor extends AbstractVisitor {
for (RegisterArg regArg : regArgs) {
copy.replaceArg(regArg, regArg.duplicate(regArg.getRegNum(), null));
}
mth.addAttr(new MethodInlineAttr(copy));
MethodInlineAttr.markForInline(mth, copy);
mth.add(AFlag.DONT_GENERATE);
}
}
@@ -277,7 +277,7 @@ public class ModVisitor extends AbstractVisitor {
}
classNode.loadAndProcess();
Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(callMthNode, co);
if (argsMap.isEmpty() && !callMthNode.getArguments(true).isEmpty()) {
if (argsMap.isEmpty() && !callMthNode.getArgRegs().isEmpty()) {
return;
}
@@ -306,7 +306,7 @@ public class ModVisitor extends AbstractVisitor {
MethodInfo callMth = callMthNode.getMethodInfo();
ClassNode cls = callMthNode.getParentClass();
ClassNode parentClass = cls.getParentClass();
List<RegisterArg> argList = callMthNode.getArguments(false);
List<RegisterArg> argList = callMthNode.getArgRegs();
int startArg = 0;
if (callMth.getArgsCount() != 0 && callMth.getArgumentsTypes().get(0).equals(parentClass.getClassInfo().getType())) {
startArg = 1;
@@ -220,7 +220,7 @@ public class PrepareForCodeGen extends AbstractVisitor {
Set<RegisterArg> regArgs = new HashSet<>();
constrInsn.getRegisterArgs(regArgs);
regArgs.remove(mth.getThisArg());
regArgs.removeAll(mth.getArguments(false));
regArgs.removeAll(mth.getArgRegs());
if (!regArgs.isEmpty()) {
mth.addWarn("Illegal instructions before constructor call");
} else {
@@ -14,11 +14,10 @@ public class SaveCode {
private SaveCode() {
}
public static void save(File dir, ClassNode cls) {
public static void save(File dir, ClassNode cls, ICodeInfo code) {
if (cls.contains(AFlag.DONT_GENERATE)) {
return;
}
ICodeInfo code = cls.getCode();
if (code == null) {
throw new JadxRuntimeException("Code not generated for class " + cls.getFullName());
}
@@ -66,7 +66,11 @@ public class DebugInfoParseVisitor extends AbstractVisitor {
RegDebugInfoAttr debugInfoAttr = new RegDebugInfoAttr(var);
if (start < 0) {
// attach to method arguments
for (RegisterArg arg : mth.getArguments(true)) {
RegisterArg thisArg = mth.getThisArg();
if (thisArg != null) {
attachDebugInfo(thisArg, var, debugInfoAttr);
}
for (RegisterArg arg : mth.getArgRegs()) {
attachDebugInfo(arg, var, debugInfoAttr);
}
start = 0;
@@ -58,7 +58,7 @@ public class DebugInfoParser {
int line = section.readUleb128();
int paramsCount = section.readUleb128();
List<RegisterArg> mthArgs = mth.getArguments(false);
List<RegisterArg> mthArgs = mth.getArgRegs();
for (int i = 0; i < paramsCount; i++) {
int nameId = section.readUleb128() - 1;
@@ -21,7 +21,13 @@ final class RenameState {
mth.getEnterBlock(),
new SSAVar[regsCount],
new int[regsCount]);
for (RegisterArg arg : mth.getArguments(true)) {
RegisterArg thisArg = mth.getThisArg();
if (thisArg != null) {
SSAVar ssaVar = state.startVar(thisArg);
ssaVar.add(AFlag.THIS);
ssaVar.add(AFlag.METHOD_ARGUMENT);
}
for (RegisterArg arg : mth.getArgRegs()) {
SSAVar ssaVar = state.startVar(arg);
ssaVar.add(AFlag.METHOD_ARGUMENT);
}
@@ -107,10 +107,15 @@ public class SSATransform extends AbstractVisitor {
}
int size = block.getPredecessors().size();
if (mth.getEnterBlock() == block) {
for (RegisterArg arg : mth.getArguments(true)) {
if (arg.getRegNum() == regNum) {
size++;
break;
RegisterArg thisArg = mth.getThisArg();
if (thisArg != null && thisArg.getRegNum() == regNum) {
size++;
} else {
for (RegisterArg arg : mth.getArgRegs()) {
if (arg.getRegNum() == regNum) {
size++;
break;
}
}
}
}
@@ -7,6 +7,7 @@ import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.nodes.ClassNode;
public class CodeGenUtils {
@@ -28,7 +29,7 @@ public class CodeGenUtils {
code.add(" */");
}
public static void addSourceFileInfo(CodeWriter code, AttrNode node) {
public static void addSourceFileInfo(CodeWriter code, ClassNode node) {
SourceFileAttr sourceFileAttr = node.get(AType.SOURCE_FILE);
if (sourceFileAttr != null) {
code.startLine("/* compiled from: ").add(sourceFileAttr.getFileName()).add(" */");
@@ -165,6 +165,20 @@ public class Utils {
return result;
}
public static <T> int indexInList(List<T> list, T element) {
if (list == null || list.isEmpty()) {
return -1;
}
int size = list.size();
for (int i = 0; i < size; i++) {
T t = list.get(i);
if (t == element) {
return i;
}
}
return -1;
}
public static <T> List<T> lockList(List<T> list) {
if (list.isEmpty()) {
return Collections.emptyList();
@@ -163,7 +163,7 @@ public abstract class IntegrationTest extends TestUtils {
protected void decompileAndCheck(JadxDecompiler d, List<ClassNode> clsList) {
if (unloadCls) {
clsList.forEach(cls -> cls.decompile());
clsList.forEach(ClassNode::decompile);
} else {
clsList.forEach(cls -> decompileWithoutUnload(d, cls));
}
@@ -221,7 +221,8 @@ public abstract class IntegrationTest extends TestUtils {
protected void generateClsCode(ClassNode cls) {
try {
cls.setCode(CodeGen.generate(cls));
ICodeInfo code = CodeGen.generate(cls);
cls.root().getCodeCache().add(cls.getTopParentClass().getRawName(), code);
} catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());