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:
@@ -31,9 +31,41 @@ public final class CallChainLinkPolicy {
|
||||
if (event != null && event.startsWith("ENUM_SET:")) {
|
||||
return true;
|
||||
}
|
||||
if (event != null && event.contains(".valueOf(")) {
|
||||
return MachineEnumCanonicalizer.isDynamicTriggerExpression(event)
|
||||
&& event != null
|
||||
&& !event.contains("valueOf");
|
||||
&& !isTrustedEnumPolymorphicWiden(trigger);
|
||||
}
|
||||
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(
|
||||
|
||||
@@ -1063,29 +1063,14 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
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);
|
||||
int eventParamIndex = typeResolver.getParameterIndex(targetMethod, event, true);
|
||||
if (eventParamIndex < 0) {
|
||||
eventParamIndex = 0;
|
||||
}
|
||||
String expectedType = typeResolver.getParameterType(targetMethod, eventParamIndex);
|
||||
|
||||
polymorphicEvents.removeIf(e -> shouldDropBarePolymorphicCandidate(e, expectedType, context));
|
||||
if (expectedType != null) {
|
||||
final String expType = expectedType;
|
||||
boolean isExpectedEnum = context.getEnumValues(expType) != null;
|
||||
@@ -2697,6 +2682,41 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
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) {
|
||||
if (expectedType == null || polymorphicEvents == null || polymorphicEvents.isEmpty()) {
|
||||
return;
|
||||
|
||||
@@ -33,4 +33,30 @@ class CallChainLinkPolicyTest {
|
||||
assertThat(CallChainLinkPolicy.resolveLinkResolution(trigger, List.of(), false))
|
||||
.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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user