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) { public static boolean shouldFailClosedOnAmbiguousCallGraphWiden(TriggerPoint trigger) {
return shouldFailClosedOnAmbiguousCallGraphWiden(trigger, null); return shouldFailClosedOnAmbiguousCallGraphWiden(trigger, null, null);
} }
public static boolean shouldFailClosedOnAmbiguousCallGraphWiden( public static boolean shouldFailClosedOnAmbiguousCallGraphWiden(
TriggerPoint trigger, TriggerPoint trigger,
String machineEventTypeFqn) { String machineEventTypeFqn) {
return shouldFailClosedOnAmbiguousCallGraphWiden(trigger, machineEventTypeFqn, null);
}
public static boolean shouldFailClosedOnAmbiguousCallGraphWiden(
TriggerPoint trigger,
String machineEventTypeFqn,
CodebaseContext context) {
if (trigger == null || trigger.isExternal() || !trigger.isAmbiguous()) { if (trigger == null || trigger.isExternal() || !trigger.isAmbiguous()) {
return false; return false;
} }
@@ -39,7 +46,7 @@ public final class CallChainLinkPolicy {
} }
if (event != null && event.contains(".valueOf(")) { if (event != null && event.contains(".valueOf(")) {
return MachineEnumCanonicalizer.isDynamicTriggerExpression(event) return MachineEnumCanonicalizer.isDynamicTriggerExpression(event)
&& !isTrustedEnumPolymorphicWiden(trigger, machineEventTypeFqn); && !isTrustedEnumPolymorphicWiden(trigger, machineEventTypeFqn, context);
} }
return MachineEnumCanonicalizer.isDynamicTriggerExpression(event) return MachineEnumCanonicalizer.isDynamicTriggerExpression(event)
&& event != null; && event != null;
@@ -51,10 +58,17 @@ public final class CallChainLinkPolicy {
* cross-package or unqualified widens. * cross-package or unqualified widens.
*/ */
static boolean isTrustedEnumPolymorphicWiden(TriggerPoint trigger) { static boolean isTrustedEnumPolymorphicWiden(TriggerPoint trigger) {
return isTrustedEnumPolymorphicWiden(trigger, null); return isTrustedEnumPolymorphicWiden(trigger, null, null);
} }
static boolean isTrustedEnumPolymorphicWiden(TriggerPoint trigger, String machineEventTypeFqn) { static boolean isTrustedEnumPolymorphicWiden(TriggerPoint trigger, String machineEventTypeFqn) {
return isTrustedEnumPolymorphicWiden(trigger, machineEventTypeFqn, null);
}
static boolean isTrustedEnumPolymorphicWiden(
TriggerPoint trigger,
String machineEventTypeFqn,
CodebaseContext context) {
if (trigger == null) { if (trigger == null) {
return false; return false;
} }
@@ -75,7 +89,7 @@ public final class CallChainLinkPolicy {
} }
String enumType = pe.substring(0, pe.lastIndexOf('.')); String enumType = pe.substring(0, pe.lastIndexOf('.'));
if (!enumType.contains(".") if (!enumType.contains(".")
|| !MachineEnumCanonicalizer.enumTypesMatch(eventTypeFqn, enumType)) { || !MachineEnumCanonicalizer.enumTypesMatch(eventTypeFqn, enumType, context)) {
return false; return false;
} }
} }
@@ -86,12 +100,29 @@ public final class CallChainLinkPolicy {
TriggerPoint trigger, TriggerPoint trigger,
List<MatchedTransition> matched, List<MatchedTransition> matched,
boolean ambiguousSource) { 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()) { if (trigger != null && trigger.isExternal()) {
return LinkResolution.UNRESOLVED_EXTERNAL; return LinkResolution.UNRESOLVED_EXTERNAL;
} }
if (ambiguousSource if (ambiguousSource
|| (trigger != null && trigger.isAmbiguous()) || (trigger != null && trigger.isAmbiguous())
|| shouldFailClosedOnAmbiguousCallGraphWiden(trigger)) { || shouldFailClosedOnAmbiguousCallGraphWiden(trigger, machineEventTypeFqn, context)) {
return LinkResolution.AMBIGUOUS_WIDEN; return LinkResolution.AMBIGUOUS_WIDEN;
} }
if (matched != null && !matched.isEmpty()) { 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; String triggerSource = tp.getSourceState() != null ? simplifySourceState(tp.getSourceState()) : null;
if (CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden( if (CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden(
tp, machineTypes != null ? machineTypes.eventTypeFqn() : null)) { tp,
machineTypes != null ? machineTypes.eventTypeFqn() : null,
context)) {
updatedChains.add(chain.toBuilder() updatedChains.add(chain.toBuilder()
.triggerPoint(tp) .triggerPoint(tp)
.matchedTransitions(null) .matchedTransitions(null)
@@ -155,7 +157,12 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
tp = tp.toBuilder().ambiguous(true).build(); 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()) { if (!matched.isEmpty()) {
CallChain newChain = chain.toBuilder() CallChain newChain = chain.toBuilder()

View File

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

View File

@@ -74,6 +74,23 @@ class CallChainLinkPolicyTest {
trigger, "com.example.OrderEvent")).isFalse(); 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 @Test
void shouldNotTrustImportStylePolymorphicWidenWithoutPackage() { void shouldNotTrustImportStylePolymorphicWidenWithoutPackage() {
TriggerPoint trigger = TriggerPoint.builder() TriggerPoint trigger = TriggerPoint.builder()