Commit Graph
51 Commits
Author SHA1 Message Date
dotCooCoo 5faa5c4d1d JavaScript: JSON.parse releases its text before the reviver runs
The text is rooted and held only while the parse runs. A reviver can use the room the text took.
2026-11-08 16:34:49 -08:00
dotCooCoo fb4c38db5d JavaScript: JSON.parse counts its text and pending names toward the heap limit
The text a non-string argument converts to stays rooted while the parse runs, so a
collection still counts it. A property name read from the text is charged to the heap
until its property is defined. Boolean.prototype.toString and valueOf are added, so a
Boolean object writes `true` or `false`. The json2 sample no longer includes U+2028:
the polyfill escapes it and section 15.12.3 does not.
2026-11-08 16:27:59 -08:00
dotCooCoo 501d82ece4 Merge main into feat/js-builtins-core 2026-11-08 16:25:35 -08:00
dotCooCoo 5bd49c000e JSON: parse counts a string it has read and a flattened text toward the heap limit
A string value the parser has read is held only by the parser until it is stored. The
property reservation can start a collection, which dropped the string from the heap
total. The value now stays rooted across that reservation, for array elements and for
object members. `JSON.parse` also charges the buffer it flattens a concatenated text
into, through the helper `parseInt` and `parseFloat` use.
2026-11-08 15:54:03 -08:00
dotCooCoo 4afee338cf JavaScript: toLocaleString writes the primitive value of Number, String and Boolean objects
`[1, 'x', true].toLocaleString()` gave `[object Number],[object String],[object Boolean]`
because the wrappers' prototypes have no `toString` on this branch and the lookup reached
Object.prototype.toString. Object.prototype.toLocaleString now returns the string form of
the object's primitive value when `toString` is not found on the object or on its chain up
to its standard prototype. A `toString` on the object or that prototype is still called.
2026-11-08 15:43:09 -08:00
dotCooCoo b6404f2f8b JavaScript: toLocaleString calls toString on the boxed value, and overwriting an element reserves no heap
Object.prototype.toLocaleString passed the primitive this value to
toString; section 15.2.4.3 calls it on ToObject(this), so a strict
toString now sees the object. The array methods' element write reserved
room for a new element even when it only replaced an own property, so a
heap at its limit stopped reverse or sort writes that add nothing.
2026-11-08 05:54:07 -08:00
dotCooCoo 923faeb35a JavaScript: the test262 interpreter runs fail when a harness file is missing or no test runs
run_tests read an unreadable harness, include or test file as an empty
string, so a broken suite checkout passed with every run counted as a
failure. It now stops with the file's path. The language run checks
that test/language exists, and the language and built-ins runs check
that at least one ES5 test ran.
2026-11-08 05:52:46 -08:00
dotCooCoo 01c89d4bc5 JavaScript: a small heap limit no longer leaves a realm without built-ins
Installing the built-ins is not bounded by the heap limit: Math and JSON
no longer reserve heap while they install, so a realm made with a tiny
heap_bytes gets every built-in and runs once the limit is raised. If
installing fails, the realm keeps the reason and every run ends with an
internal error instead of running against missing built-ins.

The json2.js test skips only when the built-in check returns false, and
fails on any other result. The parser's progress line says the app does
not run scripts yet.
2026-11-08 05:35:27 -08:00
dotCooCoo 16b291aca1 JavaScript: built-ins install under any limits, and class octal escapes match the compiler
A realm made with a low fuel or heap limit lost RegExp and Date, because
installing them compiled a pattern under that limit and the error was
dropped. The built-ins now install before the realm's limits apply.

The early pattern check read an octal class escape starting with 4 to 7
as three digits, while the compiler reads two, so `/[\477-z]/` gave a
SyntaxError for a pattern the compiler accepts. The check now reads
class octal escapes with the compiler's own reader.

docs/target-app-parity.md: the interpreter's entry lists the new
built-ins and the current language-test count.
2026-11-07 21:48:35 -08:00
dotCooCoo 328aa0e340 JavaScript: a RegExp source escapes a line terminator after a backslash, and Date.parse reads its words in place
A constructor pattern with a backslash before a line terminator wrote the raw terminator into source and toString, so the text did not read back as a literal; it now writes the terminator's escape, which matches the same unit (ECMA-262 5.1 section 15.10.2.10).

