Thread context through link policy and symbolic enum expansion

Use machine event type and CodebaseContext for trusted valueOf widen checks and resolveLinkResolution, and apply context-aware enum type matching when expanding symbolic polymorphic placeholders.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-14 06:17:30 +02:00
parent 5bc65be30d
commit 57686c575e
4 changed files with 63 additions and 8 deletions

View File

@@ -20,12 +20,19 @@ public final class CallChainLinkPolicy {
}
public static boolean shouldFailClosedOnAmbiguousCallGraphWiden(TriggerPoint trigger) {
return shouldFailClosedOnAmbiguousCallGraphWiden(trigger, null);
return shouldFailClosedOnAmbiguousCallGraphWiden(trigger, null, null);
}
public static boolean shouldFailClosedOnAmbiguousCallGraphWiden(
TriggerPoint trigger,
String machineEventTypeFqn) {
return shouldFailClosedOnAmbiguousCallGraphWiden(trigger, machineEventTypeFqn, null);
}
public static boolean shouldFailClosedOnAmbiguousCallGraphWiden(
TriggerPoint trigger,
String machineEventTypeFqn,
CodebaseContext context) {
if (trigger == null || trigger.isExternal() || !trigger.isAmbiguous()) {
return false;
}
@@ -39,7 +46,7 @@ public final class CallChainLinkPolicy {
}
if (event != null && event.contains(".valueOf(")) {
return MachineEnumCanonicalizer.isDynamicTriggerExpression(event)
&& !isTrustedEnumPolymorphicWiden(trigger, machineEventTypeFqn);
&& !isTrustedEnumPolymorphicWiden(trigger, machineEventTypeFqn, context);
}
return MachineEnumCanonicalizer.isDynamicTriggerExpression(event)
&& event != null;
@@ -51,10 +58,17 @@ public final class CallChainLinkPolicy {
* cross-package or unqualified widens.
*/
static boolean isTrustedEnumPolymorphicWiden(TriggerPoint trigger) {
return isTrustedEnumPolymorphicWiden(trigger, null);
return isTrustedEnumPolymorphicWiden(trigger, null, null);
}
static boolean isTrustedEnumPolymorphicWiden(TriggerPoint trigger, String machineEventTypeFqn) {
return isTrustedEnumPolymorphicWiden(trigger, machineEventTypeFqn, null);
}
static boolean isTrustedEnumPolymorphicWiden(
TriggerPoint trigger,
String machineEventTypeFqn,
CodebaseContext context) {
if (trigger == null) {
return false;
}
@@ -75,7 +89,7 @@ public final class CallChainLinkPolicy {
}
String enumType = pe.substring(0, pe.lastIndexOf('.'));
if (!enumType.contains(".")
|| !MachineEnumCanonicalizer.enumTypesMatch(eventTypeFqn, enumType)) {
|| !MachineEnumCanonicalizer.enumTypesMatch(eventTypeFqn, enumType, context)) {
return false;
}
}
@@ -86,12 +100,29 @@ public final class CallChainLinkPolicy {
TriggerPoint trigger,
List<MatchedTransition> matched,
boolean ambiguousSource) {
return resolveLinkResolution(trigger, matched, ambiguousSource, null, null);
}
public static LinkResolution resolveLinkResolution(
TriggerPoint trigger,
List<MatchedTransition> matched,
boolean ambiguousSource,
String machineEventTypeFqn) {
return resolveLinkResolution(trigger, matched, ambiguousSource, machineEventTypeFqn, null);
}
public static LinkResolution resolveLinkResolution(
TriggerPoint trigger,
List<MatchedTransition> matched,
boolean ambiguousSource,
String machineEventTypeFqn,
CodebaseContext context) {
if (trigger != null && trigger.isExternal()) {
return LinkResolution.UNRESOLVED_EXTERNAL;
}
if (ambiguousSource
|| (trigger != null && trigger.isAmbiguous())
|| shouldFailClosedOnAmbiguousCallGraphWiden(trigger)) {
|| shouldFailClosedOnAmbiguousCallGraphWiden(trigger, machineEventTypeFqn, context)) {
return LinkResolution.AMBIGUOUS_WIDEN;
}
if (matched != null && !matched.isEmpty()) {

View File

@@ -69,7 +69,9 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
String triggerSource = tp.getSourceState() != null ? simplifySourceState(tp.getSourceState()) : null;
if (CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden(
tp, machineTypes != null ? machineTypes.eventTypeFqn() : null)) {
tp,
machineTypes != null ? machineTypes.eventTypeFqn() : null,
context)) {
updatedChains.add(chain.toBuilder()
.triggerPoint(tp)
.matchedTransitions(null)
@@ -155,7 +157,12 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
tp = tp.toBuilder().ambiguous(true).build();
}
LinkResolution linkResolution = CallChainLinkPolicy.resolveLinkResolution(tp, matched, ambiguousSource);
LinkResolution linkResolution = CallChainLinkPolicy.resolveLinkResolution(
tp,
matched,
ambiguousSource,
machineTypes != null ? machineTypes.eventTypeFqn() : null,
context);
if (!matched.isEmpty()) {
CallChain newChain = chain.toBuilder()

View File

@@ -166,7 +166,7 @@ public final class MachineEnumCanonicalizer {
if (context != null && context.isAmbiguousSimpleName(symbolicType)) {
continue;
}
if (!enumTypesMatch(machineEventTypeFqn, symbolicType)) {
if (!enumTypesMatch(machineEventTypeFqn, symbolicType, context)) {
continue;
}
if (transitionEvents.size() == 1) {

View File

@@ -74,6 +74,23 @@ class CallChainLinkPolicyTest {
trigger, "com.example.OrderEvent")).isFalse();
}
@Test
void shouldResolveLinkWhenTrustedWidenPassesUsingMachineEventType() {
TriggerPoint trigger = TriggerPoint.builder()
.ambiguous(false)
.event("OrderEvent.valueOf(eventStr)")
.polymorphicEvents(List.of("com.example.OrderEvent.PAY", "com.example.OrderEvent.SHIP"))
.build();
assertThat(CallChainLinkPolicy.resolveLinkResolution(
trigger,
List.of(),
false,
"com.example.OrderEvent")).isEqualTo(LinkResolution.NO_MATCH);
assertThat(CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden(
trigger, "com.example.OrderEvent")).isFalse();
}
@Test
void shouldNotTrustImportStylePolymorphicWidenWithoutPackage() {
TriggerPoint trigger = TriggerPoint.builder()