stage 1
This commit is contained in:
@@ -9,6 +9,7 @@ import org.junit.jupiter.api.io.TempDir;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -88,4 +89,42 @@ public class ResolutionRobustnessTest {
|
||||
assertThat(context.getTypeDeclaration("p1.X")).isNotNull();
|
||||
assertThat(context.getTypeDeclaration("p2.X")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJdtBindingsResolution(@TempDir Path tempDir) throws IOException {
|
||||
Path comFoo = tempDir.resolve("com/foo");
|
||||
Files.createDirectories(comFoo);
|
||||
Files.writeString(comFoo.resolve("Base.java"),
|
||||
"package com.foo;\n" +
|
||||
"public class Base {\n" +
|
||||
" public void run() {}\n" +
|
||||
"}"
|
||||
);
|
||||
Files.writeString(comFoo.resolve("Child.java"),
|
||||
"package com.foo;\n" +
|
||||
"public class Child extends Base {\n" +
|
||||
" public void execute() {\n" +
|
||||
" run();\n" +
|
||||
" }\n" +
|
||||
"}"
|
||||
);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.setResolveBindings(true);
|
||||
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
|
||||
context.scan(tempDir);
|
||||
|
||||
TypeDeclaration childTd = context.getTypeDeclaration("com.foo.Child");
|
||||
assertThat(childTd).isNotNull();
|
||||
|
||||
// Resolve the binding for Child class
|
||||
org.eclipse.jdt.core.dom.ITypeBinding binding = childTd.resolveBinding();
|
||||
assertThat(binding).isNotNull();
|
||||
assertThat(binding.getQualifiedName()).isEqualTo("com.foo.Child");
|
||||
|
||||
// Verify we can get the superclass binding
|
||||
org.eclipse.jdt.core.dom.ITypeBinding superBinding = binding.getSuperclass();
|
||||
assertThat(superBinding).isNotNull();
|
||||
assertThat(superBinding.getQualifiedName()).isEqualTo("com.foo.Base");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package click.kamil.springstatemachineexporter.ast.common;
|
||||
|
||||
import org.eclipse.jdt.core.dom.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class BindingResolverTest {
|
||||
|
||||
@Test
|
||||
void testBindingResolverMethods(@TempDir Path tempDir) throws IOException {
|
||||
Path comFoo = tempDir.resolve("com/foo");
|
||||
Files.createDirectories(comFoo);
|
||||
Files.writeString(comFoo.resolve("Base.java"),
|
||||
"package com.foo;\n" +
|
||||
"public interface Base {\n" +
|
||||
" void run();\n" +
|
||||
"}"
|
||||
);
|
||||
Files.writeString(comFoo.resolve("Child.java"),
|
||||
"package com.foo;\n" +
|
||||
"public class Child implements Base {\n" +
|
||||
" public void run() {}\n" +
|
||||
" public void execute() {\n" +
|
||||
" Child c = new Child();\n" +
|
||||
" c.run();\n" +
|
||||
" }\n" +
|
||||
"}"
|
||||
);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.setResolveBindings(true);
|
||||
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
|
||||
context.scan(tempDir);
|
||||
|
||||
TypeDeclaration childTd = context.getTypeDeclaration("com.foo.Child");
|
||||
assertThat(childTd).isNotNull();
|
||||
|
||||
ITypeBinding binding = BindingResolver.resolveType(childTd);
|
||||
assertThat(binding).isNotNull();
|
||||
assertThat(BindingResolver.getFullyQualifiedName(binding)).isEqualTo("com.foo.Child");
|
||||
assertThat(BindingResolver.isSubtypeOf(binding, "com.foo.Base")).isTrue();
|
||||
assertThat(BindingResolver.isSubtypeOf(binding, "java.lang.Object")).isTrue();
|
||||
assertThat(BindingResolver.isSubtypeOf(binding, "com.foo.NonExistent")).isFalse();
|
||||
|
||||
// Find MethodDeclaration execute
|
||||
MethodDeclaration executeMethod = null;
|
||||
for (MethodDeclaration md : childTd.getMethods()) {
|
||||
if (md.getName().getIdentifier().equals("execute")) {
|
||||
executeMethod = md;
|
||||
}
|
||||
}
|
||||
assertThat(executeMethod).isNotNull();
|
||||
|
||||
// Traverse the body to find ClassInstanceCreation and MethodInvocation
|
||||
class Visitor extends ASTVisitor {
|
||||
ClassInstanceCreation cic = null;
|
||||
MethodInvocation mi = null;
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassInstanceCreation node) {
|
||||
cic = node;
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(MethodInvocation node) {
|
||||
mi = node;
|
||||
return super.visit(node);
|
||||
}
|
||||
}
|
||||
|
||||
Visitor visitor = new Visitor();
|
||||
executeMethod.accept(visitor);
|
||||
|
||||
assertThat(visitor.cic).isNotNull();
|
||||
IMethodBinding constrBinding = BindingResolver.resolveConstructor(visitor.cic);
|
||||
assertThat(constrBinding).isNotNull();
|
||||
assertThat(constrBinding.isConstructor()).isTrue();
|
||||
|
||||
assertThat(visitor.mi).isNotNull();
|
||||
IMethodBinding methodBinding = BindingResolver.resolveMethod(visitor.mi);
|
||||
assertThat(methodBinding).isNotNull();
|
||||
assertThat(methodBinding.getName()).isEqualTo("run");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package click.kamil.springstatemachineexporter.ast.common;
|
||||
|
||||
import org.eclipse.jdt.core.dom.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ControlFlowGraphTest {
|
||||
|
||||
@Test
|
||||
void testSequentialAndBranchingCfg(@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 void run(boolean flag) {\n" +
|
||||
" int a = 1;\n" +
|
||||
" if (flag) {\n" +
|
||||
" a = 2;\n" +
|
||||
" } else {\n" +
|
||||
" a = 3;\n" +
|
||||
" }\n" +
|
||||
" int b = a;\n" +
|
||||
" }\n" +
|
||||
"}"
|
||||
);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
||||
assertThat(td).isNotNull();
|
||||
|
||||
MethodDeclaration md = td.getMethods()[0];
|
||||
ControlFlowGraph cfg = CfgBuilder.build(md);
|
||||
|
||||
assertThat(cfg).isNotNull();
|
||||
assertThat(cfg.getEntryNode().getSuccessors()).hasSize(1);
|
||||
|
||||
// Entry goes to first statement: 'int a = 1;'
|
||||
ControlFlowGraph.CfgNode firstStmtNode = cfg.getEntryNode().getSuccessors().iterator().next();
|
||||
assertThat(firstStmtNode.getAstNode()).isInstanceOf(VariableDeclarationStatement.class);
|
||||
|
||||
// First stmt goes to condition of 'if (flag)'
|
||||
assertThat(firstStmtNode.getSuccessors()).hasSize(1);
|
||||
ControlFlowGraph.CfgNode condNode = firstStmtNode.getSuccessors().iterator().next();
|
||||
assertThat(condNode.getAstNode()).isInstanceOf(SimpleName.class); // flag
|
||||
|
||||
// Cond node goes to two branches: 'a = 2;' and 'a = 3;'
|
||||
assertThat(condNode.getSuccessors()).hasSize(2);
|
||||
|
||||
for (ControlFlowGraph.CfgNode branchSuccessor : condNode.getSuccessors()) {
|
||||
assertThat(branchSuccessor.getAstNode()).isInstanceOf(ExpressionStatement.class); // assignment expression statement
|
||||
|
||||
// Each branch successor should flow into the next sequential statement: 'int b = a;'
|
||||
assertThat(branchSuccessor.getSuccessors()).hasSize(1);
|
||||
ControlFlowGraph.CfgNode nextStmtNode = branchSuccessor.getSuccessors().iterator().next();
|
||||
assertThat(nextStmtNode.getAstNode()).isInstanceOf(VariableDeclarationStatement.class);
|
||||
assertThat(nextStmtNode.getSuccessors()).contains(cfg.getExitNode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package click.kamil.springstatemachineexporter.ast.common;
|
||||
|
||||
import org.eclipse.jdt.core.dom.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class JdtDataFlowModelTest {
|
||||
|
||||
@Test
|
||||
void testRecursiveJdtDataFlowResolution(@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 void run() {\n" +
|
||||
" String a = \"VAL1\";\n" +
|
||||
" String b = a;\n" +
|
||||
" String c = b;\n" +
|
||||
" }\n" +
|
||||
"}"
|
||||
);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
||||
assertThat(td).isNotNull();
|
||||
|
||||
MethodDeclaration md = td.getMethods()[0];
|
||||
VariableDeclarationStatement cStmt = (VariableDeclarationStatement) md.getBody().statements().get(2);
|
||||
VariableDeclarationFragment cFrag = (VariableDeclarationFragment) cStmt.fragments().get(0);
|
||||
Expression initializer = cFrag.getInitializer(); // b
|
||||
|
||||
DataFlowModel model = new JdtDataFlowModel(context);
|
||||
|
||||
List<Expression> defs = model.getReachingDefinitions(initializer);
|
||||
assertThat(defs).isNotEmpty();
|
||||
|
||||
Expression singleDef = defs.get(defs.size() - 1);
|
||||
assertThat(singleDef).isInstanceOf(StringLiteral.class);
|
||||
assertThat(((StringLiteral) singleDef).getLiteralValue()).isEqualTo("VAL1");
|
||||
|
||||
String val = model.resolveValue(initializer, context);
|
||||
assertThat(val).isEqualTo("VAL1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBranchDivergenceJdtDataFlowResolution(@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 void run(boolean flag) {\n" +
|
||||
" String a = \"SAME\";\n" +
|
||||
" if (flag) {\n" +
|
||||
" a = \"SAME\";\n" +
|
||||
" } else {\n" +
|
||||
" a = \"SAME\";\n" +
|
||||
" }\n" +
|
||||
" String b = a;\n" +
|
||||
" }\n" +
|
||||
"}"
|
||||
);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
||||
assertThat(td).isNotNull();
|
||||
|
||||
MethodDeclaration md = td.getMethods()[0];
|
||||
VariableDeclarationStatement bStmt = (VariableDeclarationStatement) md.getBody().statements().get(2);
|
||||
VariableDeclarationFragment bFrag = (VariableDeclarationFragment) bStmt.fragments().get(0);
|
||||
Expression initializer = bFrag.getInitializer(); // a
|
||||
|
||||
DataFlowModel model = new JdtDataFlowModel(context);
|
||||
|
||||
String val = model.resolveValue(initializer, context);
|
||||
// Both branches assign "SAME", so it converges to "SAME"
|
||||
assertThat(val).isEqualTo("SAME");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package click.kamil.springstatemachineexporter.ast.common;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
|
||||
import click.kamil.springstatemachineexporter.analysis.service.VariableTracer;
|
||||
import org.eclipse.jdt.core.dom.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class LegacyDataFlowModelTest {
|
||||
|
||||
@Test
|
||||
void testLegacyDataFlowResolution(@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 void run() {\n" +
|
||||
" String a = \"VAL1\";\n" +
|
||||
" String b = a;\n" +
|
||||
" }\n" +
|
||||
"}"
|
||||
);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
||||
assertThat(td).isNotNull();
|
||||
|
||||
MethodDeclaration md = td.getMethods()[0];
|
||||
VariableDeclarationStatement bStmt = (VariableDeclarationStatement) md.getBody().statements().get(1);
|
||||
VariableDeclarationFragment frag = (VariableDeclarationFragment) bStmt.fragments().get(0);
|
||||
Expression initializer = frag.getInitializer(); // 'a' reference
|
||||
|
||||
VariableTracer tracer = new VariableTracer(context, new ConstantResolver());
|
||||
DataFlowModel model = new LegacyDataFlowModel(tracer);
|
||||
|
||||
List<Expression> defs = model.getReachingDefinitions(initializer);
|
||||
assertThat(defs).isNotEmpty();
|
||||
|
||||
Expression resolvedExpr = defs.get(defs.size() - 1);
|
||||
assertThat(resolvedExpr).isInstanceOf(StringLiteral.class);
|
||||
assertThat(((StringLiteral) resolvedExpr).getLiteralValue()).isEqualTo("VAL1");
|
||||
|
||||
String val = model.resolveValue(initializer, context);
|
||||
assertThat(val).isEqualTo("VAL1");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package click.kamil.springstatemachineexporter.ast.common;
|
||||
|
||||
import org.eclipse.jdt.core.dom.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ReachingDefinitionsTest {
|
||||
|
||||
@Test
|
||||
void testReachingDefinitionsBranchMerge(@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 void run(boolean flag) {\n" +
|
||||
" int a = 1;\n" +
|
||||
" if (flag) {\n" +
|
||||
" a = 2;\n" +
|
||||
" } else {\n" +
|
||||
" a = 3;\n" +
|
||||
" }\n" +
|
||||
" int b = a;\n" +
|
||||
" }\n" +
|
||||
"}"
|
||||
);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
||||
assertThat(td).isNotNull();
|
||||
|
||||
MethodDeclaration md = td.getMethods()[0];
|
||||
ControlFlowGraph cfg = CfgBuilder.build(md);
|
||||
ReachingDefinitions rd = new ReachingDefinitions(cfg);
|
||||
|
||||
// Find usage 'a' in 'int b = a;'
|
||||
VariableDeclarationStatement bStmt = (VariableDeclarationStatement) md.getBody().statements().get(2);
|
||||
VariableDeclarationFragment bFrag = (VariableDeclarationFragment) bStmt.fragments().get(0);
|
||||
Expression initializer = bFrag.getInitializer(); // simpleName 'a'
|
||||
|
||||
Set<ASTNode> defs = rd.getReachingDefinitions(initializer, "a");
|
||||
assertThat(defs).hasSize(2);
|
||||
|
||||
Set<String> values = defs.stream()
|
||||
.map(ReachingDefinitions::getDefinitionExpression)
|
||||
.map(expr -> {
|
||||
if (expr instanceof NumberLiteral nl) {
|
||||
return nl.getToken();
|
||||
}
|
||||
return expr.toString();
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
assertThat(values).containsExactlyInAnyOrder("2", "3");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReachingDefinitionsKilled(@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 void run() {\n" +
|
||||
" int a = 1;\n" +
|
||||
" a = 2;\n" +
|
||||
" int b = a;\n" +
|
||||
" }\n" +
|
||||
"}"
|
||||
);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
||||
assertThat(td).isNotNull();
|
||||
|
||||
MethodDeclaration md = td.getMethods()[0];
|
||||
ControlFlowGraph cfg = CfgBuilder.build(md);
|
||||
ReachingDefinitions rd = new ReachingDefinitions(cfg);
|
||||
|
||||
// Find usage 'a' in 'int b = a;'
|
||||
VariableDeclarationStatement bStmt = (VariableDeclarationStatement) md.getBody().statements().get(2);
|
||||
VariableDeclarationFragment bFrag = (VariableDeclarationFragment) bStmt.fragments().get(0);
|
||||
Expression initializer = bFrag.getInitializer();
|
||||
|
||||
Set<ASTNode> defs = rd.getReachingDefinitions(initializer, "a");
|
||||
assertThat(defs).hasSize(1);
|
||||
|
||||
ASTNode singleDef = defs.iterator().next();
|
||||
Expression defExpr = ReachingDefinitions.getDefinitionExpression(singleDef);
|
||||
assertThat(defExpr).isInstanceOf(NumberLiteral.class);
|
||||
assertThat(((NumberLiteral) defExpr).getToken()).isEqualTo("2");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user