Skip to content

Commit 77a8b0f

Browse files
jnthntatumcopybara-github
authored andcommitted
Add option for maximum number of ERROR level issues.
If the limit is passed, the checker should stop validating and just return the current set of issues. PiperOrigin-RevId: 698122196
1 parent ef155f8 commit 77a8b0f

File tree

3 files changed

+69
-14
lines changed

3 files changed

+69
-14
lines changed

checker/checker_options.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ struct CheckerOptions {
4848
//
4949
// If exceeded, the checker should return a status with code InvalidArgument.
5050
int max_expression_node_count = 100000;
51+
52+
// Maximum number (inclusive) of error-level issues to tolerate for an input
53+
// ast.
54+
//
55+
// If exceeded, the checker will stop processing the ast and return
56+
// the current set of issues.
57+
int max_error_issues = 20;
5158
};
5259

5360
} // namespace cel

checker/internal/type_checker_impl.cc

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,8 @@ class ResolveVisitor : public AstVisitorBase {
315315

316316
const absl::Status& status() const { return status_; }
317317

318+
int error_count() const { return error_count_; }
319+
318320
void AssertExpectedType(const Expr& expr, const Type& expected_type) {
319321
Type observed = GetTypeOrDyn(&expr);
320322
if (!inference_context_->IsAssignable(observed, expected_type)) {
@@ -364,24 +366,31 @@ class ResolveVisitor : public AstVisitorBase {
364366
void ResolveSelectOperation(const Expr& expr, absl::string_view field,
365367
const Expr& operand);
366368

369+
void ReportIssue(TypeCheckIssue issue) {
370+
if (issue.severity() == Severity::kError) {
371+
error_count_++;
372+
}
373+
issues_->push_back(std::move(issue));
374+
}
375+
367376
void ReportMissingReference(const Expr& expr, absl::string_view name) {
368-
issues_->push_back(TypeCheckIssue::CreateError(
377+
ReportIssue(TypeCheckIssue::CreateError(
369378
ComputeSourceLocation(*ast_, expr.id()),
370379
absl::StrCat("undeclared reference to '", name, "' (in container '",
371380
container_, "')")));
372381
}
373382

374383
void ReportUndefinedField(int64_t expr_id, absl::string_view field_name,
375384
absl::string_view struct_name) {
376-
issues_->push_back(TypeCheckIssue::CreateError(
385+
ReportIssue(TypeCheckIssue::CreateError(
377386
ComputeSourceLocation(*ast_, expr_id),
378387
absl::StrCat("undefined field '", field_name, "' not found in struct '",
379388
struct_name, "'")));
380389
}
381390

382391
void ReportTypeMismatch(int64_t expr_id, const Type& expected,
383392
const Type& actual) {
384-
issues_->push_back(TypeCheckIssue::CreateError(
393+
ReportIssue(TypeCheckIssue::CreateError(
385394
ComputeSourceLocation(*ast_, expr_id),
386395
absl::StrCat("expected type '",
387396
inference_context_->FinalizeType(expected).DebugString(),
@@ -412,7 +421,7 @@ class ResolveVisitor : public AstVisitorBase {
412421
}
413422
if (!inference_context_->IsAssignable(value_type, field_type) &&
414423
!IsPbNullFieldAssignable(value_type, field_type)) {
415-
issues_->push_back(TypeCheckIssue::CreateError(
424+
ReportIssue(TypeCheckIssue::CreateError(
416425
ComputeSourceLocation(*ast_, field.id()),
417426
absl::StrCat(
418427
"expected type of field '", field_info->name(), "' is '",
@@ -461,6 +470,7 @@ class ResolveVisitor : public AstVisitorBase {
461470
std::vector<std::unique_ptr<VariableScope>> comprehension_vars_;
462471
std::vector<ComprehensionScope> comprehension_scopes_;
463472
absl::Status status_;
473+
int error_count_ = 0;
464474

465475
// References that were resolved and may require AST rewrites.
466476
absl::flat_hash_map<const Expr*, FunctionResolution> functions_;
@@ -546,7 +556,7 @@ void ResolveVisitor::PostVisitConst(const Expr& expr,
546556
types_[&expr] = TimestampType();
547557
break;
548558
default:
549-
issues_->push_back(TypeCheckIssue::CreateError(
559+
ReportIssue(TypeCheckIssue::CreateError(
550560
ComputeSourceLocation(*ast_, expr.id()),
551561
absl::StrCat("unsupported constant type: ",
552562
constant.kind().index())));
@@ -597,7 +607,7 @@ void ResolveVisitor::PostVisitMap(const Expr& expr, const MapExpr& map) {
597607
//
598608
// To match the Go implementation, we just warn here, but in the future
599609
// we should consider making this an error.
600-
issues_->push_back(TypeCheckIssue(
610+
ReportIssue(TypeCheckIssue(
601611
Severity::kWarning, ComputeSourceLocation(*ast_, key->id()),
602612
absl::StrCat(
603613
"unsupported map key type: ",
@@ -702,7 +712,7 @@ void ResolveVisitor::PostVisitStruct(const Expr& expr,
702712

703713
if (resolved_type.kind() != TypeKind::kStruct &&
704714
!IsWellKnownMessageType(resolved_name)) {
705-
issues_->push_back(TypeCheckIssue::CreateError(
715+
ReportIssue(TypeCheckIssue::CreateError(
706716
ComputeSourceLocation(*ast_, expr.id()),
707717
absl::StrCat("type '", resolved_name,
708718
"' does not support message creation")));
@@ -849,7 +859,7 @@ void ResolveVisitor::PostVisitComprehensionSubexpression(
849859
case TypeKind::kDyn:
850860
break;
851861
default:
852-
issues_->push_back(TypeCheckIssue::CreateError(
862+
ReportIssue(TypeCheckIssue::CreateError(
853863
ComputeSourceLocation(*ast_, comprehension.iter_range().id()),
854864
absl::StrCat(
855865
"expression of type '",
@@ -923,7 +933,7 @@ void ResolveVisitor::ResolveFunctionOverloads(const Expr& expr,
923933
inference_context_->ResolveOverload(decl, arg_types, is_receiver);
924934

925935
if (!resolution.has_value()) {
926-
issues_->push_back(TypeCheckIssue::CreateError(
936+
ReportIssue(TypeCheckIssue::CreateError(
927937
ComputeSourceLocation(*ast_, expr.id()),
928938
absl::StrCat("found no matching overload for '", decl.name(),
929939
"' applied to '(",
@@ -1085,7 +1095,7 @@ absl::optional<Type> ResolveVisitor::CheckFieldType(int64_t id,
10851095
break;
10861096
}
10871097

1088-
issues_->push_back(TypeCheckIssue::CreateError(
1098+
ReportIssue(TypeCheckIssue::CreateError(
10891099
ComputeSourceLocation(*ast_, id),
10901100
absl::StrCat("expression of type '",
10911101
inference_context_->FinalizeType(operand_type).DebugString(),
@@ -1252,25 +1262,34 @@ absl::StatusOr<ValidationResult> TypeCheckerImpl::Check(
12521262

12531263
TraversalOptions opts;
12541264
opts.use_comprehension_callbacks = true;
1255-
1265+
bool error_limit_reached = false;
12561266
auto traversal = AstTraversal::Create(ast_impl.root_expr(), opts);
1267+
12571268
for (int step = 0; step < options_.max_expression_node_count * 2; ++step) {
12581269
bool has_next = traversal.Step(visitor);
12591270
if (!visitor.status().ok()) {
12601271
return visitor.status();
12611272
}
1273+
if (visitor.error_count() > options_.max_error_issues) {
1274+
error_limit_reached = true;
1275+
break;
1276+
}
12621277
if (!has_next) {
12631278
break;
12641279
}
12651280
}
12661281

1267-
if (!traversal.IsDone()) {
1282+
if (!traversal.IsDone() && !error_limit_reached) {
12681283
return absl::InvalidArgumentError(
1269-
absl::StrCat("Max expression node count exceeded: ",
1284+
absl::StrCat("Maximum expression node count exceeded: ",
12701285
options_.max_expression_node_count));
12711286
}
12721287

1273-
if (env_.expected_type().has_value()) {
1288+
if (error_limit_reached) {
1289+
issues.push_back(TypeCheckIssue::CreateError(
1290+
{}, absl::StrCat("maximum number of ERROR issues exceeded: ",
1291+
options_.max_error_issues)));
1292+
} else if (env_.expected_type().has_value()) {
12741293
visitor.AssertExpectedType(ast_impl.root_expr(), *env_.expected_type());
12751294
}
12761295

checker/internal/type_checker_impl_test.cc

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,35 @@ TEST(TypeCheckerImplTest, ReportMissingIdentDecl) {
340340
"undeclared reference to 'y'")));
341341
}
342342

343+
TEST(TypeCheckerImplTest, ErrorLimitInclusive) {
344+
TypeCheckEnv env(GetSharedTestingDescriptorPool());
345+
346+
google::protobuf::Arena arena;
347+
ASSERT_THAT(RegisterMinimalBuiltins(&arena, env), IsOk());
348+
CheckerOptions options;
349+
options.max_error_issues = 1;
350+
351+
TypeCheckerImpl impl(std::move(env), options);
352+
ASSERT_OK_AND_ASSIGN(auto ast, MakeTestParsedAst("1 + y"));
353+
ASSERT_OK_AND_ASSIGN(ValidationResult result, impl.Check(std::move(ast)));
354+
355+
EXPECT_FALSE(result.IsValid());
356+
EXPECT_THAT(result.GetIssues(),
357+
ElementsAre(IsIssueWithSubstring(Severity::kError,
358+
"undeclared reference to 'y'")));
359+
ASSERT_OK_AND_ASSIGN(ast, MakeTestParsedAst("x + y + z"));
360+
ASSERT_OK_AND_ASSIGN(result, impl.Check(std::move(ast)));
361+
362+
EXPECT_FALSE(result.IsValid());
363+
EXPECT_THAT(
364+
result.GetIssues(),
365+
ElementsAre(
366+
IsIssueWithSubstring(Severity::kError, "undeclared reference to 'x'"),
367+
IsIssueWithSubstring(Severity::kError, "undeclared reference to 'y'"),
368+
IsIssueWithSubstring(Severity::kError,
369+
"maximum number of ERROR issues exceeded: 1")));
370+
}
371+
343372
MATCHER_P3(IsIssueWithLocation, line, column, message, "") {
344373
const TypeCheckIssue& issue = arg;
345374
if (issue.location().line == line && issue.location().column == column &&

0 commit comments

Comments
 (0)