Skip to content

Let scripts delete and replace built-in properties where JavaScript allows it - #22

Merged
NetWin merged 4 commits into
incrementfrom
feature/configurable-properties
Oct 24, 2026
Merged

NetWin merged 4 commits into
incrementfrom
feature/configurable-properties

Conversation

@NetWin

@NetWin NetWin commented Oct 24, 2026

Copy link
Copy Markdown
Owner

What changes

Built-in properties now follow JavaScript's rules for what a script can delete, replace and list as its own. Before this change, most of them could never be changed, and some were listed as own properties when JavaScript keeps them on the prototype.

  • Functions: name and length can be deleted, as in JavaScript. Afterwards they read '' and 0, which is what Function.prototype has, and 'name' in f is still true. Assigning to them still throws, before and after the delete.
  • Built-in methods such as Math.max, JSON.parse or Object.keys:
    • can be replaced (Math.max = fn); the replacement is not enumerable, so Object.keys(Math) stays [];
    • can be deleted; assigning again afterwards adds an ordinary own property.
  • Constants such as Math.PI and Number.EPSILON can still be neither written nor deleted. Deleting one now throws V8's message, Cannot delete property 'PI' of #<Object>, instead of "of a read-only object".
  • Errors: name, message and stack can be deleted.
    • Afterwards message reads '', name the constructor's name and stack undefined, as in JavaScript.
    • String(e) and the error the host receives use these values.
    • Assigning afterwards adds an own property, which Object.keys lists.
  • Type accessors are no longer own properties. A Map's or Set's size and a RegExp's source and flags live on the prototype in JavaScript.
    • Every value still answers to them, and in finds them.
    • Object.hasOwn and Object.getOwnPropertyNames now leave them out: new Map() lists [] and /a/g lists ['lastIndex'].
    • Deleting one does nothing.
  • lastIndex: delete r.lastIndex throws, as it does in Node.
  • Object.preventExtensions(Math) no longer makes Object.isSealed(Math) and Object.isFrozen(Math) report true.
  • Delete messages name a RegExp or Date as [object RegExp] / [object Date] and a namespace as #<Object>, as V8 does.

Everything a run replaces or deletes on a shared built-in stays in that run. Nested runs and the next run see the originals.

Scripts that relied on the old behaviour see these changes:

  • Assigning to a built-in method no longer throws.
  • delete f.name is no longer a no-op.

No new divergence from JavaScript.

How

  • Fixed properties:
    • FixedProperty gets an explicit writable, an optional own: false for accessors JavaScript keeps on the prototype, and an optional inherited value for what the prototype has once the own property is deleted.
    • FixedChange, the per-value record next to the property table, gets deleted and replaced. A replaced value is kept there because the built-in is shared by every run.
  • SandboxObject applies these rules in getProperty, setProperty, deleteProperty, in, Object.hasOwn, the own-key listing and seal / freeze. Deleting a configurable fixed property marks it deleted; deleting a non-configurable one throws.
  • Static members: a method is writable and configurable, a constant is neither. That is the specification's rule for every member Maple has, so the attributes are not read from the host's descriptors. Builtin.deleteProperty, which refused every delete, is gone.
  • Reads: a read on a shared value looks at the run's changes without creating an entry for them. An error reads its own name and message through the same path, so a deleted message converts as ''.
  • Delete messages: SandboxObject.describe(), overridden for namespaces, errors, RegExps and Dates, replaces the tag checks in describeObject.
  • Parity: Node now runs last in every helper, so a case that changes a built-in can restore it in a finally before anything else runs. This is noted in AGENTS.md.

