FA-43661 / Borrow checking / Open access
An owning phantom parameter is omitted from transitive drop checking · case 01
An owning phantom parameter is omitted from transitive drop checking.
ROOT CAUSE
The static analyzer mishandles owning phantom drop: an owning phantom parameter is omitted from transitive drop checking.
VERIFIED REPAIR
Apply the specified transfer or inference rule at this site: if not set(d['owned_phantom'])<=set(d['obligations']): errors.append('owning-phantom-drop').
Unsuccessful approach: The partial repair uses if not d['obligations'] and bool(d['owned_phantom']): errors.append('owning-phantom-drop'), which still violates the stipulated analysis contract.
Case contract
Check destructor-specific lifetime obligations. Destructor-read referents live at drop point; field destruction follows declaration order; explicit manual drop removes only its own drop obligation; may_dangle relaxation excludes observed parameters; owning phantom markers restore transitive drop obligations; arrays repeat element obligations even at nonzero symbolic lengths; partial move prohibited for custom-drop values; recursive drop dependencies need a visited set but may not skip distinct substitutions; unwinding runs initialized-field drops; destructor receiver borrow stays live through the whole body. 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 any(p<d['drop_point'] for p in d['observed_valid_until']): errors.append('destructor-read-live')
if d['field_order']!=d['decl_order']: errors.append('field-drop-order')
if set(d['removed'])!=set(d['manual']): errors.append('manual-drop-specific')
if bool(set(d['may_dangle'])&set(d['observed_params'])): errors.append('may-dangle-observation')
if False: errors.append('owning-phantom-drop')
if d['array_length']>0 and not set(d['element_obligations'])<=set(d['array_obligations']): errors.append('array-element-drop')
if d['custom_drop'] and bool(d['partial_moves']): errors.append('custom-drop-partial-move')
if not set(map(tuple,d['substitutions']))<=set(map(tuple,d['visited'])): errors.append('substitution-sensitive-visit')
if set(d['initialized'])!=set(d['unwind_drops']): errors.append('unwind-initialized-drops')
if d['receiver_end']<d['body_end']: errors.append('destructor-receiver')
return errors
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
base={'observed_valid_until': [], 'drop_point': 0, 'field_order': [], 'decl_order': [], 'manual': [], 'removed': [], 'may_dangle': [], 'observed_params': [], 'owned_phantom': [], 'obligations': [], 'array_length': 0, 'element_obligations': [], 'array_obligations': [], 'custom_drop': False, 'partial_moves': [], 'substitutions': [], 'visited': [], 'initialized': [], 'unwind_drops': [], 'receiver_end': 0, 'body_end': 0}
check('well formed empty obligations',solve(base),[])
check('destructor-read-live regression 0', solve(dict(base, **({'observed_valid_until':[N],'drop_point':N+1}))), ['destructor-read-live'])
check('destructor-read-live regression 1', solve(dict(base, **({'observed_valid_until':[N+1],'drop_point':N+2}))), ['destructor-read-live'])
check('field-drop-order regression 0', solve(dict(base, **({'field_order':['b','a'],'decl_order':['a','b']}))), ['field-drop-order'])
check('field-drop-order regression 1', solve(dict(base, **({'field_order':[N+1,N],'decl_order':[N,N+1]}))), ['field-drop-order'])
check('manual-drop-specific regression 0', solve(dict(base, **({'manual':['a'],'removed':['a','b']}))), ['manual-drop-specific'])
check('manual-drop-specific regression 1', solve(dict(base, **({'manual':[N],'removed':[N,N+1]}))), ['manual-drop-specific'])
check('may-dangle-observation regression 0', solve(dict(base, **({'may_dangle':['a'],'observed_params':['a']}))), ['may-dangle-observation'])
check('may-dangle-observation regression 1', solve(dict(base, **({'may_dangle':[N],'observed_params':[N]}))), ['may-dangle-observation'])
check('owning-phantom-drop regression 0', solve(dict(base, **({'owned_phantom':['a','b'],'obligations':['a']}))), ['owning-phantom-drop'])
check('owning-phantom-drop regression 1', solve(dict(base, **({'owned_phantom':[N,N+1],'obligations':[N]}))), ['owning-phantom-drop'])
check('array-element-drop regression 0', solve(dict(base, **({'array_length':1,'element_obligations':[N]}))), ['array-element-drop'])
check('array-element-drop regression 1', solve(dict(base, **({'array_length':1,'element_obligations':[N+1]}))), ['array-element-drop'])
check('custom-drop-partial-move regression 0', solve(dict(base, **({'custom_drop':True,'partial_moves':['f']}))), ['custom-drop-partial-move'])
check('custom-drop-partial-move regression 1', solve(dict(base, **({'custom_drop':True,'partial_moves':[N]}))), ['custom-drop-partial-move'])
check('substitution-sensitive-visit regression 0', solve(dict(base, **({'substitutions':[('T','a'),('T','b')],'visited':[('T','a')]}))), ['substitution-sensitive-visit'])
check('substitution-sensitive-visit regression 1', solve(dict(base, **({'substitutions':[('T',N),('T',N+1)],'visited':[('T',N)]}))), ['substitution-sensitive-visit'])
check('unwind-initialized-drops regression 0', solve(dict(base, **({'initialized':['a'],'unwind_drops':['a','b']}))), ['unwind-initialized-drops'])
check('unwind-initialized-drops regression 1', solve(dict(base, **({'initialized':[N],'unwind_drops':[N,N+1]}))), ['unwind-initialized-drops'])
check('destructor-receiver regression 0', solve(dict(base, **({'receiver_end':N,'body_end':N+1}))), ['destructor-receiver'])
check('destructor-receiver regression 1', solve(dict(base, **({'receiver_end':N+1,'body_end':N+2}))), ['destructor-receiver'])
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 |
| destructor-read-live regression 0 | ['destructor-read-live'] | ['destructor-read-live'] | Passed |
| destructor-read-live regression 1 | ['destructor-read-live'] | ['destructor-read-live'] | Passed |
| field-drop-order regression 0 | ['field-drop-order'] | ['field-drop-order'] | Passed |
| field-drop-order regression 1 | ['field-drop-order'] | ['field-drop-order'] | Passed |
| manual-drop-specific regression 0 | ['manual-drop-specific'] | ['manual-drop-specific'] | Passed |
| manual-drop-specific regression 1 | ['manual-drop-specific'] | ['manual-drop-specific'] | Passed |
| may-dangle-observation regression 0 | ['may-dangle-observation'] | ['may-dangle-observation'] | Passed |
| may-dangle-observation regression 1 | ['may-dangle-observation'] | ['may-dangle-observation'] | Passed |
| owning-phantom-drop regression 0 | [] | ['owning-phantom-drop'] | Failed |
| owning-phantom-drop regression 1 | [] | ['owning-phantom-drop'] | Failed |
| array-element-drop regression 0 | ['array-element-drop'] | ['array-element-drop'] | Passed |
| array-element-drop regression 1 | ['array-element-drop'] | ['array-element-drop'] | Passed |
| custom-drop-partial-move regression 0 | ['custom-drop-partial-move'] | ['custom-drop-partial-move'] | Passed |
| custom-drop-partial-move regression 1 | ['custom-drop-partial-move'] | ['custom-drop-partial-move'] | Passed |
| substitution-sensitive-visit regression 0 | ['substitution-sensitive-visit'] | ['substitution-sensitive-visit'] | Passed |
| substitution-sensitive-visit regression 1 | ['substitution-sensitive-visit'] | ['substitution-sensitive-visit'] | Passed |
| unwind-initialized-drops regression 0 | ['unwind-initialized-drops'] | ['unwind-initialized-drops'] | Passed |
| unwind-initialized-drops regression 1 | ['unwind-initialized-drops'] | ['unwind-initialized-drops'] | Passed |
| destructor-receiver regression 0 | ['destructor-receiver'] | ['destructor-receiver'] | Passed |
| destructor-receiver regression 1 | ['destructor-receiver'] | ['destructor-receiver'] | Passed |
SHA-256 / 8dca27142a0e5459aebb99826f17e6b09feb095d3fe93b44747c8979e61e4b62
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(d):
errors=[]
if any(p<d['drop_point'] for p in d['observed_valid_until']): errors.append('destructor-read-live')
if d['field_order']!=d['decl_order']: errors.append('field-drop-order')
if set(d['removed'])!=set(d['manual']): errors.append('manual-drop-specific')
if bool(set(d['may_dangle'])&set(d['observed_params'])): errors.append('may-dangle-observation')
if not d['obligations'] and bool(d['owned_phantom']): errors.append('owning-phantom-drop')
if d['array_length']>0 and not set(d['element_obligations'])<=set(d['array_obligations']): errors.append('array-element-drop')
if d['custom_drop'] and bool(d['partial_moves']): errors.append('custom-drop-partial-move')
if not set(map(tuple,d['substitutions']))<=set(map(tuple,d['visited'])): errors.append('substitution-sensitive-visit')
if set(d['initialized'])!=set(d['unwind_drops']): errors.append('unwind-initialized-drops')
if d['receiver_end']<d['body_end']: errors.append('destructor-receiver')
return errors
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
base={'observed_valid_until': [], 'drop_point': 0, 'field_order': [], 'decl_order': [], 'manual': [], 'removed': [], 'may_dangle': [], 'observed_params': [], 'owned_phantom': [], 'obligations': [], 'array_length': 0, 'element_obligations': [], 'array_obligations': [], 'custom_drop': False, 'partial_moves': [], 'substitutions': [], 'visited': [], 'initialized': [], 'unwind_drops': [], 'receiver_end': 0, 'body_end': 0}
check('well formed empty obligations',solve(base),[])
check('destructor-read-live regression 0', solve(dict(base, **({'observed_valid_until':[N],'drop_point':N+1}))), ['destructor-read-live'])
check('destructor-read-live regression 1', solve(dict(base, **({'observed_valid_until':[N+1],'drop_point':N+2}))), ['destructor-read-live'])
check('field-drop-order regression 0', solve(dict(base, **({'field_order':['b','a'],'decl_order':['a','b']}))), ['field-drop-order'])
check('field-drop-order regression 1', solve(dict(base, **({'field_order':[N+1,N],'decl_order':[N,N+1]}))), ['field-drop-order'])
check('manual-drop-specific regression 0', solve(dict(base, **({'manual':['a'],'removed':['a','b']}))), ['manual-drop-specific'])
check('manual-drop-specific regression 1', solve(dict(base, **({'manual':[N],'removed':[N,N+1]}))), ['manual-drop-specific'])
check('may-dangle-observation regression 0', solve(dict(base, **({'may_dangle':['a'],'observed_params':['a']}))), ['may-dangle-observation'])
check('may-dangle-observation regression 1', solve(dict(base, **({'may_dangle':[N],'observed_params':[N]}))), ['may-dangle-observation'])
check('owning-phantom-drop regression 0', solve(dict(base, **({'owned_phantom':['a','b'],'obligations':['a']}))), ['owning-phantom-drop'])
check('owning-phantom-drop regression 1', solve(dict(base, **({'owned_phantom':[N,N+1],'obligations':[N]}))), ['owning-phantom-drop'])
check('array-element-drop regression 0', solve(dict(base, **({'array_length':1,'element_obligations':[N]}))), ['array-element-drop'])
check('array-element-drop regression 1', solve(dict(base, **({'array_length':1,'element_obligations':[N+1]}))), ['array-element-drop'])
check('custom-drop-partial-move regression 0', solve(dict(base, **({'custom_drop':True,'partial_moves':['f']}))), ['custom-drop-partial-move'])
check('custom-drop-partial-move regression 1', solve(dict(base, **({'custom_drop':True,'partial_moves':[N]}))), ['custom-drop-partial-move'])
check('substitution-sensitive-visit regression 0', solve(dict(base, **({'substitutions':[('T','a'),('T','b')],'visited':[('T','a')]}))), ['substitution-sensitive-visit'])
check('substitution-sensitive-visit regression 1', solve(dict(base, **({'substitutions':[('T',N),('T',N+1)],'visited':[('T',N)]}))), ['substitution-sensitive-visit'])
check('unwind-initialized-drops regression 0', solve(dict(base, **({'initialized':['a'],'unwind_drops':['a','b']}))), ['unwind-initialized-drops'])
check('unwind-initialized-drops regression 1', solve(dict(base, **({'initialized':[N],'unwind_drops':[N,N+1]}))), ['unwind-initialized-drops'])
check('destructor-receiver regression 0', solve(dict(base, **({'receiver_end':N,'body_end':N+1}))), ['destructor-receiver'])
check('destructor-receiver regression 1', solve(dict(base, **({'receiver_end':N+1,'body_end':N+2}))), ['destructor-receiver'])
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 |
| destructor-read-live regression 0 | ['destructor-read-live'] | ['destructor-read-live'] | Passed |
| destructor-read-live regression 1 | ['destructor-read-live'] | ['destructor-read-live'] | Passed |
| field-drop-order regression 0 | ['field-drop-order'] | ['field-drop-order'] | Passed |
| field-drop-order regression 1 | ['field-drop-order'] | ['field-drop-order'] | Passed |
| manual-drop-specific regression 0 | ['manual-drop-specific'] | ['manual-drop-specific'] | Passed |
| manual-drop-specific regression 1 | ['manual-drop-specific'] | ['manual-drop-specific'] | Passed |
| may-dangle-observation regression 0 | ['may-dangle-observation'] | ['may-dangle-observation'] | Passed |
| may-dangle-observation regression 1 | ['may-dangle-observation'] | ['may-dangle-observation'] | Passed |
| owning-phantom-drop regression 0 | [] | ['owning-phantom-drop'] | Failed |
| owning-phantom-drop regression 1 | [] | ['owning-phantom-drop'] | Failed |
| array-element-drop regression 0 | ['array-element-drop'] | ['array-element-drop'] | Passed |
| array-element-drop regression 1 | ['array-element-drop'] | ['array-element-drop'] | Passed |
| custom-drop-partial-move regression 0 | ['custom-drop-partial-move'] | ['custom-drop-partial-move'] | Passed |
| custom-drop-partial-move regression 1 | ['custom-drop-partial-move'] | ['custom-drop-partial-move'] | Passed |
| substitution-sensitive-visit regression 0 | ['substitution-sensitive-visit'] | ['substitution-sensitive-visit'] | Passed |
| substitution-sensitive-visit regression 1 | ['substitution-sensitive-visit'] | ['substitution-sensitive-visit'] | Passed |
| unwind-initialized-drops regression 0 | ['unwind-initialized-drops'] | ['unwind-initialized-drops'] | Passed |
| unwind-initialized-drops regression 1 | ['unwind-initialized-drops'] | ['unwind-initialized-drops'] | Passed |
| destructor-receiver regression 0 | ['destructor-receiver'] | ['destructor-receiver'] | Passed |
| destructor-receiver regression 1 | ['destructor-receiver'] | ['destructor-receiver'] | Passed |
SHA-256 / 7adb6827425a2fc7dedd0f0f6fad5acd156056b96e8fb265ae56660508d10b3a
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(d):
errors=[]
if any(p<d['drop_point'] for p in d['observed_valid_until']): errors.append('destructor-read-live')
if d['field_order']!=d['decl_order']: errors.append('field-drop-order')
if set(d['removed'])!=set(d['manual']): errors.append('manual-drop-specific')
if bool(set(d['may_dangle'])&set(d['observed_params'])): errors.append('may-dangle-observation')
if not set(d['owned_phantom'])<=set(d['obligations']): errors.append('owning-phantom-drop')
if d['array_length']>0 and not set(d['element_obligations'])<=set(d['array_obligations']): errors.append('array-element-drop')
if d['custom_drop'] and bool(d['partial_moves']): errors.append('custom-drop-partial-move')
if not set(map(tuple,d['substitutions']))<=set(map(tuple,d['visited'])): errors.append('substitution-sensitive-visit')
if set(d['initialized'])!=set(d['unwind_drops']): errors.append('unwind-initialized-drops')
if d['receiver_end']<d['body_end']: errors.append('destructor-receiver')
return errors
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
base={'observed_valid_until': [], 'drop_point': 0, 'field_order': [], 'decl_order': [], 'manual': [], 'removed': [], 'may_dangle': [], 'observed_params': [], 'owned_phantom': [], 'obligations': [], 'array_length': 0, 'element_obligations': [], 'array_obligations': [], 'custom_drop': False, 'partial_moves': [], 'substitutions': [], 'visited': [], 'initialized': [], 'unwind_drops': [], 'receiver_end': 0, 'body_end': 0}
check('well formed empty obligations',solve(base),[])
check('destructor-read-live regression 0', solve(dict(base, **({'observed_valid_until':[N],'drop_point':N+1}))), ['destructor-read-live'])
check('destructor-read-live regression 1', solve(dict(base, **({'observed_valid_until':[N+1],'drop_point':N+2}))), ['destructor-read-live'])
check('field-drop-order regression 0', solve(dict(base, **({'field_order':['b','a'],'decl_order':['a','b']}))), ['field-drop-order'])
check('field-drop-order regression 1', solve(dict(base, **({'field_order':[N+1,N],'decl_order':[N,N+1]}))), ['field-drop-order'])
check('manual-drop-specific regression 0', solve(dict(base, **({'manual':['a'],'removed':['a','b']}))), ['manual-drop-specific'])
check('manual-drop-specific regression 1', solve(dict(base, **({'manual':[N],'removed':[N,N+1]}))), ['manual-drop-specific'])
check('may-dangle-observation regression 0', solve(dict(base, **({'may_dangle':['a'],'observed_params':['a']}))), ['may-dangle-observation'])
check('may-dangle-observation regression 1', solve(dict(base, **({'may_dangle':[N],'observed_params':[N]}))), ['may-dangle-observation'])
check('owning-phantom-drop regression 0', solve(dict(base, **({'owned_phantom':['a','b'],'obligations':['a']}))), ['owning-phantom-drop'])
check('owning-phantom-drop regression 1', solve(dict(base, **({'owned_phantom':[N,N+1],'obligations':[N]}))), ['owning-phantom-drop'])
check('array-element-drop regression 0', solve(dict(base, **({'array_length':1,'element_obligations':[N]}))), ['array-element-drop'])
check('array-element-drop regression 1', solve(dict(base, **({'array_length':1,'element_obligations':[N+1]}))), ['array-element-drop'])
check('custom-drop-partial-move regression 0', solve(dict(base, **({'custom_drop':True,'partial_moves':['f']}))), ['custom-drop-partial-move'])
check('custom-drop-partial-move regression 1', solve(dict(base, **({'custom_drop':True,'partial_moves':[N]}))), ['custom-drop-partial-move'])
check('substitution-sensitive-visit regression 0', solve(dict(base, **({'substitutions':[('T','a'),('T','b')],'visited':[('T','a')]}))), ['substitution-sensitive-visit'])
check('substitution-sensitive-visit regression 1', solve(dict(base, **({'substitutions':[('T',N),('T',N+1)],'visited':[('T',N)]}))), ['substitution-sensitive-visit'])
check('unwind-initialized-drops regression 0', solve(dict(base, **({'initialized':['a'],'unwind_drops':['a','b']}))), ['unwind-initialized-drops'])
check('unwind-initialized-drops regression 1', solve(dict(base, **({'initialized':[N],'unwind_drops':[N,N+1]}))), ['unwind-initialized-drops'])
check('destructor-receiver regression 0', solve(dict(base, **({'receiver_end':N,'body_end':N+1}))), ['destructor-receiver'])
check('destructor-receiver regression 1', solve(dict(base, **({'receiver_end':N+1,'body_end':N+2}))), ['destructor-receiver'])
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 |
| destructor-read-live regression 0 | ['destructor-read-live'] | ['destructor-read-live'] | Passed |
| destructor-read-live regression 1 | ['destructor-read-live'] | ['destructor-read-live'] | Passed |
| field-drop-order regression 0 | ['field-drop-order'] | ['field-drop-order'] | Passed |
| field-drop-order regression 1 | ['field-drop-order'] | ['field-drop-order'] | Passed |
| manual-drop-specific regression 0 | ['manual-drop-specific'] | ['manual-drop-specific'] | Passed |
| manual-drop-specific regression 1 | ['manual-drop-specific'] | ['manual-drop-specific'] | Passed |
| may-dangle-observation regression 0 | ['may-dangle-observation'] | ['may-dangle-observation'] | Passed |
| may-dangle-observation regression 1 | ['may-dangle-observation'] | ['may-dangle-observation'] | Passed |
| owning-phantom-drop regression 0 | ['owning-phantom-drop'] | ['owning-phantom-drop'] | Passed |
| owning-phantom-drop regression 1 | ['owning-phantom-drop'] | ['owning-phantom-drop'] | Passed |
| array-element-drop regression 0 | ['array-element-drop'] | ['array-element-drop'] | Passed |
| array-element-drop regression 1 | ['array-element-drop'] | ['array-element-drop'] | Passed |
| custom-drop-partial-move regression 0 | ['custom-drop-partial-move'] | ['custom-drop-partial-move'] | Passed |
| custom-drop-partial-move regression 1 | ['custom-drop-partial-move'] | ['custom-drop-partial-move'] | Passed |
| substitution-sensitive-visit regression 0 | ['substitution-sensitive-visit'] | ['substitution-sensitive-visit'] | Passed |
| substitution-sensitive-visit regression 1 | ['substitution-sensitive-visit'] | ['substitution-sensitive-visit'] | Passed |
| unwind-initialized-drops regression 0 | ['unwind-initialized-drops'] | ['unwind-initialized-drops'] | Passed |
| unwind-initialized-drops regression 1 | ['unwind-initialized-drops'] | ['unwind-initialized-drops'] | Passed |
| destructor-receiver regression 0 | ['destructor-receiver'] | ['destructor-receiver'] | Passed |
| destructor-receiver regression 1 | ['destructor-receiver'] | ['destructor-receiver'] | Passed |
SHA-256 / 3eb8c91360d7c133044378b733fc7ce05bb19aa17051332efc3dc6cebc47a0aa
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:04.323365+00:00.
Case digest / bb3280cfbe9ca5bd5bb343578d21eeb65131ea73c391ae091afabba96b9256e5