diff --git a/jadx-cli/src/main/java/jadx/cli/clst/ConvertToClsSet.java b/jadx-cli/src/main/java/jadx/cli/clst/ConvertToClsSet.java index d627e2920..aef39c9a5 100644 --- a/jadx-cli/src/main/java/jadx/cli/clst/ConvertToClsSet.java +++ b/jadx-cli/src/main/java/jadx/cli/clst/ConvertToClsSet.java @@ -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"); } } diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index fba17d704..5e9071d3d 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -81,6 +81,7 @@ public class Jadx { public static List getPreDecompilePassesList() { List 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()); diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java b/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java index 9d03fdf90..b5c74232a 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java +++ b/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java @@ -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 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 throwList = mth.getThrows(); - List 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 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 typeParameters = readArgTypesList(in); - boolean varArgs = in.readBoolean(); + int accFlags = in.readInt(); List 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 readArgTypesList(DataInputStream in) throws IOException { diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java b/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java index e7bfb086b..dab33a9f1 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java +++ b/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java @@ -91,7 +91,7 @@ public class ClspGraph { } } } - // all other methods in known ClspClass are 'simple' + // unknown method return new SimpleMethodDetails(methodInfo); } diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClspMethod.java b/jadx-core/src/main/java/jadx/core/clsp/ClspMethod.java index 113c75929..0c88fc518 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/ClspMethod.java +++ b/jadx-core/src/main/java/jadx/core/clsp/ClspMethod.java @@ -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 { private final ArgType returnType; private final List typeParameters; private final List throwList; - private final boolean varArg; + private final int accFlags; public ClspMethod(MethodInfo methodInfo, List argTypes, ArgType returnType, - List typeParameters, - boolean varArgs, List throwList) { + List typeParameters, List 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 { @Override public boolean isVarArg() { - return varArg; + return (accFlags & AccessFlags.VARARGS) != 0; + } + + @Override + public int getRawAccessFlags() { + return accFlags; } @Override diff --git a/jadx-core/src/main/java/jadx/core/clsp/SimpleMethodDetails.java b/jadx-core/src/main/java/jadx/core/clsp/SimpleMethodDetails.java index 38e366db3..63086900f 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/SimpleMethodDetails.java +++ b/jadx-core/src/main/java/jadx/core/clsp/SimpleMethodDetails.java @@ -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 + '}'; diff --git a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java index a31c20422..f3ecad087 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -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 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())); } } diff --git a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java index 1b0cfc94d..2efc962a5 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java +++ b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java @@ -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 fldMap = new HashMap<>(); private final Map mthMap = new HashMap<>(); - private final Map ovrdMap = new HashMap<>(); - private final List ovrd = new ArrayList<>(); - private final PackageNode rootPackage = new PackageNode(""); private final Set pkgSet = new TreeSet<>(); private final Set 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 clsParents = new LinkedHashSet<>(); - collectClassHierarchy(mth.getParentClass(), clsParents); - - String mthSignature = mth.getMethodInfo().makeSignature(false); - Set 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 overrideSet) { - for (MethodInfo overrideMth : overrideSet) { - OverridedMethodsNode node = ovrdMap.get(overrideMth); - if (node != null) { - return node; - } - } - return null; - } - - private MethodInfo getMthOverride(List 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 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; } diff --git a/jadx-core/src/main/java/jadx/core/deobf/OverridedMethodsNode.java b/jadx-core/src/main/java/jadx/core/deobf/OverridedMethodsNode.java deleted file mode 100644 index bcfb536d5..000000000 --- a/jadx-core/src/main/java/jadx/core/deobf/OverridedMethodsNode.java +++ /dev/null @@ -1,26 +0,0 @@ -package jadx.core.deobf; - -import java.util.Set; - -import jadx.core.dex.info.MethodInfo; - -class OverridedMethodsNode { - - private final Set methods; - - public OverridedMethodsNode(Set methodsSet) { - methods = methodsSet; - } - - public boolean contains(MethodInfo mth) { - return methods.contains(mth); - } - - public void add(MethodInfo mth) { - methods.add(mth); - } - - public Set getMethods() { - return methods; - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java index 96ff236f1..b1ea46968 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java @@ -97,5 +97,6 @@ public class AType { FIELD_INIT, FIELD_REPLACE, METHOD_INLINE, + METHOD_OVERRIDE, SKIP_MTH_ARGS)); } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodOverrideAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodOverrideAttr.java index fc6d3aa71..3c9eb9a65 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodOverrideAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodOverrideAttr.java @@ -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 overrideList; + /** + * All methods overridden by current method. Current method excluded, empty for base method. + */ + private List overrideList; - public MethodOverrideAttr(List overrideList) { + /** + * All method nodes from override hierarchy. Current method included. + */ + private List relatedMthNodes; + + public MethodOverrideAttr(List overrideList, List relatedMthNodes) { this.overrideList = overrideList; + this.relatedMthNodes = relatedMthNodes; + } + + public boolean isAtBaseMth() { + return overrideList.isEmpty(); } public List getOverrideList() { return overrideList; } + public void setOverrideList(List overrideList) { + this.overrideList = overrideList; + } + + public List getRelatedMthNodes() { + return relatedMthNodes; + } + + public void setRelatedMthNodes(List relatedMthNodes) { + this.relatedMthNodes = relatedMthNodes; + } + @Override public AType getType() { return AType.METHOD_OVERRIDE; diff --git a/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java index eae97571d..508dab3f5 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java @@ -19,12 +19,10 @@ public final class MethodInfo implements Comparable { private final ClassInfo declClass; private final String shortId; private String alias; - private boolean aliasFromPreset; private MethodInfo(ClassInfo declClass, String name, List 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 { 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(); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/IMethodDetails.java b/jadx-core/src/main/java/jadx/core/dex/nodes/IMethodDetails.java index c753a3851..8324506df 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/IMethodDetails.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/IMethodDetails.java @@ -22,6 +22,8 @@ public interface IMethodDetails extends IAttribute { boolean isVarArg(); + int getRawAccessFlags(); + @Override default AType getType() { return AType.METHOD_DETAILS; diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index e8016ba9d..b39753f97 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -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; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/AttachMethodDetails.java b/jadx-core/src/main/java/jadx/core/dex/visitors/AttachMethodDetails.java index 9acc384a3..6f1a6ca7b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/AttachMethodDetails.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/AttachMethodDetails.java @@ -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 { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java index 040ae209c..50eec1d4c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java @@ -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 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 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 collectOverrideMethods(ClassNode cls, List superTypes, String signature) { + private MethodOverrideAttr processOverrideMethods(ClassNode cls, MethodNode mth, List superTypes) { + MethodOverrideAttr result = mth.get(AType.METHOD_OVERRIDE); + if (result != null) { + return result; + } + String signature = mth.getMethodInfo().makeSignature(false); List 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 overrideList, @Nullable MethodOverrideAttr attr) { + if (overrideList.isEmpty() && attr == null) { + return null; + } + List 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 mergedOverrideList = Utils.mergeLists(cleanOverrideList, attr.getOverrideList()); + return applyOverrideAttr(mth, mergedOverrideList, true); + } + + private MethodOverrideAttr applyOverrideAttr(MethodNode mth, List overrideList, boolean update) { + // don't rename method if override list contains not resolved method + boolean dontRename = overrideList.stream().anyMatch(m -> !(m instanceof MethodNode)); + List 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 getMethodNodes(MethodNode mth, List overrideList) { + ArrayList 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 collectSuperTypes(ClassNode cls) { - Map superTypes = new HashMap<>(); + Map 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 overrideList, List superTypes) { + private void fixMethodReturnType(MethodNode mth, IMethodDetails baseMth, List 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 overrideList, List superTypes) { - for (IMethodDetails baseMth : overrideList) { - updateArgTypes(mth, baseMth, superTypes); - } - } - - private void updateArgTypes(MethodNode mth, IMethodDetails baseMth, List superTypes) { + private void fixMethodArgTypes(MethodNode mth, IMethodDetails baseMth, List superTypes) { List mthArgTypes = mth.getArgTypes(); List baseArgTypes = baseMth.getArgTypes(); if (mthArgTypes.equals(baseArgTypes)) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/methods/MutableMethodDetails.java b/jadx-core/src/main/java/jadx/core/dex/visitors/methods/MutableMethodDetails.java index 2dd73a993..0b7f60574 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/methods/MutableMethodDetails.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/methods/MutableMethodDetails.java @@ -15,6 +15,7 @@ public class MutableMethodDetails implements IMethodDetails { private List typeParams; private List 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(); diff --git a/jadx-core/src/main/java/jadx/core/utils/Utils.java b/jadx-core/src/main/java/jadx/core/utils/Utils.java index c965fdb21..43c1040bd 100644 --- a/jadx-core/src/main/java/jadx/core/utils/Utils.java +++ b/jadx-core/src/main/java/jadx/core/utils/Utils.java @@ -242,6 +242,33 @@ public class Utils { return new ImmutableList<>(list); } + /** + * Sub list from startIndex (inclusive) to list end + */ + public static List listTail(List 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 List mergeLists(List first, List second) { + if (isEmpty(first)) { + return second; + } + if (isEmpty(second)) { + return first; + } + List result = new ArrayList<>(first.size() + second.size()); + result.addAll(first); + result.addAll(second); + return result; + } + public static Map newConstStringMap(String... parameters) { int len = parameters.length; if (len == 0) { diff --git a/jadx-core/src/main/resources/clst/core.jcst b/jadx-core/src/main/resources/clst/core.jcst index ac42c5c67..17b7cc0f1 100644 Binary files a/jadx-core/src/main/resources/clst/core.jcst and b/jadx-core/src/main/resources/clst/core.jcst differ diff --git a/jadx-core/src/test/java/jadx/tests/integration/deobf/TestDontRenameClspOverriddenMethod.java b/jadx-core/src/test/java/jadx/tests/integration/deobf/TestDontRenameClspOverriddenMethod.java new file mode 100644 index 000000000..8d207fcbc --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/deobf/TestDontRenameClspOverriddenMethod.java @@ -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() {"); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/deobf/TestRenameOverriddenMethod.java b/jadx-core/src/test/java/jadx/tests/integration/deobf/TestRenameOverriddenMethod.java new file mode 100644 index 000000000..2558ed593 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/deobf/TestRenameOverriddenMethod.java @@ -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() {"); + } +}