Date.parse no longer collects every word of a text date into a list before it checks them, and reads month, weekday and zone names without copying the word.
2026-11-07 20:33:40 -08:00
dotCooCoo 971a0d6626 JavaScript: a backreference takes fuel for each 16 units before it compares them 2026-11-07 20:26:40 -08:00
dotCooCoo 52e94d4f8a JavaScript: compiling a pattern charges the room its lists reserve
- The compiler charges each working list's added capacity before it
  grows (capacity doubles), the code that joins alternatives, and the
  flattened code with its stack, instead of an estimate from
  instruction and fragment counts.
- The scan for capturing groups and the syntax check before compiling
  each take one unit of fuel per unit of the pattern.
- Bytes a dropped HeldBytes released leave the heap's total at the
  next limit check, not at the next collection.
- The progress line lists the deletable RegExp.prototype flag
  properties among the test262 failures.
2026-11-07 20:12:42 -08:00
dotCooCoo f16cf72f07 JavaScript: strings the built-ins build take fuel and stay counted in collections
The buffer that join, JSON.stringify, the URI functions, escape and
unescape write into takes one unit of fuel for every 16 code units
written, so writing a long run of separators or copying a long string
over and over ends with the fuel limit. Its bytes are added back to the
heap total after each collection until the string is finished, and a
native call that ends early gives them back, so a collection while a
string is being built no longer lets the heap estimate drop below what
is in use.
2026-11-07 20:09:09 -08:00
dotCooCoo edfd25e26c JavaScript: join reads length before it converts the separator
Array.prototype.join now reads and converts the object's length before
it converts the separator, and still converts the separator when the
length is 0, as ECMA-262 5.1 section 15.4.4.5 steps 2 to 6 give them.
2026-11-07 19:58:05 -08:00
dotCooCoo 447a3cc65f JSON: stringify unwraps Number and String objects to their primitive values
Number objects made by ToObject now carry their primitive value, as
String and Boolean objects already did (ECMA-262 5.1 sections 8.6.2
and 9.9). JSON.stringify reads it for a value, a replacer list entry
and the space argument (section 15.12.3, Str step 4, step 4.b.ii and
step 5) when the method that ToNumber or ToString calls first is not
found before the object's own prototype, so JSON.stringify(Object(3))
gives 3 and JSON.stringify(Object('abc')) gives "abc". A valueOf or
toString on the object or on its prototype still runs.
2026-11-07 19:46:01 -08:00
dotCooCoo c4aa5cec8a Merge feat/js-interpreter into feat/js-builtins-text 2026-11-07 19:34:34 -08:00
dotCooCoo cea958b782 JavaScript: text built-ins hold their working memory against the heap limit
Native code now holds the Rust memory it works in against the heap limit
until it is done with it: a matcher's capture state and stack, a pattern
being compiled, the matches and pieces of a replacement, and the text
Date.parse reads. A collection counts these bytes as live while they are
held.

- replace reads its replacement pattern once into parts that refer to
  ranges of the pattern, so literal text is not copied for each match.
  Each piece takes fuel for its units, and the result's length is
  checked against the heap as each piece is added.
- The regular expression compiler charges heap through its callback as
  it builds, including the ranges of a character class, and each class
  atom and range takes fuel. A compiled form counts the capacity of its
  lists.
- Clearing the groups inside a quantifier and clearing the matcher's
  state before each attempt take fuel.
- Flattening a rope in a String, RegExp or Date method reserves the
  flat copy on the heap and takes fuel for it.
- A RegExp object's bytes are reserved together with its compiled form,
  so a collection between the two does not drop the compiled form from
  the heap count.
