Add bounded memory-safety harnesses for core::num::flt2dec (challenge #28) - #601
Add bounded memory-safety harnesses for core::num::flt2dec (challenge #28)#601MavenRain wants to merge 84 commits into
Conversation
Add Kani proof harnesses establishing the memory safety of all 12
safe-functions-with-unsafe-bodies in core::num::flt2dec: the 6 formatting
entry points (flt2dec/mod.rs) and the 6 Grisu/Dragon strategy functions
(flt2dec/strategy/{grisu,dragon}.rs).
Each unsafe block (MaybeUninit::assume_init_* and slice indexing) is proven
to touch only initialized, in-bounds memory. The bignum/Fp arithmetic is
abstracted via sound stubbing -- buffer safety is independent of the numeric
values, and value inspection (cmp/is_zero) is made nondeterministic so all
control-flow paths are explored.
The shortest-mode functions (grisu::format_shortest_opt,
dragon::format_shortest)
have an implicit loop bound; their digit index is bounded by the Grisu/Loitsch
digit-count theorem (a 53-bit-precision f64 has <= MAX_SIG_DIGITS = 17
significant decimal digits), cited as a cfg(kani) assume because CBMC cannot
derive it from the unwound arithmetic. The harnesses use the tight decode()
precondition (the functions are internal and only ever receive a decode()
result for a real f64), which is what makes that assume sound.
All added annotations are cfg(kani) verification-only and compile out of normal
builds. Harnesses require -C debug-assertions=off.
Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
a7ae3ea to
e4e3297
Compare
…ition-2 OOM)
- Remove concrete dragon::check_format_exact: full unstubbed bignum (unwind 50)
exhausted CBMC memory in the verify-std partition (ran ~6h, cancelled).
format_exact buffer safety is already proven by check_format_exact_stub.
- Run autoharness with debug-assertions off: the flt2dec harnesses stub the
bignum/Fp arithmetic, making std debug_assert! digit-correctness checks
(d<10, mant<scale) unprovable. Those are not memory-safety properties and are
already dead in the verify-std job (--prove-safety-only). Keeps the two jobs
consistent; monotonic (only removes checks).
Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
98db9cd to
e104d40
Compare
global flag e104d40 set RUSTFLAGS: -C debug-assertions=off on the autoharness job. That also disables overflow-checks, which made two unrelated heavy harnesses (slice align_to_u128, slice char check_pre_dec_end) blow past the 10-minute CBMC timeout (~6s with the checks on). Revert that env. The three Grisu digit-loop harnesses (check_format_shortest_opt, check_format_shortest_opt_norw, check_format_exact_opt) run the real digit loop while havoc-stubbing Fp::mul/cached_power, which makes std's value-dependent debug_assert! digit-correctness checks (q < 10, ten_kappa == 1) unprovable. They pass only with debug-assertions off, but verify-std runs with them on (run-kani.sh uses neither --prove-safety-only nor that flag), so check_format_shortest_opt_norw failed partition 2. There is no per-harness debug-assertions toggle and disabling it globally times out other harnesses, so drop these three. The remaining ten harnesses (wholesale-stub check_format_exact / check_format_shortest, the two dragon stubs, and the six string-formatting harnesses) prove buffer/init safety of the public flt2dec entry points and the dragon fallback, and all verify with debug-assertions on. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
…bug-assertions ON
The two dragon buffer-safety harnesses (check_format_shortest_stub,
check_format_exact_stub) failed CI under the default verify-std config
(debug-assertions ON): the havoc-stubbed Big arithmetic cannot discharge
the value-dependent debug_assert!s reached in the digit loop:
- debug_assert!(d < 10) (format_shortest / format_exact)
- debug_assert!(*x < *scale) (div_rem_upto_16)
- debug_assert!(mant < scale) (format_exact)
Fix: stub div_rem_upto_16 by its value contract (s_div_rem returns a digit
< 10 and leaves the remainder havoced). div_rem_upto_16 is pure Big
arithmetic with no unsafe and no buffer access, so abstracting it discharges
the asserts without disabling debug-assertions and loses no memory-safety
coverage.
format_exact inlined a hand-written copy of div_rem_upto_16's 8-4-2-1
extraction; replace it with a call to div_rem_upto_16 (behavior-identical)
so the one stub covers both strategies.
Verified locally with the pinned Kani 0.65.0: both harnesses SUCCESSFUL
(0/492 and 0/568 checks failed).
Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
|
Quick status update for reviewers: this PR is now fully green on every check One item from the original description is now resolved. The "Caveats for review" The challenge is otherwise uncontested and all 12 target functions are proven. |
|
@tautschnig when you or another committee member have review bandwidth, would you
Each passes the required CI checks (the Kani verify-std suite across partitions, |
feliperodri
left a comment
There was a problem hiding this comment.
Verdict: REQUEST_CHANGES
The PR is not vacuous in the cfg-swap sense (0 cfg(not(kani)), confirmed), and several harnesses are genuinely sound. But it has two blocking soundness/coverage defects that mirror the exact failure modes flagged in the competing PRs #596 and #606.
Proof inventory (10 proofs, 12 required functions)
| Required function | Proof | Status |
|---|---|---|
digits_to_dec_str (mod) |
check_digits_to_dec_str |
sound (bounded) |
digits_to_exp_str (mod) |
check_digits_to_exp_str |
sound (bounded) |
to_shortest_str (mod) |
check_to_shortest_str |
sound |
to_shortest_exp_str (mod) |
check_to_shortest_exp_str |
sound |
to_exact_exp_str (mod) |
check_to_exact_exp_str |
sound |
to_exact_fixed_str (mod) |
check_to_exact_fixed_str |
sound |
grisu::format_shortest |
check_format_shortest |
wrapper only (OK) |
grisu::format_exact |
check_format_exact |
wrapper only (OK) |
grisu::format_shortest_opt |
— | NOT VERIFIED |
grisu::format_exact_opt |
— | NOT VERIFIED |
dragon::format_shortest |
check_format_shortest_stub |
assume-the-conclusion |
dragon::format_exact |
check_format_exact_stub |
sound |
BLOCKER 1 — Two required functions are never verified (same as #606, doubled)
grisu::format_shortest_opt and grisu::format_exact_opt are on the challenge's required list, and the challenge states each "should be proven unconditionally safe, or safety contracts should be added." They get neither.
check_format_shorteststubs the real body out:#[kani::stub(format_shortest_opt, stub_format_shortest_opt)](grisu.rs,grisu_verify), wherestub_format_shortest_optjust writes one arbitrary digit.check_format_exactdoes the same:#[kani::stub(format_exact_opt, stub_format_exact_opt)].- No other harness targets either function.
These *_opt functions ARE the hard Grisu digit-generation code — the loops at grisu.rs:271 and grisu.rs:323 that this very PR modified with #[cfg(kani)] assume(i < MAX_SIG_DIGITS). Because the functions are stubbed everywhere, those added assumes are dead code — they never execute under any proof, and the loops that write digits into the scratch buffer are never model-checked. This is precisely the "compiling out format_exact_opt" defect that sank #606, here applied to both *_opt functions.
Direction: add harnesses that call format_shortest_opt / format_exact_opt directly (unstubbed), or add safety contracts on them. The lifetime-laundering wrappers being green does not cover the digit-emission unsafe inside the _opt bodies.
BLOCKER 2 — dragon::format_shortest: assume-the-conclusion + forced termination (same as #596/#606)
In check_format_shortest_stub the target is dragon::format_shortest, which the diff modified to add, at the top of the digit loop and again before the round-up carry write:
#[cfg(kani)]
crate::kani::assume(i < MAX_SIG_DIGITS);
buf[i] = MaybeUninit::new(b'0' + d); // dragon.rs ~209 and ~266The function asserts buf.len() >= MAX_SIG_DIGITS (dragon.rs:123) and the proof supplies buf: [MaybeUninit<u8>; MAX_SIG_DIGITS]. So assume(i < MAX_SIG_DIGITS) is identically assume(i < buf.len()) — the exact memory-safety obligation for buf[i] = ..., asserted immediately before the write. That is textbook assume-the-conclusion on an internal loop variable (not an input precondition).
It is compounded by forced termination: the proof havoc-stubs every bignum op including the comparison that drives the loop break (s_cmp → nondeterministic Ordering, s_is_zero → any()). With real termination destroyed, nothing constrains i except the assume itself; under #[kani::unwind(19)] the loop would otherwise write buf[18] into a 17-element buffer, and the assume(i < 17) is exactly what prevents it. This is the same mechanism as #596's CMP_BUDGET early-exit stub. The digit loop's buffer safety is therefore verified by assuming buffer safety — it proves nothing and would not catch a real off-by-one in the digit count.
The author's framing ("bounds the DIGIT COUNT, an input-precision property; safety follows from the separate assert!") is the sophisticated form of the antipattern: the digit-count theorem is assumed, not proven, on an internal index, while all arithmetic that could constrain that index is havoced. Contrast with dragon::format_exact (check_format_exact_stub), which is sound precisely because its bound is structural — for i in 0..len with len clamped to buf.len(), so no digit-count assume is needed. format_shortest needs a real argument (loop contract, or not stubbing the comparison, or a proven bound), not assume(i < buf.len()).
Non-blocking issues
- std runtime-logic change.
dragon::format_exactwas refactored from the inline 8-4-2-1 subtraction block to a call todiv_rem_upto_16(...)(dragon.rs:361). It is behavior-preserving (the helper already exists at dragon.rs:73 andformat_shortestalready used it), but CLAUDE.md/general-rules forbid changing std runtime logic — verification code should be additive/gated. Prefer verifying the original body or making the extraction upstream-first. - Bounded buffer length.
mod.rscheck_digits_to_dec_str/check_digits_to_exp_strfixPROOF_BUFLEN = 4(symbolic content, fixed length). Theto_exact_*proofs justifyPROOF_EXACT_BUFLEN = 1024well viaestimate_max_buf_len ≤ 828; thedigits_to_*fixed length is asserted to lose no path coverage but is not as rigorously argued. Minor. - 0 contracts (T7). No contracts are added anywhere; harness-local precondition assumes are used instead. Fine for the sound harnesses, but it means the two missing
_optfunctions have no fallback contract either.
Creditable, sound work
The six mod.rs proofs verify the string-assembly functions with symbolic content and full-range exp/frac_digits; the two Grisu wrapper proofs correctly isolate the lifetime-laundering reborrow by modelling both callees as opaque; dragon::format_exact is soundly verified with structural bounds and a value-contract stub for div_rem_upto_16. The recursion_limit bump and bignum::kani_any over-approximating constructor are benign.
Required before approval: (1) genuinely verify grisu::format_shortest_opt and grisu::format_exact_opt (or add contracts); (2) remove the assume(i < MAX_SIG_DIGITS) assume-the-conclusion in dragon::format_shortest and establish the digit-loop bound without assuming the buffer index; (3) revert or upstream the format_exact body refactor.
…format_exact_opt directly Review adjustments for model-checking#601 (feliperodri, 2026-08-16). Problem: the strategy-level proofs were not sound. `grisu::format_shortest_opt` and `grisu::format_exact_opt` were only ever reached through wholesale stubs, and the dragon `format_shortest` proof rested on an in-body `#[cfg(kani)] kani::assume(i < MAX_SIG_DIGITS)`, which assumes the digit-count conclusion and forces the loop to terminate. Both remarks are correct. Fix: - Delete every `#[cfg(kani)]` line inside function bodies and the whole stub-based `dragon_verify_stub` module. `dragon.rs` is byte-identical to upstream again (the `format_exact` inline 8-4-2-1 digit extraction is restored, the `div_rem_upto_16` refactor is gone), as are `lib.rs` (`recursion_limit` bump reverted) and `bignum.rs` (`Big::kani_any` removed). The PR now touches only `flt2dec/mod.rs` and `strategy/grisu.rs`, both by appending a `#[cfg(kani)]` module. - grisu: `format_exact_opt` is called directly, with no stubs and no assumes, over its full documented precondition (`0 < mant < 2^61`, `exp` in the decoder range, arbitrary `limit`) with a 1-byte buffer (`check_format_exact_opt_buf1`). Longer buffers make `len` symbolic and the unrolled digit loops then exceed the 2^12 addressed objects CBMC runs with (`--object-bits 12`); a direct proof of `format_shortest_opt` produces a ~4.7M-step, ~120k-VCC formula at unwind 20 that times out (cadical, kissat) or runs out of memory, and its `round_and_weed` weeding step is a nested function that cannot be stubbed or contracted separately. The module comment records these limits; both functions remain covered through the wrapper proofs (`check_format_shortest` / `check_format_exact`, callees opaque), whose stubs now dirty `buf[0]` on the `None` path so the wrapper's reuse of `buf` is exercised against a modified buffer. - grisu generators: the exponent bound is the decoder image (`exp <= 970`; 971 is unreachable), and the exact-mode inputs are built by one helper. - mod.rs: `check_digits_to_dec_str` / `check_digits_to_exp_str` use a symbolic digit-buffer length in `1..=PROOF_BUFLEN` (`any_digits`), which reaches the `buf.len() == 1` path of `digits_to_exp_str` that a fixed length of 4 could not; the comment now argues the coverage branch by branch. Testing: local Kani (model-checking/kani @ 415ca503, the pinned commit), `verify-std` with debug assertions live, one harness at a time: - flt2dec_verify (mod.rs): check_to_exact_fixed_str 0/307, check_to_exact_exp_str 0/225, check_to_shortest_exp_str 0/292, check_to_shortest_str 0/228, check_digits_to_exp_str 0/120, check_digits_to_dec_str 0/146 (all in one 45 s invocation) - grisu_verify: check_format_shortest 0/61, check_format_exact 0/52, check_format_exact_opt_buf1 0/488 (15 unreachable) (one 46 s invocation) - Attempted and not shipped: grisu format_shortest_opt direct (32-byte, exact decode() image): cadical timeout 30 min, kissat timeout 45 min, exp-window variants same 4.7M-step formula then out of memory; grisu format_exact_opt with 17-byte or 8-byte buffer: CBMC "too many addressed objects" under --object-bits 12; dragon format_exact 17-byte: timeout 45 min (unwind 41) and 40 min (unwind 20); dragon format_exact 1-byte: timeout 25 min; dragon format_shortest 24-byte: no verdict within budget. rustfmt --check with rust-lang/rust's rustfmt.toml at the pinned nightly: clean. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
…verify-flt2dec-challenge-28
|
Thanks for the careful review, @feliperodri. Both blockers were correct. This push removes every in-body assume and every dragon stub, restores the upstream Shape of the PR now. (2) (3) (1)
Non-blocking items.
Local results (model-checking/kani @ 415ca503, the pinned commit,
|
feliperodri
left a comment
There was a problem hiding this comment.
Thanks @MavenRain. Reviewed Challenge 28 with our vacuity tooling. Sound (no cfg body swaps, no T7, no assume-the-conclusion), but doesn't meet criteria:
- Coverage: 7/12 effective, not 12/12 as claimed. dragon.rs is not modified —
dragon::format_shortestanddragon::format_exacthave NO harness.grisu::format_shortest_optisn't directly proved (PR body: "covered here through the wrapper proofs below (both callees modelled as opaque)").check_format_exact_opt_buf1uses buf length 1 concretely, pruning most digit-loop paths. - PR body discrepancy: mentions
kani::assume(i < MAX_SIG_DIGITS), but no such assume exists in the diff. - No contracts (0 requires/ensures), no proof_for_contract.
Between the three open Challenge 28 solutions we're prioritizing #596 (12/12, sound). This needs actual harnesses for dragon (2 fns) and format_shortest_opt with symbolic buf length.
Call both Dragon generators and Grisu's shortest generator without stubs. Replace the one-byte exact generator harness with symbolic lengths and derive valid inputs through the real f32/f64 decoder. Add cover properties for buffer boundaries and multi-digit results, plus returned-prefix checks. Keep production bodies and CI verification settings unchanged. Document the bounded proof scope and leave full Kani validation to GitHub CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
The pinned kani_core exposes cover as a function. The cover macro belongs to the standalone kani crate and is not available while verifying core. Keep all reachability conditions and use the supported function form. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Select the existing supported solver for the four large strategy harnesses after the default solver runs encountered CI timeouts and a runner shutdown. Keep the symbolic inputs, safety properties, and unwind checks unchanged. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Limit Kani's default Rayon pool to one worker for partition 2 after repeated runner shutdowns while the Dragon and Grisu exact proofs ran concurrently. Keep all harnesses, safety checks, unwind bounds, and timeouts unchanged. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Use one verification worker after the Ubuntu autoharness runner shut down while processing the direct flt2dec proofs. Allow thirty minutes per harness after the Dragon proofs reached the previous ten-minute limit. Preserve the harness selection, unwind bounds, and safety checks. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Use Kani's default pool mode with RAYON_NUM_THREADS=1 for autoharness. The pinned Kani version omits thread labels with --jobs=1, while the existing log parser requires those labels to associate proof results. Retain one verification worker and the thirty-minute harness budget. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Override KANI_JOBS for partition 2, including the merged main-branch runner script that otherwise selects two workers. Use --jobs=1 for autoharness and retain the thirty-minute per-harness timeout. Kani omits thread labels when its pool has only one worker. Teach the log parser to associate serial output with thread zero and add tests for serial success, failure, timeout, autoharness contracts, incomplete output, and interleaved parallel results. Run these small tests in CI. Validation: four Python tests passed, workflow YAML parsed, and git diff --check passed. No local Rust build or solver was run. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Evaluate the original decimal-exponent formula at each bit-count leaf. This exposes equal results to simplification before Dragon's bigint scaling branches. Assert the nonzero-mantissa domain at every model call. Check equivalence against the real estimator for every nonzero u64 mantissa and every i16 exponent in a separate GitHub CI job. Retain all 144 full-domain generators, six contracts, existing equivalence proofs, and four diagnostic probes. Validation: formatting, whitespace, and complete CI-selection checks pass locally. Compilation and all proofs run only in GitHub CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Pass the final digit and numeric state to the verified summary. Quantify all other permitted prefix bytes inside its independent proof, leaving padding uninitialized. Assert the byte predicate on every actual caller byte before invoking the summary. Preserve the arithmetic preconditions, output metadata, real rounding helper, buffer ranges, and every finite-float group. This follows the numeric interface already used by the exact-rounding proof. Validation: formatting, whitespace, and real-helper identity checks pass locally. Run the revised contract and generator proofs in GitHub CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Partition the estimator equivalence proof by all 65 bit lengths of mantissa minus one. Their union covers every nonzero u64 mantissa and retains every i16 exponent. Keep the exact estimator model unchanged. Run f64 generator groups and estimator cases in batches of at most eight proofs. This keeps their thirty-minute proof budgets within the hosted runner job limit while retaining all 144 generator harnesses and every supporting proof. Leave the other existing CI jobs unchanged. Validation: source and shell selection checks cover every target exactly once; formatting and whitespace checks pass. All proofs run in GitHub CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Several full-range Grisu exact proofs pass near the existing 30-minute timeout, while another f64 group times out. Allow 60 minutes per generator proof and divide each f64 family into eight four-harness batches. Keep contracts and equivalence cases at 30 minutes. Every focused job retains at most four hours of proof budgets within the six-hour hosted job limit. Retain all 144 full-range generator harnesses, symbolic buffer lengths, six contracts, all equivalence cases, and diagnostic probes. No solver, unwinding assertion, input domain, or memory setting changes. Validation: static workflow selection and shell syntax checks pass for all 221 targets across 57 jobs; git diff --check passes. Formal proofs run only in the PR's GitHub CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
For the two existing f64 [1, 2) probes, assert that the independently proved estimator model returns zero for the actual call arguments, then return that literal value. This keeps estimator agreement as a checked obligation while letting symbolic expansion eliminate bigint scaling branches that the diagnostic inputs cannot reach. Keep the original exact estimator model on every full-range generator harness. Preserve all 144 generator inputs, symbolic buffer lengths, loop bounds, and CI selections. Production code is unchanged. Validation: rustfmt gate 51984757, focused selection and shell checks, git diff --check. Compilation and formal proofs run only in GitHub CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Add a Kani-only identity call before power-of-ten multiplication. Each existing float partition replaces it with a bit-prefix encoding and asserts that the encoded exponent equals the actual call argument. This exposes constant bits before symbolic expansion of bigint products. An incorrect prefix fails verification instead of discarding inputs. Derive prefixes from conservative magnitude intervals for all four f32 and thirty-two f64 groups, including the subnormal ranges in group zero. Keep all 144 generator harnesses, symbolic buffer lengths, real arithmetic, and unwinding checks. The multiplication body in normal builds is unchanged. Validation: static decoder-endpoint, identity, production-preservation, generator-body and CI-selection checks; rustfmt gate a590bd14; git diff --check. Compilation and proofs run only in GitHub CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Use one checked catalog for focused CI batches and exact exclusions from the general std partitions. Preserve all 221 targets on Linux and macOS, including all 144 full-range generator proofs. Retain new or unrecognized harnesses in the general suite and fail if a catalog target is missing. Honor the explicit worker count in partitioned runs, and return without invoking Kani for an empty partition. Add six lightweight routing tests. Validation: catalog tests, static inventory and shell-selection checks, and shell syntax. Kani proofs run only in the PR's GitHub CI; full generator verification is still incomplete. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Integrate upstream 3f8a13b, including the standard-library update, and resolve the workflow and runner conflicts. Keep the shared flt2dec catalog, full input coverage on both operating systems, and one verifier per runner. Validation: ten lightweight Python tests, complete catalog and shell selection checks, bignum formatting, and whitespace relative to main. All merge whitespace reports were in files identical to upstream. Builds and Kani proofs remain delegated to the PR's GitHub CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Add Kani-only identities at the bigint addition, subtraction, and small multiplication loop counts. Only the two existing unit-exponent Dragon probes replace these identities with masked counts and assert equality with the actual count. An incorrect count fails verification. Keep the real arithmetic loops, all 144 full-range generator harnesses, input assumptions, symbolic buffers, and unwinding checks unchanged. Normal-build source is unchanged after removing the Kani-only additions. Validation: source-preservation and complete CI selection checks, formatting, and whitespace. Builds and formal verification remain on the PR's GitHub CI; the new diagnostic assertions are not yet proved. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Add two diagnostic proofs spanning every f64 significand in [2^-8, 2^18). Expose a constant table index only after asserting equality with the index computed by the real cached-power lookup. Retain the real decoder, normalization, multiplication, generator loops and symbolic buffers. Preserve all 144 full-range generator harnesses and the previous 221 CI targets. Route the two added probes through the same diagnostic settings, with 59 batches per operating system and one verifier per runner. Validation: six routing tests, complete selector/inventory checks, formatting and whitespace passed. Static inverse checks restore both complete Rust files and confirm the 26 exponent ranges. Builds and formal proofs run only in the PR's GitHub CI; the new assertions remain unproved. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Add constant-index models for bigint addition, subtraction and small multiplication. Independent proofs compare all 40 limbs, stored sizes and returned references against the real operations, with arbitrary limb contents and multipliers. Each model asserts its caller conditions. Addition and multiplication reserve one carry limb; subtraction admits all 40 limbs and checks that it does not underflow. Select these models only in the two existing Dragon unit-exponent diagnostics, retaining their checked counts. All 144 full-range generator harnesses keep their prior paths. Add three independently selected equivalence jobs per OS, preserving every previous CI target. Validation: six routing tests, complete source/inventory/selector checks, formatting, whitespace and static source-preservation checks passed. Diffclass requires CI compilation. Builds and formal verification run only in the PR's GitHub CI; the new equivalence obligations are pending. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
The Dragon arithmetic diagnostic stubs exceed rustc's default macro recursion limit. Raise it to 256 only under cfg(kani), as requested by the compiler. Proof inputs and verification checks are unchanged. Validation: rustfmt and whitespace checks locally. Compilation and Kani validation run exclusively in the PR's GitHub CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
The checked cached-power diagnostic reaches SAT solving, then exhausts its one-hour harness limit. Give Grisu shortest proofs two hours each and select two full-range harnesses per batch, preserving the existing four-hour total proof budget per GitHub job. Store per-proof timeouts in the shared catalog, so the workflow and routing tests use the same allocation. All 226 targets, both operating systems, single-worker execution and verification flags are retained. Validation: catalog tests, source/inventory/shell selection checks and whitespace checks locally. Rust and Kani run only in GitHub CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
The combined multiplication proof reaches solving in seconds but exhausts its 30-minute limit. Specialize it for all 40 accepted storage sizes, keeping arbitrary limb contents and every u32 multiplier. The forty cases retain the original assertions, covers and unwind bound. Restore CBMC's default array field splitting for the two Dragon diagnostics, whose arithmetic models use fixed limb indices. All full-range generator paths and numeric inputs remain unchanged. Validation: source preservation, complete CI routing, catalog tests, rustfmt and whitespace locally. Rust and Kani run only in GitHub CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Extract the existing quotient and remainder operations without changing the generator's control flow. Select an exact proof model that checks the power-of-ten divisor domain at each call, and add equivalence harnesses for all ten divisors over every u32 dividend. Retain every full finite-float partition and symbolic buffer length. Route the ten new helper proofs through the PR's GitHub CI. Validation: rustfmt, six catalog tests, source preservation and CI selection checks pass locally. Kani execution remains CI-only. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
The fixed-size Kissat proofs still time out during SAT solving after fast symbolic execution. Select Kani's supported Z3 backend for the same forty cases. The pinned Linux and macOS setup scripts already install Z3. All proof inputs, model code, assertions, covers, unwind limits, and CI routing remain unchanged. Local validation uses only source and format checks; proof execution remains on the PR's GitHub CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Select the supported SMT backend for Grisu shortest's integer arithmetic after it improved the exhaustive small-multiplication proofs. Preserve all full finite-float partitions, symbolic buffer lengths, generator code, assertions, covers and unwind limits. The decimal-division model's ten independent Linux proofs have passed with all 30 covers satisfied. This change only selects a solver for the existing shortest generator harnesses and diagnostics. Validation: source diff, rustfmt and whitespace checks locally. All compilation and proof execution remain on the PR's GitHub CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Avoid the SMT datatype conversion path that exits with CBMC status 6 on Grisu shortest. Select MiniSAT for its full-range cases and multiplication equivalence cases, and enable CBMC arithmetic refinement in those CI jobs. Keep all input domains, symbolic buffer lengths, generator bodies, proof bounds, assertions, covers and target routing unchanged. Local formatting, shell argument construction and inventory checks pass; proofs run in CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Match the catalog's generator-f32 and generator-f64 kinds so all 36 full Grisu shortest partitions receive the intended arithmetic refinement. The earlier generic kind comparison reached only probes and multiplication. Static selection now confirms exactly 38 Grisu shortest and 40 multiplication targets per OS. All 275 targets, proof budgets and assertions are retained. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Prove u32 carrying_mul_add for all four input words, specifying the complete two-word result. Use verified contract adapters for carrying_mul and carrying_mul_add in Dragon generators and bigint multiplication equivalence. The scalar proof calls the real primitive and covers zero inputs, maximum inputs and the maximum result. Keep every existing generator operation, numeric partition, symbolic buffer length, assertion and proof bound. Add its standalone contract to CI, retaining all 275 existing targets. Local formatting, six routing tests, full inventory and source-preservation checks pass. Full proof execution remains in GitHub CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Use CBMC's Yices-compatible SMT encoding with the installed Z3 binary for the two existing Grisu shortest diagnostic proofs. This avoids the datatype conversion path that failed before Z3 received the earlier proof. Keep full-domain generator and multiplication jobs on their current settings, and reject conflicting encoding selections. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Select Z3 for all forty bigint multiplication equivalence cases while retaining the verified scalar multiplication contract. SAT arithmetic refinement timed out on these proofs, while Z3 completed the earlier cases 0 through 23. Keep the Grisu generator and diagnostic settings unchanged. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Extract the existing normalization, cached-power lookup and multiplication statements into a helper with a separate Kani contract proof. The contract checks an interval-width bound sufficient for 17 digits, including the full decoder exponent range. All 36 finite-float partitions retain their real digit loops and symbolic buffer lengths, and must establish the helper's preconditions from the real decoder. Keep both direct scaling diagnostics unchanged and select the new contract on Linux and macOS. Local validation used installed rustfmt, six catalog tests, exact source-preservation and CI-selection checks, and a small integer-only boundary check. Kani validation runs in the PR's GitHub CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Use a deterministic u64 product-and-add model in the 40 bigint multiplication equivalence cases. Identical carry prefixes can then share the same expression instead of separate contract-result variables. Extend the existing standalone scalar proof to compare the model with the real primitive for all four u32 inputs. Preserve its original exact postcondition and covers. Generator proofs and all CI settings are unchanged. Local formatting, exact source-scope and CI-selection checks passed; GitHub CI runs the enhanced equivalence proof and dependent cases. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Add a checked 64-bit packing lemma before the existing scalar-model pair-equality assertion. The real primitive, original contract, model, arbitrary inputs and covers remain unchanged. Equal packed values determine both u32 limbs, giving the final assertion a direct equality premise. All 40 dependent multiplication cases passed on Linux in the preceding revision; the enhanced scalar-model proof remains pending. Local formatting, source-scope and whitespace checks pass. Proof execution remains in GitHub CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Preserve the complete finite-float domain while requiring the actual power-of-two significands for unequal decoded intervals. Check either the strict center bound or separation from decimal boundaries so the real rounding loop retains its trailing-zero assertion. Use the deterministic scalar multiplication models in Dragon generator proofs after CI proved their equivalence to the real primitive. Keep the standalone proof, all full-range partitions, buffer bounds and checks. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Construct each decoded exponent from a 32-value normalized-exponent range and the actual normalization shift. The 66 ranges cover every valid scaling-contract input while reducing the cached powers that each solver must consider. Replace the single scaling target with 66 contract proofs in 17 batches per operating system. Preserve the scaling contract, all generator proofs, full finite-float coverage and existing CI checks. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Route storage validity, comparison, and zero-test models through the existing identity helper used by the arithmetic models. The two unit diagnostics can then use their equality-checked count encodings for these predicates too. Keep all full-range generator inputs, production code, assertions, and proof targets unchanged. Run the proofs in GitHub CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Each existing normalized-exponent partition selects at most three cached powers. Check the real computed index against a compact encoding before returning it through the existing proof-only identity helper. Preserve every contract input, scaling assertion, cover, generator harness, and CI target. All 66 partitions still need to pass in GitHub CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
The CVC5 and CBMC installers received 92-byte responses instead of archives, so several proof jobs exited before running a harness. Give dependency setup an isolated curl configuration that fails on HTTP errors and bounds retries, connection time, and transfer time. Preserve installer failures, archive checks, and local curl configuration. Validate the wrapper with mock installers, leaving builds and proofs to CI. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Adds Kani memory-safety harnesses for float-to-decimal formatting and revises the generator proofs following review.
Verification remains incomplete. Revision
64147ad6retains the full positive finite-float domain. Grisu exact has passed every numeric partition on Linux and macOS. All 40 bigint multiplication-equivalence cases and their standalone scalar-model dependency have passed on Linux. Dragon and full-domain Grisu shortest remain unproved. Current CI.Each of the four generator families has four
f32and 32f64bit-pattern partitions. Their union covers every positive, finite, nonzero value, including subnormals and every significand bit. Sign and zero handling belong to the outer formatting layer. Buffer lengths remain symbolic within these storage bounds:i16i16The generator harnesses call the real decoder and execute the real digit loops, including the one-byte exact-buffer case. Checked power and loop-size guides assert equality with actual computed values. There are no index assumptions, nondeterministic comparisons, or forced loop exits. Assembly harnesses and the two Grisu wrapper harnesses retain their separately scoped obligations.
The proof decomposition makes these dependencies explicit:
scale_shortest. Its contract checks both the scaled interval width needed for 17 digits and the center-or-boundary condition required by shortest rounding. The precondition includes the full decoder exponent range and significand precision; unequal intervals require the two significand forms produced by the realf32andf64decoders. All 36 generator partitions must establish these facts from the real decoder. No float-bit assumptions were added. Both the scaling contract and its callers must pass before this counts as a composed proof.[-1138, 961]into 66 ranges of at most 32 relevant values. Each proof constructs the decoded exponent using the actual normalization shift. A bounded coverage check reconstructs all 110,538 exponent/shift pairs allowed by the contract bounds and checks valid inputs at both ends of each range. Every range must pass; none is a substitute for the whole domain.u32andu64, including zero. The 65 scaling-estimator cases cover every nonzerou64mantissa and everyi16exponent.u32inputs. The maximum mathematical result isu64::MAX, so its wrapping specification is exact. The standalone harness also proves the deterministic model equal to the real primitive, first comparing packed 64-bit values and then both limbs. Dragon generators and all 40 multiplication-equivalence cases use this proved model.div_rem_pow10. Ten equivalence harnesses check everyu32dividend for literal divisors 1 through10^9. The model has no assumptions and panics on an invalid divisor, so callers must prove its domain.All unwinding and memory-safety assertions remain enabled. Grisu generator harnesses use unwind 19; full Dragon
f64harnesses use 41. Exact rounding uses 33; shortest rounding uses nine with a checked precondition allowing at most eight decrements. Grisu exact's fractional error bound reaches10^18by iteration 18, exceeding the largestmaxerr,2^59.CI selects 342 dedicated targets in 95 batches per operating system, or 190 focused jobs, with one Kani worker per job. The shared catalog excludes only those exact names from general std partitions and preserves other targets, including future harnesses. Six routing tests run in CI. The 66 scaling proofs run in 17 batches per OS, at 60 minutes per proof and at most four hours of proof budgets per batch. Grisu shortest generator proofs retain 120 minutes each. The partitioned scaling proofs replace the single scaling target while covering its complete contract domain.
Full-domain Grisu shortest uses MiniSAT arithmetic refinement. The scaling and scalar multiplication proofs use Z3. The two additional Grisu shortest diagnostics still execute scaling directly and use CBMC's Yices-compatible SMT encoding with external Z3; earlier Linux runs ended during conversion without a verdict. No Yices installation is needed. Other solver settings are retained. The existing
--no-assert-contractsoption disables implicit dependency assertions; explicitproof_for_contractandstub_verifiedobligations remain active.Two audited
f60b933cjobs failed before any harness ran because CVC5 or CBMC downloads returned 92-byte invalid archives. Dependency setup in GitHub Actions now uses temporary curl settings for HTTP-error checking and bounded retries, connection time and transfer time. Installation failures still fail the job, archive checks remain, and local curl settings are preserved. CVC5 download failure, CBMC download failure.Audited CI evidence:
f32and 32f64partitions on both operating systems, zero failed checks, on merged revision6aaa21e1. All 18 batch logs were audited. Its six helper contracts, comparison and bit-scan equivalence, and all 65 estimator cases also passed. CI run.4bd2580b, zero failures among 1,481 checks per case, exact case identities, expected covers and complete 8/0/8 summaries. Size 39 includes the carry-append cover. The slowest case took 85.41 seconds. Their standalone scalar-model dependency subsequently passed atf926a7a2. 00 through 07, 08 through 15, 16 through 23, 24 through 31, 32 through 39.f926a7a2, with all 177 checks and all three covers, in 1.44 seconds. This closes the dependency for the 40 multiplication-equivalence cases. Scalar contract and model equality.The scaling summary must establish the production rounding assertion forbidding a trailing zero. Its center-or-boundary condition is a proof obligation, not an extra assumption on input floats. Earlier scaling and generator proofs timed out or ended during solver execution; the current Dragon and full-domain Grisu shortest proofs remain pending.
Local validation uses source review, installed rustfmt, whitespace, bounded shell/argument checks and catalog/inventory checks. Exact source-preservation checks cover the extractions and proof-only changes. Integer-only boundary checks reproduced the earlier scaling-contract gap and checked the new center-or-boundary condition, including all 2,300 positive finite normal powers of two across both float types. These are sanity checks; formal verification remains a CI obligation. Rust builds and Kani proofs run only in GitHub CI.
Four bounded mock-installer checks cover success and failure in CI and outside CI, including argument forwarding, exit status, temporary-configuration removal and preservation of existing curl settings. They perform no installation or download.