Commit graph

228 commits

Author SHA1 Message Date
Andreas Kling
b81269e78b Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.

Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-22 19:51:25 +02:00
Andreas Kling
b6bef6b688 Libraries: Use UTF-16 for JS-visible runtime strings
Produce JS-visible string results as UTF-16 at their source, including
numeric formatting, BigInt and BigFraction formatting, URI encoding,
console formatting, parser errors, regular expression errors, Intl and
Temporal records, LibUnicode locale boundaries, and LibWeb bindings.

Handle fractional radix formatting through the UTF-16 builder view.
2026-06-22 19:51:25 +02:00
Andreas Kling
7025dd1fa7 Libraries: Parse JS strings from UTF-16
Thread UTF-16 string input through JSON, script parsing, Date parsing,
Intl option parsing, Temporal parsing, and the helper library boundaries
that feed those parsers. Preserve ASCII fast paths where the source data
is known to be ASCII.
2026-06-22 19:51:25 +02:00
Andreas Kling
950c460efd LibJS: Keep more string construction in UTF-16
Use a typed RegExp cache key instead of serializing the UTF-16
pattern and flags into a String. This avoids another StringBuilder
path for data that is already naturally represented as UTF-16 plus
flag bits.

Also make legacy unescape operate on UTF-16 code units and build its
result with Utf16StringBuilder. This preserves direct non-ASCII input
and %uXXXX escapes, including lone surrogate code units, without
routing the result through UTF-8 storage.
2026-06-22 16:10:40 +02:00
Andreas Kling
595ae25e74 LibJS: Use Utf16StringBuilder for JS string construction
Port straightforward JS string construction sites to Utf16StringBuilder.
Make ASCII appends explicit with append_ascii(), and leave byte-output,
formatted-output, and printer plumbing on StringBuilder where that API
still matches the caller.

Add a small checked JS string length product helper. Use it for
String.prototype.repeat() before constructing the repeated result. Keep
the maximum string length policy in LibJS while allowing the UTF-16
builder to remain purpose-built and infallible. Cover overflow from the
final repeated code-unit length in the repeat tests.
2026-06-22 16:10:40 +02:00
sideshowbarker
d7c08964cb LibJS: Throw rather than crashing on a deep prototype-chain get
Problem: Converting an object with a pathologically-deep prototype chain
to a primitive was segfaulting.

Cause: Object::internal_get implements [[Get]] by recursing into the
prototype’s [[Get]] (parent->internal_get) when the property isn’t an
own property. For a sufficiently deep prototype chain, that C++
recursion exhausts the native stack, and segfaults. The bytecode
interpreter’s call-stack limit doesn’t cover this native recursion.

Fix: Before recursing into the prototype in Object::internal_get, check
VM::did_reach_stack_space_limit(), and throw a CallStackSizeExceeded
InternalError — the same way the interpreter and other recursive runtime
operations guard the native stack. The deep-chain get now throws a
catchable call-stack-size-exceeded error, rather than crashing.

Fixes https://github.com/LadybirdBrowser/ladybird/issues/3584
2026-06-20 23:34:33 +02:00
sideshowbarker
a237fbc24f LibJS: Add a regression test for binding environment type confusion
Issue #3622 documents a now-no-longer reproducible type-confusion/OOB
bug. This just adds the POC from that issue as a regression test.

Fixes https://github.com/LadybirdBrowser/ladybird/issues/3622
2026-06-20 23:34:19 +02:00
Andreas Kling
be5320b67c LibJS: Evaluate ToNumber for out-of-bounds typed array writes
The interpreter's fast path for PutByValue on a typed array treated an
out-of-bounds index as a silent no-op and returned without touching the
value. That is observably wrong: TypedArraySetElement evaluates
ToNumber(value) before checking the index, so a value with a valueOf
side effect must still have that side effect run even when the store is
ultimately discarded.

Fall back to the slow path on an out-of-bounds or otherwise invalid
index instead of reporting success. The slow path runs the full
TypedArraySetElement algorithm, which performs the coercion and then
discards the write. Direct assignment now matches Reflect.set, which
already went through the slow path.

