FAILURE MAP
← Case archive

FA-43656 / Borrow checking / Open access

A lifetime parameter observed by the destructor is incorrectly allowed to dangle · case 01

A lifetime parameter observed by the destructor is incorrectly allowed to dangle.

Verified by executionVariant 1 · 21 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

The static analyzer mishandles may dangle observation: a lifetime parameter observed by the destructor is incorrectly allowed to dangle.

VERIFIED REPAIR

Apply the specified transfer or inference rule at this site: if bool(set(d['may_dangle'])&set(d['observed_params'])): errors.append('may-dangle-observation').

Unsuccessful approach: The partial repair uses if len(set(d['may_dangle'])&set(d['observed_params']))>1: errors.append('may-dangle-observation'), 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 False: 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 fixtureActualExpectedOutcome
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']Failed
may-dangle-observation regression 1[]['may-dangle-observation']Failed
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 / 8651d43ee1fc1777e0694e38edd512cfa53193ec20872de5fe9267e9cfa6a172

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 len(set(d['may_dangle'])&set(d['observed_params']))>1: 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 fixtureActualExpectedOutcome
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']Failed
may-dangle-observation regression 1[]['may-dangle-observation']Failed
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 / e15e79b4f1bed875d960515f853c06580d720787fbbf843200caf1ff17aaa3a8

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 fixtureActualExpectedOutcome
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.323753+00:00.

Case digest / 68601dbe1b32711681bbfa1d47c5b0ead022d03a4c4c8b0c89963abf26029f39