Skip to content

Commit 2780d74

Browse files
jnthntatumcopybara-github
authored andcommitted
Add tighter bound on value stack estimate.
PiperOrigin-RevId: 996788585
1 parent 6afc839 commit 2780d74

10 files changed

Lines changed: 518 additions & 46 deletions

‎eval/compiler/BUILD‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ cc_library(
122122
"//runtime/internal:runtime_env",
123123
"@com_google_absl//absl/algorithm:container",
124124
"@com_google_absl//absl/base:nullability",
125+
"@com_google_absl//absl/cleanup",
125126
"@com_google_absl//absl/container:flat_hash_map",
126127
"@com_google_absl//absl/container:flat_hash_set",
127128
"@com_google_absl//absl/container:node_hash_map",
@@ -146,6 +147,7 @@ cc_test(
146147
":cel_expression_builder_flat_impl",
147148
":constant_folding",
148149
":flat_expr_builder",
150+
":flat_expr_builder_extensions",
149151
":qualified_reference_resolver",
150152
"//base:builtins",
151153
"//checker:validation_result",
@@ -159,8 +161,8 @@ cc_test(
159161
"//compiler",
160162
"//compiler:compiler_factory",
161163
"//compiler:standard_library",
162-
"//eval/compiler:flat_expr_builder_extensions",
163164
"//eval/eval:evaluator_core",
165+
"//eval/eval:expression_step_logic",
164166
"//eval/public:activation",
165167
"//eval/public:ast_rewrite",
166168
"//eval/public:builtin_func_registrar",
@@ -181,6 +183,7 @@ cc_test(
181183
"//eval/public/structs:cel_proto_wrapper",
182184
"//eval/public/testing:matchers",
183185
"//eval/testutil:test_message_cc_proto",
186+
"//extensions/protobuf:ast_converters",
184187
"//internal:proto_matchers",
185188
"//internal:status_macros",
186189
"//internal:testing",
@@ -202,7 +205,6 @@ cc_test(
202205
"@com_google_cel_spec//proto/cel/expr:checked_cc_proto",
203206
"@com_google_cel_spec//proto/cel/expr:syntax_cc_proto",
204207
"@com_google_cel_spec//proto/cel/expr/conformance/proto3:test_all_types_cc_proto",
205-
"@com_google_protobuf//:field_mask_cc_proto",
206208
"@com_google_protobuf//:protobuf",
207209
],
208210
)

‎eval/compiler/flat_expr_builder.cc‎

Lines changed: 95 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
#include "absl/algorithm/container.h"
3434
#include "absl/base/nullability.h"
35+
#include "absl/cleanup/cleanup.h"
3536
#include "absl/container/flat_hash_map.h"
3637
#include "absl/container/flat_hash_set.h"
3738
#include "absl/container/node_hash_map.h"
@@ -971,13 +972,13 @@ class FlatExprVisitor : public cel::AstVisitor {
971972
*std::move(field_type), select_expr.test_only(),
972973
options_.enable_empty_wrapper_null_unboxing,
973974
enable_optional_types_),
974-
expr.id());
975+
expr.id(), /*stack_delta=*/0);
975976
return;
976977
}
977978
AddStep(CreateSelectStep(std::move(field), select_expr.test_only(),
978979
options_.enable_empty_wrapper_null_unboxing,
979980
enable_optional_types_),
980-
expr.id());
981+
expr.id(), /*stack_delta=*/0);
981982
}
982983

983984
// Call node handler group.
@@ -1383,9 +1384,14 @@ class FlatExprVisitor : public cel::AstVisitor {
13831384
std::vector<std::string> fields =
13841385
std::move(status_or_resolved_fields.value().second);
13851386

1387+
size_t num_fields = fields.size();
1388+
int64_t stack_delta =
1389+
num_fields <= static_cast<size_t>(std::numeric_limits<int64_t>::max())
1390+
? 1 - static_cast<int64_t>(num_fields)
1391+
: std::numeric_limits<int16_t>::max();
13861392
AddStep(CreateCreateStructStep(std::move(resolved_name), std::move(fields),
13871393
MakeOptionalIndicesSet(struct_expr)),
1388-
expr.id());
1394+
expr.id(), stack_delta);
13891395
}
13901396

13911397
void PostVisitMap(const cel::Expr& expr,
@@ -1406,9 +1412,15 @@ class FlatExprVisitor : public cel::AstVisitor {
14061412
}
14071413
}
14081414

