Coverage Report

Created: 2026-10-09 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/proc/self/cwd/extensions/select_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 "extensions/select_optimization.h"
16
17
#include <cstddef>
18
#include <cstdint>
19
#include <iterator>
20
#include <memory>
21
#include <optional>
22
#include <string>
23
#include <utility>
24
#include <variant>
25
#include <vector>
26
27
#include "absl/algorithm/container.h"
28
#include "absl/base/nullability.h"
29
#include "absl/container/flat_hash_map.h"
30
#include "absl/functional/overload.h"
31
#include "absl/log/absl_check.h"
32
#include "absl/status/status.h"
33
#include "absl/status/statusor.h"
34
#include "absl/strings/match.h"
35
#include "absl/strings/string_view.h"
36
#include "absl/types/span.h"
37
#include "absl/types/variant.h"
38
#include "base/attribute.h"
39
#include "base/builtins.h"
40
#include "common/ast.h"
41
#include "common/ast_rewrite.h"
42
#include "common/constant.h"
43
#include "common/expr.h"
44
#include "common/function_descriptor.h"
45
#include "common/kind.h"
46
#include "common/legacy_value.h"
47
#include "common/memory.h"
48
#include "common/native_type.h"
49
#include "common/type.h"
50
#include "common/value.h"
51
#include "eval/compiler/flat_expr_builder.h"
52
#include "eval/compiler/flat_expr_builder_extensions.h"
53
#include "eval/eval/attribute_trail.h"
54
#include "eval/eval/evaluator_core.h"
55
#include "eval/eval/expression_step_base.h"
56
#include "eval/public/cel_value.h"
57
#include "eval/public/structs/proto_message_type_adapter.h"
58
#include "internal/casts.h"
59
#include "internal/number.h"
60
#include "internal/status_macros.h"
61
#include "runtime/internal/errors.h"
62
#include "runtime/internal/runtime_friend_access.h"
63
#include "runtime/internal/runtime_impl.h"
64
#include "runtime/runtime_builder.h"
65
#include "runtime/runtime_options.h"
66
#include "google/protobuf/arena.h"
67
#include "google/protobuf/descriptor.h"
68
#include "google/protobuf/message.h"
69
70
namespace cel::extensions {
71
namespace {
72
73
using ::cel::Ast;
74
using ::cel::AstRewriterBase;
75
using ::cel::CallExpr;
76
using ::cel::ConstantKind;
77
using ::cel::Expr;
78
using ::cel::ExprKind;
79
using ::cel::SelectExpr;
80
using ::google::api::expr::runtime::AttributeTrail;
81
using ::google::api::expr::runtime::CelValue;
82
using ::google::api::expr::runtime::ExecutionFrame;
83
using ::google::api::expr::runtime::ExecutionFrameBase;
84
using ::google::api::expr::runtime::ExpressionStep;
85
using ::google::api::expr::runtime::ExpressionStepBase;
86
using ::google::api::expr::runtime::GetGenericProtoTypeInfoInstance;
87
using ::google::api::expr::runtime::PlannerContext;
88
using ::google::api::expr::runtime::ProgramOptimizer;
89
using ::google::api::expr::runtime::internal::GetGenericProtoAccessApisInstance;
90
91
// Represents a single select operation (field access or indexing).
92
// For struct-typed field accesses, includes the field name and the field
93
// number.
94
struct SelectInstruction {
95
  int64_t number;
96
  std::string name;
97
};
98
99
// Represents a single qualifier in a traversal path.
100
// TODO(uncreated-issue/51): support variable indexes.
101
using QualifierInstruction =
102
    std::variant<SelectInstruction, std::string, int64_t, uint64_t, bool>;
103
104
struct SelectPath {
105
  Expr* operand;
106
  std::vector<QualifierInstruction> select_instructions;
107
  bool test_only;
108
  // TODO(uncreated-issue/54): support for optionals.
109
};
110
111
// Generates the AST representation of the qualification path for the optimized
112
// select branch. I.e., the list-typed second argument of the cel.@attribute
113
// call.
114
Expr MakeSelectPathExpr(
115
0
    const std::vector<QualifierInstruction>& select_instructions) {
116
0
  Expr result;
117
0
  auto& ast_list = result.mutable_list_expr().mutable_elements();
118
0
  ast_list.reserve(select_instructions.size());
119
0
  auto visitor = absl::Overload(
120
0
      [&](const SelectInstruction& instruction) {
121
0
        Expr ast_instruction;
122
0
        Expr field_number;
123
0
        field_number.mutable_const_expr().set_int64_value(instruction.number);
124
0
        Expr field_name;
125
0
        field_name.mutable_const_expr().set_string_value(instruction.name);
126
0
        auto& field_specifier =
127
0
            ast_instruction.mutable_list_expr().mutable_elements();
128
0
        field_specifier.emplace_back().set_expr(std::move(field_number));
129
0
        field_specifier.emplace_back().set_expr(std::move(field_name));
130
131
0
        ast_list.emplace_back().set_expr(std::move(ast_instruction));
132
0
      },
133
0
      [&](absl::string_view instruction) {
134
0
        Expr const_expr;
135
0
        const_expr.mutable_const_expr().set_string_value(instruction);
136
0
        ast_list.emplace_back().set_expr(std::move(const_expr));
137
0
      },
138
0
      [&](int64_t instruction) {
139
0
        Expr const_expr;
140
0
        const_expr.mutable_const_expr().set_int64_value(instruction);
141
0
        ast_list.emplace_back().set_expr(std::move(const_expr));
142
0
      },
143
0
      [&](uint64_t instruction) {
144
0
        Expr const_expr;
145
0
        const_expr.mutable_const_expr().set_uint64_value(instruction);
146
0
        ast_list.emplace_back().set_expr(std::move(const_expr));
147
0
      },
148
0
      [&](bool instruction) {
149
0
        Expr const_expr;
150
0
        const_expr.mutable_const_expr().set_bool_value(instruction);
151
0
        ast_list.emplace_back().set_expr(std::move(const_expr));
152
0
      });
153
154
0
  for (const auto& instruction : select_instructions) {
155
0
    absl::visit(visitor, instruction);
156
0
  }
157
0
  return result;
158
0
}
159
160
// Returns a single select operation based on the inferred type of the operand
161
// and the field name. If the operand type doesn't define the field, returns
162
// nullopt.
163
std::optional<SelectInstruction> GetSelectInstruction(
164
    const StructType& runtime_type, PlannerContext& planner_context,
165
0
    absl::string_view field_name) {
166
0
  auto field_or = planner_context.type_reflector()
167
0
                      .FindStructTypeFieldByName(runtime_type, field_name)
168
0
                      .value_or(std::nullopt);
169
0
  if (field_or.has_value()) {
170
0
    return SelectInstruction{field_or->number(), std::string(field_or->name())};
171
0
  }
172
0
  return std::nullopt;
173
0
}
174
175
0
absl::StatusOr<SelectQualifier> SelectQualifierFromList(const ListExpr& list) {
176
0
  if (list.elements().size() != 2) {
177
0
    return absl::InvalidArgumentError("Invalid cel.attribute select list");
178
0
  }
179
180
0
  const Expr& field_number = list.elements()[0].expr();
181
0
  const Expr& field_name = list.elements()[1].expr();
182
183
0
  if (!field_number.has_const_expr() ||
184
0
      !field_number.const_expr().has_int64_value()) {
185
0
    return absl::InvalidArgumentError(
186
0
        "Invalid cel.attribute field select number");
187
0
  }
188
189
0
  if (!field_name.has_const_expr() ||
190
0
      !field_name.const_expr().has_string_value()) {
191
0
    return absl::InvalidArgumentError(
192
0
        "Invalid cel.attribute field select name");
193
0
  }
194
195
0
  return FieldSpecifier{field_number.const_expr().int64_value(),
196
0
                        field_name.const_expr().string_value()};
197
0
}
198
199
// Returns a qualifier instruction derived from a unoptimized ast.
200
absl::StatusOr<QualifierInstruction> SelectInstructionFromConstant(
201
0
    const Constant& constant) {
202
0
  if (constant.has_int_value()) {
203
0
    return QualifierInstruction(constant.int_value());
204
0
  } else if (constant.has_uint_value()) {
205
0
    return QualifierInstruction(constant.uint_value());
206
0
  } else if (constant.has_bool_value()) {
207
0
    return QualifierInstruction(constant.bool_value());
208
0
  } else if (constant.has_string_value()) {
209
0
    return QualifierInstruction(constant.string_value());
210
0
  } else if (constant.has_double_value()) {
211
0
    cel::internal::Number number(constant.double_value());
212
0
    if (number.LosslessConvertibleToInt()) {
213
0
      return QualifierInstruction(number.AsInt());
214
0
    } else if (number.LosslessConvertibleToUint()) {
215
0
      return QualifierInstruction(number.AsUint());
216
0
    }
217
0
  }
218
219
0
  return absl::InvalidArgumentError("invalid index constant for cel.attribute");
220
0
}
221
222
absl::StatusOr<SelectQualifier> SelectQualifierFromConstant(
223
0
    const Constant& constant) {
224
0
  if (constant.has_int_value()) {
225
0
    return AttributeQualifier::OfInt(constant.int_value());
226
0
  } else if (constant.has_uint_value()) {
227
0
    return AttributeQualifier::OfUint(constant.uint_value());
228
0
  } else if (constant.has_bool_value()) {
229
0
    return AttributeQualifier::OfBool(constant.bool_value());
230
0
  } else if (constant.has_string_value()) {
231
0
    return AttributeQualifier::OfString(constant.string_value());
232
0
  }
233
  // TODO(uncreated-issue/51): double keys could possibly be valid selectors, but
234
  // the other stacks don't implement the optimization yet and we normalize the
235
  // key to a uint or int if we do the late AST rewrite during planning.
236
237
0
  return absl::InvalidArgumentError("invalid cel.attribute constant");
238
0
}
239
240
0
absl::StatusOr<size_t> ListIndexFromQualifier(const AttributeQualifier& qual) {
241
0
  int64_t value = -1;
242
0
  switch (qual.kind()) {
243
0
    case Kind::kInt:
244
0
      value = *qual.GetInt64Key();
245
0
      break;
246
0
    default:
247
      // TODO(uncreated-issue/51): type-checker will reject an unsigned literal, but
248
      // should be supported as a dyn / variable.
249
0
      return runtime_internal::CreateNoMatchingOverloadError(
250
0
          cel::builtin::kIndex);
251
0
  }
252
253
0
  if (value < 0) {
254
0
    return absl::InvalidArgumentError("list index less than 0");
255
0
  }
256
257
0
  return static_cast<size_t>(value);
258
0
}
259
260
absl::StatusOr<Value> MapKeyFromQualifier(const AttributeQualifier& qual,
261
0
                                          google::protobuf::Arena* absl_nonnull arena) {
262
0
  switch (qual.kind()) {
263
0
    case Kind::kInt:
264
0
      return cel::IntValue(*qual.GetInt64Key());
265
0
    case Kind::kUint:
266
0
      return cel::UintValue(*qual.GetUint64Key());
267
0
    case Kind::kBool:
268
0
      return cel::BoolValue(*qual.GetBoolKey());
269
0
    case Kind::kString:
270
      // Safe as `qual` is valid for the lifetime of the expression/program.
271
0
      return StringValue::WrapUnsafe(*qual.GetStringKey());
272
0
    default:
273
0
      return runtime_internal::CreateNoMatchingOverloadError(
274
0
          cel::builtin::kIndex);
275
0
  }
276
0
}
277
278
// // Helper for StructValue::GetFieldByName. Used for opting out of old
279
// reflection implementation.
280
absl::StatusOr<Value> WrappedStructGet(
281
    const Value& target, absl::string_view field,
282
    const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool,
283
    google::protobuf::MessageFactory* absl_nonnull message_factory,
284
    google::protobuf::Arena* absl_nonnull arena,
285
0
    bool enable_use_new_field_select_implementation) {
286
0
  if (!enable_use_new_field_select_implementation) {
287
0
    if (const google::protobuf::Message* message =
288
0
            cel::interop_internal::GetLegacyMessage(target);
289
0
        message != nullptr) {
290
0
      CelValue::MessageWrapper message_wrapper(
291
0
          message, &GetGenericProtoTypeInfoInstance());
292
0
      CEL_ASSIGN_OR_RETURN(CelValue cel_value,
293
0
                           GetGenericProtoAccessApisInstance().GetField(
294
0
                               field, message_wrapper,
295
0
                               ProtoWrapperTypeOptions::kUnsetProtoDefault,
296
0
                               MemoryManagerRef::Pooling(arena)));
297
0
      Value result;
298
0
      CEL_RETURN_IF_ERROR(cel::ModernValue(arena, cel_value, result));
299
0
      return result;
300
0
    }
301
0
  }
302
0
  return target.GetStruct().GetFieldByName(field, descriptor_pool,
303
0
                                           message_factory, arena);
304
0
}
305
306
// Helper for StructValue::Qualify. Used for opting out of old reflection
307
// implementation.
308
absl::StatusOr<std::pair<Value, int>> WrappedStructQualify(
309
    const StructValue& struct_value,
310
    absl::Span<const SelectQualifier> qualifiers, bool presence_test,
311
    const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool,
312
    google::protobuf::MessageFactory* absl_nonnull message_factory,
313
    google::protobuf::Arena* absl_nonnull arena,
314
0
    bool enable_use_new_field_select_implementation) {
315
0
  if (!enable_use_new_field_select_implementation) {
316
0
    if (const google::protobuf::Message* message =
317
0
            cel::interop_internal::GetLegacyMessage(struct_value);
318
0
        message != nullptr) {
319
0
      CelValue::MessageWrapper message_wrapper(
320
0
          message, &GetGenericProtoTypeInfoInstance());
321
0
      CEL_ASSIGN_OR_RETURN(auto legacy_result,
322
0
                           GetGenericProtoAccessApisInstance().Qualify(
323
0
                               qualifiers, message_wrapper, presence_test,
324
0
                               MemoryManagerRef::Pooling(arena)));
325
0
      Value result;
326
0
      CEL_RETURN_IF_ERROR(cel::ModernValue(arena, legacy_result.value, result));
327
0
      return std::pair<Value, int>{std::move(result),
328
0
                                   legacy_result.qualifier_count};
329
0
    }
330
0
  }
331
0
  return struct_value.Qualify(qualifiers, presence_test, descriptor_pool,
332
0
                              message_factory, arena);
333
0
}
334
335
absl::StatusOr<Value> ApplyQualifier(
336
    const Value& operand, const SelectQualifier& qualifier,
337
    const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool,
338
    google::protobuf::MessageFactory* absl_nonnull message_factory,
339
    google::protobuf::Arena* absl_nonnull arena,
340
0
    bool enable_use_new_field_select_implementation) {
341
0
  return absl::visit(
342
0
      absl::Overload(
343
0
          [&](const FieldSpecifier& field_specifier) -> absl::StatusOr<Value> {
344
0
            if (!operand.Is<StructValue>()) {
345
0
              return cel::ErrorValue::From(
346
0
                  cel::runtime_internal::CreateNoMatchingOverloadError(
347
0
                      "<select>"),
348
0
                  arena);
349
0
            }
350
0
            return WrappedStructGet(operand, field_specifier.name,
351
0
                                    descriptor_pool, message_factory, arena,
352
0
                                    enable_use_new_field_select_implementation);
353
0
          },
354
0
          [&](const AttributeQualifier& qualifier) -> absl::StatusOr<Value> {
355
0
            if (operand.Is<ListValue>()) {
356
0
              auto index_or = ListIndexFromQualifier(qualifier);
357
0
              if (!index_or.ok()) {
358
0
                return cel::ErrorValue::From(index_or.status(), arena);
359
0
              }
360
0
              return operand.GetList().Get(*index_or, descriptor_pool,
361
0
                                           message_factory, arena);
362
0
            } else if (operand.Is<MapValue>()) {
363
0
              auto key_or = MapKeyFromQualifier(qualifier, arena);
364
0
              if (!key_or.ok()) {
365
0
                return cel::ErrorValue::From(key_or.status(), arena);
366
0
              }
367
0
              return operand.GetMap().Get(*key_or, descriptor_pool,
368
0
                                          message_factory, arena);
369
0
            }
370
0
            return cel::ErrorValue::From(
371
0
                cel::runtime_internal::CreateNoMatchingOverloadError(
372
0
                    cel::builtin::kIndex),
373
0
                arena);
374
0
          }),
375
0
      qualifier);
376
0
}
377
378
absl::StatusOr<Value> FallbackSelect(
379
    const Value& root, absl::Span<const SelectQualifier> select_path,
380
    bool presence_test,
381
    const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool,
382
    google::protobuf::MessageFactory* absl_nonnull message_factory,
383
    google::protobuf::Arena* absl_nonnull arena,
384
0
    bool enable_use_new_field_select_implementation) {
385
0
  const Value* elem = &root;
386
0
  Value result;
387
388
0
  for (const auto& instruction :
389
0
       select_path.subspan(0, select_path.size() - 1)) {
390
0
    CEL_ASSIGN_OR_RETURN(
391
0
        result,
392
0
        ApplyQualifier(*elem, instruction, descriptor_pool, message_factory,
393
0
                       arena, enable_use_new_field_select_implementation));
394
0
    if (result->Is<ErrorValue>()) {
395
0
      return result;
396
0
    }
397
0
    elem = &result;
398
0
  }
399
400
0
  const auto& last_instruction = select_path.back();
401
0
  if (presence_test) {
402
0
    return absl::visit(
403
0
        absl::Overload(
404
0
            [&](const FieldSpecifier& field_specifier)
405
0
                -> absl::StatusOr<Value> {
406
0
              if (!elem->Is<StructValue>()) {
407
0
                return cel::ErrorValue::From(
408
0
                    cel::runtime_internal::CreateNoMatchingOverloadError(
409
0
                        "<select>"),
410
0
                    arena);
411
0
              }
412
0
              CEL_ASSIGN_OR_RETURN(
413
0
                  bool present,
414
0
                  elem->GetStruct().HasFieldByName(field_specifier.name));
415
0
              return cel::BoolValue(present);
416
0
            },
417
0
            [&](const AttributeQualifier& qualifier) -> absl::StatusOr<Value> {
418
0
              if (!elem->Is<MapValue>() || qualifier.kind() != Kind::kString) {
419
0
                return cel::ErrorValue::From(
420
0
                    cel::runtime_internal::CreateNoMatchingOverloadError("has"),
421
0
                    arena);
422
0
              }
423
424
0
              return elem->GetMap().Has(
425
0
                  StringValue::WrapUnsafe(*qualifier.GetStringKey()),
426
0
                  descriptor_pool, message_factory, arena);
427
0
            }),
428
0
        last_instruction);
429
0
  }
430
431
0
  return ApplyQualifier(*elem, last_instruction, descriptor_pool,
432
0
                        message_factory, arena,
433
0
                        enable_use_new_field_select_implementation);
434
0
}
435
436
absl::StatusOr<std::vector<SelectQualifier>> SelectInstructionsFromCall(
437
0
    const CallExpr& call) {
438
0
  if (call.args().size() < 2 || !call.args()[1].has_list_expr()) {
439
0
    return absl::InvalidArgumentError("Invalid cel.attribute call");
440
0
  }
441
0
  std::vector<SelectQualifier> instructions;
442
0
  const auto& ast_path = call.args()[1].list_expr().elements();
443
0
  instructions.reserve(ast_path.size());
444
445
0
  for (const ListExprElement& element : ast_path) {
446
    // Optimized field select.
447
0
    if (element.has_expr()) {
448
0
      const auto& element_expr = element.expr();
449
0
      if (element_expr.has_list_expr()) {
450
0
        CEL_ASSIGN_OR_RETURN(instructions.emplace_back(),
451
0
                             SelectQualifierFromList(element_expr.list_expr()));
452
0
      } else if (element_expr.has_const_expr()) {
453
0
        CEL_ASSIGN_OR_RETURN(
454
0
            instructions.emplace_back(),
455
0
            SelectQualifierFromConstant(element_expr.const_expr()));
456
0
      } else {
457
0
        return absl::InvalidArgumentError("Invalid cel.attribute call");
458
0
      }
459
0
    } else {
460
0
      return absl::InvalidArgumentError("Invalid cel.attribute call");
461
0
    }
462
0
  }
463
464
  // TODO(uncreated-issue/54): support for optionals.
465
466
0
  return instructions;
467
0
}
468
469
class RewriterImpl : public AstRewriterBase {
470
 public:
471
  RewriterImpl(const Ast& ast, PlannerContext& planner_context)
472
0
      : ast_(ast), planner_context_(planner_context) {}
473
474
0
  void PreVisitExpr(const Expr& expr) override { path_.push_back(&expr); }
475
476
0
  void PreVisitSelect(const Expr& expr, const SelectExpr& select) override {
477
0
    const Expr& operand = select.operand();
478
0
    const std::string& field_name = select.field();
479
    // Select optimization can generalize to lists and maps, but for now only
480
    // support message traversal.
481
0
    const TypeSpec checker_type = ast_.GetTypeOrDyn(operand.id());
482
483
0
    std::optional<Type> rt_type =
484
0
        (checker_type.has_message_type())
485
0
            ? GetRuntimeType(checker_type.message_type().type())
486
0
            : std::nullopt;
487
0
    if (rt_type.has_value() && (*rt_type).Is<StructType>()) {
488
0
      const StructType& runtime_type = rt_type->GetStruct();
489
0
      std::optional<SelectInstruction> field_or =
490
0
          GetSelectInstruction(runtime_type, planner_context_, field_name);
491
0
      if (field_or.has_value()) {
492
0
        candidates_[&expr] = std::move(field_or).value();
493
0
      }
494
0
    } else if (checker_type.has_map_type()) {
495
0
      candidates_[&expr] = QualifierInstruction(field_name);
496
0
    }
497
    // else
498
    // TODO(uncreated-issue/54): add support for either dyn or any. Excluded to
499
    // simplify program plan.
500
0
  }
501
502
0
  void PreVisitCall(const Expr& expr, const CallExpr& call) override {
503
0
    if (call.args().size() != 2 || call.function() != ::cel::builtin::kIndex) {
504
0
      return;
505
0
    }
506
507
0
    const auto& qualifier_expr = call.args()[1];
508
0
    if (qualifier_expr.has_const_expr()) {
509
0
      auto qualifier_or =
510
0
          SelectInstructionFromConstant(qualifier_expr.const_expr());
511
0
      if (!qualifier_or.ok()) {
512
        // TODO(uncreated-issue/54): should warn, but by default warnings fail overall
513
        // program planning.
514
0
        return;
515
0
      }
516
0
      candidates_[&expr] = std::move(qualifier_or).value();
517
0
    }
518
    // TODO(uncreated-issue/54): support variable indexes
519
0
  }
520
521
0
  bool PostVisitRewrite(Expr& expr) override {
522
0
    if (!progress_status_.ok()) {
523
0
      return false;
524
0
    }
525
0
    path_.pop_back();
526
0
    auto candidate_iter = candidates_.find(&expr);
527
0
    if (candidate_iter == candidates_.end()) {
528
0
      return false;
529
0
    }
530
531
    // On post visit, filter candidates that aren't rooted on a message or a
532
    // select chain.
533
0
    const QualifierInstruction& candidate = candidate_iter->second;
534
0
    if (!HasOptimizeableRoot(&expr, candidate)) {
535
0
      candidates_.erase(candidate_iter);
536
0
      return false;
537
0
    }
538
539
0
    if (!path_.empty() && candidates_.find(path_.back()) != candidates_.end()) {
540
      // parent is optimizeable, defer rewriting until we consider the parent.
541
0
      return false;
542
0
    }
543
544
0
    SelectPath path = GetSelectPath(&expr);
545
546
    // generate the new cel.attribute call.
547
0
    absl::string_view fn = path.test_only ? kCelHasField : kCelAttribute;
548
549
0
    Expr operand(std::move(*path.operand));
550
0
    Expr call;
551
0
    call.set_id(expr.id());
552
0
    call.mutable_call_expr().set_function(std::string(fn));
553
0
    call.mutable_call_expr().mutable_args().reserve(2);
554
555
0
    call.mutable_call_expr().mutable_args().push_back(std::move(operand));
556
0
    call.mutable_call_expr().mutable_args().push_back(
557
0
        MakeSelectPathExpr(path.select_instructions));
558
559
    // TODO(uncreated-issue/54): support for optionals.
560
0
    expr = std::move(call);
561
562
0
    return true;
563
0
  }
564
565
0
  absl::Status GetProgressStatus() const { return progress_status_; }
566
567
 private:
568
0
  SelectPath GetSelectPath(Expr* expr) {
569
0
    SelectPath result;
570
0
    result.test_only = false;
571
0
    Expr* operand = expr;
572
0
    auto candidate_iter = candidates_.find(operand);
573
0
    while (candidate_iter != candidates_.end()) {
574
0
      result.select_instructions.push_back(candidate_iter->second);
575
0
      if (operand->has_select_expr()) {
576
0
        if (operand->select_expr().test_only()) {
577
0
          result.test_only = true;
578
0
        }
579
0
        operand = &(operand->mutable_select_expr().mutable_operand());
580
0
      } else {
581
0
        ABSL_DCHECK(operand->has_call_expr());
582
0
        operand = &(operand->mutable_call_expr().mutable_args()[0]);
583
0
      }
584
0
      candidate_iter = candidates_.find(operand);
585
0
    }
586
0
    absl::c_reverse(result.select_instructions);
587
0
    result.operand = operand;
588
0
    return result;
589
0
  }
590
591
  // Check whether the candidate has a message type as a root (the operand for
592
  // the batched select operation).
593
  // Called on post visit.
594
  bool HasOptimizeableRoot(const Expr* expr,
595
0
                           const QualifierInstruction& candidate) {
596
0
    if (absl::holds_alternative<SelectInstruction>(candidate)) {
597
0
      return true;
598
0
    }
599
0
    const Expr* operand = nullptr;
600
0
    if (expr->has_call_expr() && expr->call_expr().args().size() == 2 &&
601
0
        expr->call_expr().function() == ::cel::builtin::kIndex) {
602
0
      operand = &expr->call_expr().args()[0];
603
0
    } else if (expr->has_select_expr()) {
604
0
      operand = &expr->select_expr().operand();
605
0
    }
606
607
0
    if (operand == nullptr) {
608
0
      return false;
609
0
    }
610
611
0
    return candidates_.find(operand) != candidates_.end();
612
0
  }
613
614
0
  std::optional<Type> GetRuntimeType(absl::string_view type_name) {
615
0
    return planner_context_.type_reflector().FindType(type_name).value_or(
616
0
        std::nullopt);
617
0
  }
618
619
0
  void SetProgressStatus(const absl::Status& status) {
620
0
    if (progress_status_.ok() && !status.ok()) {
621
0
      progress_status_ = status;
622
0
    }
623
0
  }
624
625
  const Ast& ast_;
626
  PlannerContext& planner_context_;
627
  // ids of potentially optimizeable expr nodes.
628
  absl::flat_hash_map<const Expr*, QualifierInstruction> candidates_;
629
  std::vector<const Expr*> path_;
630
  absl::Status progress_status_;
631
};
632
633
class OptimizedSelectImpl {
634
 public:
635
  OptimizedSelectImpl(std::vector<SelectQualifier> select_path,
636
                      std::vector<AttributeQualifier> qualifiers,
637
                      bool presence_test, SelectOptimizationOptions options)
638
0
      : select_path_(std::move(select_path)),
639
0
        qualifiers_(std::move(qualifiers)),
640
0
        presence_test_(presence_test),
641
0
        options_(options)
642
643
0
  {
644
0
    ABSL_DCHECK(!select_path_.empty());
645
0
  }
646
647
  // Move constructible.
648
  OptimizedSelectImpl(const OptimizedSelectImpl&) = delete;
649
  OptimizedSelectImpl& operator=(const OptimizedSelectImpl&) = delete;
650
0
  OptimizedSelectImpl(OptimizedSelectImpl&&) = default;
651
  OptimizedSelectImpl& operator=(OptimizedSelectImpl&&) = delete;
652
653
  absl::StatusOr<Value> ApplySelect(ExecutionFrameBase& frame,
654
                                    const StructValue& struct_value) const;
655
656
  AttributeTrail GetAttributeTrail(const AttributeTrail& operand_trail) const;
657
658
0
  std::optional<Attribute> attribute() const { return attribute_; }
659
660
0
  const std::vector<AttributeQualifier>& qualifiers() const {
661
0
    return qualifiers_;
662
0
  }
663
664
 private:
665
  std::optional<Attribute> attribute_;
666
  std::vector<SelectQualifier> select_path_;
667
  std::vector<AttributeQualifier> qualifiers_;
668
  bool presence_test_;
669
  SelectOptimizationOptions options_;
670
};
671
672
// Check for unknowns or missing attributes.
673
absl::StatusOr<std::optional<Value>> CheckForMarkedAttributes(
674
0
    ExecutionFrameBase& frame, const AttributeTrail& attribute_trail) {
675
0
  if (attribute_trail.empty()) {
676
0
    return std::nullopt;
677
0
  }
678
679
0
  if (frame.unknown_processing_enabled() &&
680
0
      frame.attribute_utility().CheckForUnknownExact(attribute_trail)) {
681
    // Check if the inferred attribute is marked. Only matches if this attribute
682
    // or a parent is marked unknown (use_partial = false).
683
    // Partial matches (i.e. descendant of this attribute is marked) aren't
684
    // considered yet in case another operation would select an unmarked
685
    // descended attribute.
686
    //
687
    // TODO(uncreated-issue/51): this may return a more specific attribute than the
688
    // declared pattern. Follow up will truncate the returned attribute to match
689
    // the pattern.
690
0
    return frame.attribute_utility().CreateUnknownSet(
691
0
        attribute_trail.attribute());
692
0
  }
693
694
0
  if (frame.missing_attribute_errors_enabled() &&
695
0
      frame.attribute_utility().CheckForMissingAttribute(attribute_trail)) {
696
0
    return frame.attribute_utility().CreateMissingAttributeError(
697
0
        attribute_trail.attribute(), frame.arena());
698
0
  }
699
700
0
  return std::nullopt;
701
0
}
702
703
absl::StatusOr<Value> OptimizedSelectImpl::ApplySelect(
704
0
    ExecutionFrameBase& frame, const StructValue& struct_value) const {
705
0
  auto value_or =
706
0
      (options_.force_fallback_implementation)
707
0
          ? absl::UnimplementedError("Forced fallback impl")
708
0
          : WrappedStructQualify(
709
0
                struct_value, select_path_, presence_test_,
710
0
                frame.descriptor_pool(), frame.message_factory(), frame.arena(),
711
0
                frame.options().enable_use_new_field_select_implementation);
712
713
0
  if (!value_or.ok()) {
714
0
    if (value_or.status().code() == absl::StatusCode::kUnimplemented) {
715
0
      return FallbackSelect(
716
0
          struct_value, select_path_, presence_test_, frame.descriptor_pool(),
717
0
          frame.message_factory(), frame.arena(),
718
0
          frame.options().enable_use_new_field_select_implementation);
719
0
    }
720
721
0
    return value_or.status();
722
0
  }
723
724
0
  if (value_or->second < 0 || value_or->second >= select_path_.size()) {
725
0
    return std::move(value_or->first);
726
0
  }
727
728
0
  return FallbackSelect(
729
0
      value_or->first,
730
0
      absl::MakeConstSpan(select_path_).subspan(value_or->second),
731
0
      presence_test_, frame.descriptor_pool(), frame.message_factory(),
732
0
      frame.arena(),
733
0
      frame.options().enable_use_new_field_select_implementation);
734
0
}
735
736
AttributeTrail OptimizedSelectImpl::GetAttributeTrail(
737
0
    const AttributeTrail& operand_trail) const {
738
0
  if (operand_trail.empty()) {
739
0
    return AttributeTrail();
740
0
  }
741
0
  std::vector<AttributeQualifier> qualifiers = std::vector<AttributeQualifier>(
742
0
      operand_trail.attribute().qualifier_path().begin(),
743
0
      operand_trail.attribute().qualifier_path().end());
744
0
  qualifiers.reserve(qualifiers_.size() + qualifiers.size());
745
0
  absl::c_copy(qualifiers_, std::back_inserter(qualifiers));
746
0
  return AttributeTrail(
747
0
      Attribute(std::string(operand_trail.attribute().variable_name()),
748
0
                std::move(qualifiers)));
749
0
}
750
751
class StackMachineImpl : public ExpressionStepBase {
752
 public:
753
  StackMachineImpl(int expr_id, OptimizedSelectImpl impl)
754
0
      : ExpressionStepBase(expr_id), impl_(std::move(impl)) {}
755
756
  void Evaluate(ExecutionFrame* frame) const override;
757
758
 private:
759
  // Get the effective attribute for the optimized select expression.
760
  // Assumes the operand is the top of stack if the attribute wasn't known at
761
  // plan time.
762
  AttributeTrail GetAttributeTrail(ExecutionFrame* frame) const;
763
764
  OptimizedSelectImpl impl_;
765
};
766
767
AttributeTrail StackMachineImpl::GetAttributeTrail(
768
0
    ExecutionFrame* frame) const {
769
0
  const auto& attr = frame->value_stack().PeekAttribute();
770
0
  return impl_.GetAttributeTrail(attr);
771
0
}
772
773
0
void StackMachineImpl::Evaluate(ExecutionFrame* frame) const {
774
  // Default empty.
775
0
  AttributeTrail attribute_trail;
776
  // TODO(uncreated-issue/51): add support for variable qualifiers and string literal
777
  // variable names.
778
0
  constexpr size_t kStackInputs = 1;
779
780
  // For now, we expect the operand to be top of stack.
781
0
  const Value& operand = frame->value_stack().Peek();
782
783
0
  if (operand->Is<ErrorValue>() || operand->Is<UnknownValue>()) {
784
    // Just forward the error which is already top of stack.
785
0
    return;
786
0
  }
787
788
0
  if (frame->enable_attribute_tracking()) {
789
    // Compute the attribute trail then check for any marked values.
790
    // When possible, this is computed at plan time based on the optimized
791
    // select arguments.
792
    // TODO(uncreated-issue/51): add support variable qualifiers
793
0
    attribute_trail = GetAttributeTrail(frame);
794
0
    absl::StatusOr<std::optional<Value>> value =
795
0
        CheckForMarkedAttributes(*frame, attribute_trail);
796
0
    if (!value.ok()) {
797
0
      frame->Abort(std::move(value).status());
798
0
      return;
799
0
    }
800
0
    if (value->has_value()) {
801
0
      frame->value_stack().Pop(kStackInputs);
802
0
      frame->value_stack().Push(std::move(*value).value(),
803
0
                                std::move(attribute_trail));
804
0
      return;
805
0
    }
806
0
  }
807
808
0
  if (!operand->Is<StructValue>()) {
809
0
    frame->Abort(absl::InvalidArgumentError(
810
0
        "Expected struct type for select optimization."));
811
0
    return;
812
0
  }
813
814
0
  absl::StatusOr<Value> result = impl_.ApplySelect(*frame, operand.GetStruct());
815
0
  if (!result.ok()) {
816
0
    frame->Abort(std::move(result).status());
817
0
    return;
818
0
  }
819
820
0
  frame->value_stack().Pop(kStackInputs);
821
0
  frame->value_stack().Push(*std::move(result), std::move(attribute_trail));
822
0
}
823
824
class SelectOptimizer : public ProgramOptimizer {
825
 public:
826
  explicit SelectOptimizer(const SelectOptimizationOptions& options)
827
0
      : options_(options) {}
828
829
0
  absl::Status OnPreVisit(PlannerContext& context, const Expr& node) override {
830
0
    return absl::OkStatus();
831
0
  }
832
833
  absl::Status OnPostVisit(PlannerContext& context, const Expr& node) override;
834
835
 private:
836
  SelectOptimizationOptions options_;
837
};
838
839
absl::Status SelectOptimizer::OnPostVisit(PlannerContext& context,
840
0
                                          const Expr& node) {
841
0
  if (!node.has_call_expr()) {
842
0
    return absl::OkStatus();
843
0
  }
844
845
0
  absl::string_view fn = node.call_expr().function();
846
0
  if (fn != kCelHasField && fn != kCelAttribute) {
847
0
    return absl::OkStatus();
848
0
  }
849
850
0
  if (node.call_expr().args().size() < 2 ||
851
0
      node.call_expr().args().size() > 3) {
852
0
    return absl::InvalidArgumentError("Invalid cel.attribute call");
853
0
  }
854
855
0
  if (node.call_expr().args().size() == 3) {
856
0
    return absl::UnimplementedError("Optionals not yet supported");
857
0
  }
858
859
0
  CEL_ASSIGN_OR_RETURN(std::vector<SelectQualifier> instructions,
860
0
                       SelectInstructionsFromCall(node.call_expr()));
861
862
0
  if (instructions.empty()) {
863
0
    return absl::InvalidArgumentError("Invalid cel.attribute no select steps.");
864
0
  }
865
866
0
  bool presence_test = false;
867
868
0
  if (fn == kCelHasField) {
869
0
    presence_test = true;
870
0
  }
871
872
0
  const Expr& operand = node.call_expr().args()[0];
873
0
  absl::string_view identifier;
874
0
  if (operand.has_ident_expr()) {
875
0
    identifier = operand.ident_expr().name();
876
0
  }
877
878
0
  if (absl::StrContains(identifier, ".")) {
879
0
    return absl::UnimplementedError("qualified identifiers not supported.");
880
0
  }
881
882
0
  std::vector<AttributeQualifier> qualifiers;
883
0
  qualifiers.reserve(instructions.size());
884
0
  for (const auto& instruction : instructions) {
885
0
    qualifiers.push_back(
886
0
        absl::visit(absl::Overload(
887
0
                        [](const FieldSpecifier& field) {
888
0
                          return AttributeQualifier::OfString(field.name);
889
0
                        },
890
0
                        [](const AttributeQualifier& q) { return q; }),
891
0
                    instruction));
892
0
  }
893
894
  // TODO(uncreated-issue/51): If the first argument is a string literal, the custom
895
  // step needs to handle variable lookup.
896
0
  auto* subexpression = context.program_builder().GetSubexpression(&node);
897
0
  if (subexpression == nullptr || subexpression->IsFlattened()) {
898
    // No information on the subprogram, can't optimize.
899
0
    return absl::OkStatus();
900
0
  }
901
902
0
  OptimizedSelectImpl impl(std::move(instructions), std::move(qualifiers),
903
0
                           presence_test, options_);
904
905
0
  google::api::expr::runtime::ExecutionPath path;
906
907
  // else, we need to preserve the original plan for the first argument.
908
0
  if (context.GetSubplan(operand).empty()) {
909
    // Indicates another extension modified the step. Nothing to do here.
910
0
    return absl::OkStatus();
911
0
  }
912
0
  CEL_ASSIGN_OR_RETURN(auto operand_subplan, context.ExtractSubplan(operand));
913
0
  absl::c_move(operand_subplan, std::back_inserter(path));
914
915
0
  path.push_back(ExpressionStep::MakeGenericStep(
916
0
      std::make_unique<StackMachineImpl>(node.id(), std::move(impl)),
917
0
      node.id()));
918
919
0
  return context.ReplaceSubplan(node, std::move(path));
920
0
}
921
922
google::api::expr::runtime::FlatExprBuilder* GetFlatExprBuilder(
923
0
    RuntimeBuilder& builder) {
924
0
  auto& runtime =
925
0
      runtime_internal::RuntimeFriendAccess::GetMutableRuntime(builder);
926
0
  if (runtime_internal::RuntimeFriendAccess::RuntimeTypeId(runtime) ==
927
0
      NativeTypeId::For<runtime_internal::RuntimeImpl>()) {
928
0
    auto& runtime_impl =
929
0
        cel::internal::down_cast<runtime_internal::RuntimeImpl&>(runtime);
930
0
    return &runtime_impl.expr_builder();
931
0
  }
932
0
  return nullptr;
933
0
}
934
935
}  // namespace
936
937
absl::Status SelectOptimizationAstUpdater::UpdateAst(PlannerContext& context,
938
0
                                                     Ast& ast) const {
939
0
  RewriterImpl rewriter(ast, context);
940
0
  AstRewrite(ast.mutable_root_expr(), rewriter);
941
0
  return rewriter.GetProgressStatus();
942
0
}
943
944
google::api::expr::runtime::ProgramOptimizerFactory
945
CreateSelectOptimizationProgramOptimizer(
946
0
    const SelectOptimizationOptions& options) {
947
0
  return [=](PlannerContext& context, const Ast& ast) {
948
0
    return std::make_unique<SelectOptimizer>(options);
949
0
  };
950
0
}
951
952
absl::Status EnableSelectOptimization(
953
0
    cel::RuntimeBuilder& builder, const SelectOptimizationOptions& options) {
954
0
  auto* flat_expr_builder = GetFlatExprBuilder(builder);
955
0
  if (flat_expr_builder == nullptr) {
956
0
    return absl::InvalidArgumentError(
957
0
        "SelectOptimization requires default runtime implementation");
958
0
  }
959
960
0
  flat_expr_builder->AddAstTransform(
961
0
      std::make_unique<SelectOptimizationAstUpdater>());
962
  // Add overloads for select optimization signature.
963
  // These are never bound, only used to prevent the builder from failing on
964
  // the overloads check.
965
0
  CEL_RETURN_IF_ERROR(builder.function_registry().RegisterLazyFunction(
966
0
      FunctionDescriptor(kCelAttribute, false, {Kind::kAny, Kind::kList})));
967
968
0
  CEL_RETURN_IF_ERROR(builder.function_registry().RegisterLazyFunction(
969
0
      FunctionDescriptor(kCelHasField, false, {Kind::kAny, Kind::kList})));
970
  // Add runtime implementation.
971
0
  flat_expr_builder->AddProgramOptimizer(
972
0
      CreateSelectOptimizationProgramOptimizer(options));
973
0
  return absl::OkStatus();
974
0
}
975
976
}  // namespace cel::extensions