2026-11-07 19:29:53 -08:00
dotCooCoo 65691ceb7b JavaScript: unshift with no arguments reads and writes each element
Section 15.4.4.13 steps 6 and 7 move every element by the argument count, so with no arguments each element is read and put back in place: getters and setters run, and a read-only element throws a TypeError.
2026-11-07 19:27:44 -08:00
dotCooCoo 2d13f219f6 JavaScript: getOwnPropertyNames lists an array's length after its other names
Section 15.2.3.4 leaves the order of the names open. An array's length now comes after its indices and other properties, so Object.getOwnPropertyNames([0, 1, 2]) gives 0,1,2,length, as later editions and test262 expect. length is not enumerable, so Object.keys and for-in are unchanged.
2026-11-07 19:23:56 -08:00
dotCooCoo 813fe78a8c Merge feat/js-interpreter into feat/js-builtins-core
Takes #6178's rooting of every native call's arguments, the fuel per argument apply reads and the Error objects as NativeError prototypes. The bound-function and apply arm that rooted arguments only for those calls is removed, since every native call now roots them; the parsing line in docs/target-app-parity.md takes #6178's wording.
2026-11-07 19:12:18 -08:00
dotCooCoo c7baa679d9 JavaScript: apply takes fuel for each argument it reads
Function.prototype.apply reads up to 65,536 elements from an array-like
object. Each element read now takes a unit of fuel, so a long list ends
the run with the fuel limit as other loops in the interpreter do.
2026-11-07 19:02:53 -08:00
dotCooCoo a9e0d95226 JavaScript: assigning an array's length converts the value twice, as defining it does
Section 15.4.5.1 steps 3.c and 3.d convert the new length once with
ToUint32 and once with ToNumber. [[Put]] reaches that algorithm through
[[DefineOwnProperty]], so a.length = v now calls v's valueOf twice, the
same as Object.defineProperty(a, 'length', {value: v}).

Tests count the valueOf calls on both paths, check that JSON.stringify
converts Number and String objects with ToNumber and ToString, and check
that toLocaleString calls each primitive element's method on the object
ToObject makes.
2026-11-07 19:00:44 -08:00
dotCooCoo 60e617f861 JavaScript: parseInt and parseFloat count their copies toward the heap limit
parseInt collected every digit into a vector outside the heap count and
then copied the digits again for a base-10 parse, and parseFloat copied
its whole decimal prefix the same way, so one call on a long string could
allocate more than the heap limit. parseInt now counts the digits in
place, reads bases other than 10 straight from the string, and both
functions reserve heap for the base-10 copy before they make it. A
concatenation that the global functions flatten is charged to the heap,
as sort keys are.
2026-11-07 18:28:44 -08:00
dotCooCoo 02fffcd00c JavaScript: String, Number, RegExp and Date built-ins
A realm now has String, Number, RegExp and Date as ECMAScript 5.1
sections 15.5, 15.7, 15.9 and 15.10 define them, with Annex B's substr,
getYear, setYear and toGMTString.

- String: every ES5 prototype method; split, replace, match and search
  take strings and RegExps. Each new string's length is checked against
  the heap limit before it is built, and scans take fuel.
- Number: toString(radix), toFixed, toExponential and toPrecision
  format from the number's exact decimal value.
- RegExp: a backtracking matcher keeps its choice points and capture
  state on the heap, within the heap room left, and takes fuel for every
  step, so a catastrophic pattern stops at the fuel limit. Patterns
  nested past Options::max_depth give a SyntaxError. Flags g, i and m,
  lastIndex, exec, test and toString.
- Date: chrono converts days to calendar dates, shifting by whole
  400-year cycles past its years. Realm::set_clock and
  Realm::set_time_zone set the clock and local time (UTC by default).
  Without a hint, a Date converts to a primitive as a string.

cargo xtask test262 also runs the suite's built-ins tests for the four
and prints their counts.
2026-11-07 18:24:27 -08:00
dotCooCoo 28bc571e34 JavaScript: bind converts the target's length to an integer
A host function has no own length, so a script can define one with a
fraction, and the bound function's length kept it. Function.prototype.bind
now converts the target's length to an integer before it subtracts the
bound arguments, as later editions specify.
2026-11-07 17:55:51 -08:00
dotCooCoo e03cf7c5f7 JavaScript: defining a new property checks the heap limit first
Object.defineProperty, Object.defineProperties and Object.create added
each new property without checking the heap limit, so one call could
take the heap estimate past the limit before the run stopped. The
property's size is now reserved before it is added, with the object and
the descriptor's values kept reachable while a collection runs.
2026-11-07 17:45:16 -08:00
dotCooCoo d21ab8f4e2 JavaScript: join with an empty separator does not walk the holes of a sparse array
Array.prototype.join wrote the separator once per index between the
elements it found, so new Array(4294967295).join("") ran four billion
empty writes without taking fuel. Separators are now written in one step
per gap, and an empty separator writes nothing.
2026-11-07 17:38:17 -08:00
dotCooCoo aa3bcc4c77 JavaScript: each NativeError prototype is an Error object
Object.prototype.toString gives "[object Error]" for TypeError.prototype
and the other NativeError prototypes, as ECMAScript 5.1 section 15.11.7.7
defines them. Before, only Error.prototype had the Error class.
2026-11-07 17:22:26 -08:00
dotCooCoo 3e09521b1f JavaScript: sort keys and replacer names count toward the heap limit
Array.prototype.sort without a comparison function and the property list
of a JSON.stringify replacer array keep a string for each element. A key
that comes from flattening a concatenation is a new buffer: it is now
charged to the heap, and keys that are not elements are rooted so that a
collection counts them. Keys that share an existing string's buffer are
not charged again.
2026-11-07 17:22:06 -08:00
dotCooCoo 263c7cb926 JavaScript: arguments from apply stay alive while a native function runs
A native function called through apply could run JavaScript that started a
collection, which freed the arguments apply had read from the array-like
object. The native call now roots its receiver and arguments.

The parsing entry in docs/target-app-parity.md no longer says that nothing
runs scripts.
2026-11-07 17:10:48 -08:00
dotCooCoo 1470dd5ca9 JavaScript: built-ins keep a new value rooted while they reserve room to store it
Array methods that store an element (map, filter, slice, splice, concat,
shift, unshift and sort), the JSON.parse reviver walk and JSON.stringify
reserved heap for a value that only native code held. When the heap was
near its limit, that reservation ran a collection that freed the value
before it was stored, and the script later failed with "object out of
range". The value now stays on the temporary roots until it is stored,
and stringify roots an object from toJSON or a replacer before it writes
the separator and key.
2026-11-07 17:09:47 -08:00
dotCooCoo 7436a38f44 JavaScript: Object, Function, Array, Error, Math and JSON built-ins
vectorcraft-js now provides the ECMAScript 5.1 built-ins: the Object
property-descriptor functions, create, keys, seal and freeze (15.2),
Function.prototype.bind (15.3.4.5), Array with every ES5 method (15.4),
the error types (15.11), Math (15.8), JSON with revivers, replacers and
indentation (15.12), and the global functions with Annex B's escape and
unescape (15.1, B.2). Prototype methods are writable and configurable.

The array methods work on array-like objects and visit only the indices
that exist, so a sparse array of length 2^32-1 costs as much as its
elements, and they see indices a callback adds. join writes a cycle back
to an array as an empty string; JSON.stringify throws a TypeError on a
cycle. JSON parsing, writing and the reviver walk use no native
recursion. Every loop takes fuel and every result is checked against
the heap limit, so new Array(4294967295).join("x") ends with the heap
limit and a huge sort or stringify ends with a limit.

