Coverage Report

Created: 2026-10-09 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/proc/self/cwd/eval/compiler/regex_precompilation_optimization.cc
Line
Count
Source
1
// Copyright 2023 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 "eval/compiler/regex_precompilation_optimization.h"
16
17
#include <cstddef>
18
#include <cstdint>
19
#include <memory>
20
#include <optional>
21
#include <string>
22
#include <utility>
23
#include <vector>
24
25
#include "absl/base/nullability.h"
26
#include "absl/container/flat_hash_map.h"
27
#include "absl/status/status.h"
28
#include "absl/status/statusor.h"
29
#include "absl/strings/string_view.h"
30
#include "base/builtins.h"
31
#include "common/ast.h"
32
#include "common/expr.h"
33
#include "common/native_type.h"
34
#include "common/value.h"
35
#include "eval/compiler/flat_expr_builder_extensions.h"
36
#include "eval/eval/evaluator_core.h"
37
#include "eval/eval/regex_match_step.h"
38
#include "internal/re2_options.h"
39
#include "internal/status_macros.h"
40
#include "re2/re2.h"
41
42
namespace google::api::expr::runtime {
43
namespace {
44
45
using ::cel::Ast;
46
using ::cel::CallExpr;
47
using ::cel::Expr;
48
using ::cel::NativeTypeId;
49
using ::cel::Reference;
50
using ::cel::Value;
51
52
using ReferenceMap = absl::flat_hash_map<int64_t, Reference>;
53
54
bool IsFunctionOverload(const Expr& expr, absl::string_view function,
55
                        absl::string_view overload, size_t arity,
56
0
                        const ReferenceMap& reference_map) {
57
0
  if (!expr.has_call_expr()) {
58
0
    return false;
59
0
  }
60
0
  const auto& call_expr = expr.call_expr();
61
0
  if (call_expr.function() != function) {
62
0
    return false;
63
0
  }
64
0
  if (call_expr.args().size() + (call_expr.has_target() ? 1 : 0) != arity) {
65
0
    return false;
66
0
  }
67
68
  // If parse-only and opted in to the optimization, assume this is the intended
69
  // overload. This will still only change the evaluation plan if the second arg
70
  // is a constant string.
71
0
  if (reference_map.empty()) {
72
0
    return true;
73
0
  }
74
75
0
  auto reference = reference_map.find(expr.id());
76
0
  if (reference != reference_map.end() &&
77
0
      reference->second.overload_id().size() == 1 &&
78
0
      reference->second.overload_id().front() == overload) {
79
0
    return true;
80
0
  }
81
0
  return false;
82
0
}
83
84
// Abstraction for deduplicating regular expressions over the course of a single
85
// create expression call. Should not be used during evaluation. Uses
86
// std::shared_ptr and std::weak_ptr.
87
class RegexProgramBuilder final {
88
 public:
89
  explicit RegexProgramBuilder(int max_program_size)
90
0
      : max_program_size_(max_program_size) {}
91
92
  absl::StatusOr<std::shared_ptr<const RE2>> BuildRegexProgram(
93
0
      std::string pattern) {
94
0
    auto existing = programs_.find(pattern);
95
0
    if (existing != programs_.end()) {
96
0
      if (auto program = existing->second.lock(); program) {
97
0
        return program;
98
0
      }
99
0
      programs_.erase(existing);
100
0
    }
101
0
    auto program =
102
0
        std::make_shared<RE2>(pattern, cel::internal::MakeRE2Options());
103
0
    CEL_RETURN_IF_ERROR(cel::internal::CheckRE2(*program, max_program_size_));
104
0
    programs_.insert({std::move(pattern), program});
105
0
    return program;
106
0
  }
107
108
 private:
109
  const int max_program_size_;
110
  absl::flat_hash_map<std::string, std::weak_ptr<const RE2>> programs_;
111
};
112
113
class RegexPrecompilationOptimization : public ProgramOptimizer {
114
 public:
115
  explicit RegexPrecompilationOptimization(const ReferenceMap& reference_map,
116
                                           int regex_max_program_size)
117
0
      : reference_map_(reference_map),
118
0
        regex_program_builder_(regex_max_program_size) {}
119
120
0
  absl::Status OnPreVisit(PlannerContext& context, const Expr& node) override {
121
0
    return absl::OkStatus();
122
0
  }
123
124
0
  absl::Status OnPostVisit(PlannerContext& context, const Expr& node) override {
125
    // Check that this is the correct matches overload instead of a user defined
126
    // overload.
127
0
    if (!IsFunctionOverload(node, cel::builtin::kRegexMatch, "matches_string",
128
0
                            2, reference_map_)) {
129
0
      return absl::OkStatus();
130
0
    }
131
132
0
    ProgramBuilder::Subexpression* subexpression =
133
0
        context.program_builder().GetSubexpression(&node);
134
135
0
    const CallExpr& call_expr = node.call_expr();
136
0
    const Expr& pattern_expr = call_expr.args().back();
137
138
    // Try to check if the regex is valid, whether or not we can actually update
139
    // the plan.
140
0
    std::optional<std::string> pattern =
141
0
        GetConstantString(context, subexpression, node, pattern_expr);
142
0
    if (!pattern.has_value()) {
143
0
      return absl::OkStatus();
144
0
    }
145
146
0
    CEL_ASSIGN_OR_RETURN(
147
0
        std::shared_ptr<const RE2> regex_program,
148
0
        regex_program_builder_.BuildRegexProgram(std::move(pattern).value()));
149
150
0
    if (subexpression == nullptr || subexpression->IsFlattened()) {
151
      // Already modified, can't update further.
152
0
      return absl::OkStatus();
153
0
    }
154
155
0
    const Expr& subject_expr =
156
0
        call_expr.has_target() ? call_expr.target() : call_expr.args().front();
157
158
0
    return RewritePlan(context, subexpression, node, subject_expr,
159
0
                       std::move(regex_program));
160
0
  }
161
162
 private:
163
  std::optional<std::string> GetConstantString(
164
      PlannerContext& context,
165
      ProgramBuilder::Subexpression* absl_nullable subexpression,
166
0
      const Expr& call_expr, const Expr& re_expr) const {
167
0
    if (re_expr.has_const_expr() && re_expr.const_expr().has_string_value()) {
168
0
      return re_expr.const_expr().string_value();
169
0
    }
170
171
0
    if (subexpression == nullptr || subexpression->IsFlattened()) {
172
      // Already modified, can't recover the input pattern.
173
0
      return std::nullopt;
174
0
    }
175
0
    std::optional<Value> constant;
176
      // otherwise stack-machine program.
177
0
      ExecutionPathView re_plan = context.GetSubplan(re_expr);
178
0
      if (re_plan.size() == 1) {
179
0
        cel::Value val;
180
0
        if (GetIfConstant(re_plan[0], val)) {
181
0
          constant = std::move(val);
182
0
        }
183
0
      }
184
185
0
    if (constant.has_value() && constant->IsString()) {
186
0
      return constant->GetString().ToString();
187
0
    }
188
189
0
    return std::nullopt;
190
0
  }
191
192
  absl::Status RewritePlan(
193
      PlannerContext& context,
194
      ProgramBuilder::Subexpression* absl_nonnull subexpression,
195
      const Expr& call, const Expr& subject,
196
0
      std::shared_ptr<const RE2> regex_program) {
197
0
    return RewriteStackMachinePlan(context, call, subject,
198
0
                                   std::move(regex_program));
199
0
  }
200
201
  absl::Status RewriteStackMachinePlan(
202
      PlannerContext& context, const Expr& call, const Expr& subject,
203
0
      std::shared_ptr<const RE2> regex_program) {
204
0
    if (context.GetSubplan(subject).empty()) {
205
      // This subexpression was already optimized, nothing to do.
206
0
      return absl::OkStatus();
207
0
    }
208
209
0
    CEL_ASSIGN_OR_RETURN(ExecutionPath new_plan,
210
0
                         context.ExtractSubplan(subject));
211
0
    CEL_ASSIGN_OR_RETURN(auto step,
212
0
                         CreateRegexMatchStep(std::move(regex_program)));
213
0
    new_plan.push_back(
214
0
        ExpressionStep::MakeGenericStep(std::move(step), call.id()));
215
216
0
    return context.ReplaceSubplan(call, std::move(new_plan));
217
0
  }
218
219
  const ReferenceMap& reference_map_;
220
  RegexProgramBuilder regex_program_builder_;
221
};
222
223
}  // namespace
224
225
ProgramOptimizerFactory CreateRegexPrecompilationExtension(
226
0
    int regex_max_program_size) {
227
0
  return [=](PlannerContext& context, const Ast& ast) {
228
0
    return std::make_unique<RegexPrecompilationOptimization>(
229
0
        ast.reference_map(), regex_max_program_size);
230
0
  };
231
0
}
232
}  // namespace google::api::expr::runtime