diff --git a/checker/src/main/java/dev/cel/checker/Env.java b/checker/src/main/java/dev/cel/checker/Env.java index 97908dca3..dd1f92c2c 100644 --- a/checker/src/main/java/dev/cel/checker/Env.java +++ b/checker/src/main/java/dev/cel/checker/Env.java @@ -242,12 +242,12 @@ private static CelStandardDeclarations newStandardDeclarations(CelOptions celOpt } break; default: - if (!celOptions.enableHeterogeneousNumericComparisons() - && overload instanceof Comparison) { - Comparison comparison = (Comparison) overload; - if (comparison.isHeterogeneousComparison()) { - return false; - } + // Heterogeneous numeric comparisons require UnsignedLong at runtime so uint + // values do not share Long with int and dispatch to signed int64 overloads. + if (!celOptions.enableUnsignedLongs() + && overload instanceof Comparison + && ((Comparison) overload).isHeterogeneousComparison()) { + return false; } break; } diff --git a/checker/src/test/java/dev/cel/checker/CelStandardDeclarationsTest.java b/checker/src/test/java/dev/cel/checker/CelStandardDeclarationsTest.java index 867946fda..8c612b8e3 100644 --- a/checker/src/test/java/dev/cel/checker/CelStandardDeclarationsTest.java +++ b/checker/src/test/java/dev/cel/checker/CelStandardDeclarationsTest.java @@ -22,6 +22,7 @@ import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.checker.CelStandardDeclarations.StandardFunction; import dev.cel.checker.CelStandardDeclarations.StandardFunction.Overload.Arithmetic; +import dev.cel.checker.CelStandardDeclarations.StandardFunction.Overload.Comparison; import dev.cel.checker.CelStandardDeclarations.StandardIdentifier; import dev.cel.common.CelOptions; import dev.cel.common.CelValidationException; @@ -327,14 +328,36 @@ public void standardEnvironment_subsetEnvironment() throws Exception { @TestParameters("{expression: '2u <= 1'}") @TestParameters("{expression: '2u <= 1.0'}") @TestParameters("{expression: '1.0 <= 2u'}") - public void heterogeneousEqualityDisabled_mixedTypeComparisons_throws(String expression) { + public void standardEnvironment_excludeHeterogeneousComparisons_throws(String expression) { CelCompiler celCompiler = CelCompilerFactory.standardCelCompilerBuilder() - .setOptions(CelOptions.current().enableHeterogeneousNumericComparisons(false).build()) + .setStandardDeclarations( + CelStandardDeclarations.newBuilder() + .filterFunctions( + (function, overload) -> + !(overload instanceof Comparison + && ((Comparison) overload).isHeterogeneousComparison())) + .build()) + .build(); + CelValidationException e = + assertThrows(CelValidationException.class, () -> celCompiler.compile(expression).getAst()); + + assertThat(e).hasMessageThat().contains("found no matching overload for"); + } + + @Test + @TestParameters("{expression: '1 < 2.0'}") + @TestParameters("{expression: '1 < 2u'}") + @TestParameters("{expression: '2u < 1.0'}") + public void unsignedLongsDisabled_mixedTypeComparisons_throws(String expression) { + CelCompiler celCompiler = + CelCompilerFactory.standardCelCompilerBuilder() + .setOptions(CelOptions.current().enableUnsignedLongs(false).build()) .build(); CelValidationException e = assertThrows(CelValidationException.class, () -> celCompiler.compile(expression).getAst()); + assertThat(e).hasMessageThat().contains("found no matching overload for"); } diff --git a/common/src/main/java/dev/cel/common/CelOptions.java b/common/src/main/java/dev/cel/common/CelOptions.java index 632715fe5..31e05de2f 100644 --- a/common/src/main/java/dev/cel/common/CelOptions.java +++ b/common/src/main/java/dev/cel/common/CelOptions.java @@ -15,6 +15,7 @@ package dev.cel.common; import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CheckReturnValue; import com.google.errorprone.annotations.Immutable; @@ -80,7 +81,13 @@ public enum ProtoUnsetFieldOptions { public abstract boolean enableTimestampEpoch(); - public abstract boolean enableHeterogeneousNumericComparisons(); + /** + * @deprecated Cross-type numeric comparisons are always enabled. + */ + @Deprecated + public boolean enableHeterogeneousNumericComparisons() { + return true; + } public abstract boolean enableNamespacedDeclarations(); @@ -148,7 +155,6 @@ public static Builder newBuilder() { .enableCompileTimeOverloadResolution(false) .enableHomogeneousLiterals(false) .enableTimestampEpoch(false) - .enableHeterogeneousNumericComparisons(false) .enableNamespacedDeclarations(true) .enableJsonFieldNames(false) // Evaluation options @@ -183,7 +189,6 @@ public static Builder current() { .enableUnsignedLongs(true) .enableRegexPartialMatch(true) .enableTimestampEpoch(true) - .enableHeterogeneousNumericComparisons(true) .errorOnDuplicateMapKeys(true) .evaluateCanonicalTypesToNativeValues(true) .errorOnIntWrap(true) @@ -320,20 +325,22 @@ public abstract static class Builder { public abstract Builder enableTimestampEpoch(boolean value); /** - * Enable the {@code less_equals_double_int64} and other cross-type numeric comparisons for - * {@code double}, {@code int64}, and {@code uint64} values. - * - *

This option is enabled by default in {@link CelOptions#current}. - * - *

TODO: Remove this feature once it has been auto-enabled. - * - * @deprecated This option is now enabled by default. If you are passing {@code true}, simply - * remove this method call. If you are passing {@code false} to disable this feature, subset - * the environment instead using {@code dev.cel.checker.CelStandardDeclarations} and {@code + * @deprecated Cross-type numeric comparisons are always enabled ({@code false} throws {@link + * IllegalArgumentException}). Remove calls passing {@code true}, or subset using {@code + * dev.cel.checker.CelStandardDeclarations} and {@code * dev.cel.runtime.CelStandardFunctions}. */ @Deprecated - public abstract Builder enableHeterogeneousNumericComparisons(boolean value); + @CanIgnoreReturnValue + public Builder enableHeterogeneousNumericComparisons(boolean value) { + if (!value) { + throw new IllegalArgumentException( + "Misconfigured CelOptions: enableHeterogeneousNumericComparisons cannot be disabled." + + " Subset the environment instead using CelStandardDeclarations and" + + " CelStandardFunctions."); + } + return this; + } /** * Enables the usage of namespaced functions and identifiers. This causes the type-checker to diff --git a/common/src/test/java/dev/cel/common/CelOptionsTest.java b/common/src/test/java/dev/cel/common/CelOptionsTest.java index d551f1d01..456e3dd56 100644 --- a/common/src/test/java/dev/cel/common/CelOptionsTest.java +++ b/common/src/test/java/dev/cel/common/CelOptionsTest.java @@ -15,6 +15,7 @@ package dev.cel.common; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import org.junit.Test; import org.junit.runner.RunWith; @@ -37,4 +38,29 @@ public void current_defaults() { assertThat(CelOptions.current().build().enablePrattParser()).isFalse(); assertThat(CelOptions.current().build().enableIeee754ContainerEquality()).isTrue(); } + + @Test + @SuppressWarnings("deprecation") // Testing deprecated compatibility methods + public void enableHeterogeneousNumericComparisons_true_isNoOp() { + CelOptions.Builder builder = CelOptions.newBuilder(); + + assertThat(builder.enableHeterogeneousNumericComparisons(true)).isSameInstanceAs(builder); + assertThat(CelOptions.DEFAULT.enableHeterogeneousNumericComparisons()).isTrue(); + assertThat(CelOptions.LEGACY.enableHeterogeneousNumericComparisons()).isTrue(); + } + + @Test + @SuppressWarnings("deprecation") // Testing deprecated compatibility methods + public void enableHeterogeneousNumericComparisons_false_throws() { + CelOptions.Builder builder = CelOptions.DEFAULT.toBuilder(); + + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> builder.enableHeterogeneousNumericComparisons(false)); + + assertThat(e) + .hasMessageThat() + .contains("enableHeterogeneousNumericComparisons cannot be disabled"); + } } diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java index 61e6ead60..e2da9de05 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java @@ -445,20 +445,11 @@ private static void assertAllowedCelOptions(CelOptions celOptions) { throw new IllegalArgumentException( prefix + "unwrapWellKnownTypesOnFunctionDispatch cannot be disabled."); } - - // Disallowed options in favor of subsetting - String subsettingError = "Subset the environment instead using setStandardFunctions method."; - if (!celOptions.enableTimestampEpoch()) { - throw new IllegalArgumentException( - prefix + "enableTimestampEpoch cannot be disabled. " + subsettingError); - } - - if (!celOptions.enableHeterogeneousNumericComparisons()) { throw new IllegalArgumentException( prefix - + "enableHeterogeneousNumericComparisons cannot be disabled. " - + subsettingError); + + "enableTimestampEpoch cannot be disabled. Subset the environment instead using" + + " setStandardFunctions method."); } } diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java index a41ab9656..ea30dd942 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java @@ -409,7 +409,7 @@ private ImmutableSet newStandardFunctionBindings( } break; default: - if (!options.enableHeterogeneousNumericComparisons()) { + if (!options.enableUnsignedLongs()) { return !CelStandardFunctions.isHeterogeneousComparison( standardOverload); } diff --git a/runtime/src/test/java/dev/cel/runtime/CelStandardFunctionsTest.java b/runtime/src/test/java/dev/cel/runtime/CelStandardFunctionsTest.java index 43b878b71..a18d6d110 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelStandardFunctionsTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelStandardFunctionsTest.java @@ -187,17 +187,17 @@ public void standardEnvironment_subsetEnvironment() throws Exception { @TestParameters("{expression: '2u <= 1'}") @TestParameters("{expression: '2u <= 1.0'}") @TestParameters("{expression: '1.0 <= 2u'}") - public void heterogeneousEqualityDisabled_mixedTypeComparisons_throws(String expression) { + public void unsignedLongsDisabled_mixedTypeComparisons_throws(String expression) + throws Exception { CelCompiler celCompiler = CelCompilerFactory.standardCelCompilerBuilder().build(); CelRuntime celRuntime = CelRuntimeFactory.legacyCelRuntimeBuilder() - .setOptions(CelOptions.current().enableHeterogeneousNumericComparisons(false).build()) + .setOptions(CelOptions.current().enableUnsignedLongs(false).build()) .build(); + CelRuntime.Program program = celRuntime.createProgram(celCompiler.compile(expression).getAst()); + + CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); - CelEvaluationException e = - assertThrows( - CelEvaluationException.class, - () -> celRuntime.createProgram(celCompiler.compile(expression).getAst()).eval()); assertThat(e).hasMessageThat().contains("No matching overload for function"); }