feat(api): allow to get method code (#2305)

This commit is contained in:
Skylot
2024-10-17 19:20:07 +01:00
parent 742d30d770
commit 249801880c
7 changed files with 124 additions and 62 deletions
@@ -2,7 +2,6 @@ package jadx.api;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.jetbrains.annotations.ApiStatus;
@@ -79,15 +78,9 @@ public final class JavaMethod implements JavaNode {
return Collections.emptyList();
}
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
return ovrdAttr.getRelatedMthNodes().stream()
.map(m -> {
JavaMethod javaMth = decompiler.convertMethodNode(m);
if (javaMth == null) {
LOG.warn("Failed convert to java method: {}", m);
}
return javaMth;
})
.filter(Objects::nonNull)
return ovrdAttr.getRelatedMthNodes()
.stream()
.map(decompiler::convertMethodNode)
.collect(Collectors.toList());
}
@@ -104,6 +97,10 @@ public final class JavaMethod implements JavaNode {
return mth.getDefPosition();
}
public String getCodeStr() {
return mth.getCodeStr();
}
@Override
public void removeAlias() {
this.mth.getMethodInfo().removeAlias();
@@ -1,5 +1,13 @@
package jadx.api.utils;
import java.util.function.BiFunction;
import jadx.api.ICodeInfo;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.core.dex.nodes.MethodNode;
public class CodeUtils {
public static String getLineForPos(String code, int pos) {
@@ -47,4 +55,71 @@ public class CodeUtils {
line++;
}
}
/**
* Cut method code (including comments and annotations) from class code.
*
* @return method code or empty string if metadata is not available
*/
public static String extractMethodCode(MethodNode mth, ICodeInfo codeInfo) {
int end = getMethodEnd(mth, codeInfo);
if (end == -1) {
return "";
}
int start = getMethodStart(mth, codeInfo);
if (end < start) {
return "";
}
return codeInfo.getCodeStr().substring(start, end);
}
/**
* Search first empty line before method definition to include comments and annotations
*/
private static int getMethodStart(MethodNode mth, ICodeInfo codeInfo) {
int pos = mth.getDefPosition();
String newLineStr = mth.root().getArgs().getCodeNewLineStr();
String emptyLine = newLineStr + newLineStr;
int emptyLinePos = codeInfo.getCodeStr().lastIndexOf(emptyLine, pos);
return emptyLinePos == -1 ? pos : emptyLinePos + emptyLine.length();
}
/**
* Search method end position in provided class code info.
*
* @return end pos or -1 if metadata not available
*/
public static int getMethodEnd(MethodNode mth, ICodeInfo codeInfo) {
if (!codeInfo.hasMetadata()) {
return -1;
}
// skip nested nodes DEF/END until first unpaired END annotation (end of this method)
Integer end = codeInfo.getCodeMetadata().searchDown(mth.getDefPosition() + 1, new BiFunction<>() {
int nested = 0;
@Override
public Integer apply(Integer pos, ICodeAnnotation ann) {
switch (ann.getAnnType()) {
case DECLARATION:
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
switch (node.getAnnType()) {
case CLASS:
case METHOD:
nested++;
break;
}
break;
case END:
if (nested == 0) {
return pos;
}
nested--;
break;
}
return null;
}
});
return end == null ? -1 : end;
}
}
@@ -5,6 +5,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -668,6 +669,13 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return insnsCount;
}
/**
* Returns method code with comments and annotations
*/
public String getCodeStr() {
return CodeUtils.extractMethodCode(this, getTopParentClass().getCode());
}
@Override
public boolean isVarArg() {
return accFlags.isVarArgs();
@@ -693,6 +701,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return javaNode;
}
@ApiStatus.Internal
public void setJavaNode(JavaMethod javaNode) {
this.javaNode = javaNode;
}