[GH-ISSUE #6526] [Bug] Class declaration self-references resolve to the mutable outer binding #3918

Closed
opened 2026-10-31 00:45:13 +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/6526

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

Source-only finding; no runtime reproduction was performed. A named class declaration creates only the outer binding in the semantic index. References to the class name inside its own methods consequently resolve to the outer variable, which can be reassigned, instead of the class's separate immutable inner binding. Named class expressions already receive an inner binding.

For the input below, Service in new Service() and the outer assignment are indexed as the same binding. This makes an unrelated replacement a possible constructor target inside create() and creates a false capture/dependency on the outer variable. These are source-derived consequences, not recorded runtime output.

Reproduction

Proposed check, not run: pass the following source to analyzeJavaScriptSemantics(source) in a focused source test, or analyze the containing JavaScript artifact through analyze-javascript-application.

class Service {
  static create() { return new Service(); }
}
const Original = Service;
Service = function Replacement() {};

Inspect the two Service bindings that should exist, the binding ID for the constructor reference inside create, and its call-target candidates. The current declaration branch supplies only one Service binding. Actual CLI/MCP output and reproduction frequency: NOT_ENOUGH_INFO; no runtime analysis was run.

Expected behavior

The class scope should own an immutable Service binding for self-references. The outer assignment should affect only the outer binding; new Service() inside create() should retain the original class as its constructor target.

ECMAScript ClassDefinitionEvaluation creates an immutable class-name binding in the class environment. REA's named class-expression regression already requires distinct inner/outer binding IDs for the equivalent named-expression case.

Impact and scope

Incorrect lexical identity can contaminate constructor/capture relationships when a named class declaration refers to itself and its outer name is reassigned. It also misstates the binding identity without reassignment. This concerns static evidence, not execution of the target. Frequency in real artifacts and complete downstream graph effects are NOT_ENOUGH_INFO. Manual inspection of class self-references is a possible workaround; its adequacy for affected workflows was not tested.

REA version

Audited source: main commit 49c9c9d4a14cc7d5509ec28a304dab48f85cb045; its package.json says 6.7.0. The same declaration/expression distinction is present in tag rea-agents-6.7.0, source commit 27dc42d10249f0410904a75c0997842eb03b931c. This is a release-source inspection, not evidence that the published npm artifact was reproduced.

Runtime and platform

NOT_ENOUGH_INFO: no REA runtime or provider was executed. The finding comes from TypeScript source inspection and is not tied to an observed host platform.

Target and artifact context

Ordinary JavaScript with a named class declaration and a self-reference in a static method; reduced source above. No binary/provider integration is required for the proposed source test.

Interface

Other

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Evidence was obtained by reading these exact source revisions and existing tests. Missing evidence: executed REA output, integration projection, and behavior of a published package.

Additional context

Final open and closed issue searches found no matching report. Open #4437 requests class-instance member-call resolution; this report concerns lexical identity for a direct identifier self-reference inside a class declaration. Closed #3310 concerns noncomputed method property names being counted as reads, a different binding boundary.

Triage assessment

  • Impact level: P2
  • Assessment status: provisional
  • Impact basis: Distinct JavaScript bindings collapse into one static identity, so outer reassignment can introduce incorrect constructor and capture dependencies for this bounded input. Runtime output and real-artifact prevalence were not measured.
  • Workaround status: unverified
  • Workaround basis: An analyst can inspect class self-references manually, but no end-to-end workaround preserving the affected graph relationships was tested.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6526 ### 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 Source-only finding; no runtime reproduction was performed. A named class **declaration** creates only the outer binding in the semantic index. References to the class name inside its own methods consequently resolve to the outer variable, which can be reassigned, instead of the class's separate immutable inner binding. Named class expressions already receive an inner binding. For the input below, `Service` in `new Service()` and the outer assignment are indexed as the same binding. This makes an unrelated replacement a possible constructor target inside `create()` and creates a false capture/dependency on the outer variable. These are source-derived consequences, not recorded runtime output. ### Reproduction Proposed check, not run: pass the following source to `analyzeJavaScriptSemantics(source)` in a focused source test, or analyze the containing JavaScript artifact through `analyze-javascript-application`. ```js class Service { static create() { return new Service(); } } const Original = Service; Service = function Replacement() {}; ``` Inspect the two `Service` bindings that should exist, the binding ID for the constructor reference inside `create`, and its call-target candidates. The current declaration branch supplies only one `Service` binding. Actual CLI/MCP output and reproduction frequency: `NOT_ENOUGH_INFO`; no runtime analysis was run. ### Expected behavior The class scope should own an immutable `Service` binding for self-references. The outer assignment should affect only the outer binding; `new Service()` inside `create()` should retain the original class as its constructor target. [ECMAScript ClassDefinitionEvaluation](https://tc39.es/ecma262/multipage/ecmascript-language-functions-and-classes.html#sec-runtime-semantics-classdefinitionevaluation) creates an immutable class-name binding in the class environment. REA's [named class-expression regression](https://github.com/morluto/rea/blob/49c9c9d4a14cc7d5509ec28a304dab48f85cb045/src/domain/javascript/javascriptSemanticAnalysis.structure.test.ts#L250-L275) already requires distinct inner/outer binding IDs for the equivalent named-expression case. ### Impact and scope Incorrect lexical identity can contaminate constructor/capture relationships when a named class declaration refers to itself and its outer name is reassigned. It also misstates the binding identity without reassignment. This concerns static evidence, not execution of the target. Frequency in real artifacts and complete downstream graph effects are `NOT_ENOUGH_INFO`. Manual inspection of class self-references is a possible workaround; its adequacy for affected workflows was not tested. ### REA version Audited source: `main` commit `49c9c9d4a14cc7d5509ec28a304dab48f85cb045`; its package.json says 6.7.0. The same declaration/expression distinction is present in tag `rea-agents-6.7.0`, source commit `27dc42d10249f0410904a75c0997842eb03b931c`. This is a release-source inspection, not evidence that the published npm artifact was reproduced. ### Runtime and platform `NOT_ENOUGH_INFO`: no REA runtime or provider was executed. The finding comes from TypeScript source inspection and is not tied to an observed host platform. ### Target and artifact context Ordinary JavaScript with a named class declaration and a self-reference in a static method; reduced source above. No binary/provider integration is required for the proposed source test. ### Interface Other ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence - [Outer declaration registration](https://github.com/morluto/rea/blob/49c9c9d4a14cc7d5509ec28a304dab48f85cb045/src/domain/javascript/javascriptSemanticAnalysis.ts#L1373-L1397) adds a class declaration to the enclosing scope. - [Inner registration](https://github.com/morluto/rea/blob/49c9c9d4a14cc7d5509ec28a304dab48f85cb045/src/domain/javascript/javascriptSemanticAnalysis.ts#L1400-L1442) only handles `t.isClassExpression(node)` for a class-name binding: ```ts else if (t.isClassExpression(node) && t.isIdentifier(node.id)) addBinding({ state, scope, name: node.id.name, kind: "class", /* ... */ }); ``` - [Class-scope construction](https://github.com/morluto/rea/blob/49c9c9d4a14cc7d5509ec28a304dab48f85cb045/src/domain/javascript/javascriptSemanticAnalysis.ts#L1581-L1595) starts with an empty binding map. [Lexical lookup](https://github.com/morluto/rea/blob/49c9c9d4a14cc7d5509ec28a304dab48f85cb045/src/domain/javascript/javascriptSemanticState.ts#L307-L330) therefore walks past that class scope and selects the outer `Service` binding. - Release-source counterparts: [declaration and inner-registration branches](https://github.com/morluto/rea/blob/27dc42d10249f0410904a75c0997842eb03b931c/src/domain/javascript/javascriptSemanticAnalysis.ts#L1314-L1373). Evidence was obtained by reading these exact source revisions and existing tests. Missing evidence: executed REA output, integration projection, and behavior of a published package. ### Additional context Final open and closed issue searches found no matching report. Open [#4437](https://github.com/morluto/rea/issues/4437) requests class-instance member-call resolution; this report concerns lexical identity for a direct identifier self-reference inside a class declaration. Closed [#3310](https://github.com/morluto/rea/issues/3310) concerns noncomputed method property names being counted as reads, a different binding boundary. ## Triage assessment - Impact level: P2 - Assessment status: provisional - Impact basis: Distinct JavaScript bindings collapse into one static identity, so outer reassignment can introduce incorrect constructor and capture dependencies for this bounded input. Runtime output and real-artifact prevalence were not measured. - Workaround status: unverified - Workaround basis: An analyst can inspect class self-references manually, but no end-to-end workaround preserving the affected graph relationships was tested.
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#3918
No description provided.