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:
@@ -169,7 +169,6 @@ public final class BooleanConstraintEvaluator {
|
|||||||
if (cleanValue.startsWith("\"") && cleanValue.endsWith("\"")) {
|
if (cleanValue.startsWith("\"") && cleanValue.endsWith("\"")) {
|
||||||
cleanValue = cleanValue.substring(1, cleanValue.length() - 1);
|
cleanValue = cleanValue.substring(1, cleanValue.length() - 1);
|
||||||
}
|
}
|
||||||
String suffix = cleanValue.contains(".") ? cleanValue.substring(cleanValue.lastIndexOf('.') + 1) : cleanValue;
|
|
||||||
|
|
||||||
Pattern eqPattern = Pattern.compile(
|
Pattern eqPattern = Pattern.compile(
|
||||||
"(?i)" + Pattern.quote(varName) + "\\s*==\\s*([\\w.\"']+)");
|
"(?i)" + Pattern.quote(varName) + "\\s*==\\s*([\\w.\"']+)");
|
||||||
@@ -177,10 +176,7 @@ public final class BooleanConstraintEvaluator {
|
|||||||
StringBuffer sb = new StringBuffer();
|
StringBuffer sb = new StringBuffer();
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
String rhs = matcher.group(1).replace("\"", "").replace("'", "");
|
String rhs = matcher.group(1).replace("\"", "").replace("'", "");
|
||||||
boolean matches = cleanValue.equals(rhs)
|
boolean matches = constraintValuesMatch(cleanValue, rhs);
|
||||||
|| cleanValue.endsWith("." + rhs)
|
|
||||||
|| suffix.equalsIgnoreCase(rhs)
|
|
||||||
|| cleanValue.equalsIgnoreCase(rhs);
|
|
||||||
matcher.appendReplacement(sb, matches ? "true" : "false");
|
matcher.appendReplacement(sb, matches ? "true" : "false");
|
||||||
}
|
}
|
||||||
matcher.appendTail(sb);
|
matcher.appendTail(sb);
|
||||||
@@ -191,6 +187,72 @@ public final class BooleanConstraintEvaluator {
|
|||||||
return sb.toString();
|
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}.
|
* Returns {@code true}/{@code false} when both sides are compile-time string literals; otherwise {@code null}.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -29,6 +29,20 @@ class BooleanConstraintEvaluatorBindingsTest {
|
|||||||
Map.of("commandKey", "order.pay"))).isTrue();
|
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
|
@Test
|
||||||
void shouldRejectConstraintWhenBindingIsMissing() {
|
void shouldRejectConstraintWhenBindingIsMissing() {
|
||||||
assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
|
assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
|
||||||
|
|||||||
Reference in New Issue
Block a user