mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-06 18:41:26 +00:00
* expr: an expression cannot rebind a prelude name for the next one (#2133) One runtime serves every expression on a thread and user code runs as a script at global scope, so `linear = function () {}` rebound the name for every later expression, and for every later project in the session. #1387 closed the `__` globals, the model constructors and the prelude's own prototypes; the rest of the global surface is here: the prelude's own 104 helpers, the built-ins that an expression and the prelude both call, and the prototypes those hang off. Freezing a prototype costs an expression the right to shadow an inherited name on its own object: once the inherited data property is non-writable, `o.x = …` is dropped in sloppy mode rather than creating an own property, which is the override mistake. Every name that was shadowable before is therefore replaced by an accessor whose setter writes an own property on the receiver, emulating OrdinarySetWithOwnDescriptor so the substitution does not show through. The repair covers every own writable, configurable data property of every prototype the block reaches, not a list of commonly shadowed names: a list leaves `a.map = …` throwing and `a.push = …` and `a.join = …` silently running the native method, which is worse, because the expression keeps working and quietly computes something else. `name` and `message` on an error's prototype are the one exception, and they are frozen rather than repaired. The host renders a thrown error by reading both off the object with no context to call a getter with, and nowhere safe to call one: the time budget is checked between polls of the evaluation and formatting runs after that returns, so a getter of the expression's own would run outside it, bounded only by the loop and recursion limits at some hundreds of millions of iterations. `e.name = 'MyError'` is dropped in silence as the price of keeping user code out of the failure path. The setup code reads every intrinsic it needs before the first prototype is rewritten, and indexes arrays rather than calling their methods. Repairing `Array.prototype` turns `concat` and `forEach` into accessors, and an earlier attempt at this used both while walking the remaining globals, which left every prototype after `Array.prototype` unfrozen and the prelude failing to load at all. Measured on macOS arm64, release, min of 3 x N, against this base: an inherited-name read costs ~68 ns where it is now an accessor, which is +18% on a loop doing a hundred of them and +2% to +7% on ordinary expressions. Extending the repair past `Object.prototype` is what the array-method read pays for; nothing else moves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * expr: the prelude's lock answers delete and defineProperty too (#2133) Tests for the two routes to a global binding the existing ones did not cover against a prelude function, asked for in review. `writable: false` answers an assignment and a `var`; it is `configurable: false` that answers these, and they fail differently: a `delete` of a non-configurable property evaluates to false in sloppy mode, while a `defineProperty` over one throws, so that is the one route whose refusal the user sees. Also records which mode decides that, because it was asked and the answer is not the obvious one: an expression cannot reach strict mode at all. `Runtime::script` wraps the text in `try{…}catch(__e){throw __e}`, so a `"use strict"` the user writes is an expression statement inside a block rather than a directive prologue and has no effect. A refused write is therefore always the silent kind, never a TypeError. No change to the prelude; these pass as it stands. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * expr: freeze Math and JSON plainly instead of repairing them (#2176) Math and JSON are namespaces, not prototypes: nothing inherits sin or parse, so the accessor repair keeps no shadowing there and only puts a getter call on every Math.* read of a per-frame expression. Freeze them directly and lock the two bindings so Math = {} cannot replace them for the next expression either. Prototypes keep the repair. --------- Co-authored-by: Anubhav-Rai <adobearakap@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>