Skip to content

Commit a2f7e4f

Browse files
jnthntatumcopybara-github
authored andcommitted
Add RegexPatternValidator.
PiperOrigin-RevId: 895515443
1 parent 15009ff commit a2f7e4f

8 files changed

Lines changed: 334 additions & 0 deletions

File tree

extensions/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,8 @@ cc_library(
774774
"//runtime:runtime_builder",
775775
"//runtime/internal:runtime_friend_access",
776776
"//runtime/internal:runtime_impl",
777+
"//validator",
778+
"//validator:regex_validator",
777779
"@com_google_absl//absl/base:no_destructor",
778780
"@com_google_absl//absl/base:nullability",
779781
"@com_google_absl//absl/functional:bind_front",
@@ -814,6 +816,7 @@ cc_test(
814816
"//runtime:reference_resolver",
815817
"//runtime:runtime_options",
816818
"//runtime:standard_runtime_builder_factory",
819+
"//validator",
817820
"@com_google_absl//absl/status",
818821
"@com_google_absl//absl/status:status_matchers",
819822
"@com_google_absl//absl/status:statusor",

extensions/regex_ext.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
#include "runtime/internal/runtime_friend_access.h"
4343
#include "runtime/internal/runtime_impl.h"
4444
#include "runtime/runtime_builder.h"
45+
#include "validator/regex_validator.h"
46+
#include "validator/validator.h"
4547
#include "google/protobuf/arena.h"
4648
#include "google/protobuf/descriptor.h"
4749
#include "google/protobuf/message.h"
@@ -341,4 +343,10 @@ CompilerLibrary RegexExtCompilerLibrary() {
341343
return CompilerLibrary::FromCheckerLibrary(RegexExtCheckerLibrary());
342344
}
343345

346+
Validation RegexExtValidator() {
347+
return RegexPatternValidator(
348+
/*id=*/"",
349+
{{"regex.extract", 1}, {"regex.extractAll", 1}, {"regex.replace", 1}});
350+
}
351+
344352
} // namespace cel::extensions

extensions/regex_ext.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
#include "eval/public/cel_function_registry.h"
8282
#include "eval/public/cel_options.h"
8383
#include "runtime/runtime_builder.h"
84+
#include "validator/validator.h"
8485

8586
namespace cel::extensions {
8687

@@ -119,5 +120,12 @@ CheckerLibrary RegexExtCheckerLibrary();
119120
// regex.extractAll(target: str, pattern: str) -> list<str>
120121
CompilerLibrary RegexExtCompilerLibrary();
121122

123+
// Returns a `Validation` that checks all calls to the CEL regex extension
124+
// functions.
125+
//
126+
// It validates that if the pattern is a literal string, it is a valid regular
127+
// expression.
128+
Validation RegexExtValidator();
129+
122130
} // namespace cel::extensions
123131
#endif // THIRD_PARTY_CEL_CPP_EXTENSIONS_REGEX_EXT_H_

extensions/regex_ext_test.cc

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#include "runtime/runtime.h"
4747
#include "runtime/runtime_options.h"
4848
#include "runtime/standard_runtime_builder_factory.h"
49+
#include "validator/validator.h"
4950
#include "google/protobuf/arena.h"
5051
#include "google/protobuf/extension_set.h"
5152

@@ -497,5 +498,44 @@ std::vector<RegexCheckerTestCase> createRegexCheckerParams() {
497498

498499
INSTANTIATE_TEST_SUITE_P(RegexExtCheckerLibraryTest, RegexExtCheckerLibraryTest,
499500
ValuesIn(createRegexCheckerParams()));
501+
502+
absl::StatusOr<std::unique_ptr<Compiler>> CreateRegexExtCompiler() {
503+
CEL_ASSIGN_OR_RETURN(
504+
auto builder, NewCompilerBuilder(internal::GetTestingDescriptorPool()));
505+
CEL_RETURN_IF_ERROR(builder->AddLibrary(StandardCheckerLibrary()));
506+
CEL_RETURN_IF_ERROR(builder->AddLibrary(RegexExtCompilerLibrary()));
507+
return std::move(*builder).Build();
508+
}
509+
510+
class RegexExtValidatorTest : public TestWithParam<RegexCheckerTestCase> {};
511+
512+
TEST_P(RegexExtValidatorTest, Basic) {
513+
ASSERT_OK_AND_ASSIGN(auto compiler, CreateRegexExtCompiler());
514+
515+
Validator validator;
516+
validator.AddValidation(RegexExtValidator());
517+
518+
ASSERT_OK_AND_ASSIGN(auto result, compiler->Compile(GetParam().expr_string));
519+
validator.UpdateValidationResult(result);
520+
521+
EXPECT_EQ(result.IsValid(), GetParam().error_substr.empty())
522+
<< "Expression: " << GetParam().expr_string;
523+
if (!GetParam().error_substr.empty()) {
524+
EXPECT_THAT(result.FormatError(), HasSubstr(GetParam().error_substr));
525+
}
526+
}
527+
528+
INSTANTIATE_TEST_SUITE_P(RegexExtValidatorTest, RegexExtValidatorTest,
529+
testing::ValuesIn(std::vector<RegexCheckerTestCase>{
530+
{"regex.extract('hello world', 'hello (.*)')"},
531+
{"regex.extract('hello world', 'hello ([') ",
532+
"invalid regular expression"},
533+
{"regex.extractAll('hello world', 'hello (.*)')"},
534+
{"regex.extractAll('hello world', 'hello ([') ",
535+
"invalid regular expression"},
536+
{"regex.replace('hello world', 'hello', 'hi')"},
537+
{"regex.replace('hello world', 'he([', 'hi') ",
538+
"invalid regular expression"},
539+
}));
500540
} // namespace
501541
} // namespace cel::extensions

