Skip to content

DFCC: scope skipped inner-loop locals to their enclosing scope - #9164

Merged
feliperodri merged 1 commit into
diffblue:developfrom
feliperodri:dfcc-skipped-loop-callee-locals
Sep 16, 2026
Merged

feliperodri merged 1 commit into
diffblue:developfrom
feliperodri:dfcc-skipped-loop-callee-locals

Conversation

@feliperodri

Copy link
Copy Markdown
Collaborator

What

When applying loop contracts, a loop without a contract is skipped (not abstracted) and shares its enclosing scope's write set. dfcc_cfg_infot::get_tracked_set and get_local_set resolved the active scope from the raw loop id, while get_write_set resolved outward through skipped loops via get_first_id_not_skipped_or_top_level_id. Inside a skipped loop these disagreed about which scope was active, so writes to a skipped loop's locals were checked against the enclosing loop's assigns clause and failed spuriously.

This surfaces for a callee that contains a contract-less loop — for example CBMC's built-in memcmp model, whose loop is unwound at solve time — invoked from within a loop that has a contract. The callee's loop locals (res, sc1, sc2, ...) are reported as not assignable even though they are genuinely local. Originally reported against Kani, where slice == slice lowers to the memcmp model: model-checking/kani#4790.

Fix

  • get_tracked_set / get_local_set: resolve the active scope through skipped loops (get_first_id_not_skipped_or_top_level_id), matching get_write_set.
  • gen_dfcc_loop_info: keep skipped inner loops' locals instead of excluding them (they are not abstracted, so they belong to the enclosing scope).
  • Top-level DECL collection: treat DECLs declared in skipped loops whose enclosing non-skipped scope is the top level as top-level locals.

Testing

New regression test regression/contracts-dfcc/loop_contracts_callee_with_loop: an outer loop with a contract calls a helper containing a contract-less loop. The helper's loop locals must be scoped to the callee. The test fails without the fix (4 spurious cmp.assigns ... : FAILURE, VERIFICATION FAILED) and passes with it, and its "forbidden patterns" block asserts no cmp.assigns ... is assignable: FAILURE remains.


  • Each commit message has a non-empty body, explaining why the change was made.
  • Regression tests are included, in the same commit as the fix.
  • My PR is restricted to a single bugfix.
  • No unrelated white-space or formatting changes.

When applying loop contracts, a loop without a contract is "skipped" (not
abstracted) and shares its enclosing scope's write set. get_tracked_set and
get_local_set resolved the active scope from the raw loop id, while
get_write_set resolved outward through skipped loops via
get_first_id_not_skipped_or_top_level_id. Inside a skipped loop the three
disagreed about the active scope, so writes to a skipped loop's locals were
checked against the enclosing loop's assigns clause and failed spuriously.

This surfaced for callees that contain a contract-less loop (for example the
built-in memcmp model, whose loop is unwound at solve time) invoked from
within a loop that has a contract: the callee's loop locals were reported as
not assignable. See the discussion in model-checking/kani#4790.

Resolve the active scope through skipped loops in get_tracked_set and
get_local_set, keep skipped inner loops' locals in gen_dfcc_loop_info, and
collect DECLs declared in skipped loops as top-level locals.
@codecov

codecov Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.83%. Comparing base (820ff0f) to head (09fe194).

Additional details and impacted files
@@           Coverage Diff            @@
##           develop    #9164   +/-   ##
========================================
  Coverage    80.83%   80.83%           
========================================
  Files         1717     1717           
  Lines       190069   190074    +5     
  Branches        73       73           
========================================
+ Hits        153647   153653    +6     
+ Misses       36422    36421    -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@feliperodri feliperodri added the Code Contracts Function and loop contracts label Sep 14, 2026
@feliperodri
feliperodri merged commit 70d9def into diffblue:develop Sep 16, 2026
43 checks passed
hung1962003 added a commit to hung1962003/verify-rust-std that referenced this pull request Oct 28, 2026
check_from_ptr_contract reaches CharSearcher::next_match only through
panic formatting, where it never runs. Its new loop contract made Kani
apply loop contracts to this harness, and CBMC 6.11 then rejects the
contract-less loop of its built-in strlen ("Check that len is
assignable", fixed by diffblue/cbmc#9164). Stub next_match in this
harness so the loop contract is not compiled into it; in the
instrumented program next_match still has no body, so the harness
proves the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Code Contracts Function and loop contracts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants