Skip to content

Commit 6ee7f7e

Browse files
l46kokcopybara-github
authored andcommitted
Add CelOptions for designating Regex program size
Addresses #545 PiperOrigin-RevId: 715048086
1 parent a9678c4 commit 6ee7f7e

File tree

5 files changed

+67
-9
lines changed

5 files changed

+67
-9
lines changed

WORKSPACE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ maven_install(
7878
"com.google.guava:guava-testlib:33.3.1-jre",
7979
"com.google.protobuf:protobuf-java:4.28.3",
8080
"com.google.protobuf:protobuf-java-util:4.28.3",
81-
"com.google.re2j:re2j:1.7",
81+
"com.google.re2j:re2j:1.8",
8282
"com.google.testparameterinjector:test-parameter-injector:1.18",
8383
"com.google.truth.extensions:truth-java8-extension:1.4.4",
8484
"com.google.truth.extensions:truth-proto-extension:1.4.4",

bundle/src/test/java/dev/cel/bundle/CelImplTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2032,6 +2032,39 @@ public void program_comprehensionDisabled_throws() throws Exception {
20322032
assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.ITERATION_BUDGET_EXCEEDED);
20332033
}
20342034

2035+
@Test
2036+
public void program_regexProgramSizeUnderLimit_success() throws Exception {
2037+
Cel cel =
2038+
standardCelBuilderWithMacros()
2039+
.setOptions(CelOptions.current().maxRegexProgramSize(7).build())
2040+
.build();
2041+
// See
2042+
// https://github.com/google/re2j/blob/84237cbbd0fbd637c6eb6856717c1e248daae729/javatests/com/google/re2j/PatternTest.java#L175 for program size
2043+
CelAbstractSyntaxTree ast = cel.compile("'foo'.matches('(a+b)')").getAst();
2044+
2045+
assertThat(cel.createProgram(ast).eval()).isEqualTo(false);
2046+
}
2047+
2048+
@Test
2049+
public void program_regexProgramSizeExceedsLimit_throws() throws Exception {
2050+
Cel cel =
2051+
standardCelBuilderWithMacros()
2052+
.setOptions(CelOptions.current().maxRegexProgramSize(6).build())
2053+
.build();
2054+
// See
2055+
// https://github.com/google/re2j/blob/84237cbbd0fbd637c6eb6856717c1e248daae729/javatests/com/google/re2j/PatternTest.java#L175 for program size
2056+
CelAbstractSyntaxTree ast = cel.compile("'foo'.matches('(a+b)')").getAst();
2057+
2058+
CelEvaluationException e =
2059+
assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval());
2060+
assertThat(e)
2061+
.hasMessageThat()
2062+
.contains(
2063+
"evaluation error: Regex pattern exceeds allowed program size. Allowed: 6, Provided:"
2064+
+ " 7");
2065+
assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.INVALID_ARGUMENT);
2066+
}
2067+
20352068
@Test
20362069
public void toBuilder_isImmutable() {
20372070
CelBuilder celBuilder = CelFactory.standardCelBuilder();

common/src/main/java/dev/cel/common/CelOptions.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public enum ProtoUnsetFieldOptions {
3939
// Do not bind a field if it is unset. Repeated fields are bound as empty list.
4040
SKIP,
4141
// Bind the (proto api) default value for a field.
42-
BIND_DEFAULT;
42+
BIND_DEFAULT
4343
}
4444

4545
public static final CelOptions DEFAULT = current().build();
@@ -121,6 +121,8 @@ public enum ProtoUnsetFieldOptions {
121121

122122
public abstract boolean enableComprehension();
123123

124+
public abstract int maxRegexProgramSize();
125+
124126
public abstract Builder toBuilder();
125127

126128
public ImmutableSet<ExprFeatures> toExprFeatures() {
@@ -218,7 +220,8 @@ public static Builder newBuilder() {
218220
.enableStringConversion(true)
219221
.enableStringConcatenation(true)
220222
.enableListConcatenation(true)
221-
.enableComprehension(true);
223+
.enableComprehension(true)
224+
.maxRegexProgramSize(-1);
222225
}
223226

224227
/**
@@ -571,6 +574,19 @@ public abstract static class Builder {
571574
*/
572575
public abstract Builder enableComprehension(boolean value);
573576

577+
/**
578+
* Set maximum program size for RE2J regex.
579+
*
580+
* <p>The program size is a very approximate measure of a regexp's "cost". Larger numbers are
581+
* more expensive than smaller numbers.
582+
*
583+
* <p>A negative {@code value} will disable the check.
584+
*
585+
* <p>There's no guarantee that RE2 program size has the exact same value across other CEL
586+
* implementations (C++ and Go).
587+
*/
588+
public abstract Builder maxRegexProgramSize(int value);
589+
574590
public abstract CelOptions build();
575591
}
576592
}

runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import com.google.protobuf.Timestamp;
2929
import com.google.protobuf.util.Durations;
3030
import com.google.protobuf.util.Timestamps;
31-
import com.google.re2j.PatternSyntaxException;
3231
import dev.cel.common.CelErrorCode;
3332
import dev.cel.common.CelOptions;
3433
import dev.cel.common.internal.ComparisonFunctions;
@@ -1000,7 +999,7 @@ public enum StringMatchers implements StandardOverload {
1000999
(String string, String regexp) -> {
10011000
try {
10021001
return RuntimeHelpers.matches(string, regexp, bindingHelper.celOptions);
1003-
} catch (PatternSyntaxException e) {
1002+
} catch (RuntimeException e) {
10041003
throw new CelEvaluationException(
10051004
e.getMessage(), e, CelErrorCode.INVALID_ARGUMENT);
10061005
}
@@ -1015,7 +1014,7 @@ public enum StringMatchers implements StandardOverload {
10151014
(String string, String regexp) -> {
10161015
try {
10171016
return RuntimeHelpers.matches(string, regexp, bindingHelper.celOptions);
1018-
} catch (PatternSyntaxException e) {
1017+
} catch (RuntimeException e) {
10191018
throw new CelEvaluationException(
10201019
e.getMessage(), e, CelErrorCode.INVALID_ARGUMENT);
10211020
}

runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,22 @@ public static boolean matches(String string, String regexp) {
7474
}
7575

7676
public static boolean matches(String string, String regexp, CelOptions celOptions) {
77+
Pattern pattern = Pattern.compile(regexp);
78+
int maxProgramSize = celOptions.maxRegexProgramSize();
79+
if (maxProgramSize >= 0 && pattern.programSize() > maxProgramSize) {
80+
throw new IllegalArgumentException(
81+
String.format(
82+
"Regex pattern exceeds allowed program size. Allowed: %d, Provided: %d",
83+
maxProgramSize, pattern.programSize()));
84+
}
85+
7786
if (!celOptions.enableRegexPartialMatch()) {
7887
// Uses re2 for consistency across languages.
79-
return Pattern.matches(regexp, string);
88+
return pattern.matcher(string).matches();
8089
}
81-
// Return an unanchored match for the presence of the regexp anywher in the string.
82-
return Pattern.compile(regexp).matcher(string).find();
90+
91+
// Return an unanchored match for the presence of the regexp anywhere in the string.
92+
return pattern.matcher(string).find();
8393
}
8494

8595
/** Create a compiled pattern for the given regular expression. */

0 commit comments

Comments
 (0)