This commit is contained in:
2026-06-27 19:51:26 +02:00
parent 8d66af6d24
commit a7c3b08164
3 changed files with 611 additions and 30 deletions

View File

@@ -104,6 +104,8 @@ class JdtDataFlowModelTest {
);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
context.scan(tempDir);
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
@@ -134,6 +136,8 @@ class JdtDataFlowModelTest {
);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
context.scan(tempDir);
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
@@ -179,6 +183,8 @@ class JdtDataFlowModelTest {
);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
context.scan(tempDir);
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
@@ -219,6 +225,8 @@ class JdtDataFlowModelTest {
);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
context.scan(tempDir);
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
@@ -250,4 +258,259 @@ class JdtDataFlowModelTest {
}
assertThat(hasLoopBackToBody).isTrue();
}
@Test
void testConstructorAssignmentPropagation(@TempDir Path tempDir) throws IOException {
Path comFoo = tempDir.resolve("com/foo");
Files.createDirectories(comFoo);
Files.writeString(comFoo.resolve("Test.java"),
"package com.foo;\n" +
"public class Test {\n" +
" private final String myVar;\n" +
" public Test() {\n" +
" this.myVar = \"CONSTRUCTOR_VAL\";\n" +
" }\n" +
" public void run() {\n" +
" String a = myVar;\n" +
" }\n" +
"}"
);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
context.scan(tempDir);
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
assertThat(td).isNotNull();
MethodDeclaration md = null;
for (MethodDeclaration m : td.getMethods()) {
if (m.getName().getIdentifier().equals("run")) {
md = m;
}
}
assertThat(md).isNotNull();
VariableDeclarationStatement aStmt = (VariableDeclarationStatement) md.getBody().statements().get(0);
VariableDeclarationFragment aFrag = (VariableDeclarationFragment) aStmt.fragments().get(0);
Expression initializer = aFrag.getInitializer();
DataFlowModel model = new JdtDataFlowModel(context);
String val = model.resolveValue(initializer, context);
assertThat(val).isEqualTo("CONSTRUCTOR_VAL");
}
@Test
void testObjectSensitiveInstanceFieldTracing(@TempDir Path tempDir) throws IOException {
Path comFoo = tempDir.resolve("com/foo");
Files.createDirectories(comFoo);
Files.writeString(comFoo.resolve("Test.java"),
"package com.foo;\n" +
"public class Test {\n" +
" public static class Builder {\n" +
" private final String val;\n" +
" public Builder(String val) {\n" +
" this.val = val;\n" +
" }\n" +
" public String getVal() {\n" +
" return this.val;\n" +
" }\n" +
" }\n" +
" public void run() {\n" +
" Builder b = new Builder(\"OBJECT_SENSITIVE\");\n" +
" String res = b.getVal();\n" +
" }\n" +
"}"
);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
context.scan(tempDir);
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
assertThat(td).isNotNull();
MethodDeclaration md = null;
for (MethodDeclaration m : td.getMethods()) {
if (m.getName().getIdentifier().equals("run")) {
md = m;
}
}
assertThat(md).isNotNull();
VariableDeclarationStatement resStmt = (VariableDeclarationStatement) md.getBody().statements().get(1);
VariableDeclarationFragment resFrag = (VariableDeclarationFragment) resStmt.fragments().get(0);
Expression initializer = resFrag.getInitializer(); // b.getVal()
DataFlowModel model = new JdtDataFlowModel(context);
String val = model.resolveValue(initializer, context);
assertThat(val).isEqualTo("OBJECT_SENSITIVE");
}
@Test
void testConstructorChaining(@TempDir Path tempDir) throws IOException {
Path comFoo = tempDir.resolve("com/foo");
Files.createDirectories(comFoo);
Files.writeString(comFoo.resolve("Test.java"),
"package com.foo;\n" +
"public class Test {\n" +
" public static class Command {\n" +
" private final String event;\n" +
" private final String info;\n" +
" public Command(String event, String info) {\n" +
" this.event = event;\n" +
" this.info = info;\n" +
" }\n" +
" public Command(String event) {\n" +
" this(event, \"DEFAULT_INFO\");\n" +
" }\n" +
" public Command() {\n" +
" this(\"DEFAULT_EVENT\");\n" +
" }\n" +
" public String getEvent() { return this.event; }\n" +
" public String getInfo() { return this.info; }\n" +
" }\n" +
" public void run() {\n" +
" Command cmd = new Command();\n" +
" String ev = cmd.getEvent();\n" +
" String inf = cmd.getInfo();\n" +
" }\n" +
"}"
);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
context.scan(tempDir);
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
assertThat(td).isNotNull();
MethodDeclaration md = null;
for (MethodDeclaration m : td.getMethods()) {
if (m.getName().getIdentifier().equals("run")) {
md = m;
}
}
assertThat(md).isNotNull();
VariableDeclarationStatement evStmt = (VariableDeclarationStatement) md.getBody().statements().get(1);
VariableDeclarationFragment evFrag = (VariableDeclarationFragment) evStmt.fragments().get(0);
Expression initEv = evFrag.getInitializer();
DataFlowModel model = new JdtDataFlowModel(context);
String valEv = model.resolveValue(initEv, context);
assertThat(valEv).isEqualTo("DEFAULT_EVENT");
VariableDeclarationStatement infStmt = (VariableDeclarationStatement) md.getBody().statements().get(2);
VariableDeclarationFragment infFrag = (VariableDeclarationFragment) infStmt.fragments().get(0);
Expression initInf = infFrag.getInitializer();
String valInf = model.resolveValue(initInf, context);
assertThat(valInf).isEqualTo("DEFAULT_INFO");
}
@Test
void testExplicitSuperCalls(@TempDir Path tempDir) throws IOException {
Path comFoo = tempDir.resolve("com/foo");
Files.createDirectories(comFoo);
Files.writeString(comFoo.resolve("Test.java"),
"package com.foo;\n" +
"public class Test {\n" +
" public static class Base {\n" +
" public String getValue() {\n" +
" return \"BASE_VAL\";\n" +
" }\n" +
" }\n" +
" public static class Sub extends Base {\n" +
" @Override\n" +
" public String getValue() {\n" +
" return super.getValue();\n" +
" }\n" +
" }\n" +
" public void run() {\n" +
" Sub sub = new Sub();\n" +
" String val = sub.getValue();\n" +
" }\n" +
"}"
);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
context.scan(tempDir);
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
assertThat(td).isNotNull();
MethodDeclaration md = null;
for (MethodDeclaration m : td.getMethods()) {
if (m.getName().getIdentifier().equals("run")) {
md = m;
}
}
assertThat(md).isNotNull();
VariableDeclarationStatement valStmt = (VariableDeclarationStatement) md.getBody().statements().get(1);
VariableDeclarationFragment valFrag = (VariableDeclarationFragment) valStmt.fragments().get(0);
Expression initVal = valFrag.getInitializer();
DataFlowModel model = new JdtDataFlowModel(context);
String val = model.resolveValue(initVal, context);
assertThat(val).isEqualTo("BASE_VAL");
}
@Test
void testDynamicDispatchNarrowing(@TempDir Path tempDir) throws IOException {
Path comFoo = tempDir.resolve("com/foo");
Files.createDirectories(comFoo);
Files.writeString(comFoo.resolve("Test.java"),
"package com.foo;\n" +
"public class Test {\n" +
" public interface Service {\n" +
" String getEvent();\n" +
" }\n" +
" public static class PayService implements Service {\n" +
" public String getEvent() { return \"PAY\"; }\n" +
" }\n" +
" public static class CancelService implements Service {\n" +
" public String getEvent() { return \"CANCEL\"; }\n" +
" }\n" +
" public void run() {\n" +
" Service s = new PayService();\n" +
" String ev = s.getEvent();\n" +
" }\n" +
"}"
);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
context.scan(tempDir);
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
assertThat(td).isNotNull();
MethodDeclaration md = null;
for (MethodDeclaration m : td.getMethods()) {
if (m.getName().getIdentifier().equals("run")) {
md = m;
}
}
assertThat(md).isNotNull();
VariableDeclarationStatement evStmt = (VariableDeclarationStatement) md.getBody().statements().get(1);
VariableDeclarationFragment evFrag = (VariableDeclarationFragment) evStmt.fragments().get(0);
Expression initEv = evFrag.getInitializer();
DataFlowModel model = new JdtDataFlowModel(context);
List<Expression> defs = model.getReachingDefinitions(initEv);
assertThat(defs).isNotEmpty();
String val = model.resolveValue(initEv, context);
assertThat(val).isEqualTo("PAY");
}
}

View File

@@ -338,7 +338,7 @@
},
"methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.payCast", "click.kamil.examples.statemachine.polymorphic.OrderService.processEvent" ],
"triggerPoint" : {
"event" : "(BaseEvent)new PayEvent()",
"event" : "new PayEvent()",
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
"methodName" : "processEvent",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",