cursor out of token
This commit is contained in:
@@ -1,11 +1,13 @@
|
|||||||
package click.kamil.springstatemachineexporter.analysis.model;
|
package click.kamil.springstatemachineexporter.analysis.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.extern.jackson.Jacksonized;
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Builder(toBuilder = true)
|
@Builder(toBuilder = true)
|
||||||
@@ -19,4 +21,6 @@ public class CallChain {
|
|||||||
private final List<MatchedTransition> matchedTransitions;
|
private final List<MatchedTransition> matchedTransitions;
|
||||||
/** How this chain's trigger was linked; derived from trigger flags and matchedTransitions. */
|
/** How this chain's trigger was linked; derived from trigger flags and matchedTransitions. */
|
||||||
private final LinkResolution linkResolution;
|
private final LinkResolution linkResolution;
|
||||||
|
@JsonIgnore
|
||||||
|
private final Map<String, String> pathBindings;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -794,8 +794,14 @@ public final class MachineEnumCanonicalizer {
|
|||||||
|
|
||||||
String raw;
|
String raw;
|
||||||
if (isStringOrPrimitiveType(enumTypeFqn) && canonical.startsWith(enumTypeFqn + ".")) {
|
if (isStringOrPrimitiveType(enumTypeFqn) && canonical.startsWith(enumTypeFqn + ".")) {
|
||||||
String constant = canonical.substring(enumTypeFqn.length() + 1);
|
int constantStart = enumTypeFqn.length() + 1;
|
||||||
|
if (constantStart >= canonical.length()) {
|
||||||
|
String fnRaw = toFnForm(canonical, enumTypeFqn);
|
||||||
|
raw = fnRaw != null ? fnRaw : resolvePlaceholder(state.rawName());
|
||||||
|
} else {
|
||||||
|
String constant = canonical.substring(constantStart);
|
||||||
raw = "\"" + constant + "\"";
|
raw = "\"" + constant + "\"";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
String fnRaw = toFnForm(canonical, enumTypeFqn);
|
String fnRaw = toFnForm(canonical, enumTypeFqn);
|
||||||
raw = fnRaw != null ? fnRaw : resolvePlaceholder(state.rawName());
|
raw = fnRaw != null ? fnRaw : resolvePlaceholder(state.rawName());
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
protected final PathBindingEvaluator pathBindingEvaluator;
|
protected final PathBindingEvaluator pathBindingEvaluator;
|
||||||
private final Map<String, ASTNode> parsedNodeCache = new HashMap<>();
|
private final Map<String, ASTNode> parsedNodeCache = new HashMap<>();
|
||||||
private final Map<String, List<String>> polymorphicCallCache = new HashMap<>();
|
private final Map<String, List<String>> polymorphicCallCache = new HashMap<>();
|
||||||
|
private final Map<String, Map<String, String>> pathBindingsCache = new HashMap<>();
|
||||||
protected Map<String, List<CallEdge>> graph;
|
protected Map<String, List<CallEdge>> graph;
|
||||||
|
|
||||||
private ASTNode parseExpressionString(String expr) {
|
private ASTNode parseExpressionString(String expr) {
|
||||||
@@ -82,6 +83,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
context.clearAnalysisCaches();
|
context.clearAnalysisCaches();
|
||||||
pathFinder.clearAnalysisCaches();
|
pathFinder.clearAnalysisCaches();
|
||||||
pathBindingEvaluator.clearAnalysisCaches();
|
pathBindingEvaluator.clearAnalysisCaches();
|
||||||
|
pathBindingsCache.clear();
|
||||||
parsedNodeCache.clear();
|
parsedNodeCache.clear();
|
||||||
Map<String, List<CallEdge>> callGraph = buildCallGraph();
|
Map<String, List<CallEdge>> callGraph = buildCallGraph();
|
||||||
List<CallChain> chains = new ArrayList<>();
|
List<CallChain> chains = new ArrayList<>();
|
||||||
@@ -125,6 +127,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
foundAny = true;
|
foundAny = true;
|
||||||
|
Map<String, String> pathBindings = resolvePathBindings(path, callGraph, initialBindings);
|
||||||
TriggerPoint resolvedTp = resolveTriggerPointParameters(tp, path, callGraph, initialBindings);
|
TriggerPoint resolvedTp = resolveTriggerPointParameters(tp, path, callGraph, initialBindings);
|
||||||
if (resolvedTp != null) {
|
if (resolvedTp != null) {
|
||||||
String contextMachineId = pathFinder.extractContextMachineId(path, callGraph);
|
String contextMachineId = pathFinder.extractContextMachineId(path, callGraph);
|
||||||
@@ -133,6 +136,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
.triggerPoint(resolvedTp)
|
.triggerPoint(resolvedTp)
|
||||||
.methodChain(path)
|
.methodChain(path)
|
||||||
.contextMachineId(contextMachineId)
|
.contextMachineId(contextMachineId)
|
||||||
|
.pathBindings(pathBindings.isEmpty() ? null : Map.copyOf(pathBindings))
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,6 +154,33 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
return resolveTriggerPointParameters(tp, path, callGraph, Map.of());
|
return resolveTriggerPointParameters(tp, path, callGraph, Map.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Map<String, String> resolvePathBindings(
|
||||||
|
List<String> path,
|
||||||
|
Map<String, List<CallEdge>> callGraph,
|
||||||
|
Map<String, String> initialBindings) {
|
||||||
|
if (path == null || path.size() < 2) {
|
||||||
|
return initialBindings != null ? Map.copyOf(initialBindings) : Map.of();
|
||||||
|
}
|
||||||
|
String cacheKey = pathBindingsCacheKey(path, initialBindings);
|
||||||
|
return pathBindingsCache.computeIfAbsent(cacheKey, ignored -> {
|
||||||
|
Map<String, String> traced = pathBindingEvaluator.traceBindings(path, callGraph, pathFinder);
|
||||||
|
return mergeBindings(initialBindings, traced);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String pathBindingsCacheKey(List<String> path, Map<String, String> initialBindings) {
|
||||||
|
StringJoiner joiner = new StringJoiner("\0");
|
||||||
|
for (String hop : path) {
|
||||||
|
joiner.add(hop);
|
||||||
|
}
|
||||||
|
if (initialBindings != null) {
|
||||||
|
initialBindings.entrySet().stream()
|
||||||
|
.sorted(Map.Entry.comparingByKey())
|
||||||
|
.forEach(entry -> joiner.add(entry.getKey() + "=" + entry.getValue()));
|
||||||
|
}
|
||||||
|
return joiner.toString();
|
||||||
|
}
|
||||||
|
|
||||||
protected TriggerPoint resolveTriggerPointParameters(
|
protected TriggerPoint resolveTriggerPointParameters(
|
||||||
TriggerPoint tp,
|
TriggerPoint tp,
|
||||||
List<String> path,
|
List<String> path,
|
||||||
@@ -3064,8 +3095,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
if (isExternalParameter(entryMethod, finalParamName)) {
|
if (isExternalParameter(entryMethod, finalParamName)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Map<String, String> bindings = pathBindingEvaluator.traceBindings(path, callGraph, pathFinder);
|
Map<String, String> bindings = resolvePathBindings(path, callGraph, initialBindings);
|
||||||
bindings = mergeBindings(initialBindings, bindings);
|
|
||||||
for (String boundName : bindings.keySet()) {
|
for (String boundName : bindings.keySet()) {
|
||||||
if (isExternalParameter(entryMethod, boundName)) {
|
if (isExternalParameter(entryMethod, boundName)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -3102,7 +3132,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
return sl.getLiteralValue();
|
return sl.getLiteralValue();
|
||||||
}
|
}
|
||||||
if (keyArg instanceof SimpleName sn) {
|
if (keyArg instanceof SimpleName sn) {
|
||||||
Map<String, String> bindings = pathBindingEvaluator.traceBindings(path, callGraph, pathFinder);
|
Map<String, String> bindings = resolvePathBindings(path, callGraph, Map.of());
|
||||||
String bound = bindings.get(sn.getIdentifier());
|
String bound = bindings.get(sn.getIdentifier());
|
||||||
if (bound != null) {
|
if (bound != null) {
|
||||||
return unwrapStringLiteral(bound);
|
return unwrapStringLiteral(bound);
|
||||||
@@ -3130,7 +3160,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
if (!(keyArg instanceof SimpleName sn)) {
|
if (!(keyArg instanceof SimpleName sn)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Map<String, String> bindings = pathBindingEvaluator.traceBindings(path, callGraph, pathFinder);
|
Map<String, String> bindings = resolvePathBindings(path, callGraph, Map.of());
|
||||||
if (bindings.containsKey(sn.getIdentifier())) {
|
if (bindings.containsKey(sn.getIdentifier())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -3189,7 +3219,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
if (polymorphicEvents == null || polymorphicEvents.isEmpty()) {
|
if (polymorphicEvents == null || polymorphicEvents.isEmpty()) {
|
||||||
return polymorphicEvents;
|
return polymorphicEvents;
|
||||||
}
|
}
|
||||||
Map<String, String> bindings = pathBindingEvaluator.traceBindings(path, callGraph, pathFinder);
|
Map<String, String> bindings = resolvePathBindings(path, callGraph, Map.of());
|
||||||
if (bindings.isEmpty()) {
|
if (bindings.isEmpty()) {
|
||||||
if (hasMultiClassPolymorphicPath(path)
|
if (hasMultiClassPolymorphicPath(path)
|
||||||
&& polymorphicEvents.stream().noneMatch(this::looksLikeEnumConstant)) {
|
&& polymorphicEvents.stream().noneMatch(this::looksLikeEnumConstant)) {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package click.kamil.springstatemachineexporter.ast.common;
|
package click.kamil.springstatemachineexporter.ast.common;
|
||||||
|
|
||||||
import click.kamil.springstatemachineexporter.analysis.index.AccessorInlining;
|
import click.kamil.springstatemachineexporter.analysis.index.AccessorInlining;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.index.AccessorKind;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.index.AccessorNaming;
|
||||||
import click.kamil.springstatemachineexporter.analysis.index.AccessorSummary;
|
import click.kamil.springstatemachineexporter.analysis.index.AccessorSummary;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.index.GetterChainEdge;
|
||||||
import click.kamil.springstatemachineexporter.analysis.pipeline.LibraryUnwrapRegistry;
|
import click.kamil.springstatemachineexporter.analysis.pipeline.LibraryUnwrapRegistry;
|
||||||
import click.kamil.springstatemachineexporter.analysis.pipeline.ReactiveExpressionSupport;
|
import click.kamil.springstatemachineexporter.analysis.pipeline.ReactiveExpressionSupport;
|
||||||
import click.kamil.springstatemachineexporter.analysis.pipeline.ResolutionBudget;
|
import click.kamil.springstatemachineexporter.analysis.pipeline.ResolutionBudget;
|
||||||
@@ -460,9 +463,72 @@ public class JdtDataFlowModel implements DataFlowModel {
|
|||||||
return inlineRecordComponentRead(mi, accessor.get(), receiverDefs, visited, paramBindings, instanceFieldBindings, depth);
|
return inlineRecordComponentRead(mi, accessor.get(), receiverDefs, visited, paramBindings, instanceFieldBindings, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Expression> indexedHop = tryIndexedGetterChainHop(
|
||||||
|
mi, accessor.get(), visited, paramBindings, instanceFieldBindings, depth);
|
||||||
|
if (!indexedHop.isEmpty()) {
|
||||||
|
return indexedHop;
|
||||||
|
}
|
||||||
|
|
||||||
return inlineAccessorGetter(mi, accessor.get(), receiverDefs, visited, paramBindings, instanceFieldBindings, depth);
|
return inlineAccessorGetter(mi, accessor.get(), receiverDefs, visited, paramBindings, instanceFieldBindings, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan-time {@link GetterChainEdge} fast path for trivial field-default getter hops.
|
||||||
|
* Skipped when the receiver may have been mutated before this use site.
|
||||||
|
*/
|
||||||
|
private List<Expression> tryIndexedGetterChainHop(
|
||||||
|
MethodInvocation mi,
|
||||||
|
AccessorSummary accessor,
|
||||||
|
Set<ASTNode> visited,
|
||||||
|
Map<IVariableBinding, Expression> paramBindings,
|
||||||
|
Map<IVariableBinding, Expression> instanceFieldBindings,
|
||||||
|
int depth) {
|
||||||
|
if (accessor.kind() != AccessorKind.GETTER || hasReceiverMutationsBeforeUse(mi)) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
Optional<GetterChainEdge> edge = context.getGetterChainIndex().lookup(accessor.ownerFqn(), accessor.methodName());
|
||||||
|
if (edge.isEmpty() || edge.get().fieldInitializerCic() == null) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
ClassInstanceCreation fieldDefaultCic = edge.get().fieldInitializerCic();
|
||||||
|
return getReachingDefinitions(
|
||||||
|
fieldDefaultCic, visited, paramBindings, instanceFieldBindings, depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasReceiverMutationsBeforeUse(MethodInvocation useSite) {
|
||||||
|
Expression receiver = useSite.getExpression();
|
||||||
|
if (!(receiver instanceof SimpleName receiverName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MethodDeclaration enclosingMethod = findEnclosingMethod(useSite);
|
||||||
|
if (enclosingMethod == null || enclosingMethod.getBody() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int usePosition = useSite.getStartPosition();
|
||||||
|
final boolean[] mutationFound = { false };
|
||||||
|
enclosingMethod.getBody().accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(MethodInvocation node) {
|
||||||
|
if (mutationFound[0] || node.getStartPosition() >= usePosition) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(node.getExpression() instanceof SimpleName callReceiver)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!callReceiver.getIdentifier().equals(receiverName.getIdentifier())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String callee = node.getName().getIdentifier();
|
||||||
|
if (AccessorNaming.isBeanStyleAccessorName(callee) && callee.startsWith("set")) {
|
||||||
|
mutationFound[0] = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return mutationFound[0];
|
||||||
|
}
|
||||||
|
|
||||||
private List<Expression> inlineConstantGetter(
|
private List<Expression> inlineConstantGetter(
|
||||||
AccessorSummary accessor,
|
AccessorSummary accessor,
|
||||||
Set<ASTNode> visited,
|
Set<ASTNode> visited,
|
||||||
@@ -819,8 +885,12 @@ public class JdtDataFlowModel implements DataFlowModel {
|
|||||||
}
|
}
|
||||||
ASTNode defNode = findVariableDefinitionNode(enclosingMethod, receiverName.getIdentifier());
|
ASTNode defNode = findVariableDefinitionNode(enclosingMethod, receiverName.getIdentifier());
|
||||||
if (defNode instanceof SingleVariableDeclaration) {
|
if (defNode instanceof SingleVariableDeclaration) {
|
||||||
String setterName = "set" + Character.toUpperCase(accessor.fieldName().charAt(0))
|
String fieldName = accessor.fieldName();
|
||||||
+ accessor.fieldName().substring(1);
|
if (fieldName == null || fieldName.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String setterName = "set" + Character.toUpperCase(fieldName.charAt(0))
|
||||||
|
+ fieldName.substring(1);
|
||||||
Expression setterArg = findLocalSetterArgument(
|
Expression setterArg = findLocalSetterArgument(
|
||||||
enclosingMethod,
|
enclosingMethod,
|
||||||
receiverName.getIdentifier(),
|
receiverName.getIdentifier(),
|
||||||
|
|||||||
@@ -433,6 +433,17 @@ class MachineEnumCanonicalizerTest {
|
|||||||
assertThat(only.rawName()).isEqualTo("OrderState.NEW");
|
assertThat(only.rawName()).isEqualTo("OrderState.NEW");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotThrowWhenStringMachineStateHasTypePrefixOnly() {
|
||||||
|
String stateTypeFqn = "com.company.project.OrderState";
|
||||||
|
State state = State.of("PAID", stateTypeFqn + ".");
|
||||||
|
|
||||||
|
State canonical = MachineEnumCanonicalizer.canonicalizeState(state, "String");
|
||||||
|
|
||||||
|
assertThat(canonical).isNotNull();
|
||||||
|
assertThat(canonical.fullIdentifier()).isEqualTo(stateTypeFqn + ".");
|
||||||
|
}
|
||||||
|
|
||||||
private static void writeSampleConfig(Path tempDir) throws IOException {
|
private static void writeSampleConfig(Path tempDir) throws IOException {
|
||||||
Path orderPkg = tempDir.resolve("com/example/order");
|
Path orderPkg = tempDir.resolve("com/example/order");
|
||||||
Path configPkg = tempDir.resolve("com/example/config");
|
Path configPkg = tempDir.resolve("com/example/config");
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.AstUtils;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.JdtDataFlowModel;
|
||||||
|
import org.eclipse.jdt.core.dom.Expression;
|
||||||
|
import org.eclipse.jdt.core.dom.MethodInvocation;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regression tests for path-binding cache-once-per-chain and index-first getter-chain hops.
|
||||||
|
*/
|
||||||
|
class PathResolutionOptimizationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findChainsShouldAttachPathBindingsOncePerChain(@TempDir Path tempDir) throws Exception {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class ApiController {
|
||||||
|
private final CommandGateway gateway = new CommandGateway();
|
||||||
|
public void pay() { gateway.executeViaMapper("order.pay"); }
|
||||||
|
}
|
||||||
|
enum DomainCommand { ORDER_PAY, ORDER_SHIP }
|
||||||
|
enum OrderEvent { PAY, SHIP }
|
||||||
|
class CommandGateway {
|
||||||
|
private final StringCommandMapper mapper = new StringCommandMapper();
|
||||||
|
private final CentralDispatcher central = new CentralDispatcher();
|
||||||
|
void executeViaMapper(String commandKey) {
|
||||||
|
DomainCommand command = mapper.fromString(commandKey);
|
||||||
|
central.dispatch(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class StringCommandMapper {
|
||||||
|
DomainCommand fromString(String commandKey) {
|
||||||
|
return switch (commandKey) {
|
||||||
|
case "order.pay" -> DomainCommand.ORDER_PAY;
|
||||||
|
case "order.ship" -> DomainCommand.ORDER_SHIP;
|
||||||
|
default -> throw new IllegalArgumentException(commandKey);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class CentralDispatcher {
|
||||||
|
void dispatch(DomainCommand command) {
|
||||||
|
switch (command) {
|
||||||
|
case ORDER_PAY -> orderPay();
|
||||||
|
case ORDER_SHIP -> orderShip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void orderPay() { fire(OrderEvent.PAY); }
|
||||||
|
void orderShip() { fire(OrderEvent.SHIP); }
|
||||||
|
void fire(OrderEvent event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Files.writeString(tempDir.resolve("App.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.setResolveBindings(true);
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
|
||||||
|
EntryPoint entry = EntryPoint.builder().className("com.example.ApiController").methodName("pay").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.CentralDispatcher")
|
||||||
|
.methodName("fire")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = engine.findChains(List.of(entry), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
assertThat(chains.get(0).getPathBindings())
|
||||||
|
.containsEntry("command", "DomainCommand.ORDER_PAY");
|
||||||
|
assertThat(chains.get(0).getMethodChain()).contains("com.example.CentralDispatcher.orderPay");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void indexFirstGetterChainHopShouldResolveFieldDefaultChain(@TempDir Path tempDir) throws Exception {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderService {
|
||||||
|
public void pay(Order order) {
|
||||||
|
order.getPayload().getType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class Order {
|
||||||
|
private Payload payload = new Payload();
|
||||||
|
public Payload getPayload() { return payload; }
|
||||||
|
}
|
||||||
|
class Payload {
|
||||||
|
private OrderEvent type = OrderEvent.PAY;
|
||||||
|
public OrderEvent getType() { return type; }
|
||||||
|
}
|
||||||
|
enum OrderEvent { PAY, SHIP }
|
||||||
|
""";
|
||||||
|
Files.writeString(tempDir.resolve("App.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.setResolveBindings(true);
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
JdtDataFlowModel dataFlowModel = new JdtDataFlowModel(context);
|
||||||
|
Expression found = AstUtils.findExpressionInMethod(
|
||||||
|
"com.example.OrderService.pay", "order.getPayload().getType()", context);
|
||||||
|
assertThat(found).isInstanceOf(MethodInvocation.class);
|
||||||
|
|
||||||
|
String resolved = dataFlowModel.resolveValue((MethodInvocation) found, context);
|
||||||
|
assertThat(resolved).contains("PAY");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user