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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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`.
New crate vectorcraft-js reads ECMAScript 5.1 source into a syntax tree
with source positions: a UTF-16 lexer (line and column tracking, automatic
semicolon insertion, the regexp and division goals), preprocessor lines
read as directive tokens, a recursive-descent parser to an arena tree, and
the ES5 early errors, strict mode included. Nesting deeper than
Options::max_depth (200 by default) gives a SyntaxError with its line and
column rather than overflowing the stack. Nothing runs scripts yet.
cargo xtask test262 downloads test262 at a pinned commit, checks its
SHA-256, unpacks it into the gitignored corpus/ folder and runs the ES5
syntax tests. A differential test in the engine compares the lexer with
scriptcheck on line counts, directives and unterminated literals.