Classify errors as bugs vs runtime input errors#1104
Conversation
SafeI64 had zero references outside its own file, and the checked_div helper divided before checking for b==0 (UB on division by zero). Deletion is the better fix than repair. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Elazar Gershuni <elazarg@gmail.com>
Introduces a small, deliberately shallow error hierarchy (PrevailError, RuntimeInputError, InternalError) in a new dependency-free header crab_utils/prevail_errors.hpp. The single load-bearing distinction is "is this a bug in Prevail itself, or a failure caused by external input?"; sub-classifying RuntimeInputError further is deferred until a handler differentiation justifies it. CRAB_ERROR now throws InternalError. Existing UnmarshalError and InvalidControlFlow are reparented under RuntimeInputError. The decorative MalformedElf subclass is collapsed into UnmarshalError (never separately caught). Boundary throw sites that previously raised bare std::runtime_error are retagged as RuntimeInputError; residual invariant-style throws (string_constraints, printing label lookup, ebpf_transformer logic_error, marshaller IR-shape checks) move to CRAB_ERROR. The cfg_builder.cpp:486 site whose own message said "internal error" but threw InvalidControlFlow now goes through CRAB_ERROR. Marshal.cpp's three remaining throws are reachable only from test-only paths constructing invalid IR, which is itself a Prevail bug, so they route through CRAB_ERROR as well. VerificationError stops pretending to be an exception type: it is never caught polymorphically, only stored in optional<>. It becomes a plain struct with a message + optional Label. The local non-local-exit inside ebpf_checker is preserved by introducing a small file-private VerificationFailureSignal exception type, converted at the catch site into the value-typed VerificationError. Adds missing handling at input boundaries: read_suite() now checks the ifstream open before YAML::LoadAll (a missing test file used to silently produce zero documents); yaml-cpp parsing is wrapped to preserve line/column context with the suite path. The assembly parser gains parse_with_context wrappers around stoi/stoll/stoull and named lookup helpers (lookup_op, lookup_label) so errors point at the offending token instead of leaking generic "stoi" / "map::at" what() strings. Adds [[nodiscard]] to optional-error returners. Splits main.cpp's top-level catch by category: InternalError exits 2 with a "please file an issue" message, RuntimeInputError exits 1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Elazar Gershuni <elazarg@gmail.com>
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThe PR establishes a project-wide error hierarchy (PrevailError, RuntimeInputError, InternalError) and migrates exception handling throughout the codebase. It refactors VerificationError from a runtime_error subclass to a lightweight struct, changes internal failure paths from throwing generic exceptions to using CRAB_ERROR, and adds Changes
Possibly related issues
Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/string_constraints.hpp (1)
42-58:⚠️ Potential issue | 🟡 Minor
contains()still bypasses the new bottom check.
value()now raisesCRAB_ERROR, butcontains()still callsmaybe_inv.value()directly, so bottom-state access will keep surfacing asstd::bad_optional_accessinstead of the project’s internal-error classification.Proposed fix
- bool contains(const std::string& item) const { - return maybe_inv.value().contains(item); - } + bool contains(const std::string& item) const { + return value().contains(item); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/string_constraints.hpp` around lines 42 - 58, The contains() method currently calls maybe_inv.value() directly and can throw std::bad_optional_access; change it to use the existing value() accessor (or perform the same is_bottom() check and CRAB_ERROR as in value()) so bottom-state access is handled consistently; update StringInvariant::contains to call value().contains(item) (or check is_bottom() then CRAB_ERROR) and refer to maybe_inv, value(), contains(), and is_bottom() to locate the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main.cpp`:
- Around line 169-175: The code currently catches std::runtime_error and treats
all such exceptions as user load failures; change the catch to specifically
catch RuntimeInputError (for errors from user input in ElfObject::get_programs()
/ ElfObject::list_programs()) and assign its message to load_error, then add a
separate fallback catch (e.g., catch(const std::exception& e)) that logs or
handles unexpected runtime exceptions differently (preserving the existing
InternalError behavior and returning the proper exit code). Update the catch
block that currently reads catch(const std::runtime_error& e) to catch
RuntimeInputError first and then add the generic fallback catch below it so
residual bugs aren’t masked.
In `@src/test/ebpf_yaml.cpp`:
- Around line 375-380: The catch around read_case currently catches all
std::exception (swallowing InternalError); change the catch so InternalError
escapes: in the catch(const std::exception& ex) for read_case(), check if
dynamic_cast<const InternalError*>(&ex) (or use typeid) and rethrow if true,
otherwise set tc.actual_exception = ex.what(); this keeps InternalError from
being recorded as an expected_exception while still capturing input/parsing
failures.
---
Outside diff comments:
In `@src/string_constraints.hpp`:
- Around line 42-58: The contains() method currently calls maybe_inv.value()
directly and can throw std::bad_optional_access; change it to use the existing
value() accessor (or perform the same is_bottom() check and CRAB_ERROR as in
value()) so bottom-state access is handled consistently; update
StringInvariant::contains to call value().contains(item) (or check is_bottom()
then CRAB_ERROR) and refer to maybe_inv, value(), contains(), and is_bottom() to
locate the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 5dda2780-67c3-4411-aec5-f0bbb17d9354
📒 Files selected for processing (20)
src/arith/num_safeint.hppsrc/crab/ebpf_checker.cppsrc/crab/ebpf_domain.hppsrc/crab/ebpf_transformer.cppsrc/crab/type_domain.cppsrc/crab_utils/debug.hppsrc/crab_utils/prevail_errors.hppsrc/fwd_analyzer.cppsrc/io/elf_loader.hppsrc/io/elf_reader.cppsrc/ir/cfg_builder.cppsrc/ir/marshal.cppsrc/ir/parse.cppsrc/ir/program.hppsrc/ir/unmarshal.hppsrc/linux/linux_platform.cppsrc/main.cppsrc/printing.cppsrc/string_constraints.hppsrc/test/ebpf_yaml.cpp
💤 Files with no reviewable changes (1)
- src/arith/num_safeint.hpp
CodeRabbit's review pointed out three places where the broader catches would silently absorb an InternalError as if it were a runtime input failure: 1. main.cpp's elf get_programs() / list_programs() blocks caught std::runtime_error after the InternalError arm. Any future bug type that derives from std::runtime_error but not InternalError would have been recorded as load_error or "error listing programs" rather than escaping as a bug. Narrowed to RuntimeInputError + a separate std::exception fallback that prints "error: ..." and exits 1. 2. ebpf_yaml.cpp's expected-exception matcher caught std::exception around read_case(). An InternalError raised during read_case could match an `expected-exception` clause and make a Prevail bug pass a test. Narrowed to RuntimeInputError + YAML::Exception so InternalError escapes upward. The schema-validation throws inside parse_observations and raw_options_to_options used bare std::runtime_error. Retagged them to RuntimeInputError so the narrowed catch still matches them. 3. string_constraints.hpp's contains() called maybe_inv.value() and would throw std::bad_optional_access on bottom. Routed through the value() accessor so the bottom check goes through CRAB_ERROR like the other bottom-state accesses. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Elazar Gershuni <elazarg@gmail.com>
|
Addressed all three CodeRabbit findings in bec90cc:
Tests still pass at baseline (1320 passed + 236 failed-as-expected + 1 skipped). |
Summary
PrevailError,RuntimeInputError,InternalError) in a new dependency-free headercrab_utils/prevail_errors.hpp. The single load-bearing distinction is bug vs runtime input error; sub-classifyingRuntimeInputErrorfurther is deferred until a handler differentiation justifies it.main.cpp's top-level catch arms now distinguish the two:InternalErrorexits 2 with a "this is a bug; please file an issue" message;RuntimeInputErrorexits 1 with the existingerror: ...shape.SafeI64/num_safeint.hpp) in a separate commit.Why
The codebase mixed exceptions, variants, optionals, catch-alls, and bug-style throws. A user seeing
error: ...frommain.cppcould not tell whether their input was bad or Prevail had a bug.CRAB_ERRORandInvalidControlFlowwere both used for either category.VerificationErrorderived fromstd::runtime_errordespite never being thrown (only stored inoptional<>). The goal here is classification only — making the categories greppable, typed, and surfaced distinctly to the user. The eventual policy decision (abort vs throw vsstd::expectedvs contracts) is left for follow-up work.What changed
Hierarchy and routing (
crab_utils/prevail_errors.hpp,crab_utils/debug.hpp)PrevailError,RuntimeInputError,InternalErrordefined in a new neutral header.CRAB_ERROR(...)now throwsInternalErrorinstead of barestd::runtime_error.UnmarshalErrorandInvalidControlFlowreparented underRuntimeInputError.MalformedElfcollapsed intoUnmarshalError(never separately caught anywhere).Throw-site classification
runtime_errorthrows at user/input boundaries →RuntimeInputError:printing.cppoutput-file open,ir/parse.cppconstraint parsing,linux/linux_platform.cppmap creation,crab/type_domain.cppunsupported type names.CRAB_ERROR:string_constraints.hpp"cannot iterate bottom",printing.cpplabel lookup during disassembly,crab/ebpf_transformer.cpplogic_error, thecfg_builder.cppsite whose own message said "internal error" but threwInvalidControlFlow.ir/marshal.cppIR-shape checks →CRAB_ERROR. The marshaller has no production callers — it is exercised only bytest/test_marshal.cppround-trip oracles, and reaching these throws means a test constructed an IR shape that doesn't have a valid bytecode encoding (a Prevail-side gap, not user input).VerificationErrorbecomes a plain struct: std::runtime_error, never actually caught polymorphically). Now:struct VerificationError { std::string message; std::optional<Label> where; }.EbpfCheckeris preserved by introducing a small file-privateVerificationFailureSignalexception, converted at the catch site into the value-typedVerificationError.Missing handling added
read_suite()checks theifstreamopen beforeYAML::LoadAll(a missing test file used to silently produce zero documents).read_suiteis wrapped to preserve line/column context with the suite path.parse_with_contextwrappers aroundstoi/stoll/stoulland named lookup helpers (lookup_op,lookup_label) so errors point at the offending token instead of leaking generic"stoi"/"map::at"what()strings.Other small improvements
RejectionReasonconsolidation: added anenforce_instruction_feature_supporthelper so the value-returning checker remains pure-query while the throw-shape conversion happens in one named place.[[nodiscard]]added tounmarshal(),ebpf_domain_check,check_loop_bound,check_instruction_feature_support.num_safeint.hppdeleted (zero references; itschecked_divdivided before checking for zero).Out of scope
std::expectedvs contracts. This PR makes the categories greppable so the policy decision can be made site-by-site without re-auditing throw sites.RuntimeInputError(IoError,UnsupportedFeature,InvalidProgramStructure) — deferred until a handler differentiation justifies it.parse_linear_constraints) from the production API ofEbpfDomain/AnalysisResult— tracked in Untangle test-helper string-parsing from the production API #1103.num_big.hppNumberparse-string andnarrow<T>family — theseCRAB_ERRORsites are reachable from input-driven values and may belong on the runtime-input side per call site; left alone for case-by-case reclassification.elf_reader.cpp:641-643— plausibly intentional for multi-program reloc sections; needs a comment + targeted test before changing behavior.Test plan
cmake --build build --parallelclean../bin/testspasses at the documented baseline:1557 cases = 1320 passed + 236 failed-as-expected + 1 skipped,8842 assertions = 8606 passed + 236 failed-as-expected. Zero unexpected failures.🤖 Generated with Claude Code