Fixes the staging/sm typed array out-of-bounds ToNumber test262 case
and adds a test-js regression covering direct assignment, Reflect.set,
and Reflect.defineProperty.
2026-06-17 21:24:33 +02:00
Andreas Kling
a3db2d1986 LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.

We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.

The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.

Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 21:24:33 +02:00
Sam Atkins
abfd5ab860 Tests: Make JSON.stringify deep serialization test more consistent
This test verifies that we throw an exception instead of overflowing the
stack, but some machines have a large enough stack that this wouldn't
fill it. Raise the value by 10x, which does now throw an exception for
me.
2026-06-11 14:47:27 +12:00
aplefull
52f45aef4e LibWeb+LibJS: Don't crash when serializing deeply nested values
Deeply nested structures passed to JSON.stringify or structuredClone
would cause a lot of recursion, eventually causing a crash.

We now throw an InternalError instead, same as other browser engines.
2026-06-03 13:00:01 +01:00
aplefull
105098131b Tests+Meta: Reimport WebKit regexp tests
This commit updates Meta/import-webkit-regexp-tests.py and re-imports
WebKit tests.

Removed pcre-test-1 from SKIP_TESTS because we pass it.

Removed everything except slow.js from XFAIL_TESTS. We pass everything
except slow.js, which still hits the step limit.

Moved overflow.js to SKIP_FILES because it tests WebKit's behaviour of
rejecting regexps with huge quantifiers. Ladybird, Chrome, and Firefox
just clamp them.
2026-05-26 10:00:22 +02:00
aplefull
b3bd8884ae Tests+Meta: Reimport V8 regexp tests
This commit fixes assertEquals, assertThrows/assertDoesNotThrow in
compatibility shim - we were failing some tests purely because of a
bad shim.

Two test files are moved from XFAIL_TESTS to SKIP_FILES:
1. es6/regexp-tostring.js: checks for V8-specific error message wording
2. es6/unicode-regexp-ignore-case-noi18n.js: tests V8's non-i18n build
    behavior, it doesn't case-fold non-ASCII characters, but we do it
    correctly.

The regexp-unicode-sets.js test is now included and passes.

All files that were previously in XFAIL_TESTS and SKIP_TESTS are now
passing, so both lists are cleared.
2026-05-26 10:00:22 +02:00
aplefull
c0a8dd4536 LibRegex: Reject invalid /v class set expressions in negated classes
Some string literals were incorrectly accepted inside negated classes
because the negated class check computed the actual result string set
instead of applying the structural rules.

This commit replaces class_set_expression_strings with a structural
class_set_expression_may_contain_strings check. Also removes some
incorrect tests.
2026-05-26 10:00:22 +02:00
aplefull
201c129973 LibRegex: Emit progress check for optional iterations in bounded quants
Optional iterations in bounded quantifiers like {2,3} only emitted a
zero-width progress check when the outer min was 0. It made patterns
like /(?:a*?){2,3}/ match empty string even on non-empty input.

RepeatMatcher step 2.b states that the check must fire
whenever min reaches 0, which corresponds to every optional iteration.
2026-05-26 10:00:22 +02:00
Timothy Flynn
edaed9adfa LibJS: Protect DateParser from non-ASCII input
DateParser is a GenericLexer, which is not particularly UTF-8 aware.
Other engines do not accept ASCII input, so let's just reject non-ASCII
strings outright.

While we are here, let's avoid the std ctype header. It is explicitly
undefined behavior to provide code points to isspace, isalpha, etc. that
do not fit in an unsigned char. Although this isn't reachable any longer
let's just nip it in the bud and use our safe AK character types.

Just to demonstrate, the following returns different values for me on
different systems and with different compilers:

    isdigit(0xff01)
    isalpha(0xff01)
    isspace(0xff01)
2026-05-22 17:30:36 +02:00
Andreas Kling
19c1cae030 LibJS: Preserve strict unresolvable assignment references
Resolve strict global-looking assignment targets before evaluating their
right-hand side instead of lowering them directly to SetGlobal. Preserve
the original unresolvable reference for PutValue so a RHS-created global
cannot hide the ReferenceError.

Add a runtime test for that ordering and update the bytecode expectation
for a strict top-level assignment that now uses SetResolvedBinding.
2026-05-22 14:30:35 +02:00
Andreas Kling
8b64f0079b LibJS: Allow parenthesized dynamic import under new
Parenthesized import calls are valid callees for new expressions. The
unparenthesized form remains a syntax error, but the grouped expression
must parse and then fail at runtime if the produced Promise is not a
constructor.

Add syntax coverage for both forms.
2026-05-22 14:30:35 +02:00
Andreas Kling
fb5b78bfea LibJS: Track async module evaluation order
Store the spec's [[AsyncEvaluationOrder]] value instead of a boolean for
pending async module evaluation. This lets AsyncModuleExecutionFulfilled
sort available parent modules by the order assigned during module graph
evaluation before executing them.

This matches ECMA-262 module graph ordering and fixes TLA parent
completion order, including graphs where a dynamic import later reuses a
TLA dependency.
2026-05-22 01:56:57 +02:00
Andreas Kling
54d0dc1a85 LibJS: Defer Promise jobs during module execution
Avoid draining the Promise job queue from VM::run_executable while a
SourceTextModule is still executing its body. Dynamic import queues the
load/link/evaluate continuation as a Promise job, and running that job
before ModuleEvaluation unwinds can re-enter Link() while the entry
module is still evaluating.

Track module execution depth so standalone script execution still drains
jobs at the same boundary, but module jobs are drained by
VM::run(SourceTextModule) after Link/Evaluate returns. Add coverage for
an entry module dynamically importing itself during evaluation.
2026-05-22 01:56:57 +02:00
Andreas Kling
790386e114 LibJS: Preserve Promise job ordering across calls
Only drain the standalone Promise job queue when unwinding the outermost
bytecode execution. Draining after every nested run_executable() return
let sibling Promise reactions run before current-job reactions.

Plain async function returns were relying on that extra drain because
return values were compiled as implicit awaits. Resolve completed async
functions through the Promise resolving function instead, while keeping
the async generator return-await path.

Add Promise ordering coverage and update the asm inline-call regression
to check the pre-drain state.
2026-05-22 01:56:57 +02:00
Andreas Kling
a9e54bd745 LibJS: Preserve resolved assignment bindings
Resolve dynamic identifier assignment targets before evaluating the
right hand side, then store through that saved environment record. This
matches the ECMA-262 ordering for simple assignment and var initializers
when a with binding is deleted or direct eval creates a nearer var.

Add runtime coverage for with and direct-eval cases and refresh bytecode
expectations for dynamic assignments that now snapshot the binding
before storing.
2026-05-22 01:56:57 +02:00
Andreas Kling
eeda29d9ed LibJS: Hoist sloppy labelled function declarations
Treat labelled FunctionDeclaration nodes as declaration-bearing items
when collecting block lexical and top-level var scoped declarations.
This lets Annex B declaration instantiation initialize them before
execution and copy block functions back to the var binding.

Add LibJS coverage for function bodies, direct eval, blocks, switch
cases, and duplicate labelled declarations.
2026-05-22 01:56:57 +02:00
Andreas Kling
8a64784d46 LibJS: Check strict destructured parameter names
Run deferred parameter validation whenever the surrounding context is
already strict, not only when a function body contains a Use Strict
Directive. Destructured parameter names are collected while parsing the
parameter list, so eval and arguments bindings in patterns otherwise
escaped the strict-mode early error for functions and methods.

Add parser coverage for sloppy destructuring remaining accepted, strict
source being rejected, and class methods applying their strict context.
2026-05-22 01:56:57 +02:00
Andreas Kling
2f99653b33 LibJS: Resolve super constructor before arguments
Match ECMA-262's SuperCall evaluation order by resolving the super
constructor before evaluating the argument list. This preserves the
constructor across argument-side prototype mutations and still lets
abrupt argument evaluation happen before the constructor check.

Add LibJS coverage and update bytecode baselines for the new saved
super-constructor operand. The test262 superCallOrder staging test now
passes.
2026-05-22 01:56:57 +02:00
Andreas Kling
c23b944340 LibJS: Reject parenthesized nested assignment patterns
Reject parenthesized object and array literals when a destructuring
assignment element is refined into a nested assignment pattern. These
grouped forms do not cover AssignmentPattern, while parenthesized simple
assignment targets such as identifiers and member expressions remain
valid.

Add local syntax coverage for the invalid nested patterns and the valid
simple target cases.
2026-05-22 01:56:57 +02:00
Andreas Kling
99d3b27b17 LibJS: Allow async as a for-await-of target
Apply the `async of` grammar exclusion only to ordinary for-of
statements. The for-await-of production excludes `let` at the start of
the left-hand side, but does not exclude `async of`, so `async` remains
a valid assignment target there.

Add local parser coverage for async functions and async generators.
2026-05-22 01:56:57 +02:00
Andreas Kling
45bd4087c4 LibJS: Reject unparenthesized await before exponentiation
Parse await through the unary-expression path so the existing
exponentiation early error also applies to AwaitExpression. This keeps
parenthesized await expressions valid while rejecting await on the
left-hand side of **.

Add parser coverage for the async-function constructor case.
2026-05-22 01:56:57 +02:00
Andreas Kling
72876d8b93 LibJS: Reject unconditional reserved words as labels
Reject labels whose string value is an unconditional ReservedWord,
while keeping await and yield on their contextual identifier paths.
This makes super labels a syntax error, matching ECMA-262 and V8.

Add coverage for the rejected super label form.
2026-05-22 01:56:57 +02:00
Andreas Kling
62ac304099 LibJS: Avoid naming parenthesized assignment functions
NamedEvaluation for assignment only applies when the left hand side is
an IdentifierRef. Parenthesized identifiers are still assignment
targets, but are not IdentifierRefs, so `(fn) = function () {}` must
leave the function anonymous.

Carry the parser's parenthesized-LHS bit into assignment codegen. Use
it when setting pending_lhs_name for plain and logical assignment, and
cover parenthesized assignment targets in the function-name tests.
2026-05-22 01:56:57 +02:00
Andreas Kling
22e1df58d3 LibJS: Delete through optional chain property references
The delete operator needs the Reference produced by an optional chain
when the chain ends in a property access. We evaluated the chain for
side effects and returned true, so delete object?.property reported
success without removing the property.

Emit delete-specific optional-chain bytecode. It short-circuits to true
for nullish optional hops, but performs DeleteById or DeleteByValue
when the final property reference is reached. Add runtime coverage for
named, computed, and short-circuited optional deletes.
2026-05-22 01:56:57 +02:00
Andreas Kling
4629154c61 LibJS: Iterate Set difference over copied data
Set.prototype.difference must call other.has for the entries in the
copied resultSetData, not for a live iteration over the receiver. If
other.has mutates the receiver, newly-added entries must not be visited.

Snapshot the copied result keys before calling user code so receiver
mutations cannot change which entries the operation tests. Add coverage
for the mutation case from the staging Set difference tests.
2026-05-22 01:56:57 +02:00
Andreas Kling
ca6cf670c2 LibJS: Materialize lazy function prototypes for own keys
Normal functions create their prototype lazily. [[GetOwnProperty]]
already materialized it, but [[OwnPropertyKeys]] read the current shape
directly, so Object.getOwnPropertyNames(...) omitted "prototype".

Materialize the lazy prototype before enumerating own keys. This keeps
the fast path for functions whose keys are never observed while matching
the ordinary function property list expected by engines and test262.
2026-05-22 01:56:57 +02:00
Andreas Kling
3c0923e827 LibJS: Avoid TypedArray ArrayBuffer bounds truncation
InitializeTypedArrayFromArrayBuffer used Checked<u32> for view bounds.
Valid ToIndex values at 2**32 could wrap to zero before the
out-of-range check, so constructors accepted views that should throw.

Keep the bounds math in size_t until after the spec range checks. Narrow
only when storing the values in the current TypedArray slots. Add LibJS
coverage for ArrayBuffer byteOffset and length wraparound cases.
2026-05-22 01:56:57 +02:00
Andreas Kling
84029461f8 LibJS: Tighten ISO-like Date parsing edge cases
Accept single-digit minute and second components for space-separated
ISO-like dates. This matches other engines and the SpiderMonkey test.

Keep T-separated date-times stricter by rejecting bare hour offsets like
-07, while still allowing the non-standard space-separated form.
2026-05-22 01:56:57 +02:00
Andreas Kling
5da24e18b1 LibJS: Avoid huge asIntN modulo for small negatives
Return negative BigInts unchanged when their magnitude already fits in
the requested signed width. This matches BigInt.asIntN wrapping without
allocating the enormous modulo value first.

Update coverage to expect the result produced by other engines for these
large bit counts instead of an internal OOM.
2026-05-22 01:56:57 +02:00
Andreas Kling
8b5d46d65b LibJS: Round toFixed digits exactly
Number.prototype.toFixed was using generic fixed-format double printing
after the spec steps. That loses the exact binary64 value for large
fractionDigits, so high precision output rounded from a nearby decimal.

Compute the scaled integer n with BigInteger arithmetic from the exact
binary decomposition of the double, then format that integer according
to the toFixed placement rules. Add coverage for high precision
fractional values and the signed Number.MIN_VALUE case.
2026-05-22 01:56:57 +02:00
Andreas Kling
94fea5ad2b LibJS: Re-export imported module namespaces indirectly
`import * as ns; export { ns }` was being recorded as a local
export, so two star exports of matching namespace objects resolved
through different fixture modules and became ambiguous.

Emit the same indirect namespace export metadata used by `export * as`
for both normal compilation and bytecode-cache materialization. Add a
module test that merges two matching namespace re-exports.
2026-05-22 01:56:57 +02:00
Andreas Kling
bdecc7f558 LibJS: Cache entry modules before loading imports
Host-loaded modules are inserted into the VM module registry before
their dependencies are walked, but entry modules passed directly to
`VM::run()` were not. That made an entry module self-import parse a
second Module Record and observe the second copy after evaluation.

Register named entry modules in the same registry before dependency
loading runs, so self-imports and namespace access go through the
original Module Record. Add a test-js helper for running modules
directly and cover the self-import pre-evaluation binding state.
2026-05-22 01:56:57 +02:00
Andreas Kling
410f0fdbb0 LibJS: Yield delegated iterator results directly
Sync yield* must suspend with the delegated IteratorResult object itself
on non-terminal steps. We were reading value and yielding that value,
causing Generator.prototype.next() to create a fresh result object.

Add a bytecode op for this path so ordinary yield keeps wrapping its
value, and teach generator resume to return the prebuilt result object.
This also avoids touching value before the delegated iterator is done.

Add regression coverage and update the yield* bytecode baseline.
2026-05-22 01:56:57 +02:00
Andreas Kling
6ee59d8bcb LibJS: Scope directive prologue octal tracking
Reset the legacy octal string marker at the start of each directive
prologue scan. Otherwise an octal string parsed later in an outer
function body can leak into a nested function and be treated as part of
that nested function's directive prologue.

Add coverage for a nested strict function following a non-directive
octal string in the outer function body.
2026-05-22 01:56:57 +02:00
Andreas Kling
6c7e642b67 LibJS: Reject non-of for-await loops
Reject for-await loop heads that fall through to normal for-loop or
for-in parsing. ECMA-262 only defines for-await with for-of productions,
and V8 rejects these forms as syntax errors.

Add parser coverage for the valid for-await-of form and the invalid
C-style and for-in forms.
2026-05-22 01:56:57 +02:00
Andreas Kling
bf530ad2af LibJS: Fix destructured primitive string const loops
Mark binding-pattern identifiers with their declaration kind so local
destructuring assignments use the normal TDZ and const assignment path.
This makes local const destructuring match the environment-backed path.

Also teach GetById to expose primitive string virtual index properties
before boxing, matching StringGetOwnProperty and GetByValue. Together
these fix the SpiderMonkey for-in/of const declaration coverage and the
lexical destructuring TDZ test.
2026-05-22 01:56:57 +02:00
Andreas Kling
b42aa88189 LibJS: Preserve generator yield grammar context
YieldExpression carries the surrounding In grammar parameter into its
operand. The parser was always parsing yield operands with In enabled,
accepting invalid generator for-loop heads containing `yield ... in`.

GeneratorExpression parses its optional binding identifier with the
Yield grammar parameter enabled, so `yield` is not a valid name for
a named generator expression. Reject it while still allowing sloppy
generator declarations named `yield`, and add syntax coverage for
both cases.
2026-05-22 01:56:57 +02:00
Andreas Kling
e0ee0990cf LibJS: Preserve no-In context in arrow bodies
ArrowFunction carries the surrounding In grammar parameter into its
ConciseBody. The parser recognized arrows in for-loop init expressions,
but parsed expression bodies with In always enabled, so
`for (x => 0 in 1;;)` was accepted as a normal for loop.

Thread the existing forbidden-token state through arrow parsing so the
body leaves `in` for the for-head parser. That parser then rejects the
arrow as an invalid for-in left-hand side. Add coverage in the for-loop
no-In syntax test.
2026-05-22 01:56:57 +02:00
Andreas Kling
6c2487cc9c LibJS: Allow sloppy labelled functions in function bodies
Annex B.3.2 suppresses the labelled FunctionDeclaration early error
in non-strict code when the host supports that compatibility feature.
Script and block statement lists already enabled that path, but function
bodies parsed their statement lists with it disabled.

Parse function-body statement lists with the same labelled-function
allowance while the existing strict and generator checks continue to
reject the invalid forms. Add coverage for all three cases.
2026-05-22 01:56:57 +02:00
Andreas Kling
dafe90a955 LibJS: Handle cached accessors without getter or setter
Accessor cache entries can be reused by objects with the same shape even
when the accessor functions differ. Reusing a getter cache for a later
setter-only object tried to call null instead of returning undefined.

Route cached gets through the missing-getter behavior from OrdinaryGet,
and fall back to the slow set path when a cached accessor has no setter
so strict assignment failures still come from OrdinarySet.

Add IC coverage for the missing accessor half in both directions.
2026-05-22 01:56:57 +02:00
Andreas Kling
4046f65ae7 LibJS: Treat class definition parts as strict
Record the generator's strictness for each emitted bytecode
instruction, instead of applying the executable's final strictness to
every instruction during assembly. This lets class heritage and
computed element names run with strict assignment semantics inside
sloppy scripts.

Also create the class self-binding as a strict immutable binding,
matching ClassDefinitionEvaluation, and cover strict class heritage and
computed names in the runtime tests.
2026-05-22 01:56:57 +02:00
Andreas Kling
6d36517042 LibJS: Infer names for default parameter functions
SingleNameBinding default initializers use NamedEvaluation when the
argument value is undefined and the initializer is an anonymous function
or class definition.

Set the pending inferred name while evaluating default initializers for
identifier parameters, and add coverage for function, generator, async,
arrow, and class expressions.
2026-05-22 01:56:57 +02:00
Andreas Kling
bdb16434cc LibJS: Preserve sparse elements below length failures
ArraySetLength deletes array index properties in descending order. When
a non-configurable index cannot be deleted, the length is restored to
that index plus one, and lower indexes are not touched.

The dictionary indexed storage path already restored the final length,
but removed configurable entries below the failing index too. Keep those
entries when truncation stops early and add sparse array coverage.
2026-05-22 01:56:57 +02:00