diff --git a/jadx-core/src/main/java/jadx/api/metadata/ICodeAnnotation.java b/jadx-core/src/main/java/jadx/api/metadata/ICodeAnnotation.java index 705026701..b3c4ec039 100644 --- a/jadx-core/src/main/java/jadx/api/metadata/ICodeAnnotation.java +++ b/jadx-core/src/main/java/jadx/api/metadata/ICodeAnnotation.java @@ -9,7 +9,8 @@ public interface ICodeAnnotation { VAR, VAR_REF, DECLARATION, - OFFSET + OFFSET, + END // class or method body end } AnnType getAnnType(); diff --git a/jadx-core/src/main/java/jadx/api/metadata/annotations/NodeEnd.java b/jadx-core/src/main/java/jadx/api/metadata/annotations/NodeEnd.java new file mode 100644 index 000000000..6b73c026e --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/metadata/annotations/NodeEnd.java @@ -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"; + } +} diff --git a/jadx-core/src/main/java/jadx/api/metadata/impl/CodeMetadataStorage.java b/jadx-core/src/main/java/jadx/api/metadata/impl/CodeMetadataStorage.java index de1d0e2fe..4a6f21bf0 100644 --- a/jadx-core/src/main/java/jadx/api/metadata/impl/CodeMetadataStorage.java +++ b/jadx-core/src/main/java/jadx/api/metadata/impl/CodeMetadataStorage.java @@ -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 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}"; } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index 4356d4acf..4dd720861 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -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); } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java index fa3a703f2..6b57f2e08 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -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()); diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeMetadata.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeMetadata.java index b023c6577..da57aa195 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeMetadata.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeMetadata.java @@ -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); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeMetadata2.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeMetadata2.java new file mode 100644 index 000000000..53caad179 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeMetadata2.java @@ -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 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); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java index 870f0cf64..7f8f19a1d 100644 --- a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java +++ b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java @@ -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); } diff --git a/jadx-gui/src/main/java/jadx/gui/search/providers/BaseSearchProvider.java b/jadx-gui/src/main/java/jadx/gui/search/providers/BaseSearchProvider.java index b580b6a70..f0ae473e0 100644 --- a/jadx-gui/src/main/java/jadx/gui/search/providers/BaseSearchProvider.java +++ b/jadx-gui/src/main/java/jadx/gui/search/providers/BaseSearchProvider.java @@ -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); } diff --git a/jadx-gui/src/main/java/jadx/gui/search/providers/CodeSearchProvider.java b/jadx-gui/src/main/java/jadx/gui/search/providers/CodeSearchProvider.java index 6e4e18547..0bcda5826 100644 --- a/jadx-gui/src/main/java/jadx/gui/search/providers/CodeSearchProvider.java +++ b/jadx-gui/src/main/java/jadx/gui/search/providers/CodeSearchProvider.java @@ -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) { diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/CodeNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/CodeNode.java index 282506509..f8461160c 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/CodeNode.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/CodeNode.java @@ -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 diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialog.java index ccf4a3a8a..0a9a4f25f 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialog.java @@ -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)); } } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/DiskCodeCache.java b/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/DiskCodeCache.java index c575dd822..a244ac6f2 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/DiskCodeCache.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/DiskCodeCache.java @@ -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); diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/CodeAnnotationAdapter.java b/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/CodeAnnotationAdapter.java index 80ec8dd62..9386baf72 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/CodeAnnotationAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/CodeAnnotationAdapter.java @@ -38,6 +38,7 @@ public class CodeAnnotationAdapter implements DataAdapter { 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; } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/NodeEndAdapter.java b/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/NodeEndAdapter.java new file mode 100644 index 000000000..9ae129f5d --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/NodeEndAdapter.java @@ -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 { + + @Override + public void write(DataOutput out, NodeEnd value) throws IOException { + } + + @Override + public NodeEnd read(DataInput in) throws IOException { + return NodeEnd.VALUE; + } +}