cargo xtask test262 also runs the suite's ES5 built-in tests and fetches
json2.js at a pinned commit, checked against its SHA-256, to compare its
output with the built-in JSON once Date, String.prototype.replace and
RegExp exist.
2026-11-07 16:49:38 -08:00
dotCooCoo 0b936bf60e JavaScript: the script a realm runs does not count against the re-entry limit
Limits::reentry bounds runs inside an operation (getters, setters, conversions, indirect eval). The top-level script took one of those slots, so a limit of 0 refused every script and a limit of 1 left no room for a getter. Only indirect eval now counts when it starts a global run.
2026-11-07 15:29:40 -08:00
dotCooCoo 5ebf32e973 JavaScript: shrinking an array takes fuel for each property
Assigning a smaller length to an array visits every property in one operation. It now takes fuel for each one before anything is removed, so a run with a small fuel limit or a set interrupt flag stops there and leaves the array unchanged.
2026-11-07 15:24:53 -08:00
dotCooCoo 44c17c4e9a JavaScript: the test262 runner reports a failed assertion's message
The harness's assert.js formats failure messages with the global String, which the interpreter does not provide yet, so the four failing runs reported "ReferenceError: String is not defined". The runner now defines a stand-in that converts with +, and the failures name the assertion (the completion value tests expect undefined where ECMA-262 5.1 section 12.6.3 gives 4).
2026-11-07 15:18:55 -08:00
dotCooCoo cf1162cf95 Merge feat/js-parser into feat/js-interpreter 2026-11-07 15:04:39 -08:00
dotCooCoo 4d8bee277c JavaScript: for-in takes fuel for each property and reserves heap for its names
Listing the names for a for-in loop now takes a unit of fuel for each
property it visits and reserves heap for the name list before it is
kept, so a run with small limits stops on an object built under larger
ones.
2026-11-07 14:59:24 -08:00
dotCooCoo 70cfdcb43d JavaScript: kept code and function source text count toward the heap
A collection counts each piece of code a closure or frame keeps: its instructions, tables,
names, nested functions and its source text, each shared buffer once. Code that eval and the
Function constructor compile at run time is charged when it is made, after a heap reservation.
Function.prototype.toString reserves and charges the text it copies.
2026-11-07 14:39:21 -08:00
dotCooCoo dcccbad29c JavaScript: hexadecimal and octal numbers round once to the nearest value
ToNumber on a hexadecimal string folded its digits into a double one at a
time, so a string past 2^53 could come out one unit off the literal with
the same digits. The lexer kept digits exactly in a u128 but rounded per
digit once the value passed 128 bits. Both now call one helper that keeps
the digits exactly while they fit, records whether any later digit is
set, and rounds once.
2026-11-07 14:18:23 -08:00
dotCooCoo ea319ebcda JavaScript: identifiers follow the Unicode categories of section 7.6, and label and parameter checks are linear
is_id_start accepts the letter categories (Lu, Ll, Lt, Lm, Lo, Nl) and
is_id_part adds Mn, Mc, Nd and Pc, read with unicode-general-category.
A superscript digit (No) inside an identifier and a mark such as U+09BE
at its start are now SyntaxErrors, and identifiers with marks outside
the old list, such as the Bengali and Tamil viramas, now parse.

Labels in scope are kept in a map, and duplicate labels in a chain and
duplicate parameters of a strict function are found with a hash set, so
long chains, many labeled statements and long parameter lists are
checked in linear time.
2026-11-07 14:09:25 -08:00
dotCooCoo 47db0b7a8a JavaScript: declarations check the heap before they are bound
Declaration binding instantiation reserves an upper bound of what a script's,
eval's or function's variables and function declarations add to the heap
before making them, and each declaration and parameter takes a unit of fuel.
A long declaration list now ends with the heap limit without growing the
estimate past it.

Realm::set_limits recomputes when the next collection runs, so a lowered heap
limit applies to the next run instead of after the old threshold.

ToNumber skips the same white space and line terminators as the lexer, so
U+180E is no longer white space in a numeric string.
2026-11-07 14:07:07 -08:00
dotCooCoo e1cc8d5de6 JavaScript: names and host error messages count toward the heap when they are made
A new property or binding now adds its name's code units to the heap
estimate when it is inserted, so names declared by eval or Function
source move the estimate toward a collection. Function parameter and
catch bindings no longer count their entries twice.

A host function's error message reserves its size in the heap before
the error object is made, so a message larger than the heap ends the
run with the heap limit.

The Function constructor sums its arguments' lengths and checks the
source limit before flattening or copying any of them, and builds the
source from their code units, so a lone surrogate in an argument is
kept.
2026-11-07 13:49:13 -08:00
dotCooCoo dfd0b1c949 JavaScript: a finally block keeps the try block's completion value, and host strings count toward the heap
In script and eval code a finally block that ends normally leaves the completion value of its try or catch block (ECMA-262 5.1 section 12.14), so try { 1; } finally { 2; } completes with 1. A new GetCompletion instruction saves the value when the finally block starts, and SetCompletion sets it back when the block ends normally; a break, continue or throw out of the finally block still replaces it.