Tests, parity and docs

  • Tests:
    • New tests in the function, Math, Object, error and escape specs cover deleting name / length, replacing and deleting Math.max, the delete messages, preventExtensions on shared values, and errors.
    • Isolation between runs and nested runs is tested too.
    • map.spec.ts, regexp.spec.ts and object.spec.ts cover the type accessors that are no longer own.
    • The tests that expected built-in methods to be read-only were rewritten.
  • Parity:
    • New cases for replacing and deleting Math.max (each restores the original descriptor in a finally), delete f.length, and deleting and re-adding an error's message.

    • New divergences where Maple follows JavaScript and js-interpreter (ES5) does not:

      • delete f.name;
      • deleting Math.PI or lastIndex;
      • Object.getOwnPropertyNames on a RegExp and delete r.source.

      Each has a README row.

  • Docs: language.md (Functions, and the wrappers' own properties in the value model) and builtins.md (Errors).
  • Benchmark (measure-run, 1000 runs, four alternating rounds against increment):
    • contact-summary.js and contact-ten-lines.js are unchanged.
    • A script made only of reads of built-in properties is about 1.6% slower. Timed one side per process, the same script is about 3% faster.

Node runs each case in the test process, so a case that replaces or
deletes a built-in changes it for js-interpreter and Maple too. Every
helper now runs Node last, and such a case restores the built-in in a
finally before anything else runs.
A Map's or Set's size and a RegExp's source and flags are accessors on
the prototype in JavaScript, not own properties. Every value still
answers to them and in finds them, but Object.hasOwn and
Object.getOwnPropertyNames now leave them out, and deleting one changes
nothing. lastIndex stays a RegExp's one own property.
… properties

As in JavaScript, a function's name and length can be deleted, which
leaves what Function.prototype has, and the methods of a built-in such as
Math.max can be replaced and deleted, while constants such as Math.PI and
a RegExp's lastIndex cannot be deleted. An error's name, message and
stack can be deleted too. What a run changes on a shared built-in stays
in that run.
@NetWin NetWin self-assigned this Oct 24, 2026
@github-actions

Copy link
Copy Markdown

📋 Release Preview

Changelog

0.6.0 (2026-10-24)

🚀 Features

  • builtins: support WeakMap and WeakSet (4e1af2e)

🩹 Bug Fixes

  • map: read constructor entries as any object, not only arrays (91eb05e)
  • properties: let scripts delete and replace configurable built-in properties (2f1ef57)
  • properties: stop listing type accessors as own properties (b4d9048)

🧪 Tests

  • parity: run Node last so a case can restore what it changes (c726ab5)

@NetWin
NetWin marked this pull request as ready for review October 24, 2026 21:55
Copilot AI lite review requested due to automatic review settings October 24, 2026 21:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Address the forbidden-key validation bypass in Object.hasOwn and ensure deleted or reassigned error stacks are reflected during host error conversion.

2 open issues
What changed in this PR

This pull request updates Maple’s sandbox object model so built-in properties follow JavaScript-compatible rules for deletion, replacement, inheritance, ownership, and per-run isolation.

Changes:

  • Adds configurable, replaceable built-in methods while preserving protected constants.
  • Models prototype-backed properties for functions, errors, Maps, Sets, and RegExps.
  • Updates error conversion, parity isolation, tests, documentation, and integrity behavior.
File Description
packages/​maple/​src/​values/​wrappers/​set-wrapper.ts Models Set.size as a prototype-backed accessor.
packages/​maple/​src/​values/​wrappers/​regexp-wrapper.ts Separates lastIndex from prototype-backed RegExp accessors and updates delete descriptions.
packages/​maple/​src/​values/​wrappers/​map-wrapper.ts Models Map.size as a prototype-backed accessor.
packages/​maple/​src/​values/​wrappers/​date-wrapper.ts Adds JavaScript-compatible Date descriptions for delete errors.
packages/​maple/​src/​values/​sandbox-object.ts Implements fixed-property replacement, deletion, inheritance, ownership, and integrity rules.
packages/​maple/​src/​values/​sandbox-error.ts Adds deletion-aware error properties and host conversion handling.
packages/​maple/​src/​values/​run-state.ts Adds non-allocating lookups for per-run shared-property changes.
packages/​maple/​src/​values/​own-properties.ts Tracks deleted and replaced fixed properties.
packages/​maple/​src/​values/​members.ts Updates member operations and own-property handling.
packages/​maple/​src/​values/​coercion.ts Delegates failed-delete descriptions to sandbox values.
packages/​maple/​src/​values/​builtin.ts Assigns JavaScript-like descriptors to built-in methods and constants.
packages/​maple/​src/​support/​noop.ts Provides a no-op method lookup callback for error conversion.
packages/​maple/​src/​evaluator/​language/​functions.spec.ts Tests deletion and inherited behavior for function properties.
packages/​maple/​src/​escapes.spec.ts Tests isolation of shared built-in changes across runs.
packages/​maple/​src/​errors.spec.ts Tests deletion and reassignment of error properties.
packages/​maple/​src/​builtins/​regexp/​regexp.spec.ts Tests RegExp own-property and prototype-accessor behavior.
packages/​maple/​src/​builtins/​object/​statics/​has-own.ts Routes Object.hasOwn through sandbox-aware logic.
packages/​maple/​src/​builtins/​object/​object.spec.ts Tests own names, deletion errors, and integrity levels.
packages/​maple/​src/​builtins/​math/​math.spec.ts Tests replacement and deletion of Math methods.
packages/​maple/​src/​builtins/​map/​map.spec.ts Tests Map and Set prototype-backed size.
packages/​maple/​src/​builtins/​json/​json.spec.ts Tests replacement of JSON methods.
packages/​maple/​docs/​language.md Documents function, built-in, and wrapper property behavior.
packages/​maple/​docs/​builtins.md Documents deletable error properties.
packages/​maple-parity/​src/​parity.ts Ensures Node runs last to isolate built-in mutations.
packages/​maple-parity/​src/​divergences.spec.ts Adds documented ES2015 property-model divergences.
packages/​maple-parity/​src/​cases/​math.spec.ts Adds parity tests for Math method replacement and deletion.
packages/​maple-parity/​src/​cases/​functions.spec.ts Adds function-length deletion parity coverage.
packages/​maple-parity/​src/​cases/​errors.spec.ts Adds error-message deletion and reassignment parity coverage.
packages/​maple-parity/​README.md Documents new parity differences.
AGENTS.md Records parity execution ordering and restoration requirements.

Review effort: Lite

Comment thread packages/maple/src/values/members.ts
Comment thread packages/maple/src/values/sandbox-error.ts
@NetWin
NetWin merged commit 1fd88b1 into increment Oct 24, 2026
8 checks passed
@NetWin
NetWin deleted the feature/configurable-properties branch October 24, 2026 22:18
@NetWin NetWin mentioned this pull request Oct 25, 2026
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 0.6.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants