Coverage Report

Created: 2026-09-03 06:30

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