[GH-ISSUE #6781] [Bug] using disposal leaves stale literals in exported return evidence #4135

Closed
opened 2026-10-31 00:47:04 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Oct 23, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/6781

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

The public analyze-javascript-application CLI reports literal 1 for a function that returns 2 after a using declaration disposes a local object. Its disposer writes this.value = 2 at the end of the inner block, before the return expression. The complete application graph retains the earlier literal as an exact exported return field. Explicitly calling the same disposer instead correctly yields an unknown field; omitting disposal correctly preserves literal 1.

Reproduction

From the prepared source checkout identified below, with locked dependencies and its compiled runtime, run this self-contained Python 3 block. This exact script was executed once for the final three-case probe. It creates only disposable fixtures and isolated configuration beneath /tmp. REA analyzes source statically; the separate Node process executes only the self-authored inert module.

python3 - <<'PYPROBE'
import json, os, pathlib, subprocess, tempfile
repo = pathlib.Path.cwd()
root = pathlib.Path(tempfile.mkdtemp(prefix='rea-disposal-', dir='/tmp'))
for name in ('home', 'config', 'cache', 'temp'):
    (root / name).mkdir()
env = {'PATH': os.environ['PATH'], 'HOME': str(root/'home'),
       'XDG_CONFIG_HOME': str(root/'config'), 'XDG_CACHE_HOME': str(root/'cache'),
       'TMPDIR': str(root/'temp')}
cases = {
    'using': 'export function read() { const state = {[Symbol.dispose]() { this.value = 2; }, value: 1}; { using owned = state; } return state.value; }',
    'explicit-call': 'export function read() { const state = {[Symbol.dispose]() { this.value = 2; }, value: 1}; state[Symbol.dispose](); return state.value; }',
    'no-disposal': 'export function read() { const state = {[Symbol.dispose]() { this.value = 2; }, value: 1}; return state.value; }',
}
for case, source in cases.items():
    app = root/case
    app.mkdir()
    fixture = app/'main.mjs'
    fixture.write_text(source+'\n')
    run = subprocess.run(['node', str(repo/'scripts/rea.mjs'),
        'analyze-javascript-application', str(app), '--artifact-format', 'directory', '--json'],
        cwd=repo, env=env, capture_output=True, text=True, check=True, timeout=60)
    (root/(case+'.json')).write_text(run.stdout)
    evidence = json.loads(run.stdout)['normalized_result']
    projections = [o['properties'] for n in evidence['graph']['nodes'] for o in n.get('observations', [])
        if o['properties'].get('semantic_role') == 'export-return-shapes'
        and o['properties'].get('exported_name') == 'read']
    runtime = subprocess.run(['node', '--input-type=module', '-e',
        'import {pathToFileURL} from "node:url"; const m=await import(pathToFileURL(process.argv[1])); console.log(JSON.stringify(m.read()));',
        str(fixture)], env=env, capture_output=True, text=True, check=True, timeout=10)
    print(json.dumps({'case': case, 'cli_exit': run.returncode,
        'fields': projections[0]['static_return_shapes'][0]['fields'],
        'return_shape_coverage': projections[0]['return_shape_coverage'],
        'runtime': json.loads(runtime.stdout)}), flush=True)
print('evidence_dir='+str(root))
PYPROBE

The [Symbol.dispose] method intentionally precedes value: 1, so REA's existing treatment of a later unknown computed key does not already erase the literal independently of disposal.

Expected behavior

A receiver exposed to an implicit disposal-method call must not retain an earlier literal contradicted by that method's visible receiver write. Conservative unknown is sufficient; interpreting or executing arbitrary disposer code is unnecessary. Preserve the explicit-call unknown and the no-disposal literal control.

The return-shape and receiver-exposure contract requires conservative uncertainty when objects are used as method receivers. The analyzer explicitly accepts using declaration kinds; the executed public workflow parsed and analyzed this valid module successfully. The independent Node execution establishes disposal and the resulting value for this exact fixture.

Impact and scope

The tested bounded input publishes an obsolete exact return value in application Evidence. The compact failing case occurred once in the final three-case run; earlier wrapper-object and returned-object variants also showed the obsolete value before this draft was written. There was no severe downstream incident. Other disposal forms, real-application prevalence, other platforms, MCP transport, and the published npm runtime were not tested. No workaround preserving an unchanged analyzed artifact was verified.

REA version

Source build at 98ca6c82705bf8c44f814620636852f938d827ca; package manifest 6.7.0. The independently installed npm package was not executed for this report.

Runtime and platform

Node.js v26.12.1; Linux x64. Locked checkout dependencies and the shared prepared runtime. No native provider is involved.

Target and artifact context

Three self-authored directory artifacts containing the complete main.mjs modules above. The resource is a plain local JavaScript object; its disposer only assigns a number to its own property. No hardware, external target, network, or user configuration is involved.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

  • Expected source: The linked receiver-exposure contract and executed explicit-method-call control.
  • Failure source: Executed public CLI application graph, selecting semantic_role: "export-return-shapes" and exported_name: "read"; independent Node import of the same fixture.
  • Evidence provenance: observed.
  • Local verification: reproduced through the public CLI; no helper-only substitution.
  • Reproduction completeness: complete.

Observed final probe output, excluding the generated temporary-directory location:

{"case": "using", "cli_exit": 0, "fields": [{"path": "", "presence": "present", "state": "literal", "value": 1, "reason": null}], "return_shape_coverage": {"status": "complete", "retained_return_sites": 1, "omitted_return_sites": 0, "omitted_fields": 0, "omitted_property_coverage": 0, "projection_complete": true}, "runtime": 2}
{"case": "explicit-call", "cli_exit": 0, "fields": [{"path": "", "presence": "present", "state": "unknown", "value": null, "reason": "Own property value is unknown-coverage."}], "return_shape_coverage": {"status": "complete", "retained_return_sites": 1, "omitted_return_sites": 0, "omitted_fields": 0, "omitted_property_coverage": 0, "projection_complete": true}, "runtime": 2}
{"case": "no-disposal", "cli_exit": 0, "fields": [{"path": "", "presence": "present", "state": "literal", "value": 1, "reason": null}], "return_shape_coverage": {"status": "complete", "retained_return_sites": 1, "omitted_return_sites": 0, "omitted_fields": 0, "omitted_property_coverage": 0, "projection_complete": true}, "runtime": 1}

The failing application's graph coverage was {"status":"complete","truncated":false,"omitted_count":0,"limits":[]}. All three analysis and runtime commands exited zero.

Analysis or suspected cause

Source-derived reasoning: member-mutation collection records explicit calls, constructors, tagged templates, iteration, and writes, but does not expose a using binding's resource as an implicit disposal receiver. Return projection then retains the earlier field value. This is an explanation from source, not a tested repair.

Additional context

The supplied open-issue corpus contained no Symbol.dispose, Symbol.asyncDispose, await using, or resource-disposal report. Related getter-side-effect #6669, implicit-coercion #6670, and tagged-result-reference #6673 reports use independent triggers absent here; fixing only those boundaries would leave this implicit disposal invocation. NOT_ENOUGH_INFO: introducing revision, last known good release, and published-package behavior.

Typed outcome or recovery expected

A future repair should stop emitting stale literal 1 for the using case, retain uncertainty for the explicit call, and retain literal 1 without disposal. These are proposed restoration checks; no repair or post-draft re-verification was executed.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A valid compact module produces a wrong exact exported return fact through the public static-analysis workflow, with a direct runtime oracle and discriminating controls; no severe downstream harm was established.
  • Workaround status: unverified
  • Workaround basis: Replacing implicit disposal with an explicit call changes the analyzed artifact; no practical REA workflow preserving the original source was verified to recover the correct uncertainty.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6781 ## Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ## Area Evidence, artifacts, or reconstruction ## Observed behavior The public `analyze-javascript-application` CLI reports literal `1` for a function that returns `2` after a `using` declaration disposes a local object. Its disposer writes `this.value = 2` at the end of the inner block, before the return expression. The complete application graph retains the earlier literal as an exact exported return field. Explicitly calling the same disposer instead correctly yields an unknown field; omitting disposal correctly preserves literal `1`. ## Reproduction From the prepared source checkout identified below, with locked dependencies and its compiled runtime, run this self-contained Python 3 block. This exact script was executed once for the final three-case probe. It creates only disposable fixtures and isolated configuration beneath `/tmp`. REA analyzes source statically; the separate Node process executes only the self-authored inert module. ```sh python3 - <<'PYPROBE' import json, os, pathlib, subprocess, tempfile repo = pathlib.Path.cwd() root = pathlib.Path(tempfile.mkdtemp(prefix='rea-disposal-', dir='/tmp')) for name in ('home', 'config', 'cache', 'temp'): (root / name).mkdir() env = {'PATH': os.environ['PATH'], 'HOME': str(root/'home'), 'XDG_CONFIG_HOME': str(root/'config'), 'XDG_CACHE_HOME': str(root/'cache'), 'TMPDIR': str(root/'temp')} cases = { 'using': 'export function read() { const state = {[Symbol.dispose]() { this.value = 2; }, value: 1}; { using owned = state; } return state.value; }', 'explicit-call': 'export function read() { const state = {[Symbol.dispose]() { this.value = 2; }, value: 1}; state[Symbol.dispose](); return state.value; }', 'no-disposal': 'export function read() { const state = {[Symbol.dispose]() { this.value = 2; }, value: 1}; return state.value; }', } for case, source in cases.items(): app = root/case app.mkdir() fixture = app/'main.mjs' fixture.write_text(source+'\n') run = subprocess.run(['node', str(repo/'scripts/rea.mjs'), 'analyze-javascript-application', str(app), '--artifact-format', 'directory', '--json'], cwd=repo, env=env, capture_output=True, text=True, check=True, timeout=60) (root/(case+'.json')).write_text(run.stdout) evidence = json.loads(run.stdout)['normalized_result'] projections = [o['properties'] for n in evidence['graph']['nodes'] for o in n.get('observations', []) if o['properties'].get('semantic_role') == 'export-return-shapes' and o['properties'].get('exported_name') == 'read'] runtime = subprocess.run(['node', '--input-type=module', '-e', 'import {pathToFileURL} from "node:url"; const m=await import(pathToFileURL(process.argv[1])); console.log(JSON.stringify(m.read()));', str(fixture)], env=env, capture_output=True, text=True, check=True, timeout=10) print(json.dumps({'case': case, 'cli_exit': run.returncode, 'fields': projections[0]['static_return_shapes'][0]['fields'], 'return_shape_coverage': projections[0]['return_shape_coverage'], 'runtime': json.loads(runtime.stdout)}), flush=True) print('evidence_dir='+str(root)) PYPROBE ``` The `[Symbol.dispose]` method intentionally precedes `value: 1`, so REA's existing treatment of a later unknown computed key does not already erase the literal independently of disposal. ## Expected behavior A receiver exposed to an implicit disposal-method call must not retain an earlier literal contradicted by that method's visible receiver write. Conservative unknown is sufficient; interpreting or executing arbitrary disposer code is unnecessary. Preserve the explicit-call unknown and the no-disposal literal control. The [return-shape and receiver-exposure contract](https://github.com/morluto/rea/blob/98ca6c82705bf8c44f814620636852f938d827ca/docs/javascript-application-workflows.md#L367-L391) requires conservative uncertainty when objects are used as method receivers. The analyzer explicitly accepts [using declaration kinds](https://github.com/morluto/rea/blob/98ca6c82705bf8c44f814620636852f938d827ca/src/domain/javascript/javascriptSemanticAnalysis.ts#L116-L123); the executed public workflow parsed and analyzed this valid module successfully. The independent Node execution establishes disposal and the resulting value for this exact fixture. ## Impact and scope The tested bounded input publishes an obsolete exact return value in application Evidence. The compact failing case occurred once in the final three-case run; earlier wrapper-object and returned-object variants also showed the obsolete value before this draft was written. There was no severe downstream incident. Other disposal forms, real-application prevalence, other platforms, MCP transport, and the published npm runtime were not tested. No workaround preserving an unchanged analyzed artifact was verified. ## REA version Source build at `98ca6c82705bf8c44f814620636852f938d827ca`; package manifest 6.7.0. The independently installed npm package was not executed for this report. ## Runtime and platform Node.js v26.12.1; Linux x64. Locked checkout dependencies and the shared prepared runtime. No native provider is involved. ## Target and artifact context Three self-authored directory artifacts containing the complete `main.mjs` modules above. The resource is a plain local JavaScript object; its disposer only assigns a number to its own property. No hardware, external target, network, or user configuration is involved. ## Interface CLI ## Earliest failure stage Native analysis or reconstruction ## Relevant output or evidence - Expected source: The linked receiver-exposure contract and executed explicit-method-call control. - Failure source: Executed public CLI application graph, selecting `semantic_role: "export-return-shapes"` and `exported_name: "read"`; independent Node import of the same fixture. - Evidence provenance: observed. - Local verification: reproduced through the public CLI; no helper-only substitution. - Reproduction completeness: complete. Observed final probe output, excluding the generated temporary-directory location: ```jsonl {"case": "using", "cli_exit": 0, "fields": [{"path": "", "presence": "present", "state": "literal", "value": 1, "reason": null}], "return_shape_coverage": {"status": "complete", "retained_return_sites": 1, "omitted_return_sites": 0, "omitted_fields": 0, "omitted_property_coverage": 0, "projection_complete": true}, "runtime": 2} {"case": "explicit-call", "cli_exit": 0, "fields": [{"path": "", "presence": "present", "state": "unknown", "value": null, "reason": "Own property value is unknown-coverage."}], "return_shape_coverage": {"status": "complete", "retained_return_sites": 1, "omitted_return_sites": 0, "omitted_fields": 0, "omitted_property_coverage": 0, "projection_complete": true}, "runtime": 2} {"case": "no-disposal", "cli_exit": 0, "fields": [{"path": "", "presence": "present", "state": "literal", "value": 1, "reason": null}], "return_shape_coverage": {"status": "complete", "retained_return_sites": 1, "omitted_return_sites": 0, "omitted_fields": 0, "omitted_property_coverage": 0, "projection_complete": true}, "runtime": 1} ``` The failing application's graph coverage was `{"status":"complete","truncated":false,"omitted_count":0,"limits":[]}`. All three analysis and runtime commands exited zero. ## Analysis or suspected cause Source-derived reasoning: [member-mutation collection](https://github.com/morluto/rea/blob/98ca6c82705bf8c44f814620636852f938d827ca/src/domain/javascript/javascriptSemanticMemberMutations.ts#L891-L935) records explicit calls, constructors, tagged templates, iteration, and writes, but does not expose a `using` binding's resource as an implicit disposal receiver. [Return projection](https://github.com/morluto/rea/blob/98ca6c82705bf8c44f814620636852f938d827ca/src/domain/javascript/javascriptExportReturnShapeProjection.ts#L89-L103) then retains the earlier field value. This is an explanation from source, not a tested repair. ## Additional context The supplied open-issue corpus contained no `Symbol.dispose`, `Symbol.asyncDispose`, `await using`, or resource-disposal report. Related getter-side-effect [#6669](https://github.com/morluto/rea/issues/6669), implicit-coercion [#6670](https://github.com/morluto/rea/issues/6670), and tagged-result-reference [#6673](https://github.com/morluto/rea/issues/6673) reports use independent triggers absent here; fixing only those boundaries would leave this implicit disposal invocation. NOT_ENOUGH_INFO: introducing revision, last known good release, and published-package behavior. ## Typed outcome or recovery expected A future repair should stop emitting stale literal `1` for the `using` case, retain uncertainty for the explicit call, and retain literal `1` without disposal. These are proposed restoration checks; no repair or post-draft re-verification was executed. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A valid compact module produces a wrong exact exported return fact through the public static-analysis workflow, with a direct runtime oracle and discriminating controls; no severe downstream harm was established. - Workaround status: unverified - Workaround basis: Replacing implicit disposal with an explicit call changes the analyzed artifact; no practical REA workflow preserving the original source was verified to recover the correct uncertainty.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#4135
No description provided.