fix: ignore anonymous classes in enclosing node search (#1580)
This commit is contained in:
@@ -9,7 +9,8 @@ public interface ICodeAnnotation {
|
||||
VAR,
|
||||
VAR_REF,
|
||||
DECLARATION,
|
||||
OFFSET
|
||||
OFFSET,
|
||||
END // class or method body end
|
||||
}
|
||||
|
||||
AnnType getAnnType();
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package jadx.api.metadata.annotations;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
|
||||
public class NodeEnd implements ICodeAnnotation {
|
||||
|
||||
public static final NodeEnd VALUE = new NodeEnd();
|
||||
|
||||
private NodeEnd() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnType getAnnType() {
|
||||
return AnnType.END;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "END";
|
||||
}
|
||||
}
|
||||
@@ -6,16 +6,14 @@ import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeAnnotation.AnnType;
|
||||
import jadx.api.metadata.ICodeMetadata;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class CodeMetadataStorage implements ICodeMetadata {
|
||||
@@ -55,7 +53,7 @@ public class CodeMetadataStorage implements ICodeMetadata {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ICodeAnnotation searchUp(int position, ICodeAnnotation.AnnType annType) {
|
||||
public @Nullable ICodeAnnotation searchUp(int position, AnnType annType) {
|
||||
for (ICodeAnnotation v : navMap.tailMap(position, true).values()) {
|
||||
if (v.getAnnType() == annType) {
|
||||
return v;
|
||||
@@ -65,7 +63,7 @@ public class CodeMetadataStorage implements ICodeMetadata {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ICodeAnnotation searchUp(int position, int limitPos, ICodeAnnotation.AnnType annType) {
|
||||
public @Nullable ICodeAnnotation searchUp(int position, int limitPos, AnnType annType) {
|
||||
for (ICodeAnnotation v : navMap.subMap(position, true, limitPos, true).values()) {
|
||||
if (v.getAnnType() == annType) {
|
||||
return v;
|
||||
@@ -99,28 +97,40 @@ public class CodeMetadataStorage implements ICodeMetadata {
|
||||
|
||||
@Override
|
||||
public ICodeNodeRef getNodeAt(int position) {
|
||||
return navMap.tailMap(position, true)
|
||||
.values().stream()
|
||||
.flatMap(CodeMetadataStorage::mapEnclosingNode)
|
||||
.findFirst().orElse(null);
|
||||
int nesting = 0;
|
||||
for (ICodeAnnotation ann : navMap.tailMap(position, true).values()) {
|
||||
switch (ann.getAnnType()) {
|
||||
case END:
|
||||
nesting++;
|
||||
break;
|
||||
|
||||
case DECLARATION:
|
||||
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
|
||||
AnnType nodeType = node.getAnnType();
|
||||
if (nodeType == AnnType.CLASS || nodeType == AnnType.METHOD) {
|
||||
if (nesting == 0) {
|
||||
return node;
|
||||
}
|
||||
nesting--;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeNodeRef getNodeBelow(int position) {
|
||||
return navMap.headMap(position, true).descendingMap()
|
||||
.values().stream()
|
||||
.flatMap(CodeMetadataStorage::mapEnclosingNode)
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
private static Stream<ICodeNodeRef> mapEnclosingNode(ICodeAnnotation ann) {
|
||||
if (ann instanceof NodeDeclareRef) {
|
||||
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
|
||||
if (node instanceof ClassNode || node instanceof MethodNode) {
|
||||
return Stream.of(node);
|
||||
for (ICodeAnnotation ann : navMap.headMap(position, true).descendingMap().values()) {
|
||||
if (ann.getAnnType() == AnnType.DECLARATION) {
|
||||
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
|
||||
AnnType nodeType = node.getAnnType();
|
||||
if (nodeType == AnnType.CLASS || nodeType == AnnType.METHOD) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Stream.empty();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -135,7 +145,7 @@ public class CodeMetadataStorage implements ICodeMetadata {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CodeMetadata{lines=" + lines
|
||||
+ ", annotations=\n" + Utils.listToString(navMap.entrySet(), "\n") + "\n}";
|
||||
return "CodeMetadata{\nlines=" + lines
|
||||
+ "\nannotations=\n " + Utils.listToString(navMap.descendingMap().entrySet(), "\n ") + "\n}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import jadx.api.CommentsLevel;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.metadata.annotations.NodeEnd;
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedType;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
@@ -256,6 +257,7 @@ public class ClassGen {
|
||||
addInnerClsAndMethods(clsCode);
|
||||
clsCode.decIndent();
|
||||
clsCode.startLine('}');
|
||||
clsCode.attachAnnotation(NodeEnd.VALUE);
|
||||
}
|
||||
|
||||
private void addInnerClsAndMethods(ICodeWriter clsCode) {
|
||||
@@ -369,6 +371,7 @@ public class ClassGen {
|
||||
mthGen.addInstructions(code);
|
||||
code.decIndent();
|
||||
code.startLine('}');
|
||||
code.attachAnnotation(NodeEnd.VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -761,6 +761,7 @@ public class InsnGen {
|
||||
ctor.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
code.attachDefinition(cls);
|
||||
code.add("new ");
|
||||
useClass(code, parent);
|
||||
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.tests.integration.others;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -8,6 +9,7 @@ import jadx.api.JadxInternalAccess;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeAnnotation.AnnType;
|
||||
import jadx.api.metadata.ICodeMetadata;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
@@ -53,14 +55,25 @@ public class TestCodeMetadata extends IntegrationTest {
|
||||
int callUse = callUsePlaces.get(0);
|
||||
|
||||
ICodeMetadata metadata = cls.getCode().getCodeMetadata();
|
||||
System.out.println(metadata);
|
||||
ICodeNodeRef callDef = metadata.getNodeAt(callUse);
|
||||
assertThat(callDef).isSameAs(testMth);
|
||||
|
||||
int beforeCallDef = callDefPos - 10;
|
||||
ICodeAnnotation closest = metadata.getClosestUp(beforeCallDef);
|
||||
AtomicInteger endPos = new AtomicInteger();
|
||||
ICodeAnnotation testEnd = metadata.searchUp(callDefPos, (pos, ann) -> {
|
||||
if (ann.getAnnType() == AnnType.END) {
|
||||
endPos.set(pos);
|
||||
return ann;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
assertThat(testEnd).isNotNull();
|
||||
int testEndPos = endPos.get();
|
||||
|
||||
ICodeAnnotation closest = metadata.getClosestUp(testEndPos);
|
||||
assertThat(closest).isInstanceOf(FieldNode.class); // field reference from 'return a.str;'
|
||||
|
||||
ICodeNodeRef nodeBelow = metadata.getNodeBelow(beforeCallDef);
|
||||
ICodeNodeRef nodeBelow = metadata.getNodeBelow(testEndPos);
|
||||
assertThat(nodeBelow).isSameAs(callMth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package jadx.tests.integration.others;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.metadata.ICodeMetadata;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.api.JadxInternalAccess.convertClassNode;
|
||||
import static jadx.api.JadxInternalAccess.convertMethodNode;
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestCodeMetadata2 extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
@SuppressWarnings("Convert2Lambda")
|
||||
public Runnable test(boolean a) {
|
||||
if (a) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("test");
|
||||
}
|
||||
};
|
||||
}
|
||||
System.out.println("another");
|
||||
return empty();
|
||||
}
|
||||
|
||||
public static Runnable empty() {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// empty
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
assertThat(cls).code().containsOne("return empty();");
|
||||
|
||||
MethodNode testMth = getMethod(cls, "test");
|
||||
MethodNode emptyMth = getMethod(cls, "empty");
|
||||
|
||||
JavaClass javaClass = convertClassNode(jadxDecompiler, cls);
|
||||
JavaMethod emptyJavaMethod = convertMethodNode(jadxDecompiler, emptyMth);
|
||||
List<Integer> emptyUsePlaces = javaClass.getUsePlacesFor(javaClass.getCodeInfo(), emptyJavaMethod);
|
||||
assertThat(emptyUsePlaces).hasSize(1);
|
||||
int callUse = emptyUsePlaces.get(0);
|
||||
|
||||
ICodeMetadata metadata = cls.getCode().getCodeMetadata();
|
||||
assertThat(metadata.getNodeAt(callUse)).isSameAs(testMth);
|
||||
}
|
||||
}
|
||||
@@ -228,7 +228,7 @@ public class JadxWrapper {
|
||||
return getDecompiler().getJavaNodeByRef(nodeRef);
|
||||
}
|
||||
|
||||
public JavaNode getEnclosingNode(ICodeInfo codeInfo, int pos) {
|
||||
public @Nullable JavaNode getEnclosingNode(ICodeInfo codeInfo, int pos) {
|
||||
return getDecompiler().getEnclosingNode(codeInfo, pos);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import jadx.api.JavaNode;
|
||||
import jadx.gui.search.ISearchMethod;
|
||||
import jadx.gui.search.ISearchProvider;
|
||||
import jadx.gui.search.SearchSettings;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.JNodeCache;
|
||||
@@ -33,7 +34,7 @@ public abstract class BaseSearchProvider implements ISearchProvider {
|
||||
return nodeCache.makeFrom(node);
|
||||
}
|
||||
|
||||
protected JNode convert(JavaClass cls) {
|
||||
protected JClass convert(JavaClass cls) {
|
||||
return nodeCache.makeFrom(cls);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,12 @@ import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.jobs.Cancelable;
|
||||
import jadx.gui.search.SearchSettings;
|
||||
import jadx.gui.treemodel.CodeNode;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
|
||||
import static jadx.core.utils.Utils.getOrElse;
|
||||
|
||||
public final class CodeSearchProvider extends BaseSearchProvider {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CodeSearchProvider.class);
|
||||
|
||||
@@ -68,10 +71,12 @@ public final class CodeSearchProvider extends BaseSearchProvider {
|
||||
int end = lineEnd == -1 ? clsCode.length() : lineEnd;
|
||||
String line = clsCode.substring(lineStart, end);
|
||||
this.pos = end;
|
||||
return new CodeNode(getEnclosingNode(javaClass, end), line.trim(), newPos);
|
||||
JClass rootCls = convert(javaClass);
|
||||
JNode enclosingNode = getOrElse(getEnclosingNode(javaClass, end), rootCls);
|
||||
return new CodeNode(rootCls, enclosingNode, line.trim(), newPos);
|
||||
}
|
||||
|
||||
private JNode getEnclosingNode(JavaClass javaCls, int pos) {
|
||||
private @Nullable JNode getEnclosingNode(JavaClass javaCls, int pos) {
|
||||
try {
|
||||
ICodeMetadata metadata = javaCls.getCodeInfo().getCodeMetadata();
|
||||
ICodeNodeRef nodeRef = metadata.getNodeAt(pos);
|
||||
@@ -82,7 +87,7 @@ public final class CodeSearchProvider extends BaseSearchProvider {
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Failed to resolve enclosing node", e);
|
||||
}
|
||||
return convert(javaCls);
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getClassCode(JavaClass javaClass, ICodeCache codeCache) {
|
||||
|
||||
@@ -9,11 +9,13 @@ import jadx.api.JavaNode;
|
||||
public class CodeNode extends JNode {
|
||||
private static final long serialVersionUID = 1658650786734966545L;
|
||||
|
||||
private final transient JClass rootCls;
|
||||
private final transient JNode jNode;
|
||||
private final transient String line;
|
||||
private final transient int pos;
|
||||
|
||||
public CodeNode(JNode jNode, String lineStr, int pos) {
|
||||
public CodeNode(JClass rootCls, JNode jNode, String lineStr, int pos) {
|
||||
this.rootCls = rootCls;
|
||||
this.jNode = jNode;
|
||||
this.line = lineStr;
|
||||
this.pos = pos;
|
||||
@@ -36,14 +38,7 @@ public class CodeNode extends JNode {
|
||||
|
||||
@Override
|
||||
public JClass getRootClass() {
|
||||
JClass parent = jNode.getJParent();
|
||||
if (parent != null) {
|
||||
return parent.getRootClass();
|
||||
}
|
||||
if (jNode instanceof JClass) {
|
||||
return (JClass) jNode;
|
||||
}
|
||||
return null;
|
||||
return rootCls;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,9 +23,11 @@ import jadx.api.utils.CodeUtils;
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.jobs.TaskStatus;
|
||||
import jadx.gui.treemodel.CodeNode;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JMethod;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.JNodeCache;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
@@ -104,9 +106,11 @@ public class UsageDialog extends CommonSearchDialog {
|
||||
if (line.startsWith("import ")) {
|
||||
continue;
|
||||
}
|
||||
JNodeCache nodeCache = getNodeCache();
|
||||
JavaNode enclosingNode = wrapper.getEnclosingNode(codeInfo, pos);
|
||||
JavaNode usageNode = enclosingNode == null ? topUseClass : enclosingNode;
|
||||
usageList.add(new CodeNode(getNodeCache().makeFrom(usageNode), line.trim(), pos));
|
||||
JClass rootJCls = nodeCache.makeFrom(topUseClass);
|
||||
JNode usageJNode = enclosingNode == null ? rootJCls : nodeCache.makeFrom(enclosingNode);
|
||||
usageList.add(new CodeNode(rootJCls, usageJNode, line.trim(), pos));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ import static java.nio.file.StandardOpenOption.WRITE;
|
||||
public class DiskCodeCache implements ICodeCache {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DiskCodeCache.class);
|
||||
|
||||
private static final int DATA_FORMAT_VERSION = 11;
|
||||
private static final int DATA_FORMAT_VERSION = 12;
|
||||
|
||||
private static final byte[] JADX_NAMES_MAP_HEADER = "jadxnm".getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
|
||||
+1
@@ -38,6 +38,7 @@ public class CodeAnnotationAdapter implements DataAdapter<ICodeAnnotation> {
|
||||
map.put(AnnType.VAR, new VarNodeAdapter(mthAdapter));
|
||||
map.put(AnnType.VAR_REF, VarRefAdapter.INSTANCE);
|
||||
map.put(AnnType.OFFSET, InsnCodeOffsetAdapter.INSTANCE);
|
||||
map.put(AnnType.END, new NodeEndAdapter());
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package jadx.gui.utils.codecache.disk.adapters;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
|
||||
import jadx.api.metadata.annotations.NodeEnd;
|
||||
|
||||
public class NodeEndAdapter implements DataAdapter<NodeEnd> {
|
||||
|
||||
@Override
|
||||
public void write(DataOutput out, NodeEnd value) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeEnd read(DataInput in) throws IOException {
|
||||
return NodeEnd.VALUE;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user