From 1b1e03668018793375b5c5bb7c48f6bfe3c60a79 Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Wed, 24 Jun 2026 18:25:36 +0200 Subject: [PATCH] te21 --- .../service/AbstractCallGraphEngine.java | 75 +++++++++++++++ .../service/HeuristicCallGraphEngine.java | 95 +++++-------------- .../analysis/service/JdtCallGraphEngine.java | 78 ++++----------- 3 files changed, 116 insertions(+), 132 deletions(-) diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java index 6bf758e..b38c3db 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java @@ -1652,6 +1652,81 @@ import java.util.*; return eVal.contains(".") ? eVal.substring(eVal.lastIndexOf('.', eVal.lastIndexOf('.') - 1) + 1) : eVal; } + /** + * Resolves a single argument expression to a string value. + * Handles lambdas, method references, and constructor argument unwrapping. + * Subclasses can override {@link #postProcessResolvedArgument(Expression, String)} for custom behavior. + */ + protected String resolveArgument(org.eclipse.jdt.core.dom.Expression expr) { + // Extract from lambda + if (expr instanceof org.eclipse.jdt.core.dom.LambdaExpression le) { + org.eclipse.jdt.core.dom.ASTNode body = le.getBody(); + if (body instanceof org.eclipse.jdt.core.dom.Expression bodyExpr) { + expr = bodyExpr; + } else if (body instanceof org.eclipse.jdt.core.dom.Block block) { + for (Object stmtObj : block.statements()) { + if (stmtObj instanceof org.eclipse.jdt.core.dom.ReturnStatement rs && rs.getExpression() != null) { + expr = rs.getExpression(); + break; + } + } + } + } + + // Extract from method reference + if (expr instanceof org.eclipse.jdt.core.dom.ExpressionMethodReference emr) { + org.eclipse.jdt.core.dom.TypeDeclaration td = findEnclosingType(emr); + if (td != null) { + org.eclipse.jdt.core.dom.MethodDeclaration md = context.findMethodDeclaration(td, emr.getName().getIdentifier(), true); + if (md != null && md.getBody() != null) { + for (Object stmtObj : md.getBody().statements()) { + if (stmtObj instanceof org.eclipse.jdt.core.dom.ReturnStatement rs && rs.getExpression() != null) { + expr = rs.getExpression(); + break; + } + } + } + } + } + + // Unwrap constructor first argument if it's a constant (e.g., new CustomMessage(OrderEvent.PROCESS)) + if (expr instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic && !cic.arguments().isEmpty()) { + org.eclipse.jdt.core.dom.Expression firstArg = (org.eclipse.jdt.core.dom.Expression) cic.arguments().get(0); + String resolved = constantResolver.resolve(firstArg, context); + if (resolved != null) { + expr = firstArg; + } + } + + String val = constantResolver.resolve(expr, context); + if (val == null) { + val = expr.toString(); // Fallback to raw string (handles Enums, 'new Class()', etc) + } + + // Allow subclasses to post-process (e.g., preserve getters for later substitution) + return postProcessResolvedArgument(expr, val); + } + + /** + * Hook for subclasses to customize argument resolution. + * Default implementation returns the value as-is. + */ + protected String postProcessResolvedArgument(org.eclipse.jdt.core.dom.Expression originalExpr, String resolvedValue) { + return resolvedValue; + } + + /** + * Resolves a list of argument expressions. + * Default implementation calls {@link #resolveArgument(Expression)} for each. + */ + protected List resolveArguments(List astArguments) { + List args = new ArrayList<>(); + for (Object argObj : astArguments) { + args.add(resolveArgument((org.eclipse.jdt.core.dom.Expression) argObj)); + } + return args; + } + protected abstract Map> buildCallGraph(); protected abstract String resolveCalledMethod(MethodInvocation node); protected List findPath(String start, String target, Map> graph, Set visited) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngine.java index 488e7fa..c0d2db1 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngine.java @@ -122,80 +122,31 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine { return graph; } - protected List resolveArguments(List astArguments) { - List args = new ArrayList<>(); - for (Object argObj : astArguments) { - Expression expr = (Expression) argObj; - - // Extract from lambda - if (expr instanceof LambdaExpression le) { - ASTNode body = le.getBody(); - if (body instanceof Expression bodyExpr) { - expr = bodyExpr; - } else if (body instanceof Block block) { - for (Object stmtObj : block.statements()) { - if (stmtObj instanceof ReturnStatement rs && rs.getExpression() != null) { - expr = rs.getExpression(); - break; - } - } - } - } + @Override + protected String postProcessResolvedArgument(org.eclipse.jdt.core.dom.Expression originalExpr, String resolvedValue) { + // Preserve getter calls on 'this' or 'super' for later resolution in trigger parameter tracing + // where we can substitute the actual receiver from the call edge + boolean isThisOrSuperGetter = originalExpr instanceof org.eclipse.jdt.core.dom.MethodInvocation mi + && (mi.getExpression() instanceof org.eclipse.jdt.core.dom.ThisExpression || mi.getExpression() instanceof org.eclipse.jdt.core.dom.SuperMethodInvocation) + && mi.arguments().isEmpty(); - if (expr instanceof ExpressionMethodReference emr) { - TypeDeclaration td = findEnclosingType(expr); - if (td != null) { - MethodDeclaration md = context.findMethodDeclaration(td, emr.getName().getIdentifier(), true); - if (md != null && md.getBody() != null) { - for (Object stmtObj : md.getBody().statements()) { - if (stmtObj instanceof ReturnStatement rs && rs.getExpression() != null) { - expr = rs.getExpression(); - break; - } - } - } - } - } - - // Removed eager tracing here to allow dynamic tracing in resolveTriggerPointParameters - - if (expr instanceof ClassInstanceCreation cic && !cic.arguments().isEmpty()) { - Expression firstArg = (Expression) cic.arguments().get(0); - String resolved = constantResolver.resolve(firstArg, context); - if (resolved != null) { - expr = firstArg; // Only unwrap if it's actually a constant - } - } - - String val = null; - - // Preserve getter calls on 'this' or 'super' for later resolution in trigger parameter tracing - // where we can substitute the actual receiver from the call edge - boolean isThisOrSuperGetter = expr instanceof MethodInvocation mi - && (mi.getExpression() instanceof ThisExpression || mi.getExpression() instanceof SuperMethodInvocation) - && mi.arguments().isEmpty(); - - if (!isThisOrSuperGetter) { - // Narrow resolution for "localVar.getX()" patterns where localVar is initialized - // from a ClassInstanceCreation (directly or via a factory method). This avoids - // the broad ENUM_SET fallback when the getter transforms one enum to another. - if (expr instanceof MethodInvocation getterMi - && getterMi.getExpression() instanceof SimpleName instanceSn - && getterMi.arguments().isEmpty()) { - val = tryNarrowGetterViaLocalVarChain(getterMi, instanceSn); - } - - if (val == null) val = constantResolver.resolve(expr, context); - if (val == null) { - val = expr.toString(); // Fallback to raw string (handles Enums, 'new Class()', etc) - } - } else { - val = expr.toString(); // Preserve "this.getTransitionType()" for later substitution - } - args.add(val); + if (isThisOrSuperGetter) { + return originalExpr.toString(); // Preserve "this.getTransitionType()" for later substitution } - System.out.println("DEBUG resolveArguments: " + args); - return args; + + // Narrow resolution for "localVar.getX()" patterns where localVar is initialized + // from a ClassInstanceCreation (directly or via a factory method). This avoids + // the broad ENUM_SET fallback when the getter transforms one enum to another. + if (originalExpr instanceof org.eclipse.jdt.core.dom.MethodInvocation getterMi + && getterMi.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName instanceSn + && getterMi.arguments().isEmpty()) { + String narrowed = tryNarrowGetterViaLocalVarChain(getterMi, instanceSn); + if (narrowed != null) { + return narrowed; + } + } + + return resolvedValue; } protected List resolveCalledMethodsPolymorphic(MethodInvocation node) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngine.java index 2e8c68b..d2804a2 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngine.java @@ -122,67 +122,25 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine { return graph; } - private List resolveArguments(List astArguments) { - List args = new ArrayList<>(); - for (Object argObj : astArguments) { - Expression expr = (Expression) argObj; - - // Extract from lambda - if (expr instanceof LambdaExpression le) { - ASTNode body = le.getBody(); - if (body instanceof Expression bodyExpr) { - expr = bodyExpr; - } else if (body instanceof Block block) { - for (Object stmtObj : block.statements()) { - if (stmtObj instanceof ReturnStatement rs && rs.getExpression() != null) { - expr = rs.getExpression(); - break; - } - } - } - } - - if (expr instanceof ExpressionMethodReference emr) { - TypeDeclaration td = findEnclosingType(expr); - if (td != null) { - MethodDeclaration md = context.findMethodDeclaration(td, emr.getName().getIdentifier(), true); - if (md != null && md.getBody() != null) { - for (Object stmtObj : md.getBody().statements()) { - if (stmtObj instanceof ReturnStatement rs && rs.getExpression() != null) { - expr = rs.getExpression(); - break; - } - } - } - } - } - - // Extract from constructor args (e.g., new CustomMessage(OrderEvent.PROCESS, ...)) - Expression unwrappedBuilder = unwrapMessageBuilder(expr); - if (unwrappedBuilder != expr) { - expr = unwrappedBuilder; - } - - Expression tracedExpr = traceVariable(expr); - if (tracedExpr instanceof QualifiedName || tracedExpr instanceof ClassInstanceCreation || tracedExpr instanceof StringLiteral || tracedExpr instanceof NumberLiteral) { - expr = tracedExpr; // Only accept trace if it resolves to a pure constant/primitive/constructor - } - - if (expr instanceof ClassInstanceCreation cic && !cic.arguments().isEmpty()) { - Expression firstArg = (Expression) cic.arguments().get(0); - String resolved = constantResolver.resolve(firstArg, context); - if (resolved != null) { - expr = firstArg; // Only unwrap if it's actually a constant - } - } - - String val = constantResolver.resolve(expr, context); - if (val == null) { - val = expr.toString(); // Fallback to raw string (handles Enums, 'new Class()', etc) - } - args.add(val); + @Override + protected String resolveArgument(org.eclipse.jdt.core.dom.Expression expr) { + // Extract from constructor args (e.g., new CustomMessage(OrderEvent.PROCESS, ...)) + org.eclipse.jdt.core.dom.Expression unwrappedBuilder = unwrapMessageBuilder(expr); + if (unwrappedBuilder != expr) { + expr = unwrappedBuilder; } - return args; + + // Trace variable to resolve local variable references + org.eclipse.jdt.core.dom.Expression tracedExpr = traceVariable(expr); + if (tracedExpr instanceof org.eclipse.jdt.core.dom.QualifiedName + || tracedExpr instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation + || tracedExpr instanceof org.eclipse.jdt.core.dom.StringLiteral + || tracedExpr instanceof org.eclipse.jdt.core.dom.NumberLiteral) { + expr = tracedExpr; // Only accept trace if it resolves to a pure constant/primitive/constructor + } + + // Delegate to base class for lambda, method reference, and constructor unwrapping + return super.resolveArgument(expr); } private List resolveCalledMethodsPolymorphic(MethodInvocation node) {