FA-43976 / Borrow checking / Open access
Collected lending references survive the iteration that owns their loan · case 01
Collected lending references survive the iteration that owns their loan.
ROOT CAUSE
The static analyzer mishandles lending collection: collected lending references survive the iteration that owns their loan.
VERIFIED REPAIR
Apply the specified transfer or inference rule at this site: if d['collected'] and d['collection_end']>d['iteration_end']: errors.append('lending-collection').
Unsuccessful approach: The partial repair uses if d['collected'] and d['collection_end']>d['iteration_end']+1: errors.append('lending-collection'), which still violates the stipulated analysis contract.
Case contract
Check iterator lifetime summaries. Yielded shared items borrow backing storage; lending items borrow the iterator until next call; mutable yields must have disjoint element footprints; next consumes the prior lending-item loan; double-ended iteration cannot yield a crossed element twice; chunked iteration retains remainder ownership; adapter closures cannot extend item lifetime; collection of lending references cannot outlive iteration; empty iterator yields no loan; iterator destruction releases its backing borrow but not independently owned items. Input is a fully explicit descriptor of the stated toy IR. Return rule identifiers in declaration order; absent optional obligations use the provided neutral defaults. No rule is an assertion about a production language.
Why this case matters
A finite offline static-analysis model of ownership and borrowing; it does not execute the analyzed program.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(d):
errors=[]
if not set(d['yield_origins'])<=set(d['backing_origins']): errors.append('yield-origin')
if d['lending'] and d['item_end']>d['next_point']: errors.append('lending-call-bound')
if d['mutable'] and bool(set(d['yield_a'])&set(d['yield_b'])): errors.append('mutable-yield-disjoint')
if d['next_called'] and d['prior_lending_live']: errors.append('next-reborrow-kill')
if bool(set(d['front_indices'])&set(d['back_indices'])): errors.append('double-ended-crossing')
if not set(d['remainder'])<=set(d['accounted']): errors.append('chunk-remainder')
if d['adapter_bound']>d['item_bound']: errors.append('adapter-lifetime')
if False: errors.append('lending-collection')
if d['empty'] and bool(d['yielded_loans']): errors.append('empty-yield')
if d['destroyed'] and bool(set(d['owned_items'])&set(d['invalidated_items'])): errors.append('iterator-drop-owned-items')
return errors
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
base={'yield_origins': [], 'backing_origins': [], 'lending': False, 'item_end': 0, 'next_point': 0, 'yield_a': [], 'yield_b': [], 'mutable': False, 'prior_lending_live': False, 'next_called': False, 'front_indices': [], 'back_indices': [], 'remainder': [], 'accounted': [], 'adapter_bound': 0, 'item_bound': 0, 'collected': False, 'collection_end': 0, 'iteration_end': 0, 'empty': False, 'yielded_loans': [], 'destroyed': False, 'backing_live': False, 'owned_items': [], 'invalidated_items': []}
check('well formed empty obligations',solve(base),[])
check('yield-origin regression 0', solve(dict(base, **({'yield_origins':['a','b'],'backing_origins':['a']}))), ['yield-origin'])
check('yield-origin regression 1', solve(dict(base, **({'yield_origins':[N,N+1],'backing_origins':[N]}))), ['yield-origin'])
check('lending-call-bound regression 0', solve(dict(base, **({'lending':True,'item_end':N+1,'next_point':N}))), ['lending-call-bound'])
check('lending-call-bound regression 1', solve(dict(base, **({'lending':True,'item_end':N+2,'next_point':N+1}))), ['lending-call-bound'])
check('mutable-yield-disjoint regression 0', solve(dict(base, **({'mutable':True,'yield_a':[N,N+1],'yield_b':[N]}))), ['mutable-yield-disjoint'])
check('mutable-yield-disjoint regression 1', solve(dict(base, **({'mutable':True,'yield_a':[N],'yield_b':[N,N+1]}))), ['mutable-yield-disjoint'])
check('next-reborrow-kill regression 0', solve(dict(base, **({'next_called':True,'prior_lending_live':True}))), ['next-reborrow-kill'])
check('next-reborrow-kill regression 1', solve(dict(base, **({'next_called':True,'prior_lending_live':True,'next_point':N}))), ['next-reborrow-kill'])
check('double-ended-crossing regression 0', solve(dict(base, **({'front_indices':[N,N+1],'back_indices':[N+1]}))), ['double-ended-crossing'])
check('double-ended-crossing regression 1', solve(dict(base, **({'front_indices':[N],'back_indices':[N,N+1]}))), ['double-ended-crossing'])
check('chunk-remainder regression 0', solve(dict(base, **({'remainder':[N,N+1],'accounted':[N]}))), ['chunk-remainder'])
check('chunk-remainder regression 1', solve(dict(base, **({'remainder':list(range(N+1)),'accounted':[0]}))), ['chunk-remainder'])
check('adapter-lifetime regression 0', solve(dict(base, **({'adapter_bound':N+1,'item_bound':N}))), ['adapter-lifetime'])
check('adapter-lifetime regression 1', solve(dict(base, **({'adapter_bound':N+2,'item_bound':N+1}))), ['adapter-lifetime'])
check('lending-collection regression 0', solve(dict(base, **({'collected':True,'collection_end':N+1,'iteration_end':N}))), ['lending-collection'])
check('lending-collection regression 1', solve(dict(base, **({'collected':True,'collection_end':N+2,'iteration_end':N+1}))), ['lending-collection'])
check('empty-yield regression 0', solve(dict(base, **({'empty':True,'yielded_loans':['r']}))), ['empty-yield'])
check('empty-yield regression 1', solve(dict(base, **({'empty':True,'yielded_loans':[N]}))), ['empty-yield'])
check('iterator-drop-owned-items regression 0', solve(dict(base, **({'destroyed':True,'owned_items':['x'],'invalidated_items':['x']}))), ['iterator-drop-owned-items'])
check('iterator-drop-owned-items regression 1', solve(dict(base, **({'destroyed':True,'owned_items':[N],'invalidated_items':[N]}))), ['iterator-drop-owned-items'])
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| well formed empty obligations | [] | [] | Passed |
| yield-origin regression 0 | ['yield-origin'] | ['yield-origin'] | Passed |
| yield-origin regression 1 | ['yield-origin'] | ['yield-origin'] | Passed |
| lending-call-bound regression 0 | ['lending-call-bound'] | ['lending-call-bound'] | Passed |
| lending-call-bound regression 1 | ['lending-call-bound'] | ['lending-call-bound'] | Passed |
| mutable-yield-disjoint regression 0 | ['mutable-yield-disjoint'] | ['mutable-yield-disjoint'] | Passed |
| mutable-yield-disjoint regression 1 | ['mutable-yield-disjoint'] | ['mutable-yield-disjoint'] | Passed |
| next-reborrow-kill regression 0 | ['next-reborrow-kill'] | ['next-reborrow-kill'] | Passed |
| next-reborrow-kill regression 1 | ['next-reborrow-kill'] | ['next-reborrow-kill'] | Passed |
| double-ended-crossing regression 0 | ['double-ended-crossing'] | ['double-ended-crossing'] | Passed |
| double-ended-crossing regression 1 | ['double-ended-crossing'] | ['double-ended-crossing'] | Passed |
| chunk-remainder regression 0 | ['chunk-remainder'] | ['chunk-remainder'] | Passed |
| chunk-remainder regression 1 | ['chunk-remainder'] | ['chunk-remainder'] | Passed |
| adapter-lifetime regression 0 | ['adapter-lifetime'] | ['adapter-lifetime'] | Passed |
| adapter-lifetime regression 1 | ['adapter-lifetime'] | ['adapter-lifetime'] | Passed |
| lending-collection regression 0 | [] | ['lending-collection'] | Failed |
| lending-collection regression 1 | [] | ['lending-collection'] | Failed |
| empty-yield regression 0 | ['empty-yield'] | ['empty-yield'] | Passed |
| empty-yield regression 1 | ['empty-yield'] | ['empty-yield'] | Passed |
| iterator-drop-owned-items regression 0 | ['iterator-drop-owned-items'] | ['iterator-drop-owned-items'] | Passed |
| iterator-drop-owned-items regression 1 | ['iterator-drop-owned-items'] | ['iterator-drop-owned-items'] | Passed |
SHA-256 / 1e9cb53f29e19b935a73e7bf030651a242d30f0a9bf879e1e507d0d1f2513589
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(d):
errors=[]
if not set(d['yield_origins'])<=set(d['backing_origins']): errors.append('yield-origin')
if d['lending'] and d['item_end']>d['next_point']: errors.append('lending-call-bound')
if d['mutable'] and bool(set(d['yield_a'])&set(d['yield_b'])): errors.append('mutable-yield-disjoint')
if d['next_called'] and d['prior_lending_live']: errors.append('next-reborrow-kill')
if bool(set(d['front_indices'])&set(d['back_indices'])): errors.append('double-ended-crossing')
if not set(d['remainder'])<=set(d['accounted']): errors.append('chunk-remainder')
if d['adapter_bound']>d['item_bound']: errors.append('adapter-lifetime')
if d['collected'] and d['collection_end']>d['iteration_end']+1: errors.append('lending-collection')
if d['empty'] and bool(d['yielded_loans']): errors.append('empty-yield')
if d['destroyed'] and bool(set(d['owned_items'])&set(d['invalidated_items'])): errors.append('iterator-drop-owned-items')
return errors
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
base={'yield_origins': [], 'backing_origins': [], 'lending': False, 'item_end': 0, 'next_point': 0, 'yield_a': [], 'yield_b': [], 'mutable': False, 'prior_lending_live': False, 'next_called': False, 'front_indices': [], 'back_indices': [], 'remainder': [], 'accounted': [], 'adapter_bound': 0, 'item_bound': 0, 'collected': False, 'collection_end': 0, 'iteration_end': 0, 'empty': False, 'yielded_loans': [], 'destroyed': False, 'backing_live': False, 'owned_items': [], 'invalidated_items': []}
check('well formed empty obligations',solve(base),[])
check('yield-origin regression 0', solve(dict(base, **({'yield_origins':['a','b'],'backing_origins':['a']}))), ['yield-origin'])
check('yield-origin regression 1', solve(dict(base, **({'yield_origins':[N,N+1],'backing_origins':[N]}))), ['yield-origin'])
check('lending-call-bound regression 0', solve(dict(base, **({'lending':True,'item_end':N+1,'next_point':N}))), ['lending-call-bound'])
check('lending-call-bound regression 1', solve(dict(base, **({'lending':True,'item_end':N+2,'next_point':N+1}))), ['lending-call-bound'])
check('mutable-yield-disjoint regression 0', solve(dict(base, **({'mutable':True,'yield_a':[N,N+1],'yield_b':[N]}))), ['mutable-yield-disjoint'])
check('mutable-yield-disjoint regression 1', solve(dict(base, **({'mutable':True,'yield_a':[N],'yield_b':[N,N+1]}))), ['mutable-yield-disjoint'])
check('next-reborrow-kill regression 0', solve(dict(base, **({'next_called':True,'prior_lending_live':True}))), ['next-reborrow-kill'])
check('next-reborrow-kill regression 1', solve(dict(base, **({'next_called':True,'prior_lending_live':True,'next_point':N}))), ['next-reborrow-kill'])
check('double-ended-crossing regression 0', solve(dict(base, **({'front_indices':[N,N+1],'back_indices':[N+1]}))), ['double-ended-crossing'])
check('double-ended-crossing regression 1', solve(dict(base, **({'front_indices':[N],'back_indices':[N,N+1]}))), ['double-ended-crossing'])
check('chunk-remainder regression 0', solve(dict(base, **({'remainder':[N,N+1],'accounted':[N]}))), ['chunk-remainder'])
check('chunk-remainder regression 1', solve(dict(base, **({'remainder':list(range(N+1)),'accounted':[0]}))), ['chunk-remainder'])
check('adapter-lifetime regression 0', solve(dict(base, **({'adapter_bound':N+1,'item_bound':N}))), ['adapter-lifetime'])
check('adapter-lifetime regression 1', solve(dict(base, **({'adapter_bound':N+2,'item_bound':N+1}))), ['adapter-lifetime'])
check('lending-collection regression 0', solve(dict(base, **({'collected':True,'collection_end':N+1,'iteration_end':N}))), ['lending-collection'])
check('lending-collection regression 1', solve(dict(base, **({'collected':True,'collection_end':N+2,'iteration_end':N+1}))), ['lending-collection'])
check('empty-yield regression 0', solve(dict(base, **({'empty':True,'yielded_loans':['r']}))), ['empty-yield'])
check('empty-yield regression 1', solve(dict(base, **({'empty':True,'yielded_loans':[N]}))), ['empty-yield'])
check('iterator-drop-owned-items regression 0', solve(dict(base, **({'destroyed':True,'owned_items':['x'],'invalidated_items':['x']}))), ['iterator-drop-owned-items'])
check('iterator-drop-owned-items regression 1', solve(dict(base, **({'destroyed':True,'owned_items':[N],'invalidated_items':[N]}))), ['iterator-drop-owned-items'])
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| well formed empty obligations | [] | [] | Passed |
| yield-origin regression 0 | ['yield-origin'] | ['yield-origin'] | Passed |
| yield-origin regression 1 | ['yield-origin'] | ['yield-origin'] | Passed |
| lending-call-bound regression 0 | ['lending-call-bound'] | ['lending-call-bound'] | Passed |
| lending-call-bound regression 1 | ['lending-call-bound'] | ['lending-call-bound'] | Passed |
| mutable-yield-disjoint regression 0 | ['mutable-yield-disjoint'] | ['mutable-yield-disjoint'] | Passed |
| mutable-yield-disjoint regression 1 | ['mutable-yield-disjoint'] | ['mutable-yield-disjoint'] | Passed |
| next-reborrow-kill regression 0 | ['next-reborrow-kill'] | ['next-reborrow-kill'] | Passed |
| next-reborrow-kill regression 1 | ['next-reborrow-kill'] | ['next-reborrow-kill'] | Passed |
| double-ended-crossing regression 0 | ['double-ended-crossing'] | ['double-ended-crossing'] | Passed |
| double-ended-crossing regression 1 | ['double-ended-crossing'] | ['double-ended-crossing'] | Passed |
| chunk-remainder regression 0 | ['chunk-remainder'] | ['chunk-remainder'] | Passed |
| chunk-remainder regression 1 | ['chunk-remainder'] | ['chunk-remainder'] | Passed |
| adapter-lifetime regression 0 | ['adapter-lifetime'] | ['adapter-lifetime'] | Passed |
| adapter-lifetime regression 1 | ['adapter-lifetime'] | ['adapter-lifetime'] | Passed |
| lending-collection regression 0 | [] | ['lending-collection'] | Failed |
| lending-collection regression 1 | [] | ['lending-collection'] | Failed |
| empty-yield regression 0 | ['empty-yield'] | ['empty-yield'] | Passed |
| empty-yield regression 1 | ['empty-yield'] | ['empty-yield'] | Passed |
| iterator-drop-owned-items regression 0 | ['iterator-drop-owned-items'] | ['iterator-drop-owned-items'] | Passed |
| iterator-drop-owned-items regression 1 | ['iterator-drop-owned-items'] | ['iterator-drop-owned-items'] | Passed |
SHA-256 / ea827f9df44112ee00452238d898fa4eabb27058b125b8d3a8e00e6728f30497
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(d):
errors=[]
if not set(d['yield_origins'])<=set(d['backing_origins']): errors.append('yield-origin')
if d['lending'] and d['item_end']>d['next_point']: errors.append('lending-call-bound')
if d['mutable'] and bool(set(d['yield_a'])&set(d['yield_b'])): errors.append('mutable-yield-disjoint')
if d['next_called'] and d['prior_lending_live']: errors.append('next-reborrow-kill')
if bool(set(d['front_indices'])&set(d['back_indices'])): errors.append('double-ended-crossing')
if not set(d['remainder'])<=set(d['accounted']): errors.append('chunk-remainder')
if d['adapter_bound']>d['item_bound']: errors.append('adapter-lifetime')
if d['collected'] and d['collection_end']>d['iteration_end']: errors.append('lending-collection')
if d['empty'] and bool(d['yielded_loans']): errors.append('empty-yield')
if d['destroyed'] and bool(set(d['owned_items'])&set(d['invalidated_items'])): errors.append('iterator-drop-owned-items')
return errors
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
base={'yield_origins': [], 'backing_origins': [], 'lending': False, 'item_end': 0, 'next_point': 0, 'yield_a': [], 'yield_b': [], 'mutable': False, 'prior_lending_live': False, 'next_called': False, 'front_indices': [], 'back_indices': [], 'remainder': [], 'accounted': [], 'adapter_bound': 0, 'item_bound': 0, 'collected': False, 'collection_end': 0, 'iteration_end': 0, 'empty': False, 'yielded_loans': [], 'destroyed': False, 'backing_live': False, 'owned_items': [], 'invalidated_items': []}
check('well formed empty obligations',solve(base),[])
check('yield-origin regression 0', solve(dict(base, **({'yield_origins':['a','b'],'backing_origins':['a']}))), ['yield-origin'])
check('yield-origin regression 1', solve(dict(base, **({'yield_origins':[N,N+1],'backing_origins':[N]}))), ['yield-origin'])
check('lending-call-bound regression 0', solve(dict(base, **({'lending':True,'item_end':N+1,'next_point':N}))), ['lending-call-bound'])
check('lending-call-bound regression 1', solve(dict(base, **({'lending':True,'item_end':N+2,'next_point':N+1}))), ['lending-call-bound'])
check('mutable-yield-disjoint regression 0', solve(dict(base, **({'mutable':True,'yield_a':[N,N+1],'yield_b':[N]}))), ['mutable-yield-disjoint'])
check('mutable-yield-disjoint regression 1', solve(dict(base, **({'mutable':True,'yield_a':[N],'yield_b':[N,N+1]}))), ['mutable-yield-disjoint'])
check('next-reborrow-kill regression 0', solve(dict(base, **({'next_called':True,'prior_lending_live':True}))), ['next-reborrow-kill'])
check('next-reborrow-kill regression 1', solve(dict(base, **({'next_called':True,'prior_lending_live':True,'next_point':N}))), ['next-reborrow-kill'])
check('double-ended-crossing regression 0', solve(dict(base, **({'front_indices':[N,N+1],'back_indices':[N+1]}))), ['double-ended-crossing'])
check('double-ended-crossing regression 1', solve(dict(base, **({'front_indices':[N],'back_indices':[N,N+1]}))), ['double-ended-crossing'])
check('chunk-remainder regression 0', solve(dict(base, **({'remainder':[N,N+1],'accounted':[N]}))), ['chunk-remainder'])
check('chunk-remainder regression 1', solve(dict(base, **({'remainder':list(range(N+1)),'accounted':[0]}))), ['chunk-remainder'])
check('adapter-lifetime regression 0', solve(dict(base, **({'adapter_bound':N+1,'item_bound':N}))), ['adapter-lifetime'])
check('adapter-lifetime regression 1', solve(dict(base, **({'adapter_bound':N+2,'item_bound':N+1}))), ['adapter-lifetime'])
check('lending-collection regression 0', solve(dict(base, **({'collected':True,'collection_end':N+1,'iteration_end':N}))), ['lending-collection'])
check('lending-collection regression 1', solve(dict(base, **({'collected':True,'collection_end':N+2,'iteration_end':N+1}))), ['lending-collection'])
check('empty-yield regression 0', solve(dict(base, **({'empty':True,'yielded_loans':['r']}))), ['empty-yield'])
check('empty-yield regression 1', solve(dict(base, **({'empty':True,'yielded_loans':[N]}))), ['empty-yield'])
check('iterator-drop-owned-items regression 0', solve(dict(base, **({'destroyed':True,'owned_items':['x'],'invalidated_items':['x']}))), ['iterator-drop-owned-items'])
check('iterator-drop-owned-items regression 1', solve(dict(base, **({'destroyed':True,'owned_items':[N],'invalidated_items':[N]}))), ['iterator-drop-owned-items'])
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| well formed empty obligations | [] | [] | Passed |
| yield-origin regression 0 | ['yield-origin'] | ['yield-origin'] | Passed |
| yield-origin regression 1 | ['yield-origin'] | ['yield-origin'] | Passed |
| lending-call-bound regression 0 | ['lending-call-bound'] | ['lending-call-bound'] | Passed |
| lending-call-bound regression 1 | ['lending-call-bound'] | ['lending-call-bound'] | Passed |
| mutable-yield-disjoint regression 0 | ['mutable-yield-disjoint'] | ['mutable-yield-disjoint'] | Passed |
| mutable-yield-disjoint regression 1 | ['mutable-yield-disjoint'] | ['mutable-yield-disjoint'] | Passed |
| next-reborrow-kill regression 0 | ['next-reborrow-kill'] | ['next-reborrow-kill'] | Passed |
| next-reborrow-kill regression 1 | ['next-reborrow-kill'] | ['next-reborrow-kill'] | Passed |
| double-ended-crossing regression 0 | ['double-ended-crossing'] | ['double-ended-crossing'] | Passed |
| double-ended-crossing regression 1 | ['double-ended-crossing'] | ['double-ended-crossing'] | Passed |
| chunk-remainder regression 0 | ['chunk-remainder'] | ['chunk-remainder'] | Passed |
| chunk-remainder regression 1 | ['chunk-remainder'] | ['chunk-remainder'] | Passed |
| adapter-lifetime regression 0 | ['adapter-lifetime'] | ['adapter-lifetime'] | Passed |
| adapter-lifetime regression 1 | ['adapter-lifetime'] | ['adapter-lifetime'] | Passed |
| lending-collection regression 0 | ['lending-collection'] | ['lending-collection'] | Passed |
| lending-collection regression 1 | ['lending-collection'] | ['lending-collection'] | Passed |
| empty-yield regression 0 | ['empty-yield'] | ['empty-yield'] | Passed |
| empty-yield regression 1 | ['empty-yield'] | ['empty-yield'] | Passed |
| iterator-drop-owned-items regression 0 | ['iterator-drop-owned-items'] | ['iterator-drop-owned-items'] | Passed |
| iterator-drop-owned-items regression 1 | ['iterator-drop-owned-items'] | ['iterator-drop-owned-items'] | Passed |
SHA-256 / 823152520bf9a812147baf7d5ac93ee9cce3f0701272736156f0a92269dd0af9
Verification & scope
The explicitly stated toy language is the complete scope; this is not a production compiler or a claim about Rust semantics. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:44:07.474088+00:00.
Case digest / bfd0addbb1ae44169fdd2ab846871d67285b742de91e79afceb16dacbf0d772a