Fail closed on cross-package enum switch constraints

Switch-arm equality now requires compatible enum types when both sides are package-qualified, while still accepting import-style vs FQN pairs like DomainCommand.ORDER_PAY.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-14 06:31:11 +02:00
parent d589f65cc1
commit dbeab64e1b
2 changed files with 81 additions and 5 deletions

View File

@@ -169,7 +169,6 @@ public final class BooleanConstraintEvaluator {
if (cleanValue.startsWith("\"") && cleanValue.endsWith("\"")) {
cleanValue = cleanValue.substring(1, cleanValue.length() - 1);
}
String suffix = cleanValue.contains(".") ? cleanValue.substring(cleanValue.lastIndexOf('.') + 1) : cleanValue;
Pattern eqPattern = Pattern.compile(
"(?i)" + Pattern.quote(varName) + "\\s*==\\s*([\\w.\"']+)");
@@ -177,10 +176,7 @@ public final class BooleanConstraintEvaluator {
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
String rhs = matcher.group(1).replace("\"", "").replace("'", "");
boolean matches = cleanValue.equals(rhs)
|| cleanValue.endsWith("." + rhs)
|| suffix.equalsIgnoreCase(rhs)
|| cleanValue.equalsIgnoreCase(rhs);
boolean matches = constraintValuesMatch(cleanValue, rhs);
matcher.appendReplacement(sb, matches ? "true" : "false");
}
matcher.appendTail(sb);
@@ -191,6 +187,72 @@ public final class BooleanConstraintEvaluator {
return sb.toString();
}
private static boolean constraintValuesMatch(String boundValue, String rhs) {
if (boundValue == null || rhs == null) {
return false;
}
String bound = stripOuterQuotes(boundValue);
String rhsClean = stripOuterQuotes(rhs);
if (bound.equals(rhsClean) || bound.equalsIgnoreCase(rhsClean)) {
return true;
}
if (bound.endsWith("." + rhsClean) || rhsClean.endsWith("." + bound)) {
return true;
}
String boundConstant = enumConstantName(bound);
String rhsConstant = enumConstantName(rhsClean);
if (!boundConstant.equalsIgnoreCase(rhsConstant)) {
return false;
}
String boundType = enumTypePart(bound);
String rhsType = enumTypePart(rhsClean);
if (boundType == null || rhsType == null) {
return true;
}
if (boundType.equals(rhsType)) {
return true;
}
String boundSimple = simpleTypeName(boundType);
String rhsSimple = simpleTypeName(rhsType);
if (!boundSimple.equals(rhsSimple)) {
return false;
}
boolean boundImportStyle = !boundType.contains(".");
boolean rhsImportStyle = !rhsType.contains(".");
return boundImportStyle || rhsImportStyle;
}
private static String stripOuterQuotes(String value) {
if (value == null) {
return null;
}
if (value.length() >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
return value.substring(1, value.length() - 1);
}
return value;
}
private static String enumConstantName(String ref) {
int dot = ref.lastIndexOf('.');
return dot >= 0 ? ref.substring(dot + 1) : ref;
}
private static String enumTypePart(String ref) {
int dot = ref.lastIndexOf('.');
if (dot <= 0) {
return null;
}
return ref.substring(0, dot);
}
private static String simpleTypeName(String typePart) {
int dot = typePart.lastIndexOf('.');
return dot >= 0 ? typePart.substring(dot + 1) : typePart;
}
/**
* Returns {@code true}/{@code false} when both sides are compile-time string literals; otherwise {@code null}.
*/

View File

@@ -29,6 +29,20 @@ class BooleanConstraintEvaluatorBindingsTest {
Map.of("commandKey", "order.pay"))).isTrue();
}
@Test
void shouldRejectCrossPackageQualifiedEnumSwitchConstraint() {
assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
"command == a.OrderEvent.PAY",
Map.of("command", "b.OrderEvent.PAY"))).isFalse();
}
@Test
void shouldAcceptPackageQualifiedRhsAgainstImportStyleBinding() {
assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
"command == com.example.DomainCommand.ORDER_PAY",
Map.of("command", "DomainCommand.ORDER_PAY"))).isTrue();
}
@Test
void shouldRejectConstraintWhenBindingIsMissing() {
assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(