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
@@ -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());
}
}