This commit is contained in:
2026-07-06 21:21:35 +02:00
parent 0a8f9720c9
commit 56588a835d
17 changed files with 599 additions and 384 deletions

View File

@@ -27,6 +27,7 @@ public class TriggerPoint {
private final java.util.List<String> polymorphicEvents; // NEW: stores concrete events resolved via deep polymorphism
private final boolean external;
private final String constraint;
private final boolean ambiguous;
public TriggerPoint(
String event,
@@ -41,7 +42,8 @@ public class TriggerPoint {
String eventTypeFqn,
java.util.List<String> polymorphicEvents,
boolean external,
String constraint) {
String constraint,
boolean ambiguous) {
this.className = className;
this.methodName = methodName;
this.sourceFile = sourceFile;
@@ -61,6 +63,7 @@ public class TriggerPoint {
}
this.external = external;
this.constraint = constraint;
this.ambiguous = ambiguous;
}
private static String qualifyEvent(String e, String eventTypeFqn) {

View File

@@ -239,9 +239,8 @@ public class ConstantResolver {
}
try {
java.util.Map<String, String> localVars = new java.util.HashMap<>(prebuiltLocals);
java.util.Set<String> declaredLocals = new java.util.HashSet<>(prebuiltLocals.keySet());
return evaluateBody(md.getBody(), localVars, declaredLocals, context, visited);
return evaluateBody(md.getBody(), prebuiltLocals, declaredLocals, context, visited);
} finally {
visited.remove(methodFqn);
}
@@ -267,7 +266,7 @@ public class ConstantResolver {
String varName = fragment.getName().getIdentifier();
declaredLocals.add(varName);
if (fragment.getInitializer() != null) {
String rhsVal = resolveExpressionWithParams(fragment.getInitializer(), localVars);
String rhsVal = resolveExpressionWithParams(fragment.getInitializer(), localVars, context);
if (rhsVal == null) rhsVal = resolveInternal(fragment.getInitializer(), context, visited);
if (rhsVal != null) localVars.put(varName, rhsVal);
}
@@ -276,6 +275,43 @@ public class ConstantResolver {
return super.visit(vds);
}
@Override
public boolean visit(org.eclipse.jdt.core.dom.MethodInvocation node) {
MethodDeclaration sideEffectMd = findInvokedMethod(node, context);
if (sideEffectMd != null && sideEffectMd.getBody() != null) {
TypeDeclaration currentTd = findEnclosingType(sideEffectMd);
if (sideEffectMd != null && sideEffectMd.getBody() != null) {
java.util.Map<String, String> inlineLocals = new java.util.HashMap<>();
for (java.util.Map.Entry<String, String> entry : localVars.entrySet()) {
if (entry.getKey().startsWith("this.")) {
inlineLocals.put(entry.getKey(), entry.getValue());
} else if (context.hasField(currentTd, entry.getKey())) {
inlineLocals.put(entry.getKey(), entry.getValue());
inlineLocals.put("this." + entry.getKey(), entry.getValue());
}
}
for (int i = 0; i < node.arguments().size() && i < sideEffectMd.parameters().size(); i++) {
Expression arg = (Expression) node.arguments().get(i);
String val = resolveExpressionWithParams(arg, localVars, context);
if (val == null) val = resolveInternal(arg, context, visited);
if (val != null) {
org.eclipse.jdt.core.dom.SingleVariableDeclaration param = (org.eclipse.jdt.core.dom.SingleVariableDeclaration) sideEffectMd.parameters().get(i);
inlineLocals.put(param.getName().getIdentifier(), val);
}
}
evaluateMethodBodyWithLocals(sideEffectMd, inlineLocals, context, visited);
for (java.util.Map.Entry<String, String> entry : inlineLocals.entrySet()) {
if (entry.getKey().startsWith("this.")) {
localVars.put(entry.getKey(), entry.getValue());
String bareName = entry.getKey().substring(5);
if (!declaredLocals.contains(bareName)) localVars.put(bareName, entry.getValue());
}
}
}
}
return super.visit(node);
}
@Override
public boolean visit(Assignment assignment) {
Expression lhs = assignment.getLeftHandSide();
@@ -285,7 +321,7 @@ public class ConstantResolver {
} else if (lhs instanceof FieldAccess fa) {
varName = "this." + fa.getName().getIdentifier();
}
String rhsVal = resolveExpressionWithParams(assignment.getRightHandSide(), localVars);
String rhsVal = resolveExpressionWithParams(assignment.getRightHandSide(), localVars, context);
if (rhsVal == null) rhsVal = resolveInternal(assignment.getRightHandSide(), context, visited);
if (varName != null && rhsVal != null) {
if (varName.startsWith("this.")) {
@@ -316,7 +352,7 @@ public class ConstantResolver {
String result = evaluateSwitchExpression(se, localVars, context, visited);
if (result != null) finalResult[0] = result;
} else if (rs.getExpression() != null) {
String result = resolveExpressionWithParams(rs.getExpression(), localVars);
String result = resolveExpressionWithParams(rs.getExpression(), localVars, context);
if (result == null) result = resolveInternal(rs.getExpression(), context, visited);
if (result != null) finalResult[0] = result;
}
@@ -329,7 +365,7 @@ public class ConstantResolver {
}
private String evaluateSwitchStatement(org.eclipse.jdt.core.dom.SwitchStatement ss, java.util.Map<String, String> paramValues, CodebaseContext context, Set<String> visited) {
String switchVar = resolveExpressionWithParams(ss.getExpression(), paramValues);
String switchVar = resolveExpressionWithParams(ss.getExpression(), paramValues, context);
if (switchVar == null) {
Set<String> values = new LinkedHashSet<>();
for (Object stmtObj : ss.statements()) {
@@ -381,7 +417,7 @@ public class ConstantResolver {
matchingCase = true;
} else {
for (Object exprObj : sc.expressions()) {
String caseVal = resolveExpressionWithParams((Expression) exprObj, paramValues);
String caseVal = resolveExpressionWithParams((Expression) exprObj, paramValues, context);
// Bare SimpleName case label (e.g. "case PAY"): use identifier as fallback
if (caseVal == null && exprObj instanceof org.eclipse.jdt.core.dom.SimpleName caseSn) {
caseVal = caseSn.getIdentifier();
@@ -416,7 +452,7 @@ public class ConstantResolver {
}
private String evaluateSwitchExpression(org.eclipse.jdt.core.dom.SwitchExpression se, java.util.Map<String, String> paramValues, CodebaseContext context, Set<String> visited) {
String switchVar = resolveExpressionWithParams(se.getExpression(), paramValues);
String switchVar = resolveExpressionWithParams(se.getExpression(), paramValues, context);
if (switchVar == null) {
Set<String> values = new LinkedHashSet<>();
for (Object stmtObj : se.statements()) {
@@ -468,7 +504,7 @@ public class ConstantResolver {
matchingCase = true;
} else {
for (Object exprObj : sc.expressions()) {
String caseVal = resolveExpressionWithParams((Expression) exprObj, paramValues);
String caseVal = resolveExpressionWithParams((Expression) exprObj, paramValues, context);
// Bare SimpleName case label (e.g. "case PAY"): use identifier as fallback
if (caseVal == null && exprObj instanceof org.eclipse.jdt.core.dom.SimpleName caseSn) {
caseVal = caseSn.getIdentifier();
@@ -502,7 +538,7 @@ public class ConstantResolver {
return null;
}
private String resolveExpressionWithParams(Expression expr, java.util.Map<String, String> paramValues) {
private String resolveExpressionWithParams(Expression expr, java.util.Map<String, String> paramValues, CodebaseContext context) {
if (expr instanceof SimpleName sn) {
String name = sn.getIdentifier();
if (paramValues.containsKey(name)) return paramValues.get(name);
@@ -515,7 +551,65 @@ public class ConstantResolver {
return null;
} else if (expr instanceof org.eclipse.jdt.core.dom.StringLiteral sl) {
return sl.getLiteralValue();
}
} else if (expr instanceof MethodInvocation mi) {
if ("valueOf".equals(mi.getName().getIdentifier())) {
Expression arg = null;
if (mi.arguments().size() == 1) {
arg = (Expression) mi.arguments().get(0);
} else if (mi.arguments().size() == 2) {
arg = (Expression) mi.arguments().get(1);
}
if (arg != null) {
String resolvedArg = resolveExpressionWithParams(arg, paramValues, context);
if (resolvedArg != null && resolvedArg.matches("^[a-zA-Z_][a-zA-Z0-9_]*$")) {
String enumFqn = null;
if (mi.arguments().size() == 2) {
enumFqn = resolveEnumFqn((Expression) mi.arguments().get(0), context);
} else if (mi.arguments().size() == 1 && mi.getExpression() != null) {
enumFqn = resolveEnumFqn(mi.getExpression(), context);
}
if (enumFqn != null) {
return enumFqn + "." + resolvedArg;
} else {
// If we can't resolve the exact enum type here, we can just return the bare constant
return resolvedArg;
}
}
}
} else {
MethodDeclaration sideEffectMd = findInvokedMethod(mi, context);
if (sideEffectMd != null && sideEffectMd.getBody() != null) {
TypeDeclaration currentTd = findEnclosingType(sideEffectMd);
java.util.Map<String, String> inlineLocals = new java.util.HashMap<>();
for (java.util.Map.Entry<String, String> entry : paramValues.entrySet()) {
if (entry.getKey().startsWith("this.")) {
inlineLocals.put(entry.getKey(), entry.getValue());
} else if (context.hasField(currentTd, entry.getKey())) {
inlineLocals.put(entry.getKey(), entry.getValue());
inlineLocals.put("this." + entry.getKey(), entry.getValue());
}
}
for (int i = 0; i < mi.arguments().size() && i < sideEffectMd.parameters().size(); i++) {
Expression arg = (Expression) mi.arguments().get(i);
String val = resolveExpressionWithParams(arg, paramValues, context);
if (val == null) val = resolveInternal(arg, context, new java.util.HashSet<>());
if (val != null) {
org.eclipse.jdt.core.dom.SingleVariableDeclaration param = (org.eclipse.jdt.core.dom.SingleVariableDeclaration) sideEffectMd.parameters().get(i);
inlineLocals.put(param.getName().getIdentifier(), val);
}
}
String ret = evaluateMethodBodyWithLocals(sideEffectMd, inlineLocals, context, new java.util.HashSet<>());
for (java.util.Map.Entry<String, String> entry : inlineLocals.entrySet()) {
if (entry.getKey().startsWith("this.")) {
paramValues.put(entry.getKey(), entry.getValue());
String bareName = entry.getKey().substring(5);
paramValues.put(bareName, entry.getValue());
}
}
return ret;
}
}
}
return null;
}
@@ -685,11 +779,29 @@ public class ConstantResolver {
}
private TypeDeclaration findEnclosingType(ASTNode node) {
ASTNode parent = node.getParent();
while (parent != null && !(parent instanceof TypeDeclaration)) {
parent = parent.getParent();
ASTNode current = node;
while (current != null && !(current instanceof TypeDeclaration)) {
current = current.getParent();
}
return (TypeDeclaration) parent;
return (TypeDeclaration) current;
}
private MethodDeclaration findInvokedMethod(org.eclipse.jdt.core.dom.MethodInvocation mi, CodebaseContext context) {
org.eclipse.jdt.core.dom.IMethodBinding methodBinding = mi.resolveMethodBinding();
if (methodBinding != null) {
org.eclipse.jdt.core.dom.ITypeBinding declaringClass = methodBinding.getDeclaringClass();
if (declaringClass != null) {
TypeDeclaration targetTd = context.getTypeDeclaration(declaringClass.getErasure().getQualifiedName());
if (targetTd != null) {
return context.findMethodDeclaration(targetTd, mi.getName().getIdentifier(), false);
}
}
}
TypeDeclaration currentTd = findEnclosingType(mi);
if (currentTd != null && (mi.getExpression() == null || mi.getExpression() instanceof org.eclipse.jdt.core.dom.ThisExpression)) {
return context.findMethodDeclaration(currentTd, mi.getName().getIdentifier(), true);
}
return null;
}
private MethodDeclaration findEnclosingMethod(ASTNode node) {

View File

@@ -137,11 +137,11 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
String currentParamName = event;
String resolvedValue = event;
String methodSuffix = "";
String[] extractedEntry = extractMethodSuffix(currentParamName, methodSuffix);
currentParamName = extractedEntry[0];
methodSuffix = extractedEntry[1];
if (finalParamNameRef != null) finalParamNameRef[0] = currentParamName;
String[] extractedEntry = extractMethodSuffix(currentParamName, methodSuffix);
currentParamName = extractedEntry[0];
methodSuffix = extractedEntry[1];
boolean isAmbiguous = false;
if (finalParamNameRef != null) finalParamNameRef[0] = currentParamName;
for (int i = path.size() - 1; i > 0; i--) {
String target = path.get(i);
@@ -410,6 +410,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
.stateTypeFqn(tp.getStateTypeFqn())
.eventTypeFqn(tp.getEventTypeFqn())
.external(tp.isExternal())
.ambiguous(isAmbiguous)
.build();
}
@@ -515,6 +516,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
.stateTypeFqn(tp.getStateTypeFqn())
.eventTypeFqn(tp.getEventTypeFqn())
.external(tp.isExternal())
.ambiguous(isAmbiguous)
.build();
}
}
@@ -638,6 +640,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
for (String ev : enumValues) {
polymorphicEvents.add(constantExtractor.parseEnumSetElement(ev));
}
isAmbiguous = true;
} else {
List<String> typesToInspect = new ArrayList<>();
if ("inline-instantiation".equals(sourceMethod)) {

View File

@@ -277,37 +277,17 @@ public class ConstructorAnalyzer {
}
// Evaluate constructor body regardless of whether parameters could be resolved as constants
ctor.getBody().accept(new ASTVisitor() {
@Override
public boolean visit(Assignment node) {
Expression lhs = node.getLeftHandSide();
Expression rhs = node.getRightHandSide();
String fieldName = null;
if (lhs instanceof FieldAccess fa
&& fa.getExpression() instanceof ThisExpression) {
fieldName = fa.getName().getIdentifier();
} else if (lhs instanceof SimpleName sn
&& !paramValues.containsKey(sn.getIdentifier())) {
fieldName = sn.getIdentifier();
}
if (fieldName != null) {
String paramVal = null;
if (rhs instanceof SimpleName snRhs) {
paramVal = paramValues.get(snRhs.getIdentifier());
} else if (rhsResolver != null) {
paramVal = rhsResolver.resolve(rhs, paramValues, td);
}
if (paramVal != null) {
fieldValues.put(fieldName, paramVal);
fieldValues.put("this." + fieldName, paramVal);
}
}
return super.visit(node);
java.util.Map<String, String> localVars = new java.util.HashMap<>(paramValues);
constantResolver.evaluateMethodBodyWithLocals(ctor, localVars, context, new java.util.HashSet<>());
for (java.util.Map.Entry<String, String> entry : localVars.entrySet()) {
if (entry.getKey().startsWith("this.")) {
fieldValues.put(entry.getKey(), entry.getValue());
fieldValues.put(entry.getKey().substring(5), entry.getValue());
} else if (context.hasField(td, entry.getKey())) {
fieldValues.put(entry.getKey(), entry.getValue());
fieldValues.put("this." + entry.getKey(), entry.getValue());
}
});
}
ctor.getBody().accept(new ASTVisitor() {
@Override
@@ -399,37 +379,17 @@ public class ConstructorAnalyzer {
}
if (superParamValues.isEmpty()) continue;
superCtor.getBody().accept(new ASTVisitor() {
@Override
public boolean visit(Assignment node) {
Expression lhs = node.getLeftHandSide();
Expression rhs = node.getRightHandSide();
String fieldName = null;
if (lhs instanceof FieldAccess fa
&& fa.getExpression() instanceof ThisExpression) {
fieldName = fa.getName().getIdentifier();
} else if (lhs instanceof SimpleName sn
&& !superParamValues.containsKey(sn.getIdentifier())) {
fieldName = sn.getIdentifier();
}
if (fieldName != null) {
String paramVal = null;
if (rhs instanceof SimpleName snRhs) {
paramVal = superParamValues.get(snRhs.getIdentifier());
} else if (rhsResolver != null) {
paramVal = rhsResolver.resolve(rhs, superParamValues, superTd);
}
if (paramVal != null) {
fieldValues.put(fieldName, paramVal);
fieldValues.put("this." + fieldName, paramVal);
}
}
return super.visit(node);
java.util.Map<String, String> localVars = new java.util.HashMap<>(superParamValues);
constantResolver.evaluateMethodBodyWithLocals(superCtor, localVars, context, new java.util.HashSet<>());
for (java.util.Map.Entry<String, String> entry : localVars.entrySet()) {
if (entry.getKey().startsWith("this.")) {
fieldValues.put(entry.getKey(), entry.getValue());
fieldValues.put(entry.getKey().substring(5), entry.getValue());
} else if (context.hasField(superTd, entry.getKey())) {
fieldValues.put(entry.getKey(), entry.getValue());
fieldValues.put("this." + entry.getKey(), entry.getValue());
}
});
}
superCtor.getBody().accept(new ASTVisitor() {
@Override