Skip to content

Commit 4d49aaa

Browse files
l46kokcopybara-github
authored andcommitted
Precompile constant regex patterns in ProgramPlanner
Fixes #1038 Note: This feature is only available in the newer planner runtime. PiperOrigin-RevId: 998039280
1 parent 5e3b233 commit 4d49aaa

9 files changed

Lines changed: 308 additions & 44 deletions

File tree

‎runtime/src/main/java/dev/cel/runtime/BUILD.bazel‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ cel_android_library(
427427
"//common/annotations",
428428
"//common/exceptions:divide_by_zero",
429429
"//common/exceptions:index_out_of_bounds",
430+
"//common/exceptions:invalid_argument",
430431
"//common/exceptions:numeric_overflow",
431432
"//common/internal:converter",
432433
"//common/values:values_android",
@@ -451,6 +452,7 @@ java_library(
451452
"//common/annotations",
452453
"//common/exceptions:divide_by_zero",
453454
"//common/exceptions:index_out_of_bounds",
455+
"//common/exceptions:invalid_argument",
454456
"//common/exceptions:numeric_overflow",
455457
"//common/internal:converter",
456458
"//common/values",

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@
2323
import com.google.protobuf.Duration;
2424
import com.google.protobuf.MessageLiteOrBuilder;
2525
import com.google.re2j.Pattern;
26+
import com.google.re2j.PatternSyntaxException;
2627
import dev.cel.common.CelOptions;
2728
import dev.cel.common.annotations.Internal;
2829
import dev.cel.common.exceptions.CelDivideByZeroException;
2930
import dev.cel.common.exceptions.CelIndexOutOfBoundsException;
31+
import dev.cel.common.exceptions.CelInvalidArgumentException;
3032
import dev.cel.common.exceptions.CelNumericOverflowException;
3133
import dev.cel.common.internal.Converter;
3234
import dev.cel.common.values.NullValue;
@@ -76,16 +78,24 @@ public static java.time.Duration createJavaDurationFromString(String d) {
7678
}
7779
}
7880

79-
public static boolean matches(String string, String regexp, CelOptions celOptions) {
80-
Pattern pattern = Pattern.compile(regexp);
81+
public static Pattern compileRegexPattern(String regexp, CelOptions celOptions) {
82+
Pattern pattern;
83+
try {
84+
pattern = Pattern.compile(regexp);
85+
} catch (PatternSyntaxException e) {
86+
throw new CelInvalidArgumentException(e);
87+
}
8188
int maxProgramSize = celOptions.maxRegexProgramSize();
8289
if (maxProgramSize >= 0 && pattern.programSize() > maxProgramSize) {
83-
throw new IllegalArgumentException(
90+
throw new CelInvalidArgumentException(
8491
String.format(
8592
"Regex pattern exceeds allowed program size. Allowed: %d, Provided: %d",
8693
maxProgramSize, pattern.programSize()));
8794
}
95+
return pattern;
96+
}
8897

98+
public static boolean matches(String string, Pattern pattern, CelOptions celOptions) {
8999
if (!celOptions.enableRegexPartialMatch()) {
90100
// Uses re2 for consistency across languages.
91101
return pattern.matcher(string).matches();
@@ -95,6 +105,10 @@ public static boolean matches(String string, String regexp, CelOptions celOption
95105
return pattern.matcher(string).find();
96106
}
97107

108+
public static boolean matches(String string, String regexp, CelOptions celOptions) {
109+
return matches(string, compileRegexPattern(regexp, celOptions), celOptions);
110+
}
111+
98112
/** Concatenates two lists into a new list. */
99113
public static <E> List<E> concat(List<E> first, List<E> second) {
100114
if (first instanceof ConcatenatedListView) {

‎runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ java_library(
5353
"//common/ast",
5454
"//common/ast:cel_block",
5555
"//common/exceptions:overload_not_found",
56+
"//common/exceptions:runtime_exception",
5657
"//common/types",
5758
"//common/types:type_providers",
5859
"//common/values",
@@ -470,8 +471,13 @@ java_library(
470471
deps = [
471472
":attribute",
472473
":eval_attribute",
474+
":eval_const",
475+
":eval_unary",
473476
":planned_interpretable",
474477
"//common/ast",
478+
"//common/values",
479+
"//runtime:resolved_overload",
480+
"//runtime/standard:matches",
475481
],
476482
)
477483

@@ -718,6 +724,7 @@ cel_android_library(
718724
"//common/ast:ast_android",
719725
"//common/ast:cel_block_android",
720726
"//common/exceptions:overload_not_found",
727+
"//common/exceptions:runtime_exception",
721728
"//common/types:type_providers_android",
722729
"//common/types:types_android",
723730
"//common/values:cel_value_provider_android",
@@ -1137,8 +1144,13 @@ cel_android_library(
11371144
deps = [
11381145
":attribute_android",
11391146
":eval_attribute_android",
1147+
":eval_const_android",
1148+
":eval_unary_android",
11401149
":planned_interpretable_android",
11411150
"//common/ast:ast_android",
1151+
"//common/values:values_android",
1152+
"//runtime:resolved_overload_android",
1153+
"//runtime/standard:matches_android",
11421154
],
11431155
)
11441156

‎runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ Object evalInternal(GlobalResolver resolver, ExecutionFrame frame) {
2929
return constant;
3030
}
3131

32+
Object constantValue() {
33+
return constant;
34+
}
35+
3236
static EvalConstant create(CelExpr expr, Object value) {
3337
return new EvalConstant(expr, value);
3438
}

‎runtime/src/main/java/dev/cel/runtime/planner/PlannerHelpers.java‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
package dev.cel.runtime.planner;
1616

1717
import dev.cel.common.ast.CelConstant;
18+
import dev.cel.common.ast.CelExpr;
19+
import dev.cel.common.values.CelValueConverter;
20+
import dev.cel.runtime.CelResolvedOverload;
21+
import dev.cel.runtime.standard.MatchesFunction;
22+
import java.util.Optional;
1823

1924
final class PlannerHelpers {
2025

@@ -47,5 +52,30 @@ static Object resolveConstant(CelConstant celConstant) {
4752
}
4853
}
4954

55+
static Optional<PlannedInterpretable> maybePlanRegexMatches(
56+
CelExpr expr,
57+
CelResolvedOverload resolvedOverload,
58+
PlannedInterpretable[] evaluatedArgs,
59+
CelValueConverter celValueConverter) {
60+
PlannedInterpretable regexArg = evaluatedArgs[1];
61+
if (!MatchesFunction.isMatchesOverload(resolvedOverload)
62+
|| !(regexArg instanceof EvalConstant)) {
63+
return Optional.empty();
64+
}
65+
Object rawConstant = ((EvalConstant) regexArg).constantValue();
66+
if (!(rawConstant instanceof String)) {
67+
return Optional.empty();
68+
}
69+
CelResolvedOverload unaryOverload =
70+
MatchesFunction.newPrecompiledOverload(resolvedOverload, (String) rawConstant);
71+
return Optional.of(
72+
EvalUnary.create(
73+
expr,
74+
unaryOverload.getFunctionName(),
75+
unaryOverload,
76+
evaluatedArgs[0],
77+
celValueConverter));
78+
}
79+
5080
private PlannerHelpers() {}
5181
}

‎runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import dev.cel.common.ast.CelExpr.ExprKind.Kind;
4444
import dev.cel.common.ast.CelReference;
4545
import dev.cel.common.exceptions.CelOverloadNotFoundException;
46+
import dev.cel.common.exceptions.CelRuntimeException;
4647
import dev.cel.common.types.CelKind;
4748
import dev.cel.common.types.CelType;
4849
import dev.cel.common.types.CelTypeProvider;
@@ -107,10 +108,15 @@ public PlannedProgram plan(CelAbstractSyntaxTree ast) throws CelEvaluationExcept
107108
.map(celBlock -> planBlock(celBlock, ctx))
108109
.orElseGet(() -> plan(ast.getExpr(), ctx));
109110
} catch (RuntimeException e) {
110-
throw CelEvaluationExceptionBuilder.newBuilder(e.getMessage())
111-
.setMetadata(errorMetadata, ast.getExpr().id())
112-
.setCause(e)
113-
.build();
111+
// Preserve specific CelErrorCode (e.g., INVALID_ARGUMENT, OVERLOAD_NOT_FOUND) from
112+
// plan-time validation failures.
113+
CelEvaluationExceptionBuilder builder;
114+
if (e instanceof CelRuntimeException) {
115+
builder = CelEvaluationExceptionBuilder.newBuilder((CelRuntimeException) e);
116+
} else {
117+
builder = CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(e);
118+
}
119+
throw builder.setMetadata(errorMetadata, ast.getExpr().id()).build();
114120
}
115121

116122
return PlannedProgram.create(
@@ -355,6 +361,13 @@ private PlannedInterpretable planCall(CelExpr expr, PlannerContext ctx) {
355361
evaluatedArgs[1],
356362
celValueConverter);
357363
}
364+
PlannedInterpretable regexMatches =
365+
PlannerHelpers.maybePlanRegexMatches(
366+
expr, resolvedOverload, evaluatedArgs, celValueConverter)
367+
.orElse(null);
368+
if (regexMatches != null) {
369+
return regexMatches;
370+
}
358371
return EvalBinary.create(
359372
expr,
360373
functionName,

‎runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,12 +1045,16 @@ java_library(
10451045
deps = [
10461046
":standard_overload",
10471047
"//common:options",
1048-
"//common/exceptions:invalid_argument",
1048+
"//common/annotations",
10491049
"//runtime:function_binding",
1050+
"//runtime:function_overload",
1051+
"//runtime:resolved_overload",
10501052
"//runtime:runtime_equality",
10511053
"//runtime:runtime_helpers",
10521054
"//runtime/standard:standard_function",
1055+
"@maven//:com_google_errorprone_error_prone_annotations",
10531056
"@maven//:com_google_guava_guava",
1057+
"@maven//:com_google_re2j_re2j",
10541058
],
10551059
)
10561060

@@ -1063,10 +1067,14 @@ cel_android_library(
10631067
":standard_function_android",
10641068
":standard_overload_android",
10651069
"//common:options",
1066-
"//common/exceptions:invalid_argument",
1070+
"//common/annotations",
10671071
"//runtime:function_binding_android",
1072+
"//runtime:function_overload_android",
1073+
"//runtime:resolved_overload_android",
10681074
"//runtime:runtime_equality_android",
10691075
"//runtime:runtime_helpers_android",
1076+
"@maven//:com_google_errorprone_error_prone_annotations",
1077+
"@maven//:com_google_re2j_re2j",
10701078
"@maven_android//:com_google_guava_guava",
10711079
],
10721080
)

‎runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java‎

Lines changed: 55 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,22 @@
1414

1515
package dev.cel.runtime.standard;
1616

17+
import com.google.common.collect.ImmutableList;
1718
import com.google.common.collect.ImmutableSet;
19+
import com.google.errorprone.annotations.Immutable;
20+
import com.google.re2j.Pattern;
1821
import dev.cel.common.CelOptions;
19-
import dev.cel.common.exceptions.CelInvalidArgumentException;
22+
import dev.cel.common.annotations.Internal;
2023
import dev.cel.runtime.CelFunctionBinding;
24+
import dev.cel.runtime.CelFunctionOverload;
25+
import dev.cel.runtime.CelResolvedOverload;
2126
import dev.cel.runtime.RuntimeEquality;
2227
import dev.cel.runtime.RuntimeHelpers;
2328
import java.util.Arrays;
2429

2530
/** Standard function for {@code matches}. */
2631
public final class MatchesFunction extends CelStandardFunction {
32+
private static final String MATCHES_FUNCTION = "matches";
2733
private static final MatchesFunction ALL_OVERLOADS = create(MatchesOverload.values());
2834

2935
public static MatchesFunction create() {
@@ -38,51 +44,68 @@ public static MatchesFunction create(Iterable<MatchesFunction.MatchesOverload> o
3844
return new MatchesFunction(ImmutableSet.copyOf(overloads));
3945
}
4046

47+
@Internal
48+
public static boolean isMatchesOverload(CelResolvedOverload resolvedOverload) {
49+
return resolvedOverload.getDefinition() instanceof StandardMatchesOverload;
50+
}
51+
52+
@Internal
53+
public static CelResolvedOverload newPrecompiledOverload(
54+
CelResolvedOverload resolvedOverload, String regexPattern) {
55+
CelOptions celOptions = ((StandardMatchesOverload) resolvedOverload.getDefinition()).celOptions;
56+
Pattern compiledPattern = RuntimeHelpers.compileRegexPattern(regexPattern, celOptions);
57+
String overloadId = resolvedOverload.getOverloadId();
58+
CelFunctionBinding binding =
59+
CelFunctionBinding.from(
60+
overloadId,
61+
String.class,
62+
target -> RuntimeHelpers.matches(target, compiledPattern, celOptions));
63+
return CelResolvedOverload.of(
64+
MATCHES_FUNCTION,
65+
overloadId,
66+
binding.getDefinition(),
67+
binding.isStrict(),
68+
binding.getArgTypes());
69+
}
70+
4171
/** Overloads for the standard function. */
4272
public enum MatchesOverload implements CelStandardOverload {
43-
MATCHES(
44-
(celOptions, runtimeEquality) ->
45-
CelFunctionBinding.from(
46-
"matches",
47-
String.class,
48-
String.class,
49-
(String string, String regexp) -> {
50-
try {
51-
return RuntimeHelpers.matches(string, regexp, celOptions);
52-
} catch (RuntimeException e) {
53-
throw new CelInvalidArgumentException(e);
54-
}
55-
})),
73+
MATCHES(MATCHES_FUNCTION),
5674
// Duplicate receiver-style matches overload.
57-
MATCHES_STRING(
58-
(celOptions, runtimeEquality) ->
59-
CelFunctionBinding.from(
60-
"matches_string",
61-
String.class,
62-
String.class,
63-
(String string, String regexp) -> {
64-
try {
65-
return RuntimeHelpers.matches(string, regexp, celOptions);
66-
} catch (RuntimeException e) {
67-
throw new CelInvalidArgumentException(e);
68-
}
69-
})),
75+
MATCHES_STRING("matches_string"),
7076
;
7177

72-
private final CelStandardOverload standardOverload;
78+
private final String overloadId;
7379

7480
@Override
7581
public CelFunctionBinding newFunctionBinding(
7682
CelOptions celOptions, RuntimeEquality runtimeEquality) {
77-
return standardOverload.newFunctionBinding(celOptions, runtimeEquality);
83+
return CelFunctionBinding.from(
84+
overloadId,
85+
ImmutableList.of(String.class, String.class),
86+
new StandardMatchesOverload(celOptions));
87+
}
88+
89+
MatchesOverload(String overloadId) {
90+
this.overloadId = overloadId;
91+
}
92+
}
93+
94+
@Immutable
95+
private static final class StandardMatchesOverload implements CelFunctionOverload {
96+
private final CelOptions celOptions;
97+
98+
@Override
99+
public Object apply(Object[] args) {
100+
return RuntimeHelpers.matches((String) args[0], (String) args[1], celOptions);
78101
}
79102

80-
MatchesOverload(CelStandardOverload standardOverload) {
81-
this.standardOverload = standardOverload;
103+
private StandardMatchesOverload(CelOptions celOptions) {
104+
this.celOptions = celOptions;
82105
}
83106
}
84107

85108
private MatchesFunction(ImmutableSet<CelStandardOverload> overloads) {
86-
super("matches", overloads);
109+
super(MATCHES_FUNCTION, overloads);
87110
}
88111
}

0 commit comments

Comments
 (0)