Stop enum expansion from bypassing ambiguous simple-name guard

Remove direct enumValuesMap scan in expandDeclaredEnumValues so ambiguous
OrderEvent-style names fail closed instead of picking an arbitrary package.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-14 05:26:24 +02:00
parent dcae599d21
commit a94490712f
2 changed files with 64 additions and 15 deletions

View File

@@ -2913,21 +2913,6 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
if ((enumValues == null || enumValues.isEmpty()) && declaredType.contains(".")) {
enumValues = context.getEnumValues(declaredType.substring(declaredType.lastIndexOf('.') + 1));
}
if ((enumValues == null || enumValues.isEmpty())) {
String simpleDeclared = declaredType.contains(".")
? declaredType.substring(declaredType.lastIndexOf('.') + 1)
: declaredType;
for (Map.Entry<String, List<String>> entry : context.getEnumValuesMap().entrySet()) {
String enumType = entry.getKey();
String simpleType = enumType.contains(".")
? enumType.substring(enumType.lastIndexOf('.') + 1)
: enumType;
if (simpleDeclared.equals(simpleType)) {
enumValues = entry.getValue();
break;
}
}
}
if (enumValues == null || enumValues.isEmpty()) {
return List.of();
}

View File

@@ -93,5 +93,69 @@ class AmbiguousSimpleEnumLinkingTest {
assertThat(linked.getMatchedTransitions()).isNullOrEmpty();
assertThat(linked.getLinkResolution()).isIn(LinkResolution.NO_MATCH, LinkResolution.AMBIGUOUS_WIDEN);
}
@Test
void shouldFailClosedWhenEnumExpansionHintUsesAmbiguousSimpleName(@TempDir Path tempDir) throws Exception {
Files.createDirectories(tempDir.resolve("a"));
Files.createDirectories(tempDir.resolve("b"));
Files.writeString(tempDir.resolve("a/OrderEvent.java"),
"package a; public enum OrderEvent { PAY, SHIP }");
Files.writeString(tempDir.resolve("b/OrderEvent.java"),
"package b; public enum OrderEvent { CANCEL }");
Files.writeString(tempDir.resolve("App.java"), """
package com.example;
import a.OrderEvent;
public class OrderController {
Dispatcher dispatcher = new Dispatcher();
public void transition(String eventStr) {
dispatcher.dispatch(eventStr);
}
}
class Dispatcher {
void dispatch(String eventStr) {
StateMachine sm = new StateMachine();
sm.sendEvent(OrderEvent.valueOf(normalize(eventStr)));
}
String normalize(String value) {
return value.trim().toUpperCase();
}
}
class StateMachine { void sendEvent(OrderEvent e) {} }
""");
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(false);
context.scan(tempDir);
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
EntryPoint entry = EntryPoint.builder()
.className("com.example.OrderController")
.methodName("transition")
.build();
TriggerPoint trigger = TriggerPoint.builder()
.className("com.example.StateMachine")
.methodName("sendEvent")
.event("e")
.build();
CallChain chain = engine.findChains(List.of(entry), List.of(trigger)).get(0);
Transition pay = new Transition();
pay.setSourceStates(List.of(State.of("NEW", "NEW")));
pay.setTargetStates(List.of(State.of("PAID", "PAID")));
pay.setEvent(Event.of("PAY", "a.OrderEvent.PAY"));
AnalysisResult result = AnalysisResult.builder()
.name("com.example.OrderStateMachineConfig")
.transitions(List.of(pay))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
new TransitionLinkerEnricher().enrich(result, context, null);
CallChain linked = result.getMetadata().getCallChains().get(0);
assertThat(linked.getMatchedTransitions()).isNullOrEmpty();
assertThat(linked.getLinkResolution()).isIn(LinkResolution.NO_MATCH, LinkResolution.AMBIGUOUS_WIDEN);
}
}