validator/BUILD

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,24 @@ cc_library(
109109
],
110110
)
111111

112+
cc_library(
113+
name = "regex_validator",
114+
srcs = ["regex_validator.cc"],
115+
hdrs = ["regex_validator.h"],
116+
deps = [
117+
":validator",
118+
"//common:ast",
119+
"//common:constant",
120+
"//common:expr",
121+
"//common:navigable_ast",
122+
"//common:standard_definitions",
123+
"//internal:re2_options",
124+
"@com_google_absl//absl/log:absl_check",
125+
"@com_google_absl//absl/strings",
126+
"@com_googlesource_code_re2//:re2",
127+
],
128+
)
129+
112130
cc_test(
113131
name = "homogeneous_literal_validator_test",
114132
srcs = ["homogeneous_literal_validator_test.cc"],
@@ -147,4 +165,21 @@ cc_test(
147165
],
148166
)
149167

168+
cc_test(
169+
name = "regex_validator_test",
170+
srcs = ["regex_validator_test.cc"],
171+
deps = [
172+
":regex_validator",
173+
":validator",
174+
"//common:decl",
175+
"//common:type",
176+
"//compiler",
177+
"//compiler:compiler_factory",
178+
"//compiler:standard_library",
179+
"//internal:testing",
180+
"//internal:testing_descriptor_pool",
181+
"@com_google_absl//absl/status:statusor",
182+
],
183+
)
184+
150185
licenses(["notice"])

