forward analysis 3
This commit is contained in:
@@ -27,7 +27,6 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
|||||||
hasPolyMatch = true;
|
hasPolyMatch = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
continue; // Stricter matching: do not fallback if both have qualifiers but don't match
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String simplePe = pe;
|
String simplePe = pe;
|
||||||
@@ -50,7 +49,6 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
|||||||
if (smEventRaw.equals(rawTriggerEvent) || smEventRaw.endsWith("." + rawTriggerEvent)) {
|
if (smEventRaw.equals(rawTriggerEvent) || smEventRaw.endsWith("." + rawTriggerEvent)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String triggerEvent = simplify(rawTriggerEvent);
|
String triggerEvent = simplify(rawTriggerEvent);
|
||||||
|
|||||||
@@ -56,6 +56,22 @@ public class SymbolicPathValueEstimator implements PathValueEstimator {
|
|||||||
return super.visit(node);
|
return super.visit(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean visit(InfixExpression node) {
|
||||||
|
if (node.getOperator() == InfixExpression.Operator.EQUALS) {
|
||||||
|
click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver resolver = new click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver();
|
||||||
|
String left = resolver.resolve(node.getLeftOperand(), context);
|
||||||
|
if (left != null && !left.isEmpty()) {
|
||||||
|
addValue(collectedConstants, left);
|
||||||
|
}
|
||||||
|
String right = resolver.resolve(node.getRightOperand(), context);
|
||||||
|
if (right != null && !right.isEmpty()) {
|
||||||
|
addValue(collectedConstants, right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean visit(SwitchStatement node) {
|
public boolean visit(SwitchStatement node) {
|
||||||
for (Object stmt : node.statements()) {
|
for (Object stmt : node.statements()) {
|
||||||
|
|||||||
@@ -133,54 +133,96 @@ public class ConstantResolver {
|
|||||||
if (md.getBody() == null || mi.arguments().size() != md.parameters().size()) return null;
|
if (md.getBody() == null || mi.arguments().size() != md.parameters().size()) return null;
|
||||||
|
|
||||||
java.util.Map<String, String> localVars = new java.util.HashMap<>();
|
java.util.Map<String, String> localVars = new java.util.HashMap<>();
|
||||||
|
java.util.Set<String> declaredLocals = new java.util.HashSet<>();
|
||||||
|
|
||||||
for (int i = 0; i < mi.arguments().size(); i++) {
|
for (int i = 0; i < mi.arguments().size(); i++) {
|
||||||
Expression arg = (Expression) mi.arguments().get(i);
|
Expression arg = (Expression) mi.arguments().get(i);
|
||||||
String val = resolveInternal(arg, context, visited);
|
String val = resolveInternal(arg, context, visited);
|
||||||
if (val != null) {
|
if (val != null) {
|
||||||
org.eclipse.jdt.core.dom.SingleVariableDeclaration param = (org.eclipse.jdt.core.dom.SingleVariableDeclaration) md.parameters().get(i);
|
org.eclipse.jdt.core.dom.SingleVariableDeclaration param = (org.eclipse.jdt.core.dom.SingleVariableDeclaration) md.parameters().get(i);
|
||||||
localVars.put(param.getName().getIdentifier(), val);
|
String paramName = param.getName().getIdentifier();
|
||||||
|
localVars.put(paramName, val);
|
||||||
|
declaredLocals.add(paramName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localVars.isEmpty()) return null;
|
String[] finalResult = new String[1];
|
||||||
|
|
||||||
for (Object stmtObj : md.getBody().statements()) {
|
md.getBody().accept(new ASTVisitor() {
|
||||||
if (stmtObj instanceof org.eclipse.jdt.core.dom.ExpressionStatement es) {
|
@Override
|
||||||
if (es.getExpression() instanceof Assignment assignment) {
|
public boolean visit(VariableDeclarationStatement vds) {
|
||||||
Expression lhs = assignment.getLeftHandSide();
|
for (Object fragmentObj : vds.fragments()) {
|
||||||
String varName = null;
|
if (fragmentObj instanceof VariableDeclarationFragment fragment) {
|
||||||
if (lhs instanceof SimpleName sn) {
|
String varName = fragment.getName().getIdentifier();
|
||||||
varName = sn.getIdentifier();
|
declaredLocals.add(varName);
|
||||||
} else if (lhs instanceof FieldAccess fa) {
|
if (fragment.getInitializer() != null) {
|
||||||
varName = "this." + fa.getName().getIdentifier();
|
String rhsVal = resolveExpressionWithParams(fragment.getInitializer(), localVars);
|
||||||
}
|
if (rhsVal == null) rhsVal = resolveInternal(fragment.getInitializer(), context, visited);
|
||||||
|
if (rhsVal != null) {
|
||||||
String rhsVal = resolveExpressionWithParams(assignment.getRightHandSide(), localVars);
|
localVars.put(varName, rhsVal);
|
||||||
if (rhsVal == null) rhsVal = resolveInternal(assignment.getRightHandSide(), context, visited);
|
}
|
||||||
|
|
||||||
if (varName != null && rhsVal != null) {
|
|
||||||
localVars.put(varName, rhsVal);
|
|
||||||
if (varName.startsWith("this.")) {
|
|
||||||
localVars.put(varName.substring(5), rhsVal); // alias without 'this.'
|
|
||||||
} else {
|
|
||||||
localVars.put("this." + varName, rhsVal); // alias with 'this.'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (stmtObj instanceof org.eclipse.jdt.core.dom.SwitchStatement ss) {
|
return super.visit(vds);
|
||||||
String result = evaluateSwitchStatement(ss, localVars, context, visited);
|
|
||||||
if (result != null) return result;
|
|
||||||
} else if (stmtObj instanceof org.eclipse.jdt.core.dom.ReturnStatement rs) {
|
|
||||||
if (rs.getExpression() instanceof org.eclipse.jdt.core.dom.SwitchExpression se) {
|
|
||||||
String result = evaluateSwitchExpression(se, localVars, context, visited);
|
|
||||||
if (result != null) return result;
|
|
||||||
} else {
|
|
||||||
String result = resolveExpressionWithParams(rs.getExpression(), localVars);
|
|
||||||
if (result != null) return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return null;
|
@Override
|
||||||
|
public boolean visit(Assignment assignment) {
|
||||||
|
Expression lhs = assignment.getLeftHandSide();
|
||||||
|
String varName = null;
|
||||||
|
if (lhs instanceof SimpleName sn) {
|
||||||
|
varName = sn.getIdentifier();
|
||||||
|
} else if (lhs instanceof FieldAccess fa) {
|
||||||
|
varName = "this." + fa.getName().getIdentifier();
|
||||||
|
}
|
||||||
|
|
||||||
|
String rhsVal = resolveExpressionWithParams(assignment.getRightHandSide(), localVars);
|
||||||
|
if (rhsVal == null) rhsVal = resolveInternal(assignment.getRightHandSide(), context, visited);
|
||||||
|
|
||||||
|
if (varName != null && rhsVal != null) {
|
||||||
|
if (varName.startsWith("this.")) {
|
||||||
|
localVars.put(varName, rhsVal);
|
||||||
|
String bareName = varName.substring(5);
|
||||||
|
if (!declaredLocals.contains(bareName)) {
|
||||||
|
localVars.put(bareName, rhsVal); // alias without 'this.' only if not shadowed
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
localVars.put(varName, rhsVal);
|
||||||
|
if (!declaredLocals.contains(varName)) {
|
||||||
|
localVars.put("this." + varName, rhsVal); // alias with 'this.' only if not shadowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(assignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean visit(org.eclipse.jdt.core.dom.SwitchStatement ss) {
|
||||||
|
if (finalResult[0] == null) {
|
||||||
|
String result = evaluateSwitchStatement(ss, localVars, context, visited);
|
||||||
|
if (result != null) finalResult[0] = result;
|
||||||
|
}
|
||||||
|
return super.visit(ss);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean visit(org.eclipse.jdt.core.dom.ReturnStatement rs) {
|
||||||
|
if (finalResult[0] == null) {
|
||||||
|
if (rs.getExpression() instanceof org.eclipse.jdt.core.dom.SwitchExpression se) {
|
||||||
|
String result = evaluateSwitchExpression(se, localVars, context, visited);
|
||||||
|
if (result != null) finalResult[0] = result;
|
||||||
|
} else if (rs.getExpression() != null) {
|
||||||
|
String result = resolveExpressionWithParams(rs.getExpression(), localVars);
|
||||||
|
if (result == null) result = resolveInternal(rs.getExpression(), context, visited);
|
||||||
|
if (result != null) finalResult[0] = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(rs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return finalResult[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
private String evaluateSwitchStatement(org.eclipse.jdt.core.dom.SwitchStatement ss, java.util.Map<String, String> paramValues, CodebaseContext context, Set<String> visited) {
|
private String evaluateSwitchStatement(org.eclipse.jdt.core.dom.SwitchStatement ss, java.util.Map<String, String> paramValues, CodebaseContext context, Set<String> visited) {
|
||||||
@@ -251,7 +293,7 @@ public class ConstantResolver {
|
|||||||
if (expr instanceof SimpleName sn) {
|
if (expr instanceof SimpleName sn) {
|
||||||
String name = sn.getIdentifier();
|
String name = sn.getIdentifier();
|
||||||
if (paramValues.containsKey(name)) return paramValues.get(name);
|
if (paramValues.containsKey(name)) return paramValues.get(name);
|
||||||
return name;
|
return null;
|
||||||
} else if (expr instanceof FieldAccess fa) {
|
} else if (expr instanceof FieldAccess fa) {
|
||||||
String name = "this." + fa.getName().getIdentifier();
|
String name = "this." + fa.getName().getIdentifier();
|
||||||
if (paramValues.containsKey(name)) return paramValues.get(name);
|
if (paramValues.containsKey(name)) return paramValues.get(name);
|
||||||
|
|||||||
@@ -265,8 +265,26 @@ public class GenericEventDetector {
|
|||||||
return getSimpleNameString(right);
|
return getSimpleNameString(right);
|
||||||
}
|
}
|
||||||
} else if (expr instanceof MethodInvocation mi) {
|
} else if (expr instanceof MethodInvocation mi) {
|
||||||
if ("equals".equals(mi.getName().getIdentifier()) && !mi.arguments().isEmpty()) {
|
String methodName = mi.getName().getIdentifier();
|
||||||
return getSimpleNameString((Expression) mi.arguments().get(0));
|
if (("equals".equals(methodName) || "equalsIgnoreCase".equals(methodName)) && !mi.arguments().isEmpty()) {
|
||||||
|
Expression receiver = mi.getExpression();
|
||||||
|
Expression arg = (Expression) mi.arguments().get(0);
|
||||||
|
|
||||||
|
// If receiver is null (e.g., implicit this), fall back to arg
|
||||||
|
if (receiver == null) {
|
||||||
|
return getSimpleNameString(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prioritize the one that looks like a constant (QualifiedName or StringLiteral)
|
||||||
|
if (receiver instanceof QualifiedName || receiver instanceof StringLiteral || receiver instanceof FieldAccess) {
|
||||||
|
return getSimpleNameString(receiver);
|
||||||
|
}
|
||||||
|
if (arg instanceof QualifiedName || arg instanceof StringLiteral || arg instanceof FieldAccess) {
|
||||||
|
return getSimpleNameString(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to receiver
|
||||||
|
return getSimpleNameString(receiver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -182,4 +182,200 @@ public class ConstantResolverTest {
|
|||||||
|
|
||||||
assertThat(result).isEqualTo("PAYMENT_SUCCESS");
|
assertThat(result).isEqualTo("PAYMENT_SUCCESS");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMethodEvaluationWithNestedBlocksAndVars(@TempDir Path tempDir) throws IOException {
|
||||||
|
Path dir = tempDir.resolve("com/example");
|
||||||
|
Files.createDirectories(dir);
|
||||||
|
Files.writeString(dir.resolve("Logic.java"),
|
||||||
|
"package com.example;\n" +
|
||||||
|
"public class Logic {\n" +
|
||||||
|
" public String process(String event) {\n" +
|
||||||
|
" String myVar = event;\n" +
|
||||||
|
" if (myVar != null) {\n" +
|
||||||
|
" return myVar;\n" +
|
||||||
|
" }\n" +
|
||||||
|
" return \"DEFAULT\";\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}");
|
||||||
|
|
||||||
|
Files.writeString(dir.resolve("Caller.java"),
|
||||||
|
"package com.example;\n" +
|
||||||
|
"public class Caller {\n" +
|
||||||
|
" public void call() {\n" +
|
||||||
|
" Logic logic = new Logic();\n" +
|
||||||
|
" String result = logic.process(\"MY_EVENT\");\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}");
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
TypeDeclaration callerTd = context.getTypeDeclaration("com.example.Caller");
|
||||||
|
MethodDeclaration callMethod = callerTd.getMethods()[0]; // call()
|
||||||
|
|
||||||
|
VariableDeclarationStatement vds = (VariableDeclarationStatement) callMethod.getBody().statements().get(1);
|
||||||
|
VariableDeclarationFragment fragment = (VariableDeclarationFragment) vds.fragments().get(0);
|
||||||
|
MethodInvocation mi = (MethodInvocation) fragment.getInitializer(); // logic.process("MY_EVENT")
|
||||||
|
|
||||||
|
ConstantResolver resolver = new ConstantResolver();
|
||||||
|
String result = resolver.resolve(mi, context);
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo("MY_EVENT");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMethodShadowingReturnsParameter(@TempDir Path tempDir) throws IOException {
|
||||||
|
Path dir = tempDir.resolve("com/example");
|
||||||
|
Files.createDirectories(dir);
|
||||||
|
Files.writeString(dir.resolve("Logic.java"),
|
||||||
|
"package com.example;\n" +
|
||||||
|
"public class Logic {\n" +
|
||||||
|
" public String a = \"OLD_FIELD\";\n" +
|
||||||
|
" public String process(String a) {\n" + // shadowing
|
||||||
|
" this.a = \"NEW_FIELD\";\n" +
|
||||||
|
" return a;\n" + // should return the parameter
|
||||||
|
" }\n" +
|
||||||
|
"}");
|
||||||
|
|
||||||
|
Files.writeString(dir.resolve("Caller.java"),
|
||||||
|
"package com.example;\n" +
|
||||||
|
"public class Caller {\n" +
|
||||||
|
" public void call() {\n" +
|
||||||
|
" Logic logic = new Logic();\n" +
|
||||||
|
" String result = logic.process(\"PARAM_VALUE\");\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}");
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
TypeDeclaration callerTd = context.getTypeDeclaration("com.example.Caller");
|
||||||
|
MethodDeclaration callMethod = callerTd.getMethods()[0]; // call()
|
||||||
|
|
||||||
|
VariableDeclarationStatement vds = (VariableDeclarationStatement) callMethod.getBody().statements().get(1);
|
||||||
|
VariableDeclarationFragment fragment = (VariableDeclarationFragment) vds.fragments().get(0);
|
||||||
|
MethodInvocation mi = (MethodInvocation) fragment.getInitializer(); // logic.process(...)
|
||||||
|
|
||||||
|
ConstantResolver resolver = new ConstantResolver();
|
||||||
|
String result = resolver.resolve(mi, context);
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo("PARAM_VALUE");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMethodShadowingReturnsField(@TempDir Path tempDir) throws IOException {
|
||||||
|
Path dir = tempDir.resolve("com/example");
|
||||||
|
Files.createDirectories(dir);
|
||||||
|
Files.writeString(dir.resolve("Logic.java"),
|
||||||
|
"package com.example;\n" +
|
||||||
|
"public class Logic {\n" +
|
||||||
|
" public String a = \"OLD_FIELD\";\n" +
|
||||||
|
" public String process(String a) {\n" + // shadowing
|
||||||
|
" this.a = \"NEW_FIELD_VALUE\";\n" +
|
||||||
|
" return this.a;\n" + // should return the field
|
||||||
|
" }\n" +
|
||||||
|
"}");
|
||||||
|
|
||||||
|
Files.writeString(dir.resolve("Caller.java"),
|
||||||
|
"package com.example;\n" +
|
||||||
|
"public class Caller {\n" +
|
||||||
|
" public void call() {\n" +
|
||||||
|
" Logic logic = new Logic();\n" +
|
||||||
|
" String result = logic.process(\"PARAM_VALUE\");\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}");
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
TypeDeclaration callerTd = context.getTypeDeclaration("com.example.Caller");
|
||||||
|
MethodDeclaration callMethod = callerTd.getMethods()[0]; // call()
|
||||||
|
|
||||||
|
VariableDeclarationStatement vds = (VariableDeclarationStatement) callMethod.getBody().statements().get(1);
|
||||||
|
VariableDeclarationFragment fragment = (VariableDeclarationFragment) vds.fragments().get(0);
|
||||||
|
MethodInvocation mi = (MethodInvocation) fragment.getInitializer(); // logic.process(...)
|
||||||
|
|
||||||
|
ConstantResolver resolver = new ConstantResolver();
|
||||||
|
String result = resolver.resolve(mi, context);
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo("NEW_FIELD_VALUE");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMethodImplicitFieldAssignment(@TempDir Path tempDir) throws IOException {
|
||||||
|
Path dir = tempDir.resolve("com/example");
|
||||||
|
Files.createDirectories(dir);
|
||||||
|
Files.writeString(dir.resolve("Logic.java"),
|
||||||
|
"package com.example;\n" +
|
||||||
|
"public class Logic {\n" +
|
||||||
|
" public String a = \"OLD_FIELD\";\n" +
|
||||||
|
" public String process(String p) {\n" +
|
||||||
|
" a = p;\n" + // implicit field assignment
|
||||||
|
" return this.a;\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}");
|
||||||
|
|
||||||
|
Files.writeString(dir.resolve("Caller.java"),
|
||||||
|
"package com.example;\n" +
|
||||||
|
"public class Caller {\n" +
|
||||||
|
" public void call() {\n" +
|
||||||
|
" Logic logic = new Logic();\n" +
|
||||||
|
" String result = logic.process(\"MY_EVENT\");\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}");
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
TypeDeclaration callerTd = context.getTypeDeclaration("com.example.Caller");
|
||||||
|
MethodDeclaration callMethod = callerTd.getMethods()[0]; // call()
|
||||||
|
|
||||||
|
VariableDeclarationStatement vds = (VariableDeclarationStatement) callMethod.getBody().statements().get(1);
|
||||||
|
VariableDeclarationFragment fragment = (VariableDeclarationFragment) vds.fragments().get(0);
|
||||||
|
MethodInvocation mi = (MethodInvocation) fragment.getInitializer(); // logic.process(...)
|
||||||
|
|
||||||
|
ConstantResolver resolver = new ConstantResolver();
|
||||||
|
String result = resolver.resolve(mi, context);
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo("MY_EVENT");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testZeroArgMethodEvaluation(@TempDir Path tempDir) throws IOException {
|
||||||
|
Path dir = tempDir.resolve("com/example");
|
||||||
|
Files.createDirectories(dir);
|
||||||
|
Files.writeString(dir.resolve("Logic.java"),
|
||||||
|
"package com.example;\n" +
|
||||||
|
"public class Logic {\n" +
|
||||||
|
" public String a = \"MY_CONSTANT\";\n" +
|
||||||
|
" public String getEvent() {\n" +
|
||||||
|
" return this.a;\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}");
|
||||||
|
|
||||||
|
Files.writeString(dir.resolve("Caller.java"),
|
||||||
|
"package com.example;\n" +
|
||||||
|
"public class Caller {\n" +
|
||||||
|
" public void call() {\n" +
|
||||||
|
" Logic logic = new Logic();\n" +
|
||||||
|
" String result = logic.getEvent();\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}");
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
TypeDeclaration callerTd = context.getTypeDeclaration("com.example.Caller");
|
||||||
|
MethodDeclaration callMethod = callerTd.getMethods()[0]; // call()
|
||||||
|
|
||||||
|
VariableDeclarationStatement vds = (VariableDeclarationStatement) callMethod.getBody().statements().get(1);
|
||||||
|
VariableDeclarationFragment fragment = (VariableDeclarationFragment) vds.fragments().get(0);
|
||||||
|
MethodInvocation mi = (MethodInvocation) fragment.getInitializer(); // logic.getEvent()
|
||||||
|
|
||||||
|
ConstantResolver resolver = new ConstantResolver();
|
||||||
|
String result = resolver.resolve(mi, context);
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo("MY_CONSTANT");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user