Compare commits
3 Commits
f5067490a3
...
89736a4aa1
| Author | SHA1 | Date | |
|---|---|---|---|
| 89736a4aa1 | |||
| 092fbe49fa | |||
| 61f5549589 |
@@ -238,6 +238,20 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!found) {
|
||||||
|
// Bridge polymorphic interfaces to implementations without graph edges
|
||||||
|
String callerSimple = caller.contains(".") ? caller.substring(caller.lastIndexOf('.') + 1) : caller;
|
||||||
|
String targetSimple = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target;
|
||||||
|
if (callerSimple.equals(targetSimple)) {
|
||||||
|
String interfaceParamName = typeResolver.getParameterName(caller, paramIndex);
|
||||||
|
if (interfaceParamName != null) {
|
||||||
|
currentParamName = interfaceParamName;
|
||||||
|
if (finalParamNameRef != null) finalParamNameRef[0] = currentParamName;
|
||||||
|
resolvedValue = currentParamName + methodSuffix;
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!found) break;
|
if (!found) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,21 @@ public class TypeResolver {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getParameterName(String methodFqn, int index) {
|
||||||
|
if (methodFqn == null || !methodFqn.contains(".") || index < 0) return null;
|
||||||
|
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
||||||
|
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||||
|
if (td != null) {
|
||||||
|
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||||
|
if (md != null && index < md.parameters().size()) {
|
||||||
|
SingleVariableDeclaration svd = (SingleVariableDeclaration) md.parameters().get(index);
|
||||||
|
return svd.getName().getIdentifier();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public String getParameterType(String methodFqn, int index) {
|
public String getParameterType(String methodFqn, int index) {
|
||||||
if (methodFqn == null || !methodFqn.contains(".") || index < 0) return null;
|
if (methodFqn == null || !methodFqn.contains(".") || index < 0) return null;
|
||||||
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
||||||
|
|||||||
@@ -413,6 +413,33 @@ public class VariableTracer {
|
|||||||
public Map<String, String> buildParameterValuesMap(String caller, String target, Map<String, List<CallEdge>> callGraph, List<String> path, int pathIndex) {
|
public Map<String, String> buildParameterValuesMap(String caller, String target, Map<String, List<CallEdge>> callGraph, List<String> path, int pathIndex) {
|
||||||
Map<String, String> paramValues = new HashMap<>();
|
Map<String, String> paramValues = new HashMap<>();
|
||||||
List<CallEdge> edges = callGraph.get(caller);
|
List<CallEdge> edges = callGraph.get(caller);
|
||||||
|
|
||||||
|
boolean hasEdge = false;
|
||||||
|
if (edges != null) {
|
||||||
|
for (CallEdge edge : edges) {
|
||||||
|
if (edge.getTargetMethod().equals(target) || isHeuristicMatch(edge.getTargetMethod(), target)) {
|
||||||
|
hasEdge = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bridge polymorphic interfaces to implementations without graph edges
|
||||||
|
if (!hasEdge) {
|
||||||
|
String callerSimple = caller.contains(".") ? caller.substring(caller.lastIndexOf('.') + 1) : caller;
|
||||||
|
String targetSimple = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target;
|
||||||
|
if (callerSimple.equals(targetSimple)) {
|
||||||
|
List<String> callerParams = getParameterNames(caller);
|
||||||
|
List<String> targetParams = getParameterNames(target);
|
||||||
|
if (callerParams != null && targetParams != null && callerParams.size() == targetParams.size()) {
|
||||||
|
for (int i = 0; i < callerParams.size(); i++) {
|
||||||
|
paramValues.put(targetParams.get(i), callerParams.get(i));
|
||||||
|
}
|
||||||
|
return paramValues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (edges == null) {
|
if (edges == null) {
|
||||||
return paramValues;
|
return paramValues;
|
||||||
}
|
}
|
||||||
@@ -453,15 +480,35 @@ public class VariableTracer {
|
|||||||
return paramValues;
|
return paramValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<String> getParameterNames(String methodFqn) {
|
||||||
|
int lastDot = methodFqn.lastIndexOf('.');
|
||||||
|
if (lastDot < 0) return null;
|
||||||
|
String className = methodFqn.substring(0, lastDot);
|
||||||
|
String methodName = methodFqn.substring(lastDot + 1);
|
||||||
|
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||||
|
if (td == null) return null;
|
||||||
|
|
||||||
|
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||||
|
if (md == null) return null;
|
||||||
|
|
||||||
|
List<String> paramNames = new ArrayList<>();
|
||||||
|
for (Object paramObj : md.parameters()) {
|
||||||
|
SingleVariableDeclaration param = (SingleVariableDeclaration) paramObj;
|
||||||
|
paramNames.add(param.getName().getIdentifier());
|
||||||
|
}
|
||||||
|
return paramNames;
|
||||||
|
}
|
||||||
|
|
||||||
public String resolveArgumentValue(String argValue, String callerMethod, List<String> path, int pathIndex, Map<String, List<CallEdge>> callGraph) {
|
public String resolveArgumentValue(String argValue, String callerMethod, List<String> path, int pathIndex, Map<String, List<CallEdge>> callGraph) {
|
||||||
if (argValue == null) return null;
|
if (argValue == null) return null;
|
||||||
if (argValue.contains(".") || argValue.startsWith("new ") || argValue.matches(".*[0-9].*")) {
|
if (argValue.contains(".") || argValue.startsWith("new ") || argValue.matches(".*[0-9].*") || (argValue.startsWith("\"") && argValue.endsWith("\""))) {
|
||||||
return argValue;
|
return argValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
String tracedLocal = traceLocalVariable(callerMethod, argValue);
|
String tracedLocal = traceLocalVariable(callerMethod, argValue);
|
||||||
if (tracedLocal != null && !tracedLocal.equals(argValue)) {
|
if (tracedLocal != null && !tracedLocal.equals(argValue)) {
|
||||||
if (tracedLocal.contains(".") || tracedLocal.startsWith("new ") || tracedLocal.matches(".*[0-9].*") || tracedLocal.contains("(")) {
|
if (tracedLocal.contains(".") || tracedLocal.startsWith("new ") || tracedLocal.matches(".*[0-9].*") || tracedLocal.contains("(") || (tracedLocal.startsWith("\"") && tracedLocal.endsWith("\""))) {
|
||||||
return tracedLocal;
|
return tracedLocal;
|
||||||
}
|
}
|
||||||
argValue = tracedLocal;
|
argValue = tracedLocal;
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
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 java.util.Map;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class VariableTracerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldShortCircuitLiteralStringResolution() {
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
VariableTracer tracer = new VariableTracer(context, new ConstantResolver());
|
||||||
|
|
||||||
|
// Even with an empty context and call graph, passing a raw String literal should return instantly
|
||||||
|
// without trying to recursively traverse the path or crash.
|
||||||
|
String resolved = tracer.resolveArgumentValue("\"PAY_EVENT\"", "com.example.Order.process", List.of("com.example.Order.process"), 0, Map.of());
|
||||||
|
|
||||||
|
assertThat(resolved).isEqualTo("\"PAY_EVENT\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldBridgePolymorphicParametersPositionally(@TempDir Path tempDir) throws IOException {
|
||||||
|
Path comFoo = tempDir.resolve("com/foo");
|
||||||
|
Files.createDirectories(comFoo);
|
||||||
|
|
||||||
|
// Setup an interface and implementation with DIFFERENT parameter names
|
||||||
|
Files.writeString(comFoo.resolve("PaymentService.java"),
|
||||||
|
"package com.foo;\n" +
|
||||||
|
"public interface PaymentService {\n" +
|
||||||
|
" void submit(String eventName);\n" +
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
Files.writeString(comFoo.resolve("PaymentServiceImpl.java"),
|
||||||
|
"package com.foo;\n" +
|
||||||
|
"public class PaymentServiceImpl implements PaymentService {\n" +
|
||||||
|
" @Override\n" +
|
||||||
|
" public void submit(String e) {}\n" +
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
VariableTracer tracer = new VariableTracer(context, new ConstantResolver());
|
||||||
|
|
||||||
|
// Simulate tracing from the Interface backwards to the Implementation.
|
||||||
|
// Because this is a polymorphic bridge, there is NO direct call edge in the AST call graph.
|
||||||
|
Map<String, String> params = tracer.buildParameterValuesMap(
|
||||||
|
"com.foo.PaymentService.submit",
|
||||||
|
"com.foo.PaymentServiceImpl.submit",
|
||||||
|
Map.of(), // Empty call graph (no direct edges)
|
||||||
|
List.of("com.foo.PaymentService.submit", "com.foo.PaymentServiceImpl.submit"),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
// The tracer should have scanned both ASTs and mapped parameter 0 -> parameter 0
|
||||||
|
assertThat(params).hasSize(1);
|
||||||
|
assertThat(params).containsEntry("e", "eventName");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user