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.
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.
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.
Remove the UTF-16 storage mode and make StringBuilder solely build
UTF-8 strings again. The remaining UTF-16 adoption path now goes
through Utf16StringBuilder, keeping the direct-adoption optimization
with the type that owns UTF-16 construction.
Move the remaining call sites that constructed StringBuilder in UTF-16
mode over to Utf16StringBuilder or explicit UTF-8 to UTF-16 conversion.
Teach FormatBuilder to target Utf16StringBuilder directly so formatted
Utf16String construction no longer needs StringBuilder's UTF-16 mode.
Update the Utf16String tests to cover Utf16StringBuilder adoption and
clear behavior directly.
Add vformat support for Utf16StringBuilder and expose appendff and
try_appendff helpers on the builder itself. This lets callers build a
Utf16String on the purpose-built UTF-16 path even when using formatting.
Use the new API for Utf16String::formatted and add AK coverage for
formatting directly into a Utf16StringBuilder.
Add a purpose-built builder for constructing Utf16String values.
It keeps ASCII storage while possible, widens to UTF-16 when needed,
and can hand outline storage to Utf16StringData for direct adoption.
Add AK coverage for ASCII strings, UTF-16 widening, code points,
trimming, and long string construction.
OwnPtr now always deletes the owned object directly from clear(). Remove
the unused deleter template parameter and the DefaultDelete helper that
only supported that parameter. Drop the dedicated custom deleter test.
Add a string heap partition and route long AK string backing
allocations through it.
Give StringBuilder partition-aware outlined storage so adopted String
and Utf16String buffers are allocated from the string heap. Keep the
string heap thread-local because mimalloc heaps may only allocate from
their creating thread, while cross-thread frees are handled by mimalloc.
Cppcheck 2.17 tool found issue with struct tm under Windows
Checking AK/Time.cpp ...
Checking AK/Time.cpp: AK_OS_WINDOWS...
AK/Time.cpp:488:15: error: Uninitialized variable: &tm [uninitvar]
(void)localtime_r(×tamp, &tm);
^
32/39 files checked 87% done
Also the gmtime_X` and localtime_X calls were not checked for failures,
so those are now checked too.
Problem: is_within_range<I>(F value) — where I is an integer and F is a
floating-point type — is unexpectedly too permissive in some cases:
a. Values that are 1 past the integer range unexpectedly pass; e.g.,
is_within_range<int>(2147483648.0f) returns true — even though
2147483648 is INT_MAX + 1.
b. Fractional values whose magnitude exceeds the destination max
unexpectedly pass; e.g., is_within_range<unsigned>(4294967295.5)
returns true — even though 4294967295.5 > UINT_MAX.
c. Fractional values within the destination’s numeric range unexpectedly
pass (e.g., is_within_range<int>(2.5) returns true) — even though
they aren’t exactly representable as the destination type.
Cause: TypeBoundsChecker integer-bounds specializations compare against
NumericLimits<Destination>::max() and ::min() directly. When a caller’s
value is a float, the integer max/min get implicitly converted to a
float for the comparison. For Destination/Source pairs with the integer
extreme not exactly representable in the float, that conversion rounds
up to the next power-of-two boundary — so “value <= F(max)” accepts
values that are actually out of range by one (case a). And the
comparison itself doesn’t reject fractional values (cases b and c).
Fix: When Source is a floating-point type:
1. First gate (case a) — Compare against 2^digits; exactly representable
in any IEEE float, and equals max + 1 for unsigned / -min for two’s-
complement signed integers.
2. Second gate (cases b and c) – Round-trip check: cast value to
Destination, then cast back — and require equality. Only integer-
valued floats whose truncation matches the original pass.
Fixes https://github.com/LadybirdBrowser/ladybird/issues/6212
With the switch to mimalloc, ensure_capacity() guarantees only a
minimum capacity rather than a specific value. Keep the postcondition
as a minimum check and add a precondition so the test verifies that
ensure_capacity() grows the array when called.
Fixes#8876.
Add a helper for finding the first position in a sorted container where
needle can be inserted while preserving sort order. This gives callers a
lower-bound insertion point.
Cover empty inputs, duplicate values, custom comparators, and constexpr
use in TestBinarySearch.
StringBuilder::clear() did not reset m_utf16_builder_is_ascii. When a
UTF-16 mode builder processed a non-ASCII character then was cleared
and reused, subsequent ASCII content was stored as char16_t. The
to_utf16_string() path then corrupted the first code unit to null via
placement-new overlap in Utf16StringData::from_string_builder().
This caused the HTML parser's shared m_character_insertion_builder to
produce corrupted script text nodes when a non-ASCII character (e.g.
×) appeared in an earlier element, breaking inline script
execution with "Unexpected token Invalid" at line 1 column 1.
GCC 16 can clobber the first bytes of Utf16StringData payload when a
StringBuilder buffer is reused for string construction. The trailing
ASCII/UTF-16 storage previously started before sizeof(Utf16StringData),
inside tail padding, so placement-new of the header could zero the first
code unit.
This patch aligns the trailing storage union so payload begins after the
full header.
Display lists were the last user of SegmentedVector before the flat
command buffer replaced that storage. With no remaining includes, remove
the container and its unit test.
This is entirely unused but still could be useful in the AppKit port.
Originally this was meant for Swift interop which is why it had support
for other platforms, but now it's causing issues on systems like
FreeBSD, so lets just gate it behind the only platform it's useful for.
We also assume that Objective C blocks and Arc are supported for the
AppKit port to build so no need to check for that in CMake.
We were forming parsed JSON strings as a ByteString and converting this
to a String wrapped with a MUST. Let's instead create a String from the
get-go and let the encoding error propagate.
HashMap<_, GC::Ref<_>>::ensure() crashed under UBSan whenever the
initialization callback triggered a GC: lookup_for_writing() stamped
the target bucket as used and added it to the ordered list before the
callback ran, so the marking visitor walked the map, read the
uninitialized slot, and failed the returns_nonnull check in GC::Ref.
Split bucket reservation into two phases. lookup_for_writing() now
hands back the target in the Free state (not in the ordered list,
m_size unchanged); callers placement-new the value and then commit via
commit_inserted_bucket(). The Robin Hood displacement loop still
stamps the slot internally and un-stamps before returning, so probing
is unchanged and the whole operation remains a single hash and a
single probe.
The !has_ascii_storage() && !other.has_ascii_storage() branch did a
byte-wise __builtin_memcmp over a char16_t array, which on little-endian
does not give code-unit order: the low byte is compared first, so
0xD83D (bytes [0x3D, 0xD8]) spuriously compared less than 0x2764
(bytes [0x64, 0x27]) even though the code unit 0xD83D is greater.
No in-tree caller currently uses operator<=> for Utf16View ordering,
so this bug is dormant; the follow-up LibJS change exposes it.
Replace the memcmp branch with a per-code-unit loop, which the compiler
can auto-vectorize and which mirrors what is_code_unit_less_than already
does.
The iterator returned by SinglyLinkedList::remove() left `m_prev`
default-initialized to `nullptr`. If the caller removed another element
without first advancing, the previous node's next pointer was left
dangling to the freed node.
This caused a UAF in FinalizationRegistry's `remove_by_token()` when
two consecutive records shared an unregister token.
Make the _string, _fly_string, _utf16, and _utf16_fly_string UDL
operators constexpr, with a fast path for short (<= 7 byte) ASCII
literals that folds directly into an inline ShortString. Previously,
every "foo"_fly_string (and friends) involved an out-of-line call
into the string factory, even though the result is entirely known
at compile time.
With numerators or denominators approaching NumericLimits<u32>::max(),
we could overflow in the sum of the remainder and the rounding
contribution. Instead, divide them separately and sum them afterward.
Overflow could happen in the multiplication of the remainder seconds
back into time units. Instead, take a remainder of the time units from
the division and use that for the nanoseconds.
Now that there are no callers of is_null left, Make default constructed
StringViews represent the empty string and disallow null pointers in the
public constructors, matching ByteString and String.
Keep a private null sentinel only for Optional<StringView>.
The default constructor now initializes to an empty ASCII string rather
than a null pointer.
Also add a VERIFY in the utf16 constructor to assert the pointer is
non null, and remove the now unneeded is_null() method.
Add a maximum nesting depth check in JsonParser so deeply nested
arrays/objects from untrusted input cannot blow the call stack.
Add regression tests for excessive nesting rejection and
reasonable nesting acceptance.
Add standalone saturating_add(), saturating_sub(), and
saturating_mul() free functions for integral types.
The signed implementations are fully branchless, using
__builtin_add/sub/mul_overflow combined with bitmask selection.
We specialize `Optional<T>` for value types that inherently support some
kind of "empty" value or whose value range allow for a unlikely to be
useful sentinel value that can mean "empty", instead of the boolean flag
a regular Optional<T> needs to store. Because of padding, this often
means saving 4 to 8 bytes per instance.
By extending the new `SentinelOptional<T, Traits>`, these
specializations are significantly simplified to just having to define
what the sentinel value is, and how to identify a sentinel value.
Robin Hood displacement and `delete_bucket()` shift-up used BucketType's
implicit move operations, which bitwise-copy the `u8` storage array
instead of going through T's move constructor and destructor. This
change adds `relocate_bucket()` and `swap_buckets()` helpers that use a
fast path for trivially-relocatable types and move-construct + destroy
for others.
We have a common pattern of creating a `WeakPtr<T>` from a reference and
passing that into a lambda, to then take the strong ref when the lambda
is executed. Add `weak_callback(Weakable, lambda)` that returns a lambda
that only invokes the callback if a strong ref exists, and passes it as
the first argument.
In the benchmark added here, fmt's dragonbox is ~3x faster than our own
Ryu implementation (1197ms for dragonbox vs. 3435ms for Ryu).
Daniel Lemire recently published an article about these algorithms:
https://lemire.me/blog/2026/02/01/converting-floats-to-strings-quickly/
In this article, fmt's dragonbox implementation is actually one of the
slower ones (with the caveat that some comments note that the article is
a bit out-of-date). I've gone with fmt here because:
1. It has a readily available recent version on vcpkg.
2. It provides the methods we need to actually convert a floating point
to decimal exponential form.
3. There is an ongoing effort to replace dragonbox with a new algorithm,
zmij, which promises to be faster.
4. It is one of the only users of AK/UFixedBigInt, so we can potentially
remove that as well soon.
5. Bringing in fmt opens the door to replacing a bunch of AK::format
facilities with fmt as well.
Rework our hash functions a bit for significant better performance:
* Rename int_hash to u32_hash to mirror u64_hash.
* Make pair_int_hash call u64_hash instead of multiple u32_hash()es.
* Implement MurmurHash3's fmix32 and fmix64 for u32_hash and u64_hash.
On my machine, this speeds up u32_hash by 20%, u64_hash by ~290%, and
pair_int_hash by ~260%.
We lose the property that an input of 0 results in something that is not
0. I've experimented with an offset to both hash functions, but it
resulted in a measurable performance degradation for u64_hash. If
there's a good use case for 0 not to result in 0, we can always add in
that offset as a countermeasure in the future.
We won't attempt to destroy this wrapper later because m_kind gets set
to NullPointer. If any fields of the CallableType are not movable, this
results in a leak.
A test replicating the issue before the fix is added to a new
TestFunction.cpp file.