[GH-ISSUE #6191] [Feature] Resolve this property accesses in constructors, prototype methods and class methods #3774

Open
opened 2026-10-31 00:43:55 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @jacobmalibiran on GitHub (Oct 22, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/6191

Before submitting

  • I searched the open and closed issues and pull requests for a related proposal.
  • I understand the contribution scope must be reviewed before integration; open questions are welcome here.
  • This request does not include credentials, private samples, or sensitive target data.

Area

Evidence, artifacts, or reconstruction

Current limitation

Property reads and writes through this in constructors, prototype methods and
class methods get no property slot. Each access stays an unresolved expression
node with an object-flow unknown ("receiver is unresolved or not a retained
container"). So trace-javascript-semantics can't connect this.speed = 6 in a
constructor to this.speed read in a method. The same code written with a
module-level object resolves both accesses to one slot.

Object operations are keyed by a lexical binding (objectBindingId); this has
none, so the operation is recorded as partial
(javascriptSemanticObjects.ts:213).

Use cases

Code that keeps its state on instances: ES5 constructor/prototype code and ES2015
classes, which covers most browser games and older applications. Example: in
wayou's T-Rex runner index.js (https://wayou.github.io/t-rex-runner/), all
game state, including this.currentSpeed, is reached through this, so "where is
the speed changed?" can't be traced. #6189 / #6190 make these accesses visible as
unknowns; this request is about resolving them. Frequency across real targets:
not measured.

Desired outcome and success criteria

For the class and prototype fixtures below, this.speed accesses resolve to
one instance property slot per constructor, like the object control: the write
in the constructor and the read in update connect through that slot. Where the
receiver is uncertain (.call, .apply, .bind, detached methods, subclasses),
relations stay candidate with unknowns rather than being treated as resolved.

Expected interaction with targets

Read existing files or captured data

Target and scope details (optional)

Static analysis of local JavaScript only; no execution. Scope question: is one
abstract instance per constructor acceptable, or should instances be
distinguished by allocation site?

Verification available so far

I have a prototype or observed results

Verification details (optional)

Observed on main at 2451e187b (REA 6.7.0, Node v24.22.0, macOS arm64). Run from
the repository root after npm run build:cached:

R=$(mktemp -d); mkdir -p "$R/class" "$R/prototype" "$R/object"

cat > "$R/class/game.js" <<'JS'
export class Game {
  constructor() { this.speed = 6; }
  update() { return this.speed; }
}
JS
cat > "$R/prototype/game.js" <<'JS'
function Game() { this.speed = 6; }
Game.prototype.update = function () { return this.speed; };
JS
cat > "$R/object/game.js" <<'JS'
const game = { speed: 6 };
export function update() { return game.speed; }
JS

for v in class prototype object; do
  node scripts/rea.mjs analyze-javascript-application "$R/$v" \
    --artifact-format directory --format json > "$R/$v.json" 2>/dev/null
  printf '%-10s ' "$v"
  jq -c '.normalized_result.semantic_graph | {
    speed_slots: [.nodes[] | select(.kind == "property-slot" and .properties.name == "speed")] | length,
    speed_accesses: [.nodes[] | select(.kind == "expression" and .properties.property_path == ["speed"]) | .label] | sort,
    property_relations: [.relations[] | select(.relation | test("-property$")) | "\(.relation):\(.resolution)"] | sort,
    object_flow_unknowns: [.unknowns[] | select(.family == "object-flow") | .detail | sub("; object identity.*"; "")] | unique}' "$R/$v.json"
done

Output:

class      {"speed_slots":0,"speed_accesses":["read:/speed","write:/speed"],"property_relations":[],"object_flow_unknowns":["Static read property /speed has unknown-coverage presence; its receiver is unresolved or not a retained container","Static write property /speed has unknown-coverage presence; its receiver is unresolved or not a retained container"]}
prototype  {"speed_slots":0,"speed_accesses":["read:/speed","write:/speed"],"property_relations":["writes-property:candidate"],"object_flow_unknowns":["Static read property /speed has unknown-coverage presence; its receiver is unresolved or not a retained container","Static write property /prototype/update has unknown-coverage presence; its receiver is unresolved or not a retained container","Static write property /speed has unknown-coverage presence; its receiver is unresolved or not a retained container"]}
object     {"speed_slots":1,"speed_accesses":["read:/speed"],"property_relations":["reads-property:resolved","writes-property:resolved"],"object_flow_unknowns":[]}

Alternatives considered

  • trace-javascript-semantics with a property seed: after #6190 it lists the
    unresolved accesses, but can't connect writes to reads.
  • Reading raw source by hand, which is what the semantic graph is meant to avoid.

Possible direction

Proposal only: getter-result queries already follow this for class fields
(javascriptSemanticObjectReferences.ts:470-505).
A similar receiver for object operations could key this in a constructor and in
methods on its prototype/class to one abstract instance, keeping unknowns for
receivers that can be rebound.

References

  • #6189 (property seeds hid these accesses), #6190 (fix, draft)
  • #4437: class-instance method calls (related call-graph gap)
  • #3360: unrelated this receivers sharing one event identity (receiver identity must stay scoped)
  • Related: #6192 (objects assigned to properties), #6193 (assignment right-hand-side flow)
Originally created by @jacobmalibiran on GitHub (Oct 22, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6191 ## Before submitting - [x] I searched the open and closed issues and pull requests for a related proposal. - [x] I understand the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope) must be reviewed before integration; open questions are welcome here. - [x] This request does not include credentials, private samples, or sensitive target data. ## Area Evidence, artifacts, or reconstruction ## Current limitation Property reads and writes through `this` in constructors, prototype methods and class methods get no property slot. Each access stays an unresolved `expression` node with an `object-flow` unknown ("receiver is unresolved or not a retained container"). So `trace-javascript-semantics` can't connect `this.speed = 6` in a constructor to `this.speed` read in a method. The same code written with a module-level object resolves both accesses to one slot. Object operations are keyed by a lexical binding (`objectBindingId`); `this` has none, so the operation is recorded as `partial` ([`javascriptSemanticObjects.ts:213`](https://github.com/morluto/rea/blob/2451e187beab43a811bb6149dfd3af3ad8178327/src/domain/javascript/javascriptSemanticObjects.ts#L213)). ## Use cases Code that keeps its state on instances: ES5 constructor/prototype code and ES2015 classes, which covers most browser games and older applications. Example: in wayou's T-Rex runner `index.js` (<https://wayou.github.io/t-rex-runner/>), all game state, including `this.currentSpeed`, is reached through `this`, so "where is the speed changed?" can't be traced. #6189 / #6190 make these accesses visible as unknowns; this request is about resolving them. Frequency across real targets: not measured. ## Desired outcome and success criteria For the `class` and `prototype` fixtures below, `this.speed` accesses resolve to one instance property slot per constructor, like the `object` control: the write in the constructor and the read in `update` connect through that slot. Where the receiver is uncertain (`.call`, `.apply`, `.bind`, detached methods, subclasses), relations stay `candidate` with unknowns rather than being treated as resolved. ## Expected interaction with targets Read existing files or captured data ## Target and scope details (optional) Static analysis of local JavaScript only; no execution. Scope question: is one abstract instance per constructor acceptable, or should instances be distinguished by allocation site? ## Verification available so far I have a prototype or observed results ## Verification details (optional) Observed on `main` at `2451e187b` (REA 6.7.0, Node v24.22.0, macOS arm64). Run from the repository root after `npm run build:cached`: ```bash R=$(mktemp -d); mkdir -p "$R/class" "$R/prototype" "$R/object" cat > "$R/class/game.js" <<'JS' export class Game { constructor() { this.speed = 6; } update() { return this.speed; } } JS cat > "$R/prototype/game.js" <<'JS' function Game() { this.speed = 6; } Game.prototype.update = function () { return this.speed; }; JS cat > "$R/object/game.js" <<'JS' const game = { speed: 6 }; export function update() { return game.speed; } JS for v in class prototype object; do node scripts/rea.mjs analyze-javascript-application "$R/$v" \ --artifact-format directory --format json > "$R/$v.json" 2>/dev/null printf '%-10s ' "$v" jq -c '.normalized_result.semantic_graph | { speed_slots: [.nodes[] | select(.kind == "property-slot" and .properties.name == "speed")] | length, speed_accesses: [.nodes[] | select(.kind == "expression" and .properties.property_path == ["speed"]) | .label] | sort, property_relations: [.relations[] | select(.relation | test("-property$")) | "\(.relation):\(.resolution)"] | sort, object_flow_unknowns: [.unknowns[] | select(.family == "object-flow") | .detail | sub("; object identity.*"; "")] | unique}' "$R/$v.json" done ``` Output: ```text class {"speed_slots":0,"speed_accesses":["read:/speed","write:/speed"],"property_relations":[],"object_flow_unknowns":["Static read property /speed has unknown-coverage presence; its receiver is unresolved or not a retained container","Static write property /speed has unknown-coverage presence; its receiver is unresolved or not a retained container"]} prototype {"speed_slots":0,"speed_accesses":["read:/speed","write:/speed"],"property_relations":["writes-property:candidate"],"object_flow_unknowns":["Static read property /speed has unknown-coverage presence; its receiver is unresolved or not a retained container","Static write property /prototype/update has unknown-coverage presence; its receiver is unresolved or not a retained container","Static write property /speed has unknown-coverage presence; its receiver is unresolved or not a retained container"]} object {"speed_slots":1,"speed_accesses":["read:/speed"],"property_relations":["reads-property:resolved","writes-property:resolved"],"object_flow_unknowns":[]} ``` ## Alternatives considered - `trace-javascript-semantics` with a `property` seed: after #6190 it lists the unresolved accesses, but can't connect writes to reads. - Reading raw source by hand, which is what the semantic graph is meant to avoid. ## Possible direction Proposal only: getter-result queries already follow `this` for class fields ([`javascriptSemanticObjectReferences.ts:470-505`](https://github.com/morluto/rea/blob/2451e187beab43a811bb6149dfd3af3ad8178327/src/domain/javascript/javascriptSemanticObjectReferences.ts#L470-L505)). A similar receiver for object operations could key `this` in a constructor and in methods on its prototype/class to one abstract instance, keeping unknowns for receivers that can be rebound. ## References - #6189 (property seeds hid these accesses), #6190 (fix, draft) - #4437: class-instance method calls (related call-graph gap) - #3360: unrelated `this` receivers sharing one event identity (receiver identity must stay scoped) - Related: #6192 (objects assigned to properties), #6193 (assignment right-hand-side flow)
Author
Owner

@XinwangTao commented on GitHub (Oct 22, 2026):

赞同这个方向,而且它是 #6192/#6193 的前置基础——没有实例接收者解析,后置对象字面量和赋值边都挂不到正确的 slot。一个想提醒的点是实例间区分:同一类的两个实例(比如两辆不同的 T-Rex)应各自拥有独立 slot,否则 trace 会把 A 实例的 this.speed = 6 和 B 实例的读取串成一条边,结论比"未解析"更有误导性(#3360 的共享事件身份问题在 this 场景会放大)。另外建议在 #6189/#6190 的可见性改进上再进一步:把"receiver is unresolved"这类 unknown 里标出哪些是 this 相关,这样报告里能直接看出"这是实例解析盲区"而不是泛泛的对象流未知。

<!-- gh-comment-id:6330340052 --> @XinwangTao commented on GitHub (Oct 22, 2026): 赞同这个方向,而且它是 #6192/#6193 的前置基础——没有实例接收者解析,后置对象字面量和赋值边都挂不到正确的 slot。一个想提醒的点是实例间区分:同一类的两个实例(比如两辆不同的 T-Rex)应各自拥有独立 slot,否则 trace 会把 A 实例的 `this.speed = 6` 和 B 实例的读取串成一条边,结论比"未解析"更有误导性(#3360 的共享事件身份问题在 this 场景会放大)。另外建议在 #6189/#6190 的可见性改进上再进一步:把"receiver is unresolved"这类 unknown 里标出哪些是 this 相关,这样报告里能直接看出"这是实例解析盲区"而不是泛泛的对象流未知。
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#3774
No description provided.