Tighten bare-constant filtering and valueOf widen policy

Scope bare polymorphic enum constants to the trigger parameter type (fail
closed on ambiguous simple names and cross-enum constant names), and fail
closed on runtime valueOf widens unless every candidate is package-qualified
for the trigger's declared event type.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-14 05:40:17 +02:00
parent 5972b9df50
commit fb3218e1a2
3 changed files with 97 additions and 19 deletions

View File

@@ -31,9 +31,41 @@ public final class CallChainLinkPolicy {
if (event != null && event.startsWith("ENUM_SET:")) { if (event != null && event.startsWith("ENUM_SET:")) {
return true; return true;
} }
if (event != null && event.contains(".valueOf(")) {
return MachineEnumCanonicalizer.isDynamicTriggerExpression(event) return MachineEnumCanonicalizer.isDynamicTriggerExpression(event)
&& event != null && !isTrustedEnumPolymorphicWiden(trigger);
&& !event.contains("valueOf"); }
return MachineEnumCanonicalizer.isDynamicTriggerExpression(event)
&& event != null;
}
/**
* True when every concrete polymorphic candidate belongs to the trigger's declared event type.
* Used to allow machine-scoped symbolic expansion for generic dispatchers while rejecting
* cross-package or unqualified widens.
*/
static boolean isTrustedEnumPolymorphicWiden(TriggerPoint trigger) {
if (trigger == null) {
return false;
}
String eventTypeFqn = trigger.getEventTypeFqn();
if (eventTypeFqn == null || eventTypeFqn.isBlank()) {
return false;
}
List<String> poly = trigger.getPolymorphicEvents();
if (poly == null || poly.size() <= 1) {
return false;
}
for (String pe : poly) {
if (pe == null || pe.startsWith("<SYMBOLIC:") || pe.startsWith("ENUM_SET:") || !pe.contains(".")) {
return false;
}
String enumType = pe.substring(0, pe.lastIndexOf('.'));
if (!MachineEnumCanonicalizer.enumTypesMatch(eventTypeFqn, enumType)) {
return false;
}
}
return true;
} }
public static LinkResolution resolveLinkResolution( public static LinkResolution resolveLinkResolution(

View File

@@ -1063,29 +1063,14 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
} }
polymorphicEvents = newPolyEvents; polymorphicEvents = newPolyEvents;
polymorphicEvents.removeIf(e -> {
if (e.contains(".")) return false;
String val = e;
boolean isKnownEnumVal = false;
for (List<String> vals : context.getEnumValuesMap().values()) {
for (String v : vals) {
if (v.endsWith("." + val)) {
isKnownEnumVal = true;
break;
}
}
if (isKnownEnumVal) break;
}
if (isKnownEnumVal) return false;
return !val.equals(val.toUpperCase()) || val.length() <= 1;
});
String targetMethod = path.get(path.size() - 1); String targetMethod = path.get(path.size() - 1);
int eventParamIndex = typeResolver.getParameterIndex(targetMethod, event, true); int eventParamIndex = typeResolver.getParameterIndex(targetMethod, event, true);
if (eventParamIndex < 0) { if (eventParamIndex < 0) {
eventParamIndex = 0; eventParamIndex = 0;
} }
String expectedType = typeResolver.getParameterType(targetMethod, eventParamIndex); String expectedType = typeResolver.getParameterType(targetMethod, eventParamIndex);
polymorphicEvents.removeIf(e -> shouldDropBarePolymorphicCandidate(e, expectedType, context));
if (expectedType != null) { if (expectedType != null) {
final String expType = expectedType; final String expType = expectedType;
boolean isExpectedEnum = context.getEnumValues(expType) != null; boolean isExpectedEnum = context.getEnumValues(expType) != null;
@@ -2697,6 +2682,41 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
return current; return current;
} }
private boolean shouldDropBarePolymorphicCandidate(String candidate, String expectedType, CodebaseContext context) {
if (candidate == null || candidate.contains(".")
|| candidate.startsWith("<SYMBOLIC:") || candidate.startsWith("ENUM_SET:")) {
return false;
}
if (!candidate.equals(candidate.toUpperCase()) || candidate.length() <= 1) {
return true;
}
if (expectedType != null) {
if (context.isAmbiguousSimpleName(expectedType)) {
return true;
}
List<String> enumValues = context.getEnumValues(expectedType);
if (enumValues == null || enumValues.isEmpty()) {
return false;
}
for (String enumValue : enumValues) {
if (enumValue.endsWith("." + candidate)) {
return false;
}
}
return true;
}
int matchingEnumTypes = 0;
for (List<String> enumValues : context.getEnumValuesMap().values()) {
for (String enumValue : enumValues) {
if (enumValue.endsWith("." + candidate)) {
matchingEnumTypes++;
break;
}
}
}
return matchingEnumTypes != 1;
}
private void qualifyBareEnumConstants(List<String> polymorphicEvents, String expectedType) { private void qualifyBareEnumConstants(List<String> polymorphicEvents, String expectedType) {
if (expectedType == null || polymorphicEvents == null || polymorphicEvents.isEmpty()) { if (expectedType == null || polymorphicEvents == null || polymorphicEvents.isEmpty()) {
return; return;

View File

@@ -33,4 +33,30 @@ class CallChainLinkPolicyTest {
assertThat(CallChainLinkPolicy.resolveLinkResolution(trigger, List.of(), false)) assertThat(CallChainLinkPolicy.resolveLinkResolution(trigger, List.of(), false))
.isEqualTo(LinkResolution.UNRESOLVED_EXTERNAL); .isEqualTo(LinkResolution.UNRESOLVED_EXTERNAL);
} }
@Test
void shouldFailClosedOnValueOfAmbiguousWiden() {
TriggerPoint trigger = TriggerPoint.builder()
.ambiguous(true)
.event("OrderEvent.valueOf(eventStr)")
.polymorphicEvents(List.of("a.OrderEvent.PAY", "a.OrderEvent.SHIP"))
.build();
assertThat(CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden(trigger)).isTrue();
assertThat(CallChainLinkPolicy.resolveLinkResolution(trigger, List.of(), false))
.isEqualTo(LinkResolution.AMBIGUOUS_WIDEN);
}
@Test
void shouldAllowTrustedMachineScopedValueOfWiden() {
TriggerPoint trigger = TriggerPoint.builder()
.ambiguous(true)
.eventTypeFqn("com.example.OrderEvent")
.event("OrderEvent.valueOf(eventStr)")
.polymorphicEvents(List.of("com.example.OrderEvent.PAY", "com.example.OrderEvent.SHIP"))
.build();
assertThat(CallChainLinkPolicy.isTrustedEnumPolymorphicWiden(trigger)).isTrue();
assertThat(CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden(trigger)).isFalse();
}
} }