fix(deobf): don't rename unresolved or classpath overridden methods
Change details: - use common code for process override methods in deobfuscator (move OverrideMethodVisitor to pre-decompile stage) - add all public methods to jadx class set to allow search of override base method - add don't rename flag if override hierarchy have unresolved methods
This commit is contained in:
@@ -60,7 +60,7 @@ public class ConvertToClsSet {
|
||||
set.loadFrom(root);
|
||||
set.save(output);
|
||||
|
||||
LOG.info("Output: {}, file size: {}B", output, output.toFile().length());
|
||||
LOG.info("Output: {}", output);
|
||||
LOG.info("done");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ public class Jadx {
|
||||
public static List<IDexTreeVisitor> getPreDecompilePassesList() {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||
passes.add(new SignatureProcessor());
|
||||
passes.add(new OverrideMethodVisitor());
|
||||
passes.add(new RenameVisitor());
|
||||
passes.add(new UsageInfoVisitor());
|
||||
return passes;
|
||||
@@ -107,7 +108,6 @@ public class Jadx {
|
||||
passes.add(new BlockFinish());
|
||||
|
||||
passes.add(new AttachMethodDetails());
|
||||
passes.add(new OverrideMethodVisitor());
|
||||
|
||||
passes.add(new SSATransform());
|
||||
passes.add(new MoveInlineVisitor());
|
||||
|
||||
@@ -39,8 +39,6 @@ import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
import static jadx.core.utils.Utils.notEmpty;
|
||||
|
||||
/**
|
||||
* Classes list for import into classpath graph
|
||||
*/
|
||||
@@ -131,25 +129,13 @@ public class ClsSet {
|
||||
|
||||
private void processMethodDetails(MethodNode mth, List<ClspMethod> methods) {
|
||||
AccessInfo accessFlags = mth.getAccessFlags();
|
||||
if (accessFlags.isPrivate()) {
|
||||
if (accessFlags.isPrivate() || accessFlags.isSynthetic() || accessFlags.isBridge()) {
|
||||
return;
|
||||
}
|
||||
ArgType genericRetType = mth.getReturnType();
|
||||
boolean varArgs = accessFlags.isVarArgs();
|
||||
List<ArgType> throwList = mth.getThrows();
|
||||
List<ArgType> typeParameters = mth.getTypeParameters();
|
||||
// add only methods with additional info
|
||||
if (varArgs
|
||||
|| notEmpty(throwList)
|
||||
|| notEmpty(typeParameters)
|
||||
|| genericRetType.containsGeneric()
|
||||
|| mth.containsGenericArgs()
|
||||
|| mth.isArgsOverloaded()) {
|
||||
ClspMethod clspMethod = new ClspMethod(mth.getMethodInfo(),
|
||||
mth.getArgTypes(), genericRetType,
|
||||
typeParameters, varArgs, throwList);
|
||||
methods.add(clspMethod);
|
||||
}
|
||||
ClspMethod clspMethod = new ClspMethod(mth.getMethodInfo(), mth.getArgTypes(),
|
||||
mth.getReturnType(), mth.getTypeParameters(),
|
||||
mth.getThrows(), accessFlags.rawValue());
|
||||
methods.add(clspMethod);
|
||||
}
|
||||
|
||||
public static ArgType[] makeParentsArray(ClassNode cls) {
|
||||
@@ -245,7 +231,7 @@ public class ClsSet {
|
||||
}
|
||||
}
|
||||
int methodsCount = Stream.of(classes).mapToInt(c -> c.getMethodsMap().size()).sum();
|
||||
LOG.info("Classes: {}, methods: {}, file size: {}B", classes.length, methodsCount, out.size());
|
||||
LOG.info("Classes: {}, methods: {}, file size: {} bytes", classes.length, methodsCount, out.size());
|
||||
}
|
||||
|
||||
private static void writeMethod(DataOutputStream out, ClspMethod method, Map<String, ClspClass> names) throws IOException {
|
||||
@@ -257,7 +243,7 @@ public class ClsSet {
|
||||
writeArgTypesList(out, method.containsGenericArgs() ? method.getArgTypes() : Collections.emptyList(), names);
|
||||
writeArgType(out, method.getReturnType(), names);
|
||||
writeArgTypesList(out, method.getTypeParameters(), names);
|
||||
out.writeBoolean(method.isVarArg());
|
||||
out.writeInt(method.getRawAccessFlags());
|
||||
writeArgTypesList(out, method.getThrows(), names);
|
||||
}
|
||||
|
||||
@@ -392,12 +378,12 @@ public class ClsSet {
|
||||
genericRetType = retType;
|
||||
}
|
||||
List<ArgType> typeParameters = readArgTypesList(in);
|
||||
boolean varArgs = in.readBoolean();
|
||||
int accFlags = in.readInt();
|
||||
List<ArgType> throwList = readArgTypesList(in);
|
||||
MethodInfo methodInfo = MethodInfo.fromDetails(root, clsInfo, name, argTypes, retType);
|
||||
return new ClspMethod(methodInfo,
|
||||
genericArgTypes, genericRetType,
|
||||
typeParameters, varArgs, throwList);
|
||||
typeParameters, throwList, accFlags);
|
||||
}
|
||||
|
||||
private List<ArgType> readArgTypesList(DataInputStream in) throws IOException {
|
||||
|
||||
@@ -91,7 +91,7 @@ public class ClspGraph {
|
||||
}
|
||||
}
|
||||
}
|
||||
// all other methods in known ClspClass are 'simple'
|
||||
// unknown method
|
||||
return new SimpleMethodDetails(methodInfo);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
@@ -20,18 +21,17 @@ public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
|
||||
private final ArgType returnType;
|
||||
private final List<ArgType> typeParameters;
|
||||
private final List<ArgType> throwList;
|
||||
private final boolean varArg;
|
||||
private final int accFlags;
|
||||
|
||||
public ClspMethod(MethodInfo methodInfo,
|
||||
List<ArgType> argTypes, ArgType returnType,
|
||||
List<ArgType> typeParameters,
|
||||
boolean varArgs, List<ArgType> throwList) {
|
||||
List<ArgType> typeParameters, List<ArgType> throwList, int accFlags) {
|
||||
this.methodInfo = methodInfo;
|
||||
this.argTypes = argTypes;
|
||||
this.returnType = returnType;
|
||||
this.typeParameters = typeParameters;
|
||||
this.throwList = throwList;
|
||||
this.varArg = varArgs;
|
||||
this.accFlags = accFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,7 +69,12 @@ public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
|
||||
|
||||
@Override
|
||||
public boolean isVarArg() {
|
||||
return varArg;
|
||||
return (accFlags & AccessFlags.VARARGS) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawAccessFlags() {
|
||||
return accFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,10 +3,15 @@ package jadx.core.clsp;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
|
||||
/**
|
||||
* Method details build from MethodInfo.
|
||||
* Note: some fields have unknown values.
|
||||
*/
|
||||
public class SimpleMethodDetails implements IMethodDetails {
|
||||
|
||||
private final MethodInfo methodInfo;
|
||||
@@ -45,6 +50,11 @@ public class SimpleMethodDetails implements IMethodDetails {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawAccessFlags() {
|
||||
return AccessFlags.PUBLIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SimpleMethodDetails{" + methodInfo + '}';
|
||||
|
||||
@@ -27,7 +27,6 @@ import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
@@ -166,15 +165,14 @@ public class MethodGen {
|
||||
if (overrideAttr == null) {
|
||||
return;
|
||||
}
|
||||
code.startLine("@Override");
|
||||
code.add(" // ");
|
||||
Iterator<IMethodDetails> it = overrideAttr.getOverrideList().iterator();
|
||||
while (it.hasNext()) {
|
||||
IMethodDetails methodDetails = it.next();
|
||||
code.add(methodDetails.getMethodInfo().getDeclClass().getAliasFullName());
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
if (!overrideAttr.isAtBaseMth()) {
|
||||
code.startLine("@Override");
|
||||
code.add(" // ");
|
||||
code.add(Utils.listToString(overrideAttr.getOverrideList(), ", ", md -> md.getMethodInfo().getDeclClass().getAliasFullName()));
|
||||
}
|
||||
if (Consts.DEBUG) {
|
||||
code.startLine("// related by override: ");
|
||||
code.add(Utils.listToString(overrideAttr.getRelatedMthNodes(), ", ", m -> m.getParentClass().getFullName()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
@@ -19,6 +16,7 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
@@ -46,9 +44,6 @@ public class Deobfuscator {
|
||||
private final Map<FieldInfo, String> fldMap = new HashMap<>();
|
||||
private final Map<MethodInfo, String> mthMap = new HashMap<>();
|
||||
|
||||
private final Map<MethodInfo, OverridedMethodsNode> ovrdMap = new HashMap<>();
|
||||
private final List<OverridedMethodsNode> ovrd = new ArrayList<>();
|
||||
|
||||
private final PackageNode rootPackage = new PackageNode("");
|
||||
private final Set<String> pkgSet = new TreeSet<>();
|
||||
private final Set<String> reservedClsNames = new HashSet<>();
|
||||
@@ -92,9 +87,6 @@ public class Deobfuscator {
|
||||
clsMap.clear();
|
||||
fldMap.clear();
|
||||
mthMap.clear();
|
||||
|
||||
ovrd.clear();
|
||||
ovrdMap.clear();
|
||||
}
|
||||
|
||||
private void initIndexes() {
|
||||
@@ -121,100 +113,6 @@ public class Deobfuscator {
|
||||
for (ClassNode cls : root.getClasses()) {
|
||||
processClass(cls);
|
||||
}
|
||||
postProcess();
|
||||
}
|
||||
|
||||
private void postProcess() {
|
||||
int id = 1;
|
||||
for (OverridedMethodsNode o : ovrd) {
|
||||
boolean aliasFromPreset = false;
|
||||
String aliasToUse = null;
|
||||
for (MethodInfo mth : o.getMethods()) {
|
||||
if (mth.isAliasFromPreset()) {
|
||||
aliasToUse = mth.getAlias();
|
||||
aliasFromPreset = true;
|
||||
}
|
||||
}
|
||||
for (MethodInfo mth : o.getMethods()) {
|
||||
if (aliasToUse == null) {
|
||||
if (mth.hasAlias() && !mth.isAliasFromPreset()) {
|
||||
mth.setAlias(String.format("mo%d%s", id, prepareNamePart(mth.getName())));
|
||||
}
|
||||
aliasToUse = mth.getAlias();
|
||||
}
|
||||
mth.setAlias(aliasToUse);
|
||||
mth.setAliasFromPreset(aliasFromPreset);
|
||||
}
|
||||
id++;
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveOverriding(MethodNode mth) {
|
||||
Set<ClassNode> clsParents = new LinkedHashSet<>();
|
||||
collectClassHierarchy(mth.getParentClass(), clsParents);
|
||||
|
||||
String mthSignature = mth.getMethodInfo().makeSignature(false);
|
||||
Set<MethodInfo> overrideSet = new LinkedHashSet<>();
|
||||
for (ClassNode classNode : clsParents) {
|
||||
MethodInfo methodInfo = getMthOverride(classNode.getMethods(), mthSignature);
|
||||
if (methodInfo != null) {
|
||||
overrideSet.add(methodInfo);
|
||||
}
|
||||
}
|
||||
if (overrideSet.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
OverridedMethodsNode overrideNode = getOverrideMethodsNode(overrideSet);
|
||||
if (overrideNode == null) {
|
||||
overrideNode = new OverridedMethodsNode(overrideSet);
|
||||
ovrd.add(overrideNode);
|
||||
}
|
||||
for (MethodInfo overrideMth : overrideSet) {
|
||||
if (!ovrdMap.containsKey(overrideMth)) {
|
||||
ovrdMap.put(overrideMth, overrideNode);
|
||||
overrideNode.add(overrideMth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private OverridedMethodsNode getOverrideMethodsNode(Set<MethodInfo> overrideSet) {
|
||||
for (MethodInfo overrideMth : overrideSet) {
|
||||
OverridedMethodsNode node = ovrdMap.get(overrideMth);
|
||||
if (node != null) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private MethodInfo getMthOverride(List<MethodNode> methods, String mthSignature) {
|
||||
for (MethodNode m : methods) {
|
||||
MethodInfo mthInfo = m.getMethodInfo();
|
||||
if (mthInfo.getShortId().startsWith(mthSignature)) {
|
||||
return mthInfo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void collectClassHierarchy(ClassNode cls, Set<ClassNode> collected) {
|
||||
boolean added = collected.add(cls);
|
||||
if (added) {
|
||||
ArgType superClass = cls.getSuperClass();
|
||||
if (superClass != null) {
|
||||
ClassNode superNode = cls.root().resolveClass(superClass);
|
||||
if (superNode != null) {
|
||||
collectClassHierarchy(superNode, collected);
|
||||
}
|
||||
}
|
||||
|
||||
for (ArgType argType : cls.getInterfaces()) {
|
||||
ClassNode interfaceNode = cls.root().resolveClass(argType);
|
||||
if (interfaceNode != null) {
|
||||
collectClassHierarchy(interfaceNode, collected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processClass(ClassNode cls) {
|
||||
@@ -266,16 +164,27 @@ public class Deobfuscator {
|
||||
String alias = getMethodAlias(mth);
|
||||
if (alias != null) {
|
||||
mth.getMethodInfo().setAlias(alias);
|
||||
}
|
||||
if (mth.isVirtual()) {
|
||||
resolveOverriding(mth);
|
||||
resolveOverriding(mth, alias);
|
||||
}
|
||||
}
|
||||
|
||||
public void forceRenameMethod(MethodNode mth) {
|
||||
mth.getMethodInfo().setAlias(makeMethodAlias(mth));
|
||||
if (mth.isVirtual()) {
|
||||
resolveOverriding(mth);
|
||||
String alias = makeMethodAlias(mth);
|
||||
mth.getMethodInfo().setAlias(alias);
|
||||
resolveOverriding(mth, alias);
|
||||
}
|
||||
|
||||
private void resolveOverriding(MethodNode mth, String alias) {
|
||||
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||
if (overrideAttr != null) {
|
||||
for (MethodNode ovrdMth : overrideAttr.getRelatedMthNodes()) {
|
||||
if (ovrdMth == mth) {
|
||||
continue;
|
||||
}
|
||||
MethodInfo methodInfo = ovrdMth.getMethodInfo();
|
||||
methodInfo.setAlias(alias);
|
||||
mthMap.put(methodInfo, alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,22 +432,42 @@ public class Deobfuscator {
|
||||
|
||||
@Nullable
|
||||
private String getMethodAlias(MethodNode mth) {
|
||||
if (mth.contains(AFlag.DONT_RENAME)) {
|
||||
return null;
|
||||
}
|
||||
MethodInfo methodInfo = mth.getMethodInfo();
|
||||
if (methodInfo.isClassInit() || methodInfo.isConstructor()) {
|
||||
return null;
|
||||
}
|
||||
String alias = getAssignedAlias(methodInfo);
|
||||
if (alias != null) {
|
||||
return alias;
|
||||
}
|
||||
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||
if (overrideAttr != null) {
|
||||
for (MethodNode relatedMthNode : overrideAttr.getRelatedMthNodes()) {
|
||||
String assignedAlias = getAssignedAlias(relatedMthNode.getMethodInfo());
|
||||
if (assignedAlias != null) {
|
||||
return assignedAlias;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shouldRename(mth.getName())) {
|
||||
return makeMethodAlias(mth);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getAssignedAlias(MethodInfo methodInfo) {
|
||||
String alias = mthMap.get(methodInfo);
|
||||
if (alias != null) {
|
||||
return alias;
|
||||
}
|
||||
alias = deobfPresets.getForMth(methodInfo);
|
||||
if (alias != null) {
|
||||
mthMap.put(methodInfo, alias);
|
||||
methodInfo.setAliasFromPreset(true);
|
||||
return alias;
|
||||
}
|
||||
if (shouldRename(mth.getName())) {
|
||||
return makeMethodAlias(mth);
|
||||
String presetAlias = deobfPresets.getForMth(methodInfo);
|
||||
if (presetAlias != null) {
|
||||
mthMap.put(methodInfo, presetAlias);
|
||||
return presetAlias;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -550,7 +479,13 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
public String makeMethodAlias(MethodNode mth) {
|
||||
String alias = String.format("m%d%s", mthIndex++, prepareNamePart(mth.getName()));
|
||||
String prefix;
|
||||
if (mth.contains(AType.METHOD_OVERRIDE)) {
|
||||
prefix = "mo";
|
||||
} else {
|
||||
prefix = "m";
|
||||
}
|
||||
String alias = String.format("%s%d%s", prefix, mthIndex++, prepareNamePart(mth.getName()));
|
||||
mthMap.put(mth.getMethodInfo(), alias);
|
||||
return alias;
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
|
||||
class OverridedMethodsNode {
|
||||
|
||||
private final Set<MethodInfo> methods;
|
||||
|
||||
public OverridedMethodsNode(Set<MethodInfo> methodsSet) {
|
||||
methods = methodsSet;
|
||||
}
|
||||
|
||||
public boolean contains(MethodInfo mth) {
|
||||
return methods.contains(mth);
|
||||
}
|
||||
|
||||
public void add(MethodInfo mth) {
|
||||
methods.add(mth);
|
||||
}
|
||||
|
||||
public Set<MethodInfo> getMethods() {
|
||||
return methods;
|
||||
}
|
||||
}
|
||||
@@ -97,5 +97,6 @@ public class AType<T extends IAttribute> {
|
||||
FIELD_INIT,
|
||||
FIELD_REPLACE,
|
||||
METHOD_INLINE,
|
||||
METHOD_OVERRIDE,
|
||||
SKIP_MTH_ARGS));
|
||||
}
|
||||
|
||||
@@ -5,19 +5,45 @@ import java.util.List;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public class MethodOverrideAttr implements IAttribute {
|
||||
|
||||
private final List<IMethodDetails> overrideList;
|
||||
/**
|
||||
* All methods overridden by current method. Current method excluded, empty for base method.
|
||||
*/
|
||||
private List<IMethodDetails> overrideList;
|
||||
|
||||
public MethodOverrideAttr(List<IMethodDetails> overrideList) {
|
||||
/**
|
||||
* All method nodes from override hierarchy. Current method included.
|
||||
*/
|
||||
private List<MethodNode> relatedMthNodes;
|
||||
|
||||
public MethodOverrideAttr(List<IMethodDetails> overrideList, List<MethodNode> relatedMthNodes) {
|
||||
this.overrideList = overrideList;
|
||||
this.relatedMthNodes = relatedMthNodes;
|
||||
}
|
||||
|
||||
public boolean isAtBaseMth() {
|
||||
return overrideList.isEmpty();
|
||||
}
|
||||
|
||||
public List<IMethodDetails> getOverrideList() {
|
||||
return overrideList;
|
||||
}
|
||||
|
||||
public void setOverrideList(List<IMethodDetails> overrideList) {
|
||||
this.overrideList = overrideList;
|
||||
}
|
||||
|
||||
public List<MethodNode> getRelatedMthNodes() {
|
||||
return relatedMthNodes;
|
||||
}
|
||||
|
||||
public void setRelatedMthNodes(List<MethodNode> relatedMthNodes) {
|
||||
this.relatedMthNodes = relatedMthNodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<MethodOverrideAttr> getType() {
|
||||
return AType.METHOD_OVERRIDE;
|
||||
|
||||
@@ -19,12 +19,10 @@ public final class MethodInfo implements Comparable<MethodInfo> {
|
||||
private final ClassInfo declClass;
|
||||
private final String shortId;
|
||||
private String alias;
|
||||
private boolean aliasFromPreset;
|
||||
|
||||
private MethodInfo(ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
|
||||
this.name = name;
|
||||
this.alias = name;
|
||||
this.aliasFromPreset = false;
|
||||
this.declClass = declClass;
|
||||
this.argTypes = args;
|
||||
this.retType = retType;
|
||||
@@ -143,14 +141,6 @@ public final class MethodInfo implements Comparable<MethodInfo> {
|
||||
return !name.equals(alias);
|
||||
}
|
||||
|
||||
public boolean isAliasFromPreset() {
|
||||
return aliasFromPreset;
|
||||
}
|
||||
|
||||
public void setAliasFromPreset(boolean value) {
|
||||
aliasFromPreset = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return shortId.hashCode() + 31 * declClass.hashCode();
|
||||
|
||||
@@ -22,6 +22,8 @@ public interface IMethodDetails extends IAttribute {
|
||||
|
||||
boolean isVarArg();
|
||||
|
||||
int getRawAccessFlags();
|
||||
|
||||
@Override
|
||||
default AType<IMethodDetails> getType() {
|
||||
return AType.METHOD_DETAILS;
|
||||
|
||||
@@ -522,6 +522,11 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
return sVars;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawAccessFlags() {
|
||||
return accFlags.rawValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessInfo getAccessFlags() {
|
||||
return accFlags;
|
||||
|
||||
@@ -17,8 +17,7 @@ import jadx.core.utils.exceptions.JadxException;
|
||||
runBefore = {
|
||||
CodeShrinkVisitor.class,
|
||||
TypeInferenceVisitor.class,
|
||||
MethodInvokeVisitor.class,
|
||||
OverrideMethodVisitor.class
|
||||
MethodInvokeVisitor.class
|
||||
}
|
||||
)
|
||||
public class AttachMethodDetails extends AbstractVisitor {
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.clsp.ClspClass;
|
||||
import jadx.core.clsp.ClspMethod;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
@@ -19,13 +25,15 @@ import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.typeinference.TypeCompare;
|
||||
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "OverrideMethodVisitor",
|
||||
desc = "Mark override methods and revert type erasure",
|
||||
runBefore = {
|
||||
TypeInferenceVisitor.class
|
||||
TypeInferenceVisitor.class,
|
||||
RenameVisitor.class
|
||||
}
|
||||
)
|
||||
public class OverrideMethodVisitor extends AbstractVisitor {
|
||||
@@ -33,8 +41,10 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
List<ArgType> superTypes = collectSuperTypes(cls);
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
processMth(cls, superTypes, mth);
|
||||
if (!superTypes.isEmpty()) {
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
processMth(cls, superTypes, mth);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -43,27 +53,36 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
||||
if (mth.isConstructor() || mth.getAccessFlags().isStatic()) {
|
||||
return;
|
||||
}
|
||||
mth.remove(AType.METHOD_OVERRIDE);
|
||||
String signature = mth.getMethodInfo().makeSignature(false);
|
||||
List<IMethodDetails> overrideList = collectOverrideMethods(cls, superTypes, signature);
|
||||
if (!overrideList.isEmpty()) {
|
||||
mth.addAttr(new MethodOverrideAttr(overrideList));
|
||||
fixMethodReturnType(mth, overrideList, superTypes);
|
||||
fixMethodArgTypes(mth, overrideList, superTypes);
|
||||
MethodOverrideAttr attr = processOverrideMethods(cls, mth, superTypes);
|
||||
if (attr != null) {
|
||||
mth.addAttr(attr);
|
||||
IMethodDetails baseMth = Utils.last(attr.getOverrideList());
|
||||
if (baseMth != null) {
|
||||
fixMethodReturnType(mth, baseMth, superTypes);
|
||||
fixMethodArgTypes(mth, baseMth, superTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<IMethodDetails> collectOverrideMethods(ClassNode cls, List<ArgType> superTypes, String signature) {
|
||||
private MethodOverrideAttr processOverrideMethods(ClassNode cls, MethodNode mth, List<ArgType> superTypes) {
|
||||
MethodOverrideAttr result = mth.get(AType.METHOD_OVERRIDE);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
String signature = mth.getMethodInfo().makeSignature(false);
|
||||
List<IMethodDetails> overrideList = new ArrayList<>();
|
||||
for (ArgType superType : superTypes) {
|
||||
ClassNode classNode = cls.root().resolveClass(superType);
|
||||
if (classNode != null) {
|
||||
for (MethodNode mth : classNode.getMethods()) {
|
||||
if (!mth.getAccessFlags().isStatic()
|
||||
&& isMethodVisibleInCls(mth, cls)) {
|
||||
String mthShortId = mth.getMethodInfo().getShortId();
|
||||
if (mthShortId.startsWith(signature)) {
|
||||
overrideList.add(mth);
|
||||
for (MethodNode supMth : classNode.getMethods()) {
|
||||
String mthShortId = supMth.getMethodInfo().getShortId();
|
||||
if (!supMth.getAccessFlags().isStatic()
|
||||
&& mthShortId.startsWith(signature)
|
||||
&& isMethodVisibleInCls(supMth, cls)) {
|
||||
overrideList.add(supMth);
|
||||
MethodOverrideAttr attr = supMth.get(AType.METHOD_OVERRIDE);
|
||||
if (attr != null) {
|
||||
return buildOverrideAttr(mth, overrideList, attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +99,62 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
return overrideList;
|
||||
return buildOverrideAttr(mth, overrideList, null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private MethodOverrideAttr buildOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList, @Nullable MethodOverrideAttr attr) {
|
||||
if (overrideList.isEmpty() && attr == null) {
|
||||
return null;
|
||||
}
|
||||
List<IMethodDetails> cleanOverrideList = overrideList.stream().distinct().collect(Collectors.toList());
|
||||
if (attr == null) {
|
||||
// traced to base method
|
||||
return applyOverrideAttr(mth, cleanOverrideList, false);
|
||||
}
|
||||
// trace stopped at already processed method -> start merging
|
||||
List<IMethodDetails> mergedOverrideList = Utils.mergeLists(cleanOverrideList, attr.getOverrideList());
|
||||
return applyOverrideAttr(mth, mergedOverrideList, true);
|
||||
}
|
||||
|
||||
private MethodOverrideAttr applyOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList, boolean update) {
|
||||
// don't rename method if override list contains not resolved method
|
||||
boolean dontRename = overrideList.stream().anyMatch(m -> !(m instanceof MethodNode));
|
||||
List<MethodNode> mthNodes = getMethodNodes(mth, overrideList);
|
||||
int depth = 0;
|
||||
for (MethodNode mthNode : mthNodes) {
|
||||
if (dontRename) {
|
||||
mthNode.add(AFlag.DONT_RENAME);
|
||||
}
|
||||
if (depth == 0) {
|
||||
// skip current (first) method
|
||||
depth = 1;
|
||||
continue;
|
||||
}
|
||||
if (update) {
|
||||
MethodOverrideAttr ovrdAttr = mthNode.get(AType.METHOD_OVERRIDE);
|
||||
if (ovrdAttr != null) {
|
||||
ovrdAttr.setRelatedMthNodes(mthNodes);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
mthNode.addAttr(new MethodOverrideAttr(Utils.listTail(overrideList, depth), mthNodes));
|
||||
depth++;
|
||||
}
|
||||
return new MethodOverrideAttr(overrideList, mthNodes);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<MethodNode> getMethodNodes(MethodNode mth, List<IMethodDetails> overrideList) {
|
||||
ArrayList<MethodNode> list = new ArrayList<>();
|
||||
list.add(mth);
|
||||
for (IMethodDetails md : overrideList) {
|
||||
if (md instanceof MethodNode) {
|
||||
list.add((MethodNode) md);
|
||||
}
|
||||
}
|
||||
list.trimToSize();
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,8 +173,11 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private List<ArgType> collectSuperTypes(ClassNode cls) {
|
||||
Map<String, ArgType> superTypes = new HashMap<>();
|
||||
Map<String, ArgType> superTypes = new LinkedHashMap<>();
|
||||
collectSuperTypes(cls, superTypes);
|
||||
if (superTypes.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return new ArrayList<>(superTypes.values());
|
||||
}
|
||||
|
||||
@@ -128,24 +205,13 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private void fixMethodReturnType(MethodNode mth, List<IMethodDetails> overrideList, List<ArgType> superTypes) {
|
||||
private void fixMethodReturnType(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes) {
|
||||
ArgType returnType = mth.getReturnType();
|
||||
if (returnType == ArgType.VOID) {
|
||||
return;
|
||||
}
|
||||
int updateCount = 0;
|
||||
for (IMethodDetails baseMth : overrideList) {
|
||||
if (updateReturnType(mth, baseMth, superTypes)) {
|
||||
updateCount++;
|
||||
}
|
||||
}
|
||||
if (updateCount == 0) {
|
||||
return;
|
||||
}
|
||||
if (updateCount == 1) {
|
||||
if (updateReturnType(mth, baseMth, superTypes)) {
|
||||
mth.addComment("Return type fixed from '" + returnType + "' to match base method");
|
||||
} else {
|
||||
mth.addWarnComment("Due to multiple override return type can be incorrect, original value: " + returnType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,13 +240,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void fixMethodArgTypes(MethodNode mth, List<IMethodDetails> overrideList, List<ArgType> superTypes) {
|
||||
for (IMethodDetails baseMth : overrideList) {
|
||||
updateArgTypes(mth, baseMth, superTypes);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateArgTypes(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes) {
|
||||
private void fixMethodArgTypes(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes) {
|
||||
List<ArgType> mthArgTypes = mth.getArgTypes();
|
||||
List<ArgType> baseArgTypes = baseMth.getArgTypes();
|
||||
if (mthArgTypes.equals(baseArgTypes)) {
|
||||
|
||||
@@ -15,6 +15,7 @@ public class MutableMethodDetails implements IMethodDetails {
|
||||
private List<ArgType> typeParams;
|
||||
private List<ArgType> throwTypes;
|
||||
private boolean varArg;
|
||||
private int accFlags;
|
||||
|
||||
public MutableMethodDetails(IMethodDetails base) {
|
||||
this.mthInfo = base.getMethodInfo();
|
||||
@@ -23,6 +24,7 @@ public class MutableMethodDetails implements IMethodDetails {
|
||||
this.typeParams = Collections.unmodifiableList(base.getTypeParameters());
|
||||
this.throwTypes = Collections.unmodifiableList(base.getThrows());
|
||||
this.varArg = base.isVarArg();
|
||||
this.accFlags = base.getRawAccessFlags();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,6 +77,15 @@ public class MutableMethodDetails implements IMethodDetails {
|
||||
this.varArg = varArg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawAccessFlags() {
|
||||
return accFlags;
|
||||
}
|
||||
|
||||
public void setRawAccessFlags(int accFlags) {
|
||||
this.accFlags = accFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Mutable" + toAttrString();
|
||||
|
||||
@@ -242,6 +242,33 @@ public class Utils {
|
||||
return new ImmutableList<>(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub list from startIndex (inclusive) to list end
|
||||
*/
|
||||
public static <T> List<T> listTail(List<T> list, int startIndex) {
|
||||
if (startIndex == 0) {
|
||||
return list;
|
||||
}
|
||||
int size = list.size();
|
||||
if (startIndex >= size) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return list.subList(startIndex, size);
|
||||
}
|
||||
|
||||
public static <T> List<T> mergeLists(List<T> first, List<T> second) {
|
||||
if (isEmpty(first)) {
|
||||
return second;
|
||||
}
|
||||
if (isEmpty(second)) {
|
||||
return first;
|
||||
}
|
||||
List<T> result = new ArrayList<>(first.size() + second.size());
|
||||
result.addAll(first);
|
||||
result.addAll(second);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Map<String, String> newConstStringMap(String... parameters) {
|
||||
int len = parameters.length;
|
||||
if (len == 0) {
|
||||
|
||||
Binary file not shown.
+36
@@ -0,0 +1,36 @@
|
||||
package jadx.tests.integration.deobf;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestDontRenameClspOverriddenMethod extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
|
||||
public static class A implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
}
|
||||
}
|
||||
|
||||
public static class B extends A {
|
||||
@Override
|
||||
public void run() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
noDebugInfo();
|
||||
enableDeobfuscation();
|
||||
args.setDeobfuscationMinLength(100); // rename everything
|
||||
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.countString(2, "public void run() {");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package jadx.tests.integration.deobf;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestRenameOverriddenMethod extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
public interface I {
|
||||
void m();
|
||||
}
|
||||
|
||||
public static class A implements I {
|
||||
@Override
|
||||
public void m() {
|
||||
}
|
||||
}
|
||||
|
||||
public static class B extends A {
|
||||
@Override
|
||||
public void m() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
noDebugInfo();
|
||||
enableDeobfuscation();
|
||||
args.setDeobfuscationMinLength(100); // rename everything
|
||||
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.countString(2, "@Override")
|
||||
.countString(3, "/* renamed from: m */")
|
||||
.containsOne("void mo0m();")
|
||||
.countString(2, "public void mo0m() {");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user