A string returned by a host function is charged to the heap, and one that does not fit after a collection ends the run with the heap limit.
2026-11-07 13:32:53 -08:00
dotCooCoo d16ff70fd3 JavaScript: a collection counts the code units of property and binding names
A collection counted each property or binding at a fixed 64 bytes and
never counted the name's own buffer. Once the string that produced a long
name became garbage, the name kept its buffer alive while the estimate
dropped it, so a script could hold megabytes of names past the heap
limit. trace_object and trace_env now add each name's buffer, counting a
buffer that a flat string value or another name shares once.
2026-11-07 13:19:19 -08:00
dotCooCoo f045adba2b JavaScript: a function made by the Function constructor does not bind the name anonymous
The constructor compiles its source as (function anonymous(...) {...}), and the
compiled function bound anonymous to itself as a named function expression does.
That binding shadowed a global variable of the same name. ES5.1 section 15.3.2.1
creates the function with the global environment as its scope and no name binding,
so the constructed function now leaves the name unbound.
2026-11-07 13:09:41 -08:00
dotCooCoo e85cecfa99 JavaScript: for-in over a String object checks the heap before listing its indices
Listing a String object's index names for for-in built one name per UTF-16 unit before any
limit check, so a string within the heap limit could make the loop allocate many times that
limit. The heap budget for all the names is now checked first, after a collection if needed,
and each name takes a unit of fuel. A wrapper made by ToObject stays rooted while the names
are listed.
2026-11-07 13:02:26 -08:00
dotCooCoo 5d61122b77 JavaScript: a strict realm runs scripts as strict mode code, and apply unroots its arguments when one throws
Realm::run compiles the script with the realm's Options::strict instead of always as sloppy code, so a realm made with strict: true rejects with statements and calls functions with an undefined this. Indirect eval and Function keep their own strictness (ES5.1 10.4.2, 15.3.2.1).

Function.prototype.apply drops the temporary GC roots it holds for the argument values when an index getter throws, as it already did when reading succeeds. Before, a caught throw left them rooted until the run ended, so a loop of such calls could reach the heap limit.
2026-11-07 12:54:06 -08:00
dotCooCoo ba9e78ebfd JavaScript: a run clears the last uncaught exception and checks source length before encoding
Realm::run now resets the exception that bind_last_thrown binds, so a run
that completes, reaches a limit or does not parse leaves it undefined. The
script source, the text built by Function and the strings given to eval are
checked against the run's remaining max_source_units before they are encoded
or flattened, so an oversized source is rejected without being copied.
2026-11-07 12:45:02 -08:00
dotCooCoo d026e6ae26 JavaScript: interpreter with a garbage-collected heap and execution limits
vectorcraft-js gains a Realm that runs ECMAScript 5.1 scripts. The parsed
script compiles to stack-machine bytecode, a verifier checks every jump
target, handler, constant index and stack depth before the code runs, and
the interpreter keeps JavaScript calls on its own frame stack. Closures,
this, arguments, with, direct and indirect eval, new Function,
try/catch/finally, labeled break and continue, and for-in in insertion
order follow sections 10 to 14 of ECMA-262 5.1. Variable references are
resolved before the right-hand side of an assignment runs (11.13).

Objects and environments live in an arena with a mark-and-sweep
collector and index handles. Built-ins are limited to Object and
Object.prototype's basic methods, Function with call and apply, eval
and the error constructors.

Every run is bounded by fuel, heap bytes, frames, nested host calls and
an interrupt flag another thread can set, with smaller defaults in the
wasm build. A run that reaches a limit ends with a limit error that a
script's try cannot catch. Source passed to eval and Function counts
against Options::max_source_units.

cargo xtask test262 also runs the suite's ES5 test/language tests that
need only these built-ins and prints their counts apart from the parse
counts. Nothing in the app calls the interpreter yet.
2026-11-07 12:36:48 -08:00
dotCooCoo 6bb3d3dfbe JavaScript: a for-in variable initializer is a SyntaxError in strict mode code
Current test262 (ECMA-262 2017 Annex B.3.6) accepts `for (var x = 1 in o)` only outside strict
mode code. The parser now rejects the initializer in strict code, with the error at `in`.
2026-11-07 10:51:53 -08:00