feat(gui): add code comments (#359) (PR #1127)

* 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:
skylot
2021-03-04 17:45:48 +03:00
committed by GitHub
parent 7a14aaa17e
commit 4e5fac4b88
113 changed files with 3527 additions and 722 deletions
@@ -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();
+5 -2
View File
@@ -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());
}
}