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:")) {
|
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(
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user