validator/regex_validator.cc

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "validator/regex_validator.h"
16+
17+
#include <utility>
18+
#include <vector>
19+
20+
#include "absl/log/absl_check.h"
21+
#include "absl/strings/str_cat.h"
22+
#include "absl/strings/string_view.h"
23+
#include "common/constant.h"
24+
#include "common/expr.h"
25+
#include "common/navigable_ast.h"
26+
#include "internal/re2_options.h"
27+
#include "validator/validator.h"
28+
#include "re2/re2.h"
29+
30+
namespace cel {
31+
32+
namespace {
33+
34+
bool CheckPattern(ValidationContext& context, const NavigableAstNode& node,
35+
int arg_index) {
36+
ABSL_DCHECK(node.expr()->has_call_expr());
37+
const auto& call_expr = node.expr()->call_expr();
38+
39+
const Expr* pattern_expr = nullptr;
40+
41+
if (call_expr.has_target()) {
42+
if (arg_index == 0) {
43+
pattern_expr = &call_expr.target();
44+
} else if (call_expr.args().size() > arg_index - 1) {
45+
pattern_expr = &call_expr.args()[arg_index - 1];
46+
}
47+
} else if (call_expr.args().size() > arg_index) {
48+
pattern_expr = &call_expr.args()[arg_index];
49+
}
50+
51+
if (pattern_expr == nullptr || !pattern_expr->has_const_expr()) {
52+
return true;
53+
}
54+
55+
const auto& const_expr = pattern_expr->const_expr();
56+
if (!const_expr.has_string_value()) {
57+
return true;
58+
}
59+
60+
absl::string_view pattern_string = const_expr.string_value();
61+
RE2 re(pattern_string, internal::MakeRE2Options());
62+
if (!re.ok()) {
63+
context.ReportErrorAt(
64+
pattern_expr->id(),
65+
absl::StrCat("invalid regular expression: ", re.error()));
66+
return false;
67+
}
68+
return true;
69+
}
70+
71+
} // namespace
72+
73+
Validation RegexPatternValidator(
74+
absl::string_view id, std::vector<RegexPatternValidatorConfig> config) {
75+
return Validation(
76+
[config = std::move(config)](ValidationContext& context) -> bool {
77+
bool result = true;
78+
for (const auto& node :
79+
context.navigable_ast().Root().DescendantsPostorder()) {
80+
if (node.node_kind() == NodeKind::kCall) {
81+
for (const auto& config : config) {
82+
if (node.expr()->call_expr().function() == config.function_name) {
83+
if (!CheckPattern(context, node, config.pattern_arg_index)) {
84+
result = false;
85+
}
86+
break;
87+
}
88+
}
89+
}
90+
}
91+
return result;
92+
},
93+
id);
94+
}
95+
96+
} // namespace cel

validator/regex_validator.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef THIRD_PARTY_CEL_CPP_VALIDATOR_REGEX_VALIDATOR_H_
16+
#define THIRD_PARTY_CEL_CPP_VALIDATOR_REGEX_VALIDATOR_H_
17+
18+
#include <string>
19+
#include <vector>
20+
21+
#include "absl/strings/string_view.h"
22+
#include "common/standard_definitions.h"
23+
#include "validator/validator.h"
24+
25+
namespace cel {
26+
27+
// Configuration for the regex pattern validator.
28+
struct RegexPatternValidatorConfig {
29+
// The resolved function name.
30+
std::string function_name;
31+
// the index of the pattern argument (counting the receiver as arg 0 if
32+
// present).
33+
int pattern_arg_index;
34+
};
35+
36+
// Returns a `Validation` that checks all calls to the given regex functions
37+
// It validates that the specified argument is a valid regular expression if it
38+
// is a literal string.
39+
Validation RegexPatternValidator(
40+
absl::string_view id, std::vector<RegexPatternValidatorConfig> config);
41+
42+
// Returns a `Validation` that checks all calls to the CEL `matches` function.
43+
// It validates that if the pattern is a literal string, it is a valid regular
44+
// expression.
45+
inline Validation MatchesValidator() {
46+
return RegexPatternValidator(
47+
"cel.validator.matches",
48+
{{std::string(StandardFunctions::kRegexMatch), 1}});
49+
}
50+
51+
} // namespace cel
52+
53+
#endif // THIRD_PARTY_CEL_CPP_VALIDATOR_REGEX_VALIDATOR_H_

0 commit comments

Comments
 (0)