[GH-ISSUE #6193] [Feature] Link assignment right-hand sides to the variable or property they write #3773

Closed
opened 2026-10-31 00:43:55 +00:00 by bsakaguchi · 3 comments
Owner

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

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

A declaration carries its value forward (const next = accel links accel to
next). A later assignment doesn't link its right-hand side to what it writes,
whether the target is a variable (speed = accel), a property
(state.speed = accel) or a compound assignment (state.speed += accel). A
forward trace from accel stops at its read, so "what does this value
influence?" misses every reassignment.

#4342 deliberately leaves compound assignment values unknown (no arithmetic
interpreter). This request is about dependency edges only, not computing values.

Use cases

State that changes over time, such as counters, accumulators, game speed and
retry delays, is written by reassignment. Example: wayou's T-Rex runner index.js
(https://wayou.github.io/t-rex-runner/) increases speed with
this.currentSpeed += this.config.ACCELERATION. Even with resolved receivers
(#6191), the trace would stop before the speed write. Frequency across real
targets: not measured.

Desired outcome and success criteria

For the variable, property and compound fixtures below, a forward trace from
0.001 reaches the written binding or property slot (and the later read/return),
like the declaration control. For += and other arithmetic assignments, the
written value stays unknown while the influence edge is kept, so provenance shows
speed depends on accel without claiming a value.

Expected interaction with targets

Read existing files or captured data

Target and scope details (optional)

Static analysis of local JavaScript only; no execution. Non-goal: evaluating
arithmetic.

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)

write() { mkdir -p "$R/$1"; cat > "$R/$1/game.js" <<JS
const accel = 0.001;
let speed = 6;
const state = { speed: 6 };
export function update() { $2 }
JS
}
write declaration 'const next = accel; return next;'
write variable    'speed = accel; return speed;'
write property    'state.speed = accel; return state.speed;'
write compound    'state.speed += accel; return state.speed;'

for v in declaration variable property compound; do
  node scripts/rea.mjs analyze-javascript-application "$R/$v" \
    --artifact-format directory --format json > "$R/$v.json" 2>/dev/null
  jq -c '{application: ., query: {seed: {kind: "literal", value: 0.001}, direction: "forward-influence"}}' \
    "$R/$v.json" > "$R/query.json"
  printf '%-12s ' "$v"
  node scripts/rea.mjs trace-javascript-semantics "$R/query.json" --format json |
    jq -c '.normalized_result | {status, reached: [.nodes[] | "\(.kind):\(.label)"] | unique}'
done

Output:

declaration  {"status":"ambiguous","reached":["binding:accel","binding:next","expression:accel","expression:next","function:update","literal:0.001","return-site:return"]}
variable     {"status":"found","reached":["binding:accel","expression:accel","function:update","literal:0.001"]}
property     {"status":"found","reached":["binding:accel","expression:accel","function:update","literal:0.001"]}
compound     {"status":"found","reached":["binding:accel","expression:accel","function:update","literal:0.001"]}

Alternatives considered

  • Backward provenance from the write: it doesn't reach the right-hand side either.
  • Reading raw source by hand.

Possible direction

Proposal only: when projecting an assignment expression, add a data-flow edge from
each right-hand-side read to the assignment's write occurrence (binding or
property slot). For arithmetic and compound operators, keep the value unknown as
#4342 does. Where in the projection this belongs is NOT_ENOUGH_INFO: I haven't
traced the source.

References

  • #6189: found while tracing the T-Rex runner's speed rule
  • #4342: compound assignment values stay unknown (kept as a non-goal)
  • #537: compound assignment keeps its left-hand-side read
  • Related: #6191 (this receivers), #6192 (objects assigned to properties)
Originally created by @jacobmalibiran on GitHub (Oct 22, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6193 ## 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 A declaration carries its value forward (`const next = accel` links `accel` to `next`). A later assignment doesn't link its right-hand side to what it writes, whether the target is a variable (`speed = accel`), a property (`state.speed = accel`) or a compound assignment (`state.speed += accel`). A forward trace from `accel` stops at its read, so "what does this value influence?" misses every reassignment. #4342 deliberately leaves compound assignment *values* unknown (no arithmetic interpreter). This request is about dependency edges only, not computing values. ## Use cases State that changes over time, such as counters, accumulators, game speed and retry delays, is written by reassignment. Example: wayou's T-Rex runner `index.js` (<https://wayou.github.io/t-rex-runner/>) increases speed with `this.currentSpeed += this.config.ACCELERATION`. Even with resolved receivers (#6191), the trace would stop before the speed write. Frequency across real targets: not measured. ## Desired outcome and success criteria For the `variable`, `property` and `compound` fixtures below, a forward trace from `0.001` reaches the written binding or property slot (and the later read/return), like the `declaration` control. For `+=` and other arithmetic assignments, the written value stays unknown while the influence edge is kept, so provenance shows `speed` depends on `accel` without claiming a value. ## Expected interaction with targets Read existing files or captured data ## Target and scope details (optional) Static analysis of local JavaScript only; no execution. Non-goal: evaluating arithmetic. ## 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) write() { mkdir -p "$R/$1"; cat > "$R/$1/game.js" <<JS const accel = 0.001; let speed = 6; const state = { speed: 6 }; export function update() { $2 } JS } write declaration 'const next = accel; return next;' write variable 'speed = accel; return speed;' write property 'state.speed = accel; return state.speed;' write compound 'state.speed += accel; return state.speed;' for v in declaration variable property compound; do node scripts/rea.mjs analyze-javascript-application "$R/$v" \ --artifact-format directory --format json > "$R/$v.json" 2>/dev/null jq -c '{application: ., query: {seed: {kind: "literal", value: 0.001}, direction: "forward-influence"}}' \ "$R/$v.json" > "$R/query.json" printf '%-12s ' "$v" node scripts/rea.mjs trace-javascript-semantics "$R/query.json" --format json | jq -c '.normalized_result | {status, reached: [.nodes[] | "\(.kind):\(.label)"] | unique}' done ``` Output: ```text declaration {"status":"ambiguous","reached":["binding:accel","binding:next","expression:accel","expression:next","function:update","literal:0.001","return-site:return"]} variable {"status":"found","reached":["binding:accel","expression:accel","function:update","literal:0.001"]} property {"status":"found","reached":["binding:accel","expression:accel","function:update","literal:0.001"]} compound {"status":"found","reached":["binding:accel","expression:accel","function:update","literal:0.001"]} ``` ## Alternatives considered - Backward provenance from the write: it doesn't reach the right-hand side either. - Reading raw source by hand. ## Possible direction Proposal only: when projecting an assignment expression, add a data-flow edge from each right-hand-side read to the assignment's write occurrence (binding or property slot). For arithmetic and compound operators, keep the value unknown as #4342 does. Where in the projection this belongs is NOT_ENOUGH_INFO: I haven't traced the source. ## References - #6189: found while tracing the T-Rex runner's speed rule - #4342: compound assignment values stay unknown (kept as a non-goal) - #537: compound assignment keeps its left-hand-side read - Related: #6191 (`this` receivers), #6192 (objects assigned to properties)
Author
Owner

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

这个方向很合理——把依赖边与值计算彻底分离是对的边界:#4342 不解释复合赋值的值,不影响这里只建数据流边。实际目标里靠 reassignment 驱动的可变状态(计数器、累加器、重试延迟、游戏速度)非常常见,前向追踪在这些绑定的第二次写入处断链确实会让"这个值影响了什么"漏掉整段下游。一个补充建议:多次赋值的变量应保留全部赋值边而非只留最后一条,否则审计场景会丢掉中间状态(比如先加速后减速的轨迹);另外 #6191、#6192 落地后建议把三者的 fixture 串成一条完整链路用例,验证从属性访问 → 对象属性建模 → 赋值边能一路贯通。

<!-- gh-comment-id:6330315305 --> @XinwangTao commented on GitHub (Oct 22, 2026): 这个方向很合理——把依赖边与值计算彻底分离是对的边界:#4342 不解释复合赋值的值,不影响这里只建数据流边。实际目标里靠 reassignment 驱动的可变状态(计数器、累加器、重试延迟、游戏速度)非常常见,前向追踪在这些绑定的第二次写入处断链确实会让"这个值影响了什么"漏掉整段下游。一个补充建议:多次赋值的变量应保留全部赋值边而非只留最后一条,否则审计场景会丢掉中间状态(比如先加速后减速的轨迹);另外 #6191、#6192 落地后建议把三者的 fixture 串成一条完整链路用例,验证从属性访问 → 对象属性建模 → 赋值边能一路贯通。
Author
Owner

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

Proposed implementation in #6206 — dependency edges only, no value evaluation, keeping the #4342 boundary.

All four fixtures from this issue now trace forward from 0.001 through the written binding/property slot to the return, matching the declaration control. Compound assignments retain the previous-value read and keep the written value unknown.

On the suggestion above: multiple assignments to the same variable each produce their own defines edges — none are collapsed to the last write — so an accelerate-then-decelerate trajectory keeps every intermediate edge.

<!-- gh-comment-id:6330907420 --> @IvenKooLab commented on GitHub (Oct 22, 2026): Proposed implementation in #6206 — dependency edges only, no value evaluation, keeping the #4342 boundary. All four fixtures from this issue now trace forward from `0.001` through the written binding/property slot to the return, matching the `declaration` control. Compound assignments retain the previous-value read and keep the written value unknown. On the suggestion above: multiple assignments to the same variable each produce their own `defines` edges — none are collapsed to the last write — so an accelerate-then-decelerate trajectory keeps every intermediate edge.
Author
Owner

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

PR #6206 的实现完整覆盖了建议:四个 fixture 全通过、复合赋值保持值未知与 #4342 边界一致,多次赋值保留全部 defines 边这一点也确认了,加速后减速的轨迹不会丢中间状态。剩下一个可选的后续验证点:等 #6191/#6192 合并后,把属性访问 → 对象属性建模 → 赋值边串成一条端到端 fixture,验证这三块在同一个真实目标上能贯通。

<!-- gh-comment-id:6332632504 --> @XinwangTao commented on GitHub (Oct 22, 2026): PR #6206 的实现完整覆盖了建议:四个 fixture 全通过、复合赋值保持值未知与 #4342 边界一致,多次赋值保留全部 defines 边这一点也确认了,加速后减速的轨迹不会丢中间状态。剩下一个可选的后续验证点:等 #6191/#6192 合并后,把属性访问 → 对象属性建模 → 赋值边串成一条端到端 fixture,验证这三块在同一个真实目标上能贯通。
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#3773
No description provided.