* feat(gui): add code comments (#359) * refactor: replace instanceof search with method dispatch in RegionGen * fix: various bug fixes and improvements for code comments * fix(gui): support multiline code comments * fix: resolve code differences after class reload * fix(gui): add search for comments, allow search in active tab only * fix: correct search for inner classes * fix(gui): run full index on search dialog open
This commit is contained in:
@@ -2,42 +2,27 @@ package jadx.api;
|
||||
|
||||
public final class CodePosition {
|
||||
|
||||
private final JavaNode node;
|
||||
private final int line;
|
||||
private final int offset;
|
||||
private int usagePosition = -1;
|
||||
private final int pos;
|
||||
|
||||
public CodePosition(JavaNode node, int line, int offset) {
|
||||
this.node = node;
|
||||
public CodePosition(int line, int offset, int pos) {
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
public CodePosition(int line) {
|
||||
this(line, 0, -1);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public CodePosition(int line, int offset) {
|
||||
this.node = null;
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
this(line, offset, -1);
|
||||
}
|
||||
|
||||
public int getUsagePosition() {
|
||||
return usagePosition;
|
||||
}
|
||||
|
||||
public CodePosition setUsagePosition(int usagePosition) {
|
||||
this.usagePosition = usagePosition;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JavaNode getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
public JavaClass getJavaClass() {
|
||||
JavaClass parent = node.getDeclaringClass();
|
||||
if (parent == null && node instanceof JavaClass) {
|
||||
return (JavaClass) node;
|
||||
}
|
||||
return parent;
|
||||
public int getPos() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
@@ -72,8 +57,8 @@ public final class CodePosition {
|
||||
if (offset != 0) {
|
||||
sb.append(':').append(offset);
|
||||
}
|
||||
if (node != null) {
|
||||
sb.append(' ').append(node);
|
||||
if (pos > 0) {
|
||||
sb.append('@').append(pos);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
|
||||
public interface ICodeWriter {
|
||||
@@ -51,4 +53,8 @@ public interface ICodeWriter {
|
||||
String getCodeStr();
|
||||
|
||||
int getLength();
|
||||
|
||||
StringBuilder getRawBuf();
|
||||
|
||||
Map<CodePosition, Object> getRawAnnotations();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.impl.AnnotatedCodeWriter;
|
||||
import jadx.api.impl.InMemoryCodeCache;
|
||||
|
||||
@@ -78,6 +79,8 @@ public class JadxArgs {
|
||||
|
||||
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
|
||||
|
||||
private ICodeData codeData;
|
||||
|
||||
public JadxArgs() {
|
||||
// use default options
|
||||
}
|
||||
@@ -384,6 +387,14 @@ public class JadxArgs {
|
||||
this.codeWriterProvider = codeWriterProvider;
|
||||
}
|
||||
|
||||
public ICodeData getCodeData() {
|
||||
return codeData;
|
||||
}
|
||||
|
||||
public void setCodeData(ICodeData codeData) {
|
||||
this.codeData = codeData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JadxArgs{" + "inputFiles=" + inputFiles
|
||||
|
||||
@@ -30,7 +30,11 @@ import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.nodes.*;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.nodes.VariableNode;
|
||||
import jadx.core.dex.visitors.SaveCode;
|
||||
import jadx.core.export.ExportGradleProject;
|
||||
import jadx.core.utils.Utils;
|
||||
@@ -428,6 +432,15 @@ public final class JadxDecompiler implements Closeable {
|
||||
throw new JadxRuntimeException("JavaField not found by FieldNode: " + fld);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JavaClass searchJavaClassByOrigFullName(String fullName) {
|
||||
return getRoot().getClasses().stream()
|
||||
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
|
||||
.findFirst()
|
||||
.map(this::getJavaClassByNode)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
JavaNode convertNode(Object obj) {
|
||||
if (!(obj instanceof LineAttrNode)) {
|
||||
@@ -481,7 +494,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
if (defLine == 0) {
|
||||
return null;
|
||||
}
|
||||
return new CodePosition(jCls, defLine, 0);
|
||||
return new CodePosition(defLine, 0, javaNode.getDefPos());
|
||||
}
|
||||
|
||||
public JadxArgs getArgs() {
|
||||
|
||||
@@ -131,7 +131,7 @@ public final class JavaClass implements JavaNode {
|
||||
return decompiler;
|
||||
}
|
||||
|
||||
private Map<CodePosition, Object> getCodeAnnotations() {
|
||||
public Map<CodePosition, Object> getCodeAnnotations() {
|
||||
ICodeInfo code = getCodeInfo();
|
||||
if (code == null) {
|
||||
return Collections.emptyMap();
|
||||
@@ -139,6 +139,10 @@ public final class JavaClass implements JavaNode {
|
||||
return code.getAnnotations();
|
||||
}
|
||||
|
||||
public Object getAnnotationAt(CodePosition pos) {
|
||||
return getCodeAnnotations().get(pos);
|
||||
}
|
||||
|
||||
public Map<CodePosition, JavaNode> getUsageMap() {
|
||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||
if (map.isEmpty() || decompiler == null) {
|
||||
@@ -229,6 +233,11 @@ public final class JavaClass implements JavaNode {
|
||||
return cls.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return cls.getDefPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls);
|
||||
|
||||
@@ -49,6 +49,11 @@ public final class JavaField implements JavaNode {
|
||||
return field.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return field.getDefPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JavaNode> getUseIn() {
|
||||
return getDeclaringClass().getRootDecompiler().convertNodes(field.getUseIn());
|
||||
|
||||
@@ -85,6 +85,11 @@ public final class JavaMethod implements JavaNode {
|
||||
return mth.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return mth.getDefPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
|
||||
@@ -14,5 +14,7 @@ public interface JavaNode {
|
||||
|
||||
int getDecompiledLine();
|
||||
|
||||
int getDefPos();
|
||||
|
||||
List<JavaNode> getUseIn();
|
||||
}
|
||||
|
||||
@@ -44,6 +44,11 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JavaNode> getUseIn() {
|
||||
return Collections.emptyList();
|
||||
|
||||
@@ -43,6 +43,11 @@ public class JavaVariable implements JavaNode {
|
||||
return node.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return node.getDefPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JavaNode> getUseIn() {
|
||||
return Collections.emptyList();
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package jadx.api.data;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface ICodeComment extends Comparable<ICodeComment> {
|
||||
|
||||
IJavaNodeRef getNodeRef();
|
||||
|
||||
String getComment();
|
||||
|
||||
/**
|
||||
* Instruction offset inside method
|
||||
*/
|
||||
int getOffset();
|
||||
|
||||
enum AttachType {
|
||||
VAR_DECLARE
|
||||
}
|
||||
|
||||
@Nullable
|
||||
AttachType getAttachType();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package jadx.api.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ICodeData {
|
||||
|
||||
long getUpdateId();
|
||||
|
||||
List<ICodeComment> getComments();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package jadx.api.data;
|
||||
|
||||
public interface IJavaNodeRef extends Comparable<IJavaNodeRef> {
|
||||
|
||||
enum RefType {
|
||||
CLASS, FIELD, METHOD
|
||||
}
|
||||
|
||||
RefType getType();
|
||||
|
||||
String getDeclaringClass();
|
||||
|
||||
String getShortId();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package jadx.api.data.annotations;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
|
||||
public class CustomOffsetRef implements ICodeRawOffset {
|
||||
private final int offset;
|
||||
private final ICodeComment.AttachType attachType;
|
||||
|
||||
public CustomOffsetRef(int offset, ICodeComment.AttachType attachType) {
|
||||
this.offset = offset;
|
||||
this.attachType = attachType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public ICodeComment.AttachType getAttachType() {
|
||||
return attachType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CustomOffsetRef{offset=" + offset + ", attachType=" + attachType + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package jadx.api.data.annotations;
|
||||
|
||||
public interface ICodeRawOffset {
|
||||
int getOffset();
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package jadx.api.data.annotations;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
|
||||
public class InsnCodeOffset implements ICodeRawOffset {
|
||||
|
||||
public static void attach(ICodeWriter code, InsnNode insn) {
|
||||
if (insn == null) {
|
||||
return;
|
||||
}
|
||||
if (code.isMetadataSupported()) {
|
||||
InsnCodeOffset ann = from(insn);
|
||||
if (ann != null) {
|
||||
code.attachLineAnnotation(ann);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void attach(ICodeWriter code, int offset) {
|
||||
if (offset >= 0 && code.isMetadataSupported()) {
|
||||
code.attachLineAnnotation(new InsnCodeOffset(offset));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static InsnCodeOffset from(InsnNode insn) {
|
||||
int offset = insn.getOffset();
|
||||
if (offset < 0) {
|
||||
return null;
|
||||
}
|
||||
return new InsnCodeOffset(offset);
|
||||
}
|
||||
|
||||
private final int offset;
|
||||
|
||||
public InsnCodeOffset(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "offset=" + offset;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package jadx.api.data.impl;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaNodeRef;
|
||||
|
||||
public class JadxCodeComment implements ICodeComment {
|
||||
|
||||
private IJavaNodeRef nodeRef;
|
||||
private String comment;
|
||||
private int offset;
|
||||
private AttachType attachType;
|
||||
|
||||
public JadxCodeComment(IJavaNodeRef nodeRef, String comment) {
|
||||
this(nodeRef, comment, -1, null);
|
||||
}
|
||||
|
||||
public JadxCodeComment(IJavaNodeRef nodeRef, String comment, int offset) {
|
||||
this(nodeRef, comment, offset, null);
|
||||
}
|
||||
|
||||
public JadxCodeComment(IJavaNodeRef nodeRef, String comment, int offset, AttachType attachType) {
|
||||
this.nodeRef = nodeRef;
|
||||
this.comment = comment;
|
||||
this.offset = offset;
|
||||
this.attachType = attachType;
|
||||
}
|
||||
|
||||
public JadxCodeComment() {
|
||||
// for json deserialization
|
||||
}
|
||||
|
||||
@Override
|
||||
public IJavaNodeRef getNodeRef() {
|
||||
return nodeRef;
|
||||
}
|
||||
|
||||
public void setNodeRef(IJavaNodeRef nodeRef) {
|
||||
this.nodeRef = nodeRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttachType getAttachType() {
|
||||
return attachType;
|
||||
}
|
||||
|
||||
public void setAttachType(AttachType attachType) {
|
||||
this.attachType = attachType;
|
||||
}
|
||||
|
||||
private static final Comparator<ICodeComment> COMPARATOR = Comparator
|
||||
.comparing(ICodeComment::getNodeRef)
|
||||
.thenComparing(ICodeComment::getOffset);
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ICodeComment other) {
|
||||
return COMPARATOR.compare(this, other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JadxCodeComment{" + nodeRef + ", comment='" + comment + '\'' + ", offset=" + offset + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package jadx.api.data.impl;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.ICodeData;
|
||||
|
||||
public class JadxCodeData implements ICodeData {
|
||||
|
||||
private long updateId = System.currentTimeMillis();
|
||||
private List<ICodeComment> comments = Collections.emptyList();
|
||||
|
||||
@Override
|
||||
public long getUpdateId() {
|
||||
return updateId;
|
||||
}
|
||||
|
||||
public void markUpdate() {
|
||||
updateId = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ICodeComment> getComments() {
|
||||
return comments;
|
||||
}
|
||||
|
||||
public void setComments(List<ICodeComment> comments) {
|
||||
markUpdate();
|
||||
this.comments = comments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Long.hashCode(updateId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof JadxCodeData)) {
|
||||
return false;
|
||||
}
|
||||
JadxCodeData that = (JadxCodeData) o;
|
||||
return updateId == that.updateId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package jadx.api.data.impl;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaField;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.data.IJavaNodeRef;
|
||||
|
||||
public class JadxNodeRef implements IJavaNodeRef, Comparable<IJavaNodeRef> {
|
||||
|
||||
@Nullable
|
||||
public static JadxNodeRef forJavaNode(JavaNode javaNode) {
|
||||
if (javaNode instanceof JavaClass) {
|
||||
return forCls((JavaClass) javaNode);
|
||||
}
|
||||
if (javaNode instanceof JavaMethod) {
|
||||
return forMth((JavaMethod) javaNode);
|
||||
}
|
||||
if (javaNode instanceof JavaField) {
|
||||
return forFld((JavaField) javaNode);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static JadxNodeRef forCls(JavaClass cls) {
|
||||
return new JadxNodeRef(RefType.CLASS, cls.getClassNode().getClassInfo().getFullName(), null);
|
||||
}
|
||||
|
||||
public static JadxNodeRef forCls(String clsFullName) {
|
||||
return new JadxNodeRef(RefType.CLASS, clsFullName, null);
|
||||
}
|
||||
|
||||
public static JadxNodeRef forMth(JavaMethod mth) {
|
||||
return new JadxNodeRef(RefType.METHOD,
|
||||
mth.getDeclaringClass().getClassNode().getClassInfo().getFullName(),
|
||||
mth.getMethodNode().getMethodInfo().getShortId());
|
||||
}
|
||||
|
||||
public static JadxNodeRef forFld(JavaField fld) {
|
||||
return new JadxNodeRef(RefType.FIELD,
|
||||
fld.getDeclaringClass().getClassNode().getClassInfo().getFullName(),
|
||||
fld.getFieldNode().getFieldInfo().getShortId());
|
||||
}
|
||||
|
||||
private RefType refType;
|
||||
private String declClass;
|
||||
@Nullable
|
||||
private String shortId;
|
||||
|
||||
public JadxNodeRef(RefType refType, String declClass, @Nullable String shortId) {
|
||||
this.refType = refType;
|
||||
this.declClass = declClass;
|
||||
this.shortId = shortId;
|
||||
}
|
||||
|
||||
public JadxNodeRef() {
|
||||
// for json deserialization
|
||||
}
|
||||
|
||||
@Override
|
||||
public RefType getType() {
|
||||
return refType;
|
||||
}
|
||||
|
||||
public void setRefType(RefType refType) {
|
||||
this.refType = refType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDeclaringClass() {
|
||||
return declClass;
|
||||
}
|
||||
|
||||
public void setDeclClass(String declClass) {
|
||||
this.declClass = declClass;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getShortId() {
|
||||
return shortId;
|
||||
}
|
||||
|
||||
public void setShortId(@Nullable String shortId) {
|
||||
this.shortId = shortId;
|
||||
}
|
||||
|
||||
private static final Comparator<IJavaNodeRef> COMPARATOR = Comparator
|
||||
.comparing(IJavaNodeRef::getType)
|
||||
.thenComparing(IJavaNodeRef::getDeclaringClass)
|
||||
.thenComparing(IJavaNodeRef::getShortId);
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull IJavaNodeRef other) {
|
||||
return COMPARATOR.compare(this, other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(refType, declClass, shortId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof JadxNodeRef)) {
|
||||
return false;
|
||||
}
|
||||
JadxNodeRef that = (JadxNodeRef) o;
|
||||
return refType == that.refType
|
||||
&& Objects.equals(declClass, that.declClass)
|
||||
&& Objects.equals(shortId, that.shortId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
switch (refType) {
|
||||
case CLASS:
|
||||
return declClass;
|
||||
case FIELD:
|
||||
case METHOD:
|
||||
return declClass + "->" + shortId;
|
||||
default:
|
||||
return "unknown node ref type";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,17 +65,13 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
}
|
||||
AnnotatedCodeWriter code = ((AnnotatedCodeWriter) cw);
|
||||
line--;
|
||||
int startLine = line;
|
||||
int startPos = getLength();
|
||||
for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
|
||||
Object val = entry.getValue();
|
||||
if (val instanceof DefinitionWrapper) {
|
||||
LineAttrNode node = ((DefinitionWrapper) val).getNode();
|
||||
node.setDefPosition(node.getDefPosition() + this.buf.length());
|
||||
}
|
||||
CodePosition pos = entry.getKey();
|
||||
int usagePos = pos.getUsagePosition() + getLength();
|
||||
attachAnnotation(val,
|
||||
new CodePosition(line + pos.getLine(), pos.getOffset())
|
||||
.setUsagePosition(usagePos));
|
||||
CodePosition codePos = entry.getKey();
|
||||
int newLine = startLine + codePos.getLine();
|
||||
int newPos = startPos + codePos.getPos();
|
||||
attachAnnotation(entry.getValue(), new CodePosition(newLine, codePos.getOffset(), newPos));
|
||||
}
|
||||
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
|
||||
attachSourceLine(line + entry.getKey(), entry.getValue());
|
||||
@@ -119,19 +115,21 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
|
||||
@Override
|
||||
public void attachDefinition(LineAttrNode obj) {
|
||||
obj.setDefPosition(buf.length());
|
||||
attachAnnotation(obj);
|
||||
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
|
||||
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset, getLength()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachAnnotation(Object obj) {
|
||||
attachAnnotation(obj, new CodePosition(line, offset + 1).setUsagePosition(getLength()));
|
||||
attachAnnotation(obj, new CodePosition(line, offset + 1, getLength()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachLineAnnotation(Object obj) {
|
||||
attachAnnotation(obj, new CodePosition(line, 0));
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
attachAnnotation(obj, new CodePosition(line, 0, getLength() - offset));
|
||||
}
|
||||
|
||||
private void attachAnnotation(Object obj, CodePosition pos) {
|
||||
@@ -165,13 +163,20 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
return new AnnotatedCodeInfo(code, lineMap, annotations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<CodePosition, Object> getRawAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
private void processDefinitionAnnotations() {
|
||||
if (!annotations.isEmpty()) {
|
||||
annotations.entrySet().removeIf(entry -> {
|
||||
Object v = entry.getValue();
|
||||
if (v instanceof DefinitionWrapper) {
|
||||
LineAttrNode l = ((DefinitionWrapper) v).getNode();
|
||||
l.setDecompiledLine(entry.getKey().getLine());
|
||||
CodePosition codePos = entry.getKey();
|
||||
l.setDecompiledLine(codePos.getLine());
|
||||
l.setDefPosition(codePos.getPos());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package jadx.api.impl;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxArgs;
|
||||
@@ -228,6 +232,16 @@ public class SimpleCodeWriter implements ICodeWriter {
|
||||
return buf.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringBuilder getRawBuf() {
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<CodePosition, Object> getRawAnnotations() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCodeStr() {
|
||||
removeFirstEmptyLine();
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.visitors.AttachCommentsVisitor;
|
||||
import jadx.core.dex.visitors.AttachMethodDetails;
|
||||
import jadx.core.dex.visitors.AttachTryCatchVisitor;
|
||||
import jadx.core.dex.visitors.ClassModifier;
|
||||
@@ -71,8 +72,9 @@ public class Jadx {
|
||||
}
|
||||
|
||||
public static List<IDexTreeVisitor> getFallbackPassesList() {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>(3);
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||
passes.add(new AttachTryCatchVisitor());
|
||||
passes.add(new AttachCommentsVisitor());
|
||||
passes.add(new ProcessInstructionsVisitor());
|
||||
passes.add(new FallbackModeVisitor());
|
||||
return passes;
|
||||
@@ -82,6 +84,7 @@ public class Jadx {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||
passes.add(new SignatureProcessor());
|
||||
passes.add(new OverrideMethodVisitor());
|
||||
passes.add(new ProcessAnonymous());
|
||||
passes.add(new RenameVisitor());
|
||||
passes.add(new UsageInfoVisitor());
|
||||
return passes;
|
||||
@@ -97,6 +100,7 @@ public class Jadx {
|
||||
passes.add(new DebugInfoAttachVisitor());
|
||||
}
|
||||
passes.add(new AttachTryCatchVisitor());
|
||||
passes.add(new AttachCommentsVisitor());
|
||||
passes.add(new ProcessInstructionsVisitor());
|
||||
|
||||
passes.add(new BlockSplitter());
|
||||
@@ -144,7 +148,6 @@ public class Jadx {
|
||||
passes.add(new EnumVisitor());
|
||||
passes.add(new ExtractFieldInit());
|
||||
passes.add(new FixAccessModifiers());
|
||||
passes.add(new ProcessAnonymous());
|
||||
passes.add(new ClassModifier());
|
||||
passes.add(new LoopRegionVisitor());
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ public final class ProcessClass {
|
||||
try {
|
||||
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
|
||||
cls.remove(AFlag.CLASS_DEEP_RELOAD);
|
||||
cls.unload();
|
||||
cls.deepUnload();
|
||||
cls.root().runPreDecompileStageForClass(cls);
|
||||
}
|
||||
|
||||
@@ -121,8 +121,9 @@ public class ClassGen {
|
||||
if (Consts.DEBUG_USAGE) {
|
||||
addClassUsageInfo(code, cls);
|
||||
}
|
||||
CodeGenUtils.addComments(code, cls);
|
||||
insertDecompilationProblems(code, cls);
|
||||
CodeGenUtils.addSourceFileInfo(code, cls);
|
||||
CodeGenUtils.addComments(code, cls);
|
||||
addClassDeclaration(code);
|
||||
addClassBody(code);
|
||||
}
|
||||
@@ -145,7 +146,6 @@ public class ClassGen {
|
||||
|
||||
annotationGen.addForClass(clsCode);
|
||||
insertRenameInfo(clsCode, cls);
|
||||
CodeGenUtils.addSourceFileInfo(clsCode, cls);
|
||||
clsCode.startLineWithNum(cls.getSourceLine()).add(af.makeString());
|
||||
if (af.isInterface()) {
|
||||
if (af.isAnnotation()) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.plugins.input.data.MethodHandleType;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
@@ -69,7 +70,6 @@ public class InsnGen {
|
||||
protected final MethodNode mth;
|
||||
protected final RootNode root;
|
||||
protected final boolean fallback;
|
||||
protected final boolean attachInsns;
|
||||
|
||||
protected enum Flags {
|
||||
BODY_ONLY,
|
||||
@@ -82,7 +82,6 @@ public class InsnGen {
|
||||
this.mth = mgen.getMethodNode();
|
||||
this.root = mth.root();
|
||||
this.fallback = fallback;
|
||||
this.attachInsns = root.getArgs().isJsonOutput();
|
||||
}
|
||||
|
||||
private boolean isFallback() {
|
||||
@@ -260,9 +259,7 @@ public class InsnGen {
|
||||
} else {
|
||||
if (flag != Flags.INLINE) {
|
||||
code.startLineWithNum(insn.getSourceLine());
|
||||
if (attachInsns) {
|
||||
code.attachLineAnnotation(insn);
|
||||
}
|
||||
InsnCodeOffset.attach(code, insn);
|
||||
if (insn.contains(AFlag.COMMENT_OUT)) {
|
||||
code.add("// ");
|
||||
}
|
||||
@@ -278,6 +275,7 @@ public class InsnGen {
|
||||
makeInsnBody(code, insn, EMPTY_FLAGS);
|
||||
if (flag != Flags.INLINE) {
|
||||
code.add(';');
|
||||
CodeGenUtils.addCodeComments(code, insn);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.core.Consts;
|
||||
@@ -44,7 +45,7 @@ import jadx.core.utils.exceptions.JadxOverflowException;
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.BLOCK_DUMP;
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.COMMENTED_DUMP;
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.FALLBACK_MODE;
|
||||
import static jadx.core.dex.nodes.VariableNode.*;
|
||||
import static jadx.core.dex.nodes.VariableNode.VarKind;
|
||||
|
||||
public class MethodGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
|
||||
@@ -289,17 +290,19 @@ public class MethodGen {
|
||||
}
|
||||
|
||||
public void addFallbackMethodCode(ICodeWriter code, FallbackOption fallbackOption) {
|
||||
// load original instructions
|
||||
try {
|
||||
mth.unload();
|
||||
mth.load();
|
||||
for (IDexTreeVisitor visitor : Jadx.getFallbackPassesList()) {
|
||||
DepthTraversal.visit(visitor, mth);
|
||||
if (fallbackOption != FALLBACK_MODE) {
|
||||
// load original instructions
|
||||
try {
|
||||
mth.unload();
|
||||
mth.load();
|
||||
for (IDexTreeVisitor visitor : Jadx.getFallbackPassesList()) {
|
||||
DepthTraversal.visit(visitor, mth);
|
||||
}
|
||||
} catch (DecodeException e) {
|
||||
LOG.error("Error reload instructions in fallback mode:", e);
|
||||
code.startLine("// Can't load method instructions: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
} catch (DecodeException e) {
|
||||
LOG.error("Error reload instructions in fallback mode:", e);
|
||||
code.startLine("// Can't load method instructions: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
InsnNode[] insnArr = mth.getInstructions();
|
||||
if (insnArr == null) {
|
||||
@@ -333,7 +336,6 @@ public class MethodGen {
|
||||
public static void addFallbackInsns(ICodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) {
|
||||
int startIndent = code.getIndent();
|
||||
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
|
||||
boolean attachInsns = mth.root().getArgs().isJsonOutput();
|
||||
InsnNode prevInsn = null;
|
||||
for (InsnNode insn : insnArr) {
|
||||
if (insn == null) {
|
||||
@@ -360,11 +362,9 @@ public class MethodGen {
|
||||
code.startLine("*/");
|
||||
code.startLine("// ");
|
||||
} else {
|
||||
code.startLine();
|
||||
}
|
||||
if (attachInsns) {
|
||||
code.attachLineAnnotation(insn);
|
||||
code.startLineWithNum(insn.getSourceLine());
|
||||
}
|
||||
InsnCodeOffset.attach(code, insn);
|
||||
RegisterArg resArg = insn.getResult();
|
||||
if (resArg != null) {
|
||||
ArgType varType = resArg.getInitType();
|
||||
@@ -382,6 +382,7 @@ public class MethodGen {
|
||||
if (catchAttr != null) {
|
||||
code.add(" // " + catchAttr);
|
||||
}
|
||||
CodeGenUtils.addCodeComments(code, insn);
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Error generate fallback instruction: ", e.getCause());
|
||||
code.setIndent(startIndent);
|
||||
|
||||
@@ -135,9 +135,6 @@ public class NameGen {
|
||||
if (!NameMapper.isValidAndPrintable(name)) {
|
||||
name = getFallbackName(var);
|
||||
}
|
||||
if (Consts.DEBUG) {
|
||||
name += '_' + getFallbackName(var);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.annotations.CustomOffsetRef;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.fldinit.FieldInitAttr;
|
||||
@@ -26,7 +29,6 @@ import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.VariableNode;
|
||||
import jadx.core.dex.regions.Region;
|
||||
@@ -44,6 +46,7 @@ import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -57,28 +60,8 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
|
||||
public void makeRegion(ICodeWriter code, IContainer cont) throws CodegenException {
|
||||
if (cont instanceof IBlock) {
|
||||
makeSimpleBlock((IBlock) cont, code);
|
||||
} else if (cont instanceof IRegion) {
|
||||
if (cont instanceof Region) {
|
||||
makeSimpleRegion(code, (Region) cont);
|
||||
} else {
|
||||
declareVars(code, cont);
|
||||
if (cont instanceof IfRegion) {
|
||||
makeIf((IfRegion) cont, code, true);
|
||||
} else if (cont instanceof SwitchRegion) {
|
||||
makeSwitch((SwitchRegion) cont, code);
|
||||
} else if (cont instanceof LoopRegion) {
|
||||
makeLoop((LoopRegion) cont, code);
|
||||
} else if (cont instanceof TryCatchRegion) {
|
||||
makeTryCatch((TryCatchRegion) cont, code);
|
||||
} else if (cont instanceof SynchronizedRegion) {
|
||||
makeSynchronizedRegion((SynchronizedRegion) cont, code);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new CodegenException("Not processed container: " + cont);
|
||||
}
|
||||
declareVars(code, cont);
|
||||
cont.generate(this, code);
|
||||
}
|
||||
|
||||
private void declareVars(ICodeWriter code, IContainer cont) {
|
||||
@@ -88,24 +71,30 @@ public class RegionGen extends InsnGen {
|
||||
code.startLine();
|
||||
declareVar(code, v);
|
||||
code.add(';');
|
||||
attachVariableCommentsData(code, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void makeSimpleRegion(ICodeWriter code, Region region) throws CodegenException {
|
||||
declareVars(code, region);
|
||||
for (IContainer c : region.getSubBlocks()) {
|
||||
makeRegion(code, c);
|
||||
private void attachVariableCommentsData(ICodeWriter code, CodeVar v) {
|
||||
RegisterArg assignReg = v.getSsaVars().get(0).getAssign();
|
||||
if (code.isMetadataSupported()) {
|
||||
InsnNode parentInsn = assignReg.getParentInsn();
|
||||
if (parentInsn != null) {
|
||||
int offset = parentInsn.getOffset();
|
||||
code.attachLineAnnotation(new CustomOffsetRef(offset, ICodeComment.AttachType.VAR_DECLARE));
|
||||
}
|
||||
}
|
||||
CodeGenUtils.addCodeComments(code, assignReg);
|
||||
}
|
||||
|
||||
public void makeRegionIndent(ICodeWriter code, IContainer region) throws CodegenException {
|
||||
private void makeRegionIndent(ICodeWriter code, IContainer region) throws CodegenException {
|
||||
code.incIndent();
|
||||
makeRegion(code, region);
|
||||
code.decIndent();
|
||||
}
|
||||
|
||||
private void makeSimpleBlock(IBlock block, ICodeWriter code) throws CodegenException {
|
||||
public void makeSimpleBlock(IBlock block, ICodeWriter code) throws CodegenException {
|
||||
if (block.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
@@ -121,22 +110,12 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
}
|
||||
|
||||
private void makeIf(IfRegion region, ICodeWriter code, boolean newLine) throws CodegenException {
|
||||
public void makeIf(IfRegion region, ICodeWriter code, boolean newLine) throws CodegenException {
|
||||
if (newLine) {
|
||||
code.startLineWithNum(region.getSourceLine());
|
||||
} else {
|
||||
code.attachSourceLine(region.getSourceLine());
|
||||
}
|
||||
if (attachInsns) {
|
||||
List<BlockNode> conditionBlocks = region.getConditionBlocks();
|
||||
if (!conditionBlocks.isEmpty()) {
|
||||
BlockNode blockNode = conditionBlocks.get(0);
|
||||
InsnNode lastInsn = BlockUtils.getLastInsn(blockNode);
|
||||
if (lastInsn != null) {
|
||||
code.attachLineAnnotation(lastInsn);
|
||||
}
|
||||
}
|
||||
}
|
||||
boolean comment = region.contains(AFlag.COMMENT_OUT);
|
||||
if (comment) {
|
||||
code.add("// ");
|
||||
@@ -145,6 +124,15 @@ public class RegionGen extends InsnGen {
|
||||
code.add("if (");
|
||||
new ConditionGen(this).add(code, region.getCondition());
|
||||
code.add(") {");
|
||||
if (code.isMetadataSupported()) {
|
||||
List<BlockNode> conditionBlocks = region.getConditionBlocks();
|
||||
if (!conditionBlocks.isEmpty()) {
|
||||
BlockNode blockNode = conditionBlocks.get(0);
|
||||
InsnNode lastInsn = BlockUtils.getLastInsn(blockNode);
|
||||
InsnCodeOffset.attach(code, lastInsn);
|
||||
CodeGenUtils.addCodeComments(code, lastInsn);
|
||||
}
|
||||
}
|
||||
makeRegionIndent(code, region.getThenRegion());
|
||||
if (comment) {
|
||||
code.startLine("// }");
|
||||
@@ -186,43 +174,49 @@ public class RegionGen extends InsnGen {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void makeLoop(LoopRegion region, ICodeWriter code) throws CodegenException {
|
||||
public void makeLoop(LoopRegion region, ICodeWriter code) throws CodegenException {
|
||||
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
|
||||
if (labelAttr != null) {
|
||||
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':');
|
||||
}
|
||||
code.startLineWithNum(region.getConditionSourceLine());
|
||||
|
||||
IfCondition condition = region.getCondition();
|
||||
if (condition == null) {
|
||||
// infinite loop
|
||||
code.startLine("while (true) {");
|
||||
code.add("while (true) {");
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLine('}');
|
||||
return;
|
||||
}
|
||||
InsnNode condInsn = condition.getFirstInsn();
|
||||
InsnCodeOffset.attach(code, condInsn);
|
||||
|
||||
ConditionGen conditionGen = new ConditionGen(this);
|
||||
LoopType type = region.getType();
|
||||
if (type != null) {
|
||||
if (type instanceof ForLoop) {
|
||||
ForLoop forLoop = (ForLoop) type;
|
||||
code.startLine("for (");
|
||||
code.add("for (");
|
||||
makeInsn(forLoop.getInitInsn(), code, Flags.INLINE);
|
||||
code.add("; ");
|
||||
conditionGen.add(code, condition);
|
||||
code.add("; ");
|
||||
makeInsn(forLoop.getIncrInsn(), code, Flags.INLINE);
|
||||
code.add(") {");
|
||||
CodeGenUtils.addCodeComments(code, condInsn);
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLine('}');
|
||||
return;
|
||||
}
|
||||
if (type instanceof ForEachLoop) {
|
||||
ForEachLoop forEachLoop = (ForEachLoop) type;
|
||||
code.startLine("for (");
|
||||
code.add("for (");
|
||||
declareVar(code, forEachLoop.getVarArg());
|
||||
code.add(" : ");
|
||||
addArg(code, forEachLoop.getIterableArg(), false);
|
||||
code.add(") {");
|
||||
CodeGenUtils.addCodeComments(code, condInsn);
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLine('}');
|
||||
return;
|
||||
@@ -230,37 +224,45 @@ public class RegionGen extends InsnGen {
|
||||
throw new JadxRuntimeException("Unknown loop type: " + type.getClass());
|
||||
}
|
||||
if (region.isConditionAtEnd()) {
|
||||
code.startLine("do {");
|
||||
code.add("do {");
|
||||
CodeGenUtils.addCodeComments(code, condInsn);
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLineWithNum(region.getConditionSourceLine());
|
||||
code.add("} while (");
|
||||
conditionGen.add(code, condition);
|
||||
code.add(");");
|
||||
} else {
|
||||
code.startLineWithNum(region.getConditionSourceLine());
|
||||
code.add("while (");
|
||||
conditionGen.add(code, condition);
|
||||
code.add(") {");
|
||||
CodeGenUtils.addCodeComments(code, condInsn);
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLine('}');
|
||||
}
|
||||
}
|
||||
|
||||
private void makeSynchronizedRegion(SynchronizedRegion cont, ICodeWriter code) throws CodegenException {
|
||||
public void makeSynchronizedRegion(SynchronizedRegion cont, ICodeWriter code) throws CodegenException {
|
||||
code.startLine("synchronized (");
|
||||
addArg(code, cont.getEnterInsn().getArg(0));
|
||||
InsnNode monitorEnterInsn = cont.getEnterInsn();
|
||||
addArg(code, monitorEnterInsn.getArg(0));
|
||||
code.add(") {");
|
||||
|
||||
InsnCodeOffset.attach(code, monitorEnterInsn);
|
||||
CodeGenUtils.addCodeComments(code, monitorEnterInsn);
|
||||
|
||||
makeRegionIndent(code, cont.getRegion());
|
||||
code.startLine('}');
|
||||
}
|
||||
|
||||
private void makeSwitch(SwitchRegion sw, ICodeWriter code) throws CodegenException {
|
||||
public void makeSwitch(SwitchRegion sw, ICodeWriter code) throws CodegenException {
|
||||
SwitchInsn insn = (SwitchInsn) BlockUtils.getLastInsn(sw.getHeader());
|
||||
Objects.requireNonNull(insn, "Switch insn not found in header");
|
||||
InsnArg arg = insn.getArg(0);
|
||||
code.startLine("switch (");
|
||||
addArg(code, arg, false);
|
||||
code.add(") {");
|
||||
InsnCodeOffset.attach(code, insn);
|
||||
CodeGenUtils.addCodeComments(code, insn);
|
||||
code.incIndent();
|
||||
|
||||
for (CaseInfo caseInfo : sw.getCases()) {
|
||||
@@ -304,8 +306,13 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
}
|
||||
|
||||
private void makeTryCatch(TryCatchRegion region, ICodeWriter code) throws CodegenException {
|
||||
public void makeTryCatch(TryCatchRegion region, ICodeWriter code) throws CodegenException {
|
||||
code.startLine("try {");
|
||||
|
||||
InsnNode insn = Utils.first(region.getTryCatchBlock().getInsns());
|
||||
InsnCodeOffset.attach(code, insn);
|
||||
CodeGenUtils.addCodeComments(code, insn);
|
||||
|
||||
makeRegionIndent(code, region.getTryRegion());
|
||||
// TODO: move search of 'allHandler' to 'TryCatchRegion'
|
||||
ExceptionHandler allHandler = null;
|
||||
@@ -381,6 +388,10 @@ public class RegionGen extends InsnGen {
|
||||
throw new JadxRuntimeException("Unexpected arg type in catch block: " + arg + ", class: " + arg.getClass().getSimpleName());
|
||||
}
|
||||
code.add(") {");
|
||||
|
||||
InsnCodeOffset.attach(code, handler.getHandleOffset());
|
||||
CodeGenUtils.addCodeComments(code, handler.getHandlerBlock());
|
||||
|
||||
makeRegionIndent(code, region);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.impl.AnnotatedCodeWriter;
|
||||
import jadx.api.impl.SimpleCodeWriter;
|
||||
import jadx.core.codegen.ClassGen;
|
||||
@@ -29,7 +30,6 @@ import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
@@ -193,9 +193,9 @@ public class JsonCodeGen {
|
||||
JsonCodeLine jsonCodeLine = new JsonCodeLine();
|
||||
jsonCodeLine.setCode(codeLine);
|
||||
jsonCodeLine.setSourceLine(lineMapping.get(line));
|
||||
Object obj = annotations.get(new CodePosition(line, 0));
|
||||
if (obj instanceof InsnNode) {
|
||||
long offset = ((InsnNode) obj).getOffset();
|
||||
Object obj = annotations.get(new CodePosition(line));
|
||||
if (obj instanceof InsnCodeOffset) {
|
||||
long offset = ((InsnCodeOffset) obj).getOffset();
|
||||
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
|
||||
}
|
||||
codeLines.add(jsonCodeLine);
|
||||
|
||||
@@ -42,6 +42,9 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
|
||||
@SuppressWarnings("InstantiationOfUtilityClass")
|
||||
public class AType<T extends IAttribute> {
|
||||
|
||||
// class, method, field, insn
|
||||
public static final AType<AttrList<String>> CODE_COMMENTS = new AType<>();
|
||||
|
||||
// class, method, field
|
||||
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<>();
|
||||
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
|
||||
|
||||
@@ -33,6 +33,23 @@ public abstract class AttrNode implements IAttributeNode {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends IAttribute> void copyAttributeFrom(AttrNode attrNode, AType<T> attrType) {
|
||||
IAttribute attr = attrNode.get(attrType);
|
||||
if (attr != null) {
|
||||
this.addAttr(attr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove attribute in this node, add copy from other if exists
|
||||
*/
|
||||
@Override
|
||||
public <T extends IAttribute> void rewriteAttributeFrom(AttrNode attrNode, AType<T> attrType) {
|
||||
remove(attrType);
|
||||
copyAttributeFrom(attrNode, attrType);
|
||||
}
|
||||
|
||||
private AttributeStorage initStorage() {
|
||||
AttributeStorage store = storage;
|
||||
if (store == EMPTY_ATTR_STORAGE) {
|
||||
|
||||
@@ -14,6 +14,10 @@ public interface IAttributeNode {
|
||||
|
||||
void copyAttributesFrom(AttrNode attrNode);
|
||||
|
||||
<T extends IAttribute> void copyAttributeFrom(AttrNode attrNode, AType<T> attrType);
|
||||
|
||||
<T extends IAttribute> void rewriteAttributeFrom(AttrNode attrNode, AType<T> attrType);
|
||||
|
||||
boolean contains(AFlag flag);
|
||||
|
||||
<T extends IAttribute> boolean contains(AType<T> type);
|
||||
|
||||
@@ -35,11 +35,9 @@ public abstract class NotificationAttrNode extends LineAttrNode implements ICode
|
||||
|
||||
public void addComment(String commentStr) {
|
||||
addAttr(AType.COMMENTS, commentStr);
|
||||
LOG.info("{} in {}", commentStr, this);
|
||||
}
|
||||
|
||||
public void addDebugComment(String commentStr) {
|
||||
addAttr(AType.COMMENTS, "JADX DEBUG: " + commentStr);
|
||||
LOG.debug("{} in {}", commentStr, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,10 @@ public final class FieldInfo {
|
||||
return declClass.getFullName() + '.' + name + ':' + TypeGen.signature(type);
|
||||
}
|
||||
|
||||
public String getShortId() {
|
||||
return name + ':' + TypeGen.signature(type);
|
||||
}
|
||||
|
||||
public String getRawFullId() {
|
||||
return declClass.makeRawFullName() + '.' + name + ':' + TypeGen.signature(type);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.visitors.ProcessAnonymous;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -191,9 +190,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
if (fileName.endsWith(".java")) {
|
||||
fileName = fileName.substring(0, fileName.length() - 5);
|
||||
}
|
||||
if (fileName.isEmpty()
|
||||
|| fileName.equals("SourceFile")
|
||||
|| fileName.equals("\"")) {
|
||||
if (fileName.isEmpty() || fileName.equals("SourceFile")) {
|
||||
return;
|
||||
}
|
||||
if (clsInfo != null) {
|
||||
@@ -201,12 +198,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
if (fileName.equals(name)) {
|
||||
return;
|
||||
}
|
||||
if (fileName.contains("$")
|
||||
&& fileName.endsWith('$' + name)) {
|
||||
return;
|
||||
}
|
||||
ClassInfo parentCls = clsInfo.getTopParentClass();
|
||||
if (parentCls != null && fileName.equals(parentCls.getShortName())) {
|
||||
if (fileName.contains("$") && fileName.endsWith('$' + name)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -240,14 +232,12 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
// manually added class
|
||||
return;
|
||||
}
|
||||
unload();
|
||||
clearAttributes();
|
||||
root().getConstValues().removeForClass(this);
|
||||
initialLoad(clsData);
|
||||
ProcessAnonymous.runForClass(this);
|
||||
|
||||
for (ClassNode innerClass : innerClasses) {
|
||||
innerClass.deepUnload();
|
||||
}
|
||||
innerClasses.forEach(ClassNode::deepUnload);
|
||||
}
|
||||
|
||||
private synchronized ICodeInfo decompile(boolean searchInCache) {
|
||||
@@ -375,6 +365,15 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
return null;
|
||||
}
|
||||
|
||||
public FieldNode searchFieldByShortId(String shortId) {
|
||||
for (FieldNode f : fields) {
|
||||
if (f.getFieldInfo().getShortId().equals(shortId)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public MethodNode searchMethod(MethodInfo mth) {
|
||||
return mthInfoMap.get(mth);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,16 @@ package jadx.core.dex.nodes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.codegen.RegionGen;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public interface IBlock extends IContainer {
|
||||
|
||||
List<InsnNode> getInstructions();
|
||||
|
||||
@Override
|
||||
default void generate(RegionGen regionGen, ICodeWriter code) throws CodegenException {
|
||||
regionGen.makeSimpleBlock(this, code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.codegen.RegionGen;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public interface IContainer extends IAttributeNode {
|
||||
|
||||
@@ -8,4 +11,11 @@ public interface IContainer extends IAttributeNode {
|
||||
* Unique id for use in 'toString()' method
|
||||
*/
|
||||
String baseString();
|
||||
|
||||
/**
|
||||
* Dispatch to needed generate method in RegionGen
|
||||
*/
|
||||
default void generate(RegionGen regionGen, ICodeWriter code) throws CodegenException {
|
||||
throw new CodegenException("Code generate not implemented for container: " + getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ public class RootNode {
|
||||
// sort classes by name, expect top classes before inner
|
||||
classes.sort(Comparator.comparing(ClassNode::getFullName));
|
||||
initInnerClasses();
|
||||
LOG.debug("Classes loaded: {}", classes.size());
|
||||
LOG.info("Classes loaded: {}", classes.size());
|
||||
}
|
||||
|
||||
private void addDummyClass(IClassData classData, Exception exc) {
|
||||
@@ -239,6 +239,7 @@ public class RootNode {
|
||||
|
||||
public void runPreDecompileStage() {
|
||||
for (IDexTreeVisitor pass : preDecompilePasses) {
|
||||
long start = System.currentTimeMillis();
|
||||
try {
|
||||
pass.init(this);
|
||||
} catch (Exception e) {
|
||||
@@ -247,6 +248,9 @@ public class RootNode {
|
||||
for (ClassNode cls : classes) {
|
||||
DepthTraversal.visit(pass, cls);
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("{} time: {}ms", pass.getClass().getSimpleName(), System.currentTimeMillis() - start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,12 @@ package jadx.core.dex.regions;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.codegen.RegionGen;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public final class Region extends AbstractRegion {
|
||||
|
||||
@@ -26,6 +29,13 @@ public final class Region extends AbstractRegion {
|
||||
blocks.add(region);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(RegionGen regionGen, ICodeWriter code) throws CodegenException {
|
||||
for (IContainer c : blocks) {
|
||||
regionGen.makeRegion(code, c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replaceSubBlock(IContainer oldBlock, IContainer newBlock) {
|
||||
int i = blocks.indexOf(oldBlock);
|
||||
|
||||
@@ -5,11 +5,13 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.codegen.RegionGen;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IBranchRegion;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public final class SwitchRegion extends AbstractRegion implements IBranchRegion {
|
||||
|
||||
@@ -72,6 +74,11 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
|
||||
return Collections.unmodifiableList(getCaseContainers());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(RegionGen regionGen, ICodeWriter code) throws CodegenException {
|
||||
regionGen.makeSwitch(this, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String baseString() {
|
||||
return header.baseString();
|
||||
|
||||
@@ -3,9 +3,12 @@ package jadx.core.dex.regions;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.codegen.RegionGen;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public final class SynchronizedRegion extends AbstractRegion {
|
||||
|
||||
@@ -36,6 +39,11 @@ public final class SynchronizedRegion extends AbstractRegion {
|
||||
return region.getSubBlocks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(RegionGen regionGen, ICodeWriter code) throws CodegenException {
|
||||
regionGen.makeSynchronizedRegion(this, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String baseString() {
|
||||
return Integer.toHexString(enterInsn.getOffset());
|
||||
|
||||
@@ -6,12 +6,15 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.codegen.RegionGen;
|
||||
import jadx.core.dex.nodes.IBranchRegion;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.dex.trycatch.TryCatchBlock;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public final class TryCatchRegion extends AbstractRegion implements IBranchRegion {
|
||||
|
||||
@@ -77,6 +80,11 @@ public final class TryCatchRegion extends AbstractRegion implements IBranchRegio
|
||||
return getSubBlocks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(RegionGen regionGen, ICodeWriter code) throws CodegenException {
|
||||
regionGen.makeTryCatch(this, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String baseString() {
|
||||
return tryRegion.baseString();
|
||||
|
||||
@@ -8,6 +8,8 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.instructions.ArithNode;
|
||||
import jadx.core.dex.instructions.ArithOp;
|
||||
@@ -262,6 +264,14 @@ public final class IfCondition extends AttrNode {
|
||||
return list;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public InsnNode getFirstInsn() {
|
||||
if (mode == Mode.COMPARE) {
|
||||
return compare.getInsn();
|
||||
}
|
||||
return args.get(0).getFirstInsn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
switch (mode) {
|
||||
@@ -313,5 +323,4 @@ public final class IfCondition extends AttrNode {
|
||||
result = 31 * result + (compare != null ? compare.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.codegen.RegionGen;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IBranchRegion;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
@@ -12,6 +14,7 @@ import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.regions.AbstractRegion;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public final class IfRegion extends AbstractRegion implements IBranchRegion {
|
||||
|
||||
@@ -129,6 +132,11 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(RegionGen regionGen, ICodeWriter code) throws CodegenException {
|
||||
regionGen.makeIf(this, code, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String baseString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.codegen.RegionGen;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
@@ -16,6 +18,7 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.regions.AbstractRegion;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public final class LoopRegion extends AbstractRegion {
|
||||
|
||||
@@ -165,6 +168,11 @@ public final class LoopRegion extends AbstractRegion {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(RegionGen regionGen, ICodeWriter code) throws CodegenException {
|
||||
regionGen.makeLoop(this, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String baseString() {
|
||||
return body == null ? "-" : body.baseString();
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.data.IJavaNodeRef;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "Attach comments",
|
||||
desc = "Attach comments",
|
||||
runBefore = {
|
||||
ProcessInstructionsVisitor.class
|
||||
}
|
||||
)
|
||||
public class AttachCommentsVisitor extends AbstractVisitor {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AttachCommentsVisitor.class);
|
||||
|
||||
private final CommentsData cachedCommentsData = new CommentsData();
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) {
|
||||
List<ICodeComment> clsComments = getCommentsData(cls);
|
||||
if (!clsComments.isEmpty()) {
|
||||
applyComments(cls, clsComments);
|
||||
}
|
||||
cls.getInnerClasses().forEach(this::visit);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void applyComments(ClassNode cls, List<ICodeComment> clsComments) {
|
||||
for (ICodeComment comment : clsComments) {
|
||||
IJavaNodeRef nodeRef = comment.getNodeRef();
|
||||
switch (nodeRef.getType()) {
|
||||
case CLASS:
|
||||
addComment(cls, comment.getComment());
|
||||
break;
|
||||
|
||||
case FIELD:
|
||||
FieldNode fieldNode = cls.searchFieldByShortId(nodeRef.getShortId());
|
||||
if (fieldNode == null) {
|
||||
LOG.warn("Field reference not found: {}", nodeRef);
|
||||
} else {
|
||||
addComment(fieldNode, comment.getComment());
|
||||
}
|
||||
break;
|
||||
|
||||
case METHOD:
|
||||
MethodNode methodNode = cls.searchMethodByShortId(nodeRef.getShortId());
|
||||
if (methodNode == null) {
|
||||
LOG.warn("Method reference not found: {}", nodeRef);
|
||||
} else {
|
||||
int offset = comment.getOffset();
|
||||
if (offset < 0) {
|
||||
addComment(methodNode, comment.getComment());
|
||||
} else if (comment.getAttachType() != null) {
|
||||
processCustomAttach(methodNode, comment);
|
||||
} else {
|
||||
InsnNode insn = getInsnByOffset(methodNode, offset);
|
||||
addComment(insn, comment.getComment());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static InsnNode getInsnByOffset(MethodNode mth, int offset) {
|
||||
try {
|
||||
return mth.getInstructions()[offset];
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Insn reference not found in: {} with offset: {}", mth, offset);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void processCustomAttach(MethodNode mth, ICodeComment comment) {
|
||||
ICodeComment.AttachType attachType = comment.getAttachType();
|
||||
if (attachType == null) {
|
||||
return;
|
||||
}
|
||||
switch (attachType) {
|
||||
case VAR_DECLARE:
|
||||
InsnNode insn = getInsnByOffset(mth, comment.getOffset());
|
||||
if (insn != null) {
|
||||
RegisterArg result = insn.getResult();
|
||||
if (result != null) {
|
||||
result.addAttr(AType.CODE_COMMENTS, comment.getComment());
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new JadxRuntimeException("Unexpected attach type: " + attachType);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addComment(@Nullable IAttributeNode node, String comment) {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
node.remove(AType.CODE_COMMENTS);
|
||||
node.addAttr(AType.CODE_COMMENTS, comment);
|
||||
}
|
||||
|
||||
private static final class CommentsData {
|
||||
long updateId;
|
||||
Map<String, List<ICodeComment>> clsCommentsMap;
|
||||
}
|
||||
|
||||
private List<ICodeComment> getCommentsData(ClassNode cls) {
|
||||
ICodeData additionalData = cls.root().getArgs().getCodeData();
|
||||
if (additionalData == null || additionalData.getComments().isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
synchronized (cachedCommentsData) {
|
||||
CommentsData commentsData = this.cachedCommentsData;
|
||||
if (commentsData.updateId != additionalData.getUpdateId()) {
|
||||
updateCommentsData(additionalData, commentsData);
|
||||
}
|
||||
List<ICodeComment> clsComments = commentsData.clsCommentsMap.get(cls.getClassInfo().getFullName());
|
||||
if (clsComments == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return clsComments;
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateCommentsData(ICodeData data, CommentsData commentsData) {
|
||||
Map<String, List<ICodeComment>> map = new HashMap<>();
|
||||
for (ICodeComment comment : data.getComments()) {
|
||||
String declClsId = comment.getNodeRef().getDeclaringClass();
|
||||
List<ICodeComment> comments = map.computeIfAbsent(declClsId, s -> new ArrayList<>());
|
||||
comments.add(comment);
|
||||
}
|
||||
commentsData.clsCommentsMap = map;
|
||||
commentsData.updateId = data.getUpdateId();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.ConstStringNode;
|
||||
@@ -225,6 +226,12 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
||||
}
|
||||
if (insnType == InsnType.RETURN) {
|
||||
useInsn.setSourceLine(constInsn.getSourceLine());
|
||||
if (useInsn.contains(AFlag.SYNTHETIC)) {
|
||||
useInsn.setOffset(constInsn.getOffset());
|
||||
useInsn.rewriteAttributeFrom(constInsn, AType.CODE_COMMENTS);
|
||||
} else {
|
||||
useInsn.copyAttributeFrom(constInsn, AType.CODE_COMMENTS);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -608,5 +608,6 @@ public class ModVisitor extends AbstractVisitor {
|
||||
excHandler.setArg(namedArg);
|
||||
replaceInsn(mth, block, 0, moveInsn);
|
||||
}
|
||||
block.copyAttributeFrom(insn, AType.CODE_COMMENTS); // save comment
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.clsp.ClspClass;
|
||||
import jadx.core.clsp.ClspMethod;
|
||||
@@ -41,27 +39,20 @@ import jadx.core.utils.exceptions.JadxException;
|
||||
}
|
||||
)
|
||||
public class OverrideMethodVisitor extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(OverrideMethodVisitor.class);
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) throws JadxException {
|
||||
long startTime = System.currentTimeMillis();
|
||||
for (ClassNode cls : root.getClasses()) {
|
||||
processCls(cls);
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("OverrideMethod pass time: {}ms", System.currentTimeMillis() - startTime);
|
||||
}
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
processCls(cls);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean processCls(ClassNode cls) {
|
||||
private void processCls(ClassNode cls) {
|
||||
List<ArgType> superTypes = collectSuperTypes(cls);
|
||||
if (!superTypes.isEmpty()) {
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
processMth(cls, superTypes, mth);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void processMth(ClassNode cls, List<ArgType> superTypes, MethodNode mth) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "ProcessAnonymous",
|
||||
@@ -12,19 +13,20 @@ import jadx.core.dex.nodes.RootNode;
|
||||
)
|
||||
public class ProcessAnonymous extends AbstractVisitor {
|
||||
|
||||
private boolean inlineAnonymous;
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
if (root.getArgs().isInlineAnonymousClasses()) {
|
||||
for (ClassNode cls : root.getClasses(true)) {
|
||||
markAnonymousClass(cls);
|
||||
}
|
||||
}
|
||||
inlineAnonymous = root.getArgs().isInlineAnonymousClasses();
|
||||
}
|
||||
|
||||
public static void runForClass(ClassNode cls) {
|
||||
if (cls.root().getArgs().isInlineAnonymousClasses()) {
|
||||
markAnonymousClass(cls);
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
if (!inlineAnonymous) {
|
||||
return false;
|
||||
}
|
||||
markAnonymousClass(cls);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void markAnonymousClass(ClassNode cls) {
|
||||
|
||||
@@ -7,8 +7,6 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.Consts;
|
||||
@@ -26,7 +24,6 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
public class RenameVisitor extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RenameVisitor.class);
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
@@ -34,11 +31,7 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
if (inputFiles.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
long startTime = System.currentTimeMillis();
|
||||
process(root);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Rename pass time: {}ms", System.currentTimeMillis() - startTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void process(RootNode root) {
|
||||
|
||||
@@ -749,7 +749,9 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
first = false;
|
||||
} else {
|
||||
for (InsnNode oldInsn : exitBlock.getInstructions()) {
|
||||
newRetBlock.getInstructions().add(oldInsn.copyWithoutSsa());
|
||||
InsnNode copyInsn = oldInsn.copyWithoutSsa();
|
||||
copyInsn.add(AFlag.SYNTHETIC);
|
||||
newRetBlock.getInstructions().add(copyInsn);
|
||||
}
|
||||
}
|
||||
BlockSplitter.replaceConnection(pred, exitBlock, newRetBlock);
|
||||
|
||||
@@ -6,7 +6,10 @@ import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
@@ -142,10 +145,6 @@ public class CodeShrinkVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static boolean inline(MethodNode mth, RegisterArg arg, InsnNode insn, BlockNode block) {
|
||||
InsnNode parentInsn = arg.getParentInsn();
|
||||
if (parentInsn != null && parentInsn.getType() == InsnType.RETURN) {
|
||||
parentInsn.setSourceLine(insn.getSourceLine());
|
||||
}
|
||||
if (insn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
|
||||
return assignInline(mth, arg, insn, block);
|
||||
}
|
||||
@@ -153,11 +152,27 @@ public class CodeShrinkVisitor extends AbstractVisitor {
|
||||
InsnArg wrappedArg = arg.wrapInstruction(mth, insn, false);
|
||||
boolean replaced = wrappedArg != null;
|
||||
if (replaced) {
|
||||
processCodeComment(insn, arg.getParentInsn());
|
||||
InsnRemover.removeWithoutUnbind(mth, block, insn);
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
|
||||
private static void processCodeComment(InsnNode insn, @Nullable InsnNode parentInsn) {
|
||||
if (parentInsn == null) {
|
||||
return;
|
||||
}
|
||||
if (parentInsn.getType() == InsnType.RETURN) {
|
||||
parentInsn.setSourceLine(insn.getSourceLine());
|
||||
if (parentInsn.contains(AFlag.SYNTHETIC)) {
|
||||
parentInsn.setOffset(insn.getOffset());
|
||||
parentInsn.rewriteAttributeFrom(insn, AType.CODE_COMMENTS);
|
||||
return;
|
||||
}
|
||||
}
|
||||
parentInsn.copyAttributeFrom(insn, AType.CODE_COMMENTS);
|
||||
}
|
||||
|
||||
private static boolean canMoveBetweenBlocks(MethodNode mth, InsnNode assignInsn, BlockNode assignBlock,
|
||||
BlockNode useBlock, InsnNode useInsn) {
|
||||
if (!BlockUtils.isPathExists(assignBlock, useBlock)) {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package jadx.core.dex.visitors.usage;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.input.data.ICodeReader;
|
||||
import jadx.api.plugins.input.insns.InsnData;
|
||||
import jadx.api.plugins.input.insns.Opcode;
|
||||
@@ -27,17 +24,14 @@ import jadx.core.dex.visitors.RenameVisitor;
|
||||
}
|
||||
)
|
||||
public class UsageInfoVisitor extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UsageInfoVisitor.class);
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
UsageInfo usageInfo = new UsageInfo(root);
|
||||
for (ClassNode cls : root.getClasses()) {
|
||||
processClass(cls, usageInfo);
|
||||
}
|
||||
usageInfo.apply();
|
||||
LOG.debug("Dependency collection done in {}ms", System.currentTimeMillis() - startTime);
|
||||
}
|
||||
|
||||
private static void processClass(ClassNode cls, UsageInfo usageInfo) {
|
||||
|
||||
@@ -2,24 +2,79 @@ package jadx.core.utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
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.ClassNode;
|
||||
import jadx.core.dex.nodes.ICodeNode;
|
||||
|
||||
public class CodeGenUtils {
|
||||
|
||||
public static void addComments(ICodeWriter code, AttrNode node) {
|
||||
public static void addComments(ICodeWriter code, IAttributeNode node) {
|
||||
List<String> comments = node.getAll(AType.COMMENTS);
|
||||
if (!comments.isEmpty()) {
|
||||
comments.stream().distinct()
|
||||
.forEach(comment -> code.startLine("/* ").addMultiLine(comment).add(" */"));
|
||||
}
|
||||
addCodeComments(code, node);
|
||||
}
|
||||
|
||||
public static void addCodeComments(ICodeWriter code, @Nullable IAttributeNode node) {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
List<String> comments = node.getAll(AType.CODE_COMMENTS);
|
||||
if (comments.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (node instanceof ICodeNode) {
|
||||
// for classes, fields and methods add on line before node declaration
|
||||
code.startLine();
|
||||
} else {
|
||||
code.add(' ');
|
||||
}
|
||||
if (comments.size() == 1) {
|
||||
String comment = comments.get(0);
|
||||
if (!comment.contains("\n")) {
|
||||
code.add("// ").add(comment);
|
||||
return;
|
||||
}
|
||||
}
|
||||
addMultiLineComment(code, comments);
|
||||
}
|
||||
|
||||
private static void addMultiLineComment(ICodeWriter code, List<String> comments) {
|
||||
boolean first = true;
|
||||
String indent = "";
|
||||
Object lineAnn = null;
|
||||
for (String comment : comments) {
|
||||
for (String line : comment.split("\n")) {
|
||||
if (first) {
|
||||
first = false;
|
||||
StringBuilder buf = code.getRawBuf();
|
||||
int startLinePos = buf.lastIndexOf(ICodeWriter.NL) + 1;
|
||||
indent = Utils.strRepeat(" ", buf.length() - startLinePos);
|
||||
if (code.isMetadataSupported()) {
|
||||
lineAnn = code.getRawAnnotations().get(new CodePosition(code.getLine()));
|
||||
}
|
||||
} else {
|
||||
code.newLine().add(indent);
|
||||
if (lineAnn != null) {
|
||||
code.attachLineAnnotation(lineAnn);
|
||||
}
|
||||
}
|
||||
code.add("// ").add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void addRenamedComment(ICodeWriter code, AttrNode node, String origName) {
|
||||
@@ -35,7 +90,13 @@ public class CodeGenUtils {
|
||||
public static void addSourceFileInfo(ICodeWriter code, ClassNode node) {
|
||||
SourceFileAttr sourceFileAttr = node.get(AType.SOURCE_FILE);
|
||||
if (sourceFileAttr != null) {
|
||||
code.startLine("/* compiled from: ").add(sourceFileAttr.getFileName()).add(" */");
|
||||
String fileName = sourceFileAttr.getFileName();
|
||||
String topClsName = node.getTopParentClass().getClassInfo().getShortName();
|
||||
if (topClsName.contains(fileName)) {
|
||||
// ignore similar name
|
||||
return;
|
||||
}
|
||||
code.startLine("/* compiled from: ").add(fileName).add(" */");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
public class GsonUtils {
|
||||
|
||||
public static <T> InterfaceReplace<T> interfaceReplace(Class<T> replaceCls) {
|
||||
return new InterfaceReplace<>(replaceCls);
|
||||
}
|
||||
|
||||
private static final class InterfaceReplace<T> implements JsonSerializer<T>, JsonDeserializer<T> {
|
||||
private final Class<T> replaceCls;
|
||||
|
||||
private InterfaceReplace(Class<T> replaceCls) {
|
||||
this.replaceCls = replaceCls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
return context.deserialize(json, this.replaceCls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return context.serialize(src, this.replaceCls);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,28 @@ public class RegionUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static InsnNode getFirstInsn(IContainer container) {
|
||||
if (container instanceof IBlock) {
|
||||
IBlock block = (IBlock) container;
|
||||
List<InsnNode> insnList = block.getInstructions();
|
||||
if (insnList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return insnList.get(0);
|
||||
} else if (container instanceof IBranchRegion) {
|
||||
return null;
|
||||
} else if (container instanceof IRegion) {
|
||||
IRegion region = (IRegion) container;
|
||||
List<IContainer> blocks = region.getSubBlocks();
|
||||
if (blocks.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return getFirstInsn(blocks.get(0));
|
||||
} else {
|
||||
throw new JadxRuntimeException(unknownContainerType(container));
|
||||
}
|
||||
}
|
||||
|
||||
public static InsnNode getLastInsn(IContainer container) {
|
||||
if (container instanceof IBlock) {
|
||||
IBlock block = (IBlock) container;
|
||||
|
||||
@@ -308,6 +308,23 @@ public class Utils {
|
||||
return list.get(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static <T> T first(List<T> list) {
|
||||
if (list.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return list.get(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static <T> T first(Iterable<T> list) {
|
||||
Iterator<T> it = list.iterator();
|
||||
if (!it.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
return it.next();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static <T> T last(List<T> list) {
|
||||
if (list.isEmpty()) {
|
||||
@@ -316,6 +333,20 @@ public class Utils {
|
||||
return list.get(list.size() - 1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static <T> T last(Iterable<T> list) {
|
||||
Iterator<T> it = list.iterator();
|
||||
if (!it.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
while (true) {
|
||||
T next = it.next();
|
||||
if (!it.hasNext()) {
|
||||
return next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T getOrElse(@Nullable T obj, T defaultObj) {
|
||||
if (obj == null) {
|
||||
return defaultObj;
|
||||
|
||||
@@ -7,13 +7,18 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
|
||||
public class JadxAssertions extends Assertions {
|
||||
|
||||
public static JadxClassNodeAssertions assertThat(ClassNode actual) {
|
||||
Assertions.assertThat(actual).isNotNull();
|
||||
return new JadxClassNodeAssertions(actual);
|
||||
public static JadxClassNodeAssertions assertThat(ClassNode cls) {
|
||||
Assertions.assertThat(cls).isNotNull();
|
||||
return new JadxClassNodeAssertions(cls);
|
||||
}
|
||||
|
||||
public static JadxCodeAssertions assertThat(ICodeInfo actual) {
|
||||
Assertions.assertThat(actual).isNotNull();
|
||||
return new JadxCodeAssertions(actual.getCodeStr());
|
||||
public static JadxCodeInfoAssertions assertThat(ICodeInfo codeInfo) {
|
||||
Assertions.assertThat(codeInfo).isNotNull();
|
||||
return new JadxCodeInfoAssertions(codeInfo);
|
||||
}
|
||||
|
||||
public static JadxCodeAssertions assertThat(String code) {
|
||||
Assertions.assertThat(code).isNotNull();
|
||||
return new JadxCodeAssertions(code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.tests.api.utils.assertj;
|
||||
|
||||
import org.assertj.core.api.AbstractObjectAssert;
|
||||
import org.assertj.core.api.Assertions;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
@@ -13,10 +14,17 @@ public class JadxClassNodeAssertions extends AbstractObjectAssert<JadxClassNodeA
|
||||
super(cls, JadxClassNodeAssertions.class);
|
||||
}
|
||||
|
||||
public JadxCodeInfoAssertions decompile() {
|
||||
isNotNull();
|
||||
ICodeInfo codeInfo = actual.getCode();
|
||||
Assertions.assertThat(codeInfo).isNotNull();
|
||||
return new JadxCodeInfoAssertions(codeInfo);
|
||||
}
|
||||
|
||||
public JadxCodeAssertions code() {
|
||||
isNotNull();
|
||||
ICodeInfo code = actual.getCode();
|
||||
assertThat(code).isNotNull();
|
||||
Assertions.assertThat(code).isNotNull();
|
||||
String codeStr = code.getCodeStr();
|
||||
assertThat(codeStr).isNotBlank();
|
||||
return new JadxCodeAssertions(codeStr);
|
||||
@@ -25,9 +33,9 @@ public class JadxClassNodeAssertions extends AbstractObjectAssert<JadxClassNodeA
|
||||
public JadxCodeAssertions reloadCode(IntegrationTest testInstance) {
|
||||
isNotNull();
|
||||
ICodeInfo code = actual.reloadCode();
|
||||
assertThat(code).isNotNull();
|
||||
Assertions.assertThat(code).isNotNull();
|
||||
String codeStr = code.getCodeStr();
|
||||
assertThat(codeStr).isNotBlank();
|
||||
Assertions.assertThat(codeStr).isNotBlank();
|
||||
|
||||
JadxCodeAssertions codeAssertions = new JadxCodeAssertions(codeStr);
|
||||
codeAssertions.print();
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package jadx.tests.api.utils.assertj;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.assertj.core.api.AbstractObjectAssert;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.data.annotations.ICodeRawOffset;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class JadxCodeInfoAssertions extends AbstractObjectAssert<JadxCodeInfoAssertions, ICodeInfo> {
|
||||
public JadxCodeInfoAssertions(ICodeInfo cls) {
|
||||
super(cls, JadxCodeInfoAssertions.class);
|
||||
}
|
||||
|
||||
public JadxCodeAssertions code() {
|
||||
isNotNull();
|
||||
String codeStr = actual.getCodeStr();
|
||||
assertThat(codeStr).isNotBlank();
|
||||
return new JadxCodeAssertions(codeStr);
|
||||
}
|
||||
|
||||
public JadxCodeInfoAssertions checkCodeOffsets() {
|
||||
long dupOffsetCount = actual.getAnnotations().values().stream()
|
||||
.filter(o -> o instanceof ICodeRawOffset)
|
||||
.collect(Collectors.groupingBy(o -> ((ICodeRawOffset) o).getOffset(), Collectors.toList()))
|
||||
.values().stream()
|
||||
.filter(list -> list.size() > 1)
|
||||
.count();
|
||||
assertThat(dupOffsetCount)
|
||||
.describedAs("Found duplicated code offsets")
|
||||
.isEqualTo(0);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,8 @@ public class TestClassReGen extends IntegrationTest {
|
||||
@Test
|
||||
public void test() {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
assertThat(cls.getCode())
|
||||
assertThat(cls)
|
||||
.code()
|
||||
.containsOnlyOnce("private int intField = 5;")
|
||||
.containsOnlyOnce("public static class A {")
|
||||
.containsOnlyOnce("public int test() {");
|
||||
@@ -32,8 +33,8 @@ public class TestClassReGen extends IntegrationTest {
|
||||
cls.searchMethodByShortName("test").getMethodInfo().setAlias("testRenamed");
|
||||
cls.searchFieldByName("intField").getFieldInfo().setAlias("intFieldRenamed");
|
||||
|
||||
assertThat(cls.reloadCode())
|
||||
.print()
|
||||
assertThat(cls)
|
||||
.reloadCode(this)
|
||||
.containsOnlyOnce("private int intFieldRenamed = 5;")
|
||||
.containsOnlyOnce("public static class ARenamed {")
|
||||
.containsOnlyOnce("public int testRenamed() {");
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package jadx.tests.integration.others;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaNodeRef.RefType;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestCodeComments extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
private int intField = 5;
|
||||
|
||||
public static class A {
|
||||
}
|
||||
|
||||
public int test() {
|
||||
System.out.println("Hello");
|
||||
System.out.println("comment");
|
||||
return intField;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
String baseClsId = TestCls.class.getName();
|
||||
ICodeComment clsComment = new JadxCodeComment(JadxNodeRef.forCls(baseClsId), "class comment");
|
||||
ICodeComment innerClsComment = new JadxCodeComment(JadxNodeRef.forCls(baseClsId + ".A"), "inner class comment");
|
||||
ICodeComment fldComment = new JadxCodeComment(new JadxNodeRef(RefType.FIELD, baseClsId, "intField:I"), "field comment");
|
||||
JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId, "test()I");
|
||||
ICodeComment mthComment = new JadxCodeComment(mthRef, "method comment");
|
||||
ICodeComment insnComment = new JadxCodeComment(mthRef, "insn comment", 11);
|
||||
|
||||
JadxCodeData codeData = new JadxCodeData();
|
||||
getArgs().setCodeData(codeData);
|
||||
codeData.setComments(Arrays.asList(clsComment, innerClsComment, fldComment, mthComment, insnComment));
|
||||
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
assertThat(cls)
|
||||
.decompile()
|
||||
.checkCodeOffsets()
|
||||
.code()
|
||||
.containsOne("// class comment")
|
||||
.containsOne("// inner class comment")
|
||||
.containsOne("// field comment")
|
||||
.containsOne("// method comment")
|
||||
.containsOne("System.out.println(\"comment\"); // insn comment");
|
||||
|
||||
String code = cls.getCode().getCodeStr();
|
||||
assertThat(cls)
|
||||
.reloadCode(this)
|
||||
.isEqualTo(code);
|
||||
|
||||
ICodeComment updInsnComment = new JadxCodeComment(mthRef, "updated insn comment", 11);
|
||||
codeData.setComments(Collections.singletonList(updInsnComment));
|
||||
assertThat(cls)
|
||||
.reloadCode(this)
|
||||
.containsOne("System.out.println(\"comment\"); // updated insn comment")
|
||||
.doesNotContain("class comment")
|
||||
.containsOne(" comment");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package jadx.tests.integration.others;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaNodeRef.RefType;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestCodeComments2 extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
public int test(boolean z) {
|
||||
if (z) {
|
||||
System.out.println("z");
|
||||
return 1;
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
String baseClsId = TestCls.class.getName();
|
||||
JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId, "test(Z)I");
|
||||
ICodeComment insnComment = new JadxCodeComment(mthRef, "return comment", 10);
|
||||
ICodeComment insnComment2 = new JadxCodeComment(mthRef, "another return comment", 11);
|
||||
|
||||
JadxCodeData codeData = new JadxCodeData();
|
||||
codeData.setComments(Arrays.asList(insnComment, insnComment2));
|
||||
getArgs().setCodeData(codeData);
|
||||
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.decompile()
|
||||
.checkCodeOffsets()
|
||||
.code()
|
||||
.containsOne("// " + insnComment.getComment())
|
||||
.containsOne("// " + insnComment2.getComment());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package jadx.tests.integration.others;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaNodeRef.RefType;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestCodeComments2a extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
private int f;
|
||||
|
||||
public int test(boolean z) {
|
||||
if (z) {
|
||||
System.out.println("z");
|
||||
return new Random().nextInt();
|
||||
}
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
String baseClsId = TestCls.class.getName();
|
||||
JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId, "test(Z)I");
|
||||
ICodeComment insnComment = new JadxCodeComment(mthRef, "return comment", 18);
|
||||
ICodeComment insnComment2 = new JadxCodeComment(mthRef, "another return comment", 19);
|
||||
|
||||
JadxCodeData codeData = new JadxCodeData();
|
||||
codeData.setComments(Arrays.asList(insnComment, insnComment2));
|
||||
getArgs().setCodeData(codeData);
|
||||
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.decompile()
|
||||
.checkCodeOffsets()
|
||||
.code()
|
||||
.containsOne("// " + insnComment.getComment())
|
||||
.containsOne("// " + insnComment2.getComment());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package jadx.tests.integration.others;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaNodeRef.RefType;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestCodeCommentsMultiline extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
public int test(boolean z) {
|
||||
if (z) {
|
||||
System.out.println("z");
|
||||
return 1;
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
String baseClsId = TestCls.class.getName();
|
||||
JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId, "test(Z)I");
|
||||
ICodeComment insnComment = new JadxCodeComment(mthRef, "multi\nline\ncomment", 11);
|
||||
|
||||
JadxCodeData codeData = new JadxCodeData();
|
||||
codeData.setComments(Collections.singletonList(insnComment));
|
||||
getArgs().setCodeData(codeData);
|
||||
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne("// multi")
|
||||
.containsOne("// line")
|
||||
.containsOne("// comment");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package jadx.tests.integration.others;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaNodeRef.RefType;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestCodeCommentsOverride extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
public interface I {
|
||||
void mth();
|
||||
}
|
||||
|
||||
public static class A implements I {
|
||||
@Override
|
||||
public void mth() {
|
||||
System.out.println("mth");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
String baseClsId = TestCls.class.getName();
|
||||
JadxNodeRef iMthRef = new JadxNodeRef(RefType.METHOD, baseClsId + ".I", "mth()V");
|
||||
ICodeComment iMthComment = new JadxCodeComment(iMthRef, "interface mth comment");
|
||||
|
||||
JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId + ".A", "mth()V");
|
||||
ICodeComment mthComment = new JadxCodeComment(mthRef, "mth comment");
|
||||
|
||||
JadxCodeData codeData = new JadxCodeData();
|
||||
codeData.setComments(Arrays.asList(iMthComment, mthComment));
|
||||
getArgs().setCodeData(codeData);
|
||||
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
assertThat(cls)
|
||||
.decompile()
|
||||
.checkCodeOffsets()
|
||||
.code()
|
||||
.containsOne("@Override")
|
||||
.containsOne("// " + iMthComment.getComment())
|
||||
.containsOne("// " + mthComment.getComment());
|
||||
|
||||
assertThat(cls)
|
||||
.reloadCode(this)
|
||||
.containsOne("@Override")
|
||||
.containsOne("// " + iMthComment.getComment())
|
||||
.containsOne("// " + mthComment.getComment());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user