1409-
AddStep(CreateCreateStructStepForMap(map_expr.entries().size(),
1415+
size_t num_entries = map_expr.entries().size();
1416+
int64_t stack_delta =
1417+
num_entries <=
1418+
static_cast<size_t>(std::numeric_limits<int64_t>::max() / 2)
1419+
? 1 - 2 * static_cast<int64_t>(num_entries)
1420+
: std::numeric_limits<int16_t>::max();
1421+
AddStep(CreateCreateStructStepForMap(num_entries,
14101422
MakeOptionalIndicesSet(map_expr)),
1411-
expr.id());
1423+
expr.id(), stack_delta);
14121424
}
14131425

14141426
absl::Status progress_status() const { return progress_status_; }
@@ -1471,21 +1483,22 @@ class FlatExprVisitor : public cel::AstVisitor {
14711483
// may free the step at that point.
14721484
template <typename T>
14731485
std::enable_if_t<std::is_base_of_v<ExpressionStepLogic, T>, T*> AddStep(
1474-
std::unique_ptr<T> step, int64_t expr_id = -1) {
1486+
std::unique_ptr<T> step, int64_t expr_id = -1, int64_t stack_delta = 1) {
14751487
if (progress_status_.ok() && !PlanningSuppressed()) {
14761488
T* ptr = step.get();
1477-
program_builder_.AddStep(
1478-
ExpressionStep::MakeGenericStep(std::move(step), expr_id));
1489+
program_builder_.AddStep(ExpressionStep::MakeGenericStep(
1490+
std::move(step), expr_id, stack_delta));
14791491
return ptr;
14801492
}
14811493
return nullptr;
14821494
}
14831495

14841496
template <typename T>
14851497
std::enable_if_t<std::is_base_of_v<ExpressionStepLogic, T>, T*> AddStep(
1486-
absl::StatusOr<std::unique_ptr<T>> step, int64_t expr_id = -1) {
1498+
absl::StatusOr<std::unique_ptr<T>> step, int64_t expr_id = -1,
1499+
int64_t stack_delta = 1) {
14871500
if (step.ok()) {
1488-
return AddStep(*std::move(step), expr_id);
1501+
return AddStep(*std::move(step), expr_id, stack_delta);
14891502
} else {
14901503
SetProgressStatusIfError(step.status());
14911504
}
@@ -1728,7 +1741,7 @@ FlatExprVisitor::CallHandlerResult FlatExprVisitor::HandleIndex(
17281741
}
17291742

17301743
AddStep(CreateContainerAccessStep(call_expr, enable_optional_types_),
1731-
expr.id());
1744+
expr.id(), /*stack_delta=*/-1);
17321745
return CallHandlerResult::kIntercepted;
17331746
}
17341747

@@ -2041,7 +2054,7 @@ void ExhaustiveTernaryCondVisitor::PreVisit(const cel::Expr* expr) {
20412054
}
20422055

20432056
void ExhaustiveTernaryCondVisitor::PostVisit(const cel::Expr* expr) {
2044-
visitor_->AddStep(CreateTernaryStep(), expr->id());
2057+
visitor_->AddStep(CreateTernaryStep(), expr->id(), /*stack_delta=*/-2);
20452058
}
20462059

20472060
void ComprehensionVisitor::PreVisit(const cel::Expr* expr) {
@@ -2232,6 +2245,52 @@ std::vector<ExecutionPathView> FlattenExpressionTable(
22322245
return subexpression_indexes;
22332246
}
22342247

2248+
std::optional<int64_t> CheckedDeltaAdd(int64_t current,
2249+
std::optional<int64_t> delta) {
2250+
if (!delta.has_value()) {
2251+
return std::nullopt;
2252+
}
2253+
if (*delta > 0 && current > std::numeric_limits<int64_t>::max() - *delta) {
2254+
return std::nullopt;
2255+
}
2256+
if (*delta < 0 && current < std::numeric_limits<int64_t>::min() - *delta) {
2257+
return std::nullopt;
2258+
}
2259+
current += *delta;
2260+
if (current < 0) {
2261+
return std::nullopt;
2262+
}
2263+
return current;
2264+
}
2265+
2266+
// Conservative estimate of the maximum value stack size needed for the given
2267+
// subexpressions.
2268+
//
2269+
// If overflow occurs, returns fallback_size, which is the total number of
2270+
// steps in the program.
2271+
size_t EstimateMaxStackSize(absl::Span<const ExecutionPathView> subexpressions,
2272+
size_t fallback_size) {
2273+
size_t total_max_stack = 0;
2274+
for (ExecutionPathView path : subexpressions) {
2275+
int64_t current = 0;
2276+
int64_t max_depth = 0;
2277+
for (const ExpressionStep& step : path) {
2278+
std::optional<int64_t> next = CheckedDeltaAdd(current, step.StackDelta());
2279+
if (!next.has_value()) {
2280+
return fallback_size;
2281+
}
2282+
current = *next;
2283+
max_depth = std::max(max_depth, current);
2284+
}
2285+
if (static_cast<uint64_t>(max_depth) >
2286+
std::numeric_limits<size_t>::max() - total_max_stack) {
2287+
return fallback_size;
2288+
}
2289+
total_max_stack += static_cast<size_t>(max_depth);
2290+
}
2291+
return total_max_stack;
2292+
}
2293+
22352294
absl::Status CheckAstExtensions(
22362295
const std::vector<cel::ExtensionSpec>& extensions,
22372296
bool enable_select_optimization) {
@@ -2266,11 +2325,27 @@ absl::StatusOr<FlatExpression> FlatExprBuilder::CreateExpressionImpl(
22662325
? RuntimeIssue::Severity::kWarning
22672326
: RuntimeIssue::Severity::kError;
22682327
IssueCollector issue_collector(max_severity);
2328+
auto record_issues = absl::MakeCleanup([issues, &issue_collector] {
2329+
if (issues == nullptr) {
2330+
return;
2331+
}
2332+
*issues = issue_collector.ExtractIssues();
2333+
});
22692334

2270-
CEL_ASSIGN_OR_RETURN(std::vector<cel::ExtensionSpec> runtime_extensions,
2271-
ExtractAndValidateRuntimeExtensions(*ast));
2272-
CEL_RETURN_IF_ERROR(
2273-
CheckAstExtensions(runtime_extensions, enable_select_optimization_));
2335+
absl::StatusOr<std::vector<cel::ExtensionSpec>> runtime_extensions =
2336+
ExtractAndValidateRuntimeExtensions(*ast);
2337+
2338+
if (!runtime_extensions.ok()) {
2339+
CEL_RETURN_IF_ERROR(issue_collector.AddIssue(
2340+
RuntimeIssue::CreateError(runtime_extensions.status())));
2341+
} else {
2342+
absl::Status status =
2343+
CheckAstExtensions(*runtime_extensions, enable_select_optimization_);
2344+
if (!status.ok()) {
2345+
CEL_RETURN_IF_ERROR(
2346+
issue_collector.AddIssue(RuntimeIssue::CreateError(status)));
2347+
}
2348+
}
22742349

22752350
Resolver resolver(container_, function_registry_, type_registry_,
22762351
GetTypeProvider(),
@@ -2318,17 +2393,16 @@ absl::StatusOr<FlatExpression> FlatExprBuilder::CreateExpressionImpl(
23182393
"Expression too large to be executed, exceeds uint32_t limits.");
23192394
}
23202395

2321-
if (issues != nullptr) {
2322-
(*issues) = issue_collector.ExtractIssues();
2323-
}
2324-
23252396
ExecutionPath execution_path;
23262397
std::vector<ExecutionPathView> subexpressions =
23272398
FlattenExpressionTable(program_builder, execution_path);
2399+
size_t value_stack_size =
2400+
EstimateMaxStackSize(subexpressions, execution_path.size());
23282401

23292402
return FlatExpression(std::move(execution_path), std::move(subexpressions),
23302403
visitor.slot_count(), visitor.iter_stack_count(),
2331-
GetTypeProvider(), options_, std::move(arena));
2404+
GetTypeProvider(), options_, std::move(arena),
2405+
value_stack_size);
23322406
}
23332407
const cel::TypeProvider& FlatExprBuilder::GetTypeProvider() const {
23342408
return use_legacy_type_provider_

‎eval/compiler/flat_expr_builder_extensions.h‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,9 +334,9 @@ class PlannerContext {
334334
absl::Status AddSubplanStep(const cel::Expr& node, ExpressionStep step);
335335
absl::Status AddSubplanStep(const cel::Expr& node,
336336
std::unique_ptr<ExpressionStepLogic> step,
337-
int64_t expr_id = -1) {
338-
return AddSubplanStep(
339-
node, ExpressionStep::MakeGenericStep(std::move(step), expr_id));
337+
int64_t expr_id = -1, int64_t stack_delta = 1) {
338+
return AddSubplanStep(node, ExpressionStep::MakeGenericStep(
339+
std::move(step), expr_id, stack_delta));
340340
}
341341

342342
const Resolver& resolver() const { return resolver_; }

0 commit comments

Comments
 (0)