Merge branch 'LadybirdBrowser:master' into master

This commit is contained in:
Harshit Dagar 2025-03-22 07:46:34 +05:30 committed by GitHub
commit cca6e845dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
1176 changed files with 42777 additions and 7354 deletions

1
.github/FUNDING.yml vendored
View file

@ -1,2 +1,3 @@
custom: https://donorbox.org/ladybird
open_collective: ladybird
polar: LadybirdBrowser

View file

@ -46,9 +46,6 @@ endif()
serenity_lib(AK ak)
serenity_install_headers(AK)
serenity_install_sources(AK)
include(stacktrace)
configure_file(Backtrace.h.in Backtrace.h @ONLY)
link_stacktrace_library(AK STD_DEFINITION AK_HAS_STD_STACKTRACE)

View file

@ -2,6 +2,7 @@
* Copyright (C) 2016 Apple Inc. All rights reserved.
* Copyright (c) 2021, Gunnar Beutner <gbeutner@serenityos.org>
* Copyright (c) 2018-2023, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2025, Andrew Kaster <andrew@ladybird.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@ -34,8 +35,14 @@
#include <AK/ScopeGuard.h>
#include <AK/Span.h>
#include <AK/StdLibExtras.h>
#include <AK/TypeCasts.h>
#include <AK/Types.h>
// BlockRuntime methods for Objective-C block closure support.
extern "C" void* _Block_copy(void const*);
extern "C" void _Block_release(void const*);
extern "C" size_t Block_size(void const*);
namespace AK {
// These annotations are used to avoid capturing a variable with local storage in a lambda that outlives it
@ -48,6 +55,17 @@ namespace AK {
# define IGNORE_USE_IN_ESCAPING_LAMBDA
#endif
namespace Detail {
#ifdef AK_HAS_OBJC_ARC
inline constexpr bool HaveObjcArc = true;
#else
inline constexpr bool HaveObjcArc = false;
#endif
// validated in TestFunction.mm
inline constexpr size_t block_layout_size = 32;
}
template<typename>
class Function;
@ -84,7 +102,7 @@ public:
if (!m_size)
return {};
if (auto* wrapper = callable_wrapper())
return ReadonlyBytes { wrapper, m_size };
return ReadonlyBytes { wrapper->raw_callable(), m_size };
return {};
}
@ -102,6 +120,13 @@ public:
init_with_callable(move(f), CallableKind::FunctionPointer);
}
template<typename BlockType>
Function(BlockType b)
requires((IsBlockClosure<BlockType> && IsCallableWithArguments<BlockType, Out, In...>))
{
init_with_callable(move(b), CallableKind::Block);
}
Function(Function&& other)
{
move_from(move(other));
@ -141,6 +166,15 @@ public:
return *this;
}
template<typename BlockType>
Function& operator=(BlockType&& block)
requires((IsBlockClosure<BlockType> && IsCallableWithArguments<BlockType, Out, In...>))
{
clear();
init_with_callable(static_cast<RemoveCVReference<BlockType>>(block), CallableKind::Block);
return *this;
}
Function& operator=(nullptr_t)
{
clear();
@ -160,6 +194,7 @@ private:
enum class CallableKind {
FunctionPointer,
FunctionObject,
Block,
};
class CallableWrapperBase {
@ -169,6 +204,7 @@ private:
virtual Out call(In...) = 0;
virtual void destroy() = 0;
virtual void init_and_swap(u8*, size_t) = 0;
virtual void const* raw_callable() const = 0;
};
template<typename CallableType>
@ -189,7 +225,15 @@ private:
void destroy() final override
{
delete this;
if constexpr (IsBlockClosure<CallableType>) {
if constexpr (Detail::HaveObjcArc)
m_callable = nullptr;
else
_Block_release(m_callable);
} else {
// This code is a bit too clever for gcc. Pinky promise we're only deleting heap objects.
AK_IGNORE_DIAGNOSTIC("-Wfree-nonheap-object", delete this);
}
}
// NOLINTNEXTLINE(readability-non-const-parameter) False positive; destination is used in a placement new expression
@ -199,6 +243,14 @@ private:
new (destination) CallableWrapper { move(m_callable) };
}
void const* raw_callable() const final override
{
if constexpr (IsBlockClosure<CallableType>)
return static_cast<u8 const*>(bridge_cast<void>(m_callable)) + Detail::block_layout_size;
else
return &m_callable;
}
private:
CallableType m_callable;
};
@ -207,6 +259,7 @@ private:
NullPointer,
Inline,
Outline,
Block,
};
CallableWrapperBase* callable_wrapper() const
@ -215,6 +268,7 @@ private:
case FunctionKind::NullPointer:
return nullptr;
case FunctionKind::Inline:
case FunctionKind::Block:
return bit_cast<CallableWrapperBase*>(&m_storage);
case FunctionKind::Outline:
return *bit_cast<CallableWrapperBase**>(&m_storage);
@ -234,12 +288,22 @@ private:
}
m_deferred_clear = false;
auto* wrapper = callable_wrapper();
if (m_kind == FunctionKind::Inline) {
switch (m_kind) {
case FunctionKind::Inline:
VERIFY(wrapper);
wrapper->~CallableWrapperBase();
} else if (m_kind == FunctionKind::Outline) {
break;
case FunctionKind::Outline:
VERIFY(wrapper);
wrapper->destroy();
break;
case FunctionKind::Block:
VERIFY(wrapper);
wrapper->destroy();
wrapper->~CallableWrapperBase();
break;
case FunctionKind::NullPointer:
break;
}
m_kind = FunctionKind::NullPointer;
}
@ -256,18 +320,33 @@ private:
}
VERIFY(m_call_nesting_level == 0);
using WrapperType = CallableWrapper<Callable>;
if (callable_kind == CallableKind::FunctionObject)
m_size = sizeof(Callable);
else
m_size = 0;
if constexpr (IsBlockClosure<Callable>) {
auto block_size = Block_size(bridge_cast<void>(callable));
VERIFY(block_size >= Detail::block_layout_size);
m_size = block_size - Detail::block_layout_size;
}
if constexpr (alignof(Callable) > inline_alignment || sizeof(WrapperType) > inline_capacity) {
*bit_cast<CallableWrapperBase**>(&m_storage) = new WrapperType(forward<Callable>(callable));
m_kind = FunctionKind::Outline;
} else {
static_assert(sizeof(WrapperType) <= inline_capacity);
new (m_storage) WrapperType(forward<Callable>(callable));
m_kind = FunctionKind::Inline;
if constexpr (IsBlockClosure<Callable>) {
if constexpr (Detail::HaveObjcArc) {
new (m_storage) WrapperType(forward<Callable>(callable));
} else {
new (m_storage) WrapperType(reinterpret_cast<Callable>(_Block_copy(callable)));
}
m_kind = FunctionKind::Block;
} else {
new (m_storage) WrapperType(forward<Callable>(callable));
m_kind = FunctionKind::Inline;
}
}
if (callable_kind == CallableKind::FunctionObject)
m_size = sizeof(WrapperType);
else
m_size = 0;
}
void move_from(Function&& other)
@ -279,8 +358,9 @@ private:
case FunctionKind::NullPointer:
break;
case FunctionKind::Inline:
case FunctionKind::Block:
other_wrapper->init_and_swap(m_storage, inline_capacity);
m_kind = FunctionKind::Inline;
m_kind = other.m_kind;
break;
case FunctionKind::Outline:
*bit_cast<CallableWrapperBase**>(&m_storage) = other_wrapper;

View file

@ -41,6 +41,12 @@ template<typename T, typename... Ts>
return (... || (forward<T>(to_compare) >= forward<Ts>(valid_values)));
}
template<typename T, typename... Ts>
[[nodiscard]] constexpr bool first_is_equal_to_all_of(T&& to_compare, Ts&&... valid_values)
{
return (... && (forward<T>(to_compare) == forward<Ts>(valid_values)));
}
template<typename T, typename... Ts>
[[nodiscard]] constexpr bool first_is_smaller_than_all_of(T&& to_compare, Ts&&... valid_values)
{
@ -67,6 +73,7 @@ template<typename T, typename... Ts>
}
#if USING_AK_GLOBALLY
using AK::first_is_equal_to_all_of;
using AK::first_is_larger_or_equal_than_all_of;
using AK::first_is_larger_or_equal_than_one_of;
using AK::first_is_larger_than_all_of;

View file

@ -18,6 +18,12 @@ constexpr bool is_space(int ch)
return ch == '\t' || ch == '\n' || ch == '\r' || ch == ' ';
}
ErrorOr<JsonValue> JsonParser::parse(StringView input)
{
JsonParser parser(input);
return parser.parse_json();
}
// ECMA-404 9 String
// Boils down to
// STRING = "\"" *("[^\"\\]" | "\\" ("[\"\\bfnrt]" | "u[0-9A-Za-z]{4}")) "\""
@ -335,7 +341,7 @@ ErrorOr<JsonValue> JsonParser::parse_helper()
return Error::from_string_literal("JsonParser: Unexpected character");
}
ErrorOr<JsonValue> JsonParser::parse()
ErrorOr<JsonValue> JsonParser::parse_json()
{
auto result = TRY(parse_helper());
ignore_while(is_space);

View file

@ -13,14 +13,15 @@ namespace AK {
class JsonParser : private GenericLexer {
public:
static ErrorOr<JsonValue> parse(StringView);
private:
explicit JsonParser(StringView input)
: GenericLexer(input)
{
}
ErrorOr<JsonValue> parse();
private:
ErrorOr<JsonValue> parse_json();
ErrorOr<JsonValue> parse_helper();
ErrorOr<ByteString> consume_and_unescape_string();

View file

@ -190,7 +190,7 @@ JsonValue::JsonValue(JsonArray&& value)
ErrorOr<JsonValue> JsonValue::from_string(StringView input)
{
return JsonParser(input).parse();
return JsonParser::parse(input);
}
String JsonValue::serialized() const

View file

@ -265,6 +265,14 @@
# define LSAN_UNREGISTER_ROOT_REGION(base, size)
#endif
#if __has_feature(blocks)
# define AK_HAS_BLOCKS
#endif
#if __has_feature(objc_arc)
# define AK_HAS_OBJC_ARC
#endif
#ifndef AK_OS_SERENITY
# ifdef AK_OS_WINDOWS
// FIXME: No idea where to get this, but it's 4096 anyway :^)

View file

@ -142,6 +142,27 @@ inline constexpr bool IsFunction<Ret(Args...) const volatile&&> = true;
template<class Ret, class... Args>
inline constexpr bool IsFunction<Ret(Args..., ...) const volatile&&> = true;
template<class>
inline constexpr bool IsBlockClosure = false;
#ifdef AK_HAS_BLOCKS
template<class Ret, class... Args>
inline constexpr bool IsBlockClosure<Ret (^)(Args...)> = true;
template<class Ret, class... Args>
inline constexpr bool IsBlockClosure<Ret (^)(Args..., ...)> = true;
template<class Ret, class... Args>
inline constexpr bool IsBlockClosure<Ret (^const)(Args...)> = true;
template<class Ret, class... Args>
inline constexpr bool IsBlockClosure<Ret (^const)(Args..., ...)> = true;
template<class Ret, class... Args>
inline constexpr bool IsBlockClosure<Ret (^volatile)(Args...)> = true;
template<class Ret, class... Args>
inline constexpr bool IsBlockClosure<Ret (^volatile)(Args..., ...)> = true;
template<class Ret, class... Args>
inline constexpr bool IsBlockClosure<Ret (^const volatile)(Args...)> = true;
template<class Ret, class... Args>
inline constexpr bool IsBlockClosure<Ret (^const volatile)(Args..., ...)> = true;
#endif
template<class T>
inline constexpr bool IsRvalueReference = false;
template<class T>
@ -641,6 +662,7 @@ using AK::Detail::InvokeResult;
using AK::Detail::IsArithmetic;
using AK::Detail::IsAssignable;
using AK::Detail::IsBaseOf;
using AK::Detail::IsBlockClosure;
using AK::Detail::IsClass;
using AK::Detail::IsConst;
using AK::Detail::IsConstructible;

View file

@ -73,10 +73,21 @@ ALWAYS_INLINE CopyConst<InputType, OutputType>* as(InputType* input)
return result;
}
template<typename OutputType, typename InputType>
ALWAYS_INLINE CopyConst<InputType, OutputType>* bridge_cast(InputType input)
{
#ifdef AK_HAS_OBJC_ARC
return (__bridge CopyConst<InputType, OutputType>*)(input);
#else
return static_cast<CopyConst<InputType, OutputType>*>(input);
#endif
}
}
#if USING_AK_GLOBALLY
using AK::as;
using AK::as_if;
using AK::bridge_cast;
using AK::is;
#endif

View file

@ -0,0 +1,175 @@
<!doctype html>
<html>
<head>
<title>Task Manager</title>
<style>
@media (prefers-color-scheme: dark) {
:root {
--table-border: gray;
--table-row-odd: rgb(57, 57, 57);
--table-row-hover: rgb(80, 79, 79);
}
}
@media (prefers-color-scheme: light) {
:root {
--table-border: gray;
--table-row-odd: rgb(229, 229, 229);
--table-row-hover: rgb(199, 198, 198);
}
}
html {
color-scheme: light dark;
font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 10pt;
}
table {
width: 100%;
border-collapse: collapse;
}
th {
text-align: left;
border-bottom: 1px solid var(--table-border);
}
th:hover {
background-color: var(--table-row-hover);
cursor: pointer;
}
th.sorted-ascending:after {
content: " ▲";
}
th.sorted-descending:after {
content: " ▼";
}
td,
th {
padding: 4px;
border: 1px solid var(--table-border);
}
tbody tr:nth-of-type(2n + 1) {
background-color: var(--table-row-odd);
}
tbody tr:hover {
background-color: var(--table-row-hover);
}
</style>
</head>
<body>
<table>
<thead>
<tr>
<th id="name">Name</th>
<th id="pid">PID</th>
<th id="cpu">CPU</th>
<th id="memory">Memory</th>
</tr>
</thead>
<tbody id="process-table"></tbody>
</table>
<script type="text/javascript">
const cpuFormatter = new Intl.NumberFormat([], {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
const memoryFormatter = new Intl.NumberFormat([], {
style: "unit",
unit: "byte",
notation: "compact",
unitDisplay: "narrow",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
const Direction = Object.freeze({
ascending: 1,
descending: 2,
});
processes.processes = [];
processes.sortDirection = Direction.ascending;
processes.sortKey = "pid";
const renderSortedProcesses = () => {
document.querySelectorAll("th").forEach(header => {
header.classList.remove("sorted-ascending");
header.classList.remove("sorted-descending");
});
if (processes.sortDirection === Direction.ascending) {
document.getElementById(processes.sortKey).classList.add("sorted-ascending");
} else {
document.getElementById(processes.sortKey).classList.add("sorted-descending");
}
const multiplier = processes.sortDirection === Direction.ascending ? 1 : -1;
processes.processes.sort((lhs, rhs) => {
const lhsValue = lhs[processes.sortKey];
const rhsValue = rhs[processes.sortKey];
if (typeof lhsValue === "string") {
return multiplier * lhsValue.localeCompare(rhsValue);
}
return multiplier * (lhsValue - rhsValue);
});
let oldTable = document.getElementById("process-table");
let newTable = document.createElement("tbody");
newTable.setAttribute("id", "process-table");
const insertColumn = (row, value) => {
let column = row.insertCell();
column.innerText = value;
};
processes.processes.forEach(process => {
let row = newTable.insertRow();
insertColumn(row, process.name);
insertColumn(row, process.pid);
insertColumn(row, cpuFormatter.format(process.cpu));
insertColumn(row, memoryFormatter.format(process.memory));
});
oldTable.parentNode.replaceChild(newTable, oldTable);
};
processes.loadProcessStatistics = statistics => {
processes.processes = JSON.parse(statistics);
renderSortedProcesses();
};
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll("th").forEach(header => {
header.addEventListener("click", () => {
processes.sortDirection = header.classList.contains("sorted-descending")
? Direction.ascending
: Direction.descending;
processes.sortKey = header.getAttribute("id");
renderSortedProcesses();
});
});
setInterval(() => {
processes.updateProcessStatistics();
}, 1000);
processes.updateProcessStatistics();
});
</script>
</body>
</html>

View file

@ -129,14 +129,14 @@ cmake -GNinja -BBuild/release
Finally, run `ninja` (or the generator you're using) to start the build:
```
ninja -CBuild/release
ninja -C Build/release
```
For more information, see [Custom CMake build directory](#custom-cmake-build-directory) and [Running manually](#running-manually).
### macOS:
Xcode 14 versions before 14.3 might crash while building ladybird. Xcode 14.3 or clang from homebrew may be required to successfully build ladybird.
Xcode 15 or clang from homebrew is required to successfully build ladybird.
```
xcode-select --install
@ -214,7 +214,7 @@ The simplest way to build and run ladybird is via the ladybird.sh script:
```bash
# From /path/to/ladybird
./Meta/ladybird.sh run ladybird
./Meta/ladybird.sh run
```
On macOS, to build using clang from homebrew:
@ -231,11 +231,14 @@ The above commands will build a Release version of Ladybird. To instead build a
`Meta/ladybird.sh` script with the value of the `BUILD_PRESET` environment variable set to `Debug`, like this:
```bash
BUILD_PRESET=Debug ./Meta/ladybird.sh run ladybird
BUILD_PRESET=Debug ./Meta/ladybird.sh run
```
Note that debug symbols are available in both Release and Debug builds.
If you want to run other applications, such as the headless-browser, the JS REPL, or the WebAssembly REPL, specify an
executable with `./Meta/ladybird.sh run <executable_name>`.
### The User Interfaces
Ladybird will be built with one of the following browser frontends, depending on the platform:
@ -305,9 +308,9 @@ a suitable C++ compiler (g++ >= 13, clang >= 14, Apple Clang >= 14.3) via the CM
CMAKE_C_COMPILER cmake options.
```
cmake -GNinja -B MyBuildDir
cmake --preset default -B MyBuildDir
# optionally, add -DCMAKE_CXX_COMPILER=<suitable compiler> -DCMAKE_C_COMPILER=<matching c compiler>
cmake --build MyBuildDir
cmake --build --preset default MyBuildDir
ninja -C MyBuildDir run-ladybird
```
@ -367,15 +370,8 @@ Simply run the `ladybird.sh` script as normal, and then make sure to codesign th
Now you can open the Instruments app and point it to the Ladybird app bundle.
If you want to use Xcode itself for debugging, you will need to generate an Xcode project.
The `ladybird.sh` build script does not know how to generate Xcode projects, so creating the project must be done manually.
```
cmake -GXcode -B Build/release
```
After generating an Xcode project into the specified build directory, you can open `ladybird.xcodeproj` in Xcode. The project has a ton of targets, many of which are generated code.
The only target that needs a scheme is the ladybird app bundle.
Building the project with Xcode is not supported. The Xcode project generated by CMake does not properly execute custom
targets, and does not handle all target names in the project.
### Building on OpenIndiana

View file

@ -59,5 +59,5 @@ bool Paintable::is_visible() const
## JavaScript
Some properties have special rules for getting the computed value from JS. For these, you will need to add to
`ResolvedCSSStyleDeclaration::style_value_for_property()`. Shorthands that are constructed in an unusual way (as in, not
using `ShorthandStyleValue`) also need handling inside `CSSStyleDeclaration::get_property_internal()`.
`CSSStyleProperties::style_value_for_computed_property()`. Shorthands that are constructed in an unusual way (as in, not
using `ShorthandStyleValue`) also need handling inside `CSSStyleProperties::get_property_internal()`.

View file

@ -1,9 +1,13 @@
set(SOURCES
Deflate.cpp
GenericZlib.cpp
Gzip.cpp
PackBitsDecoder.cpp
Zlib.cpp
Gzip.cpp
)
serenity_lib(LibCompress compress)
target_link_libraries(LibCompress PRIVATE LibCore LibCrypto)
find_package(ZLIB REQUIRED)
target_link_libraries(LibCompress PRIVATE ZLIB::ZLIB)

View file

@ -1,25 +1,19 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Array.h>
#include <AK/Assertions.h>
#include <AK/BinarySearch.h>
#include <AK/MemoryStream.h>
#include <LibCompress/Deflate.h>
#include <LibCompress/Huffman.h>
#include <LibCompress/DeflateTables.h>
#include <zlib.h>
namespace Compress {
static constexpr u8 deflate_special_code_length_copy = 16;
static constexpr u8 deflate_special_code_length_zeros = 17;
static constexpr u8 deflate_special_code_length_long_zeros = 18;
static constexpr int EndOfBlock = 256;
CanonicalCode const& CanonicalCode::fixed_literal_codes()
{
static CanonicalCode code;
@ -165,807 +159,28 @@ ErrorOr<u32> CanonicalCode::read_symbol(LittleEndianInputBitStream& stream) cons
return Error::from_string_literal("Symbol exceeds maximum symbol number");
}
DeflateDecompressor::CompressedBlock::CompressedBlock(DeflateDecompressor& decompressor, CanonicalCode literal_codes, Optional<CanonicalCode> distance_codes)
: m_decompressor(decompressor)
, m_literal_codes(literal_codes)
, m_distance_codes(distance_codes)
{
}
ErrorOr<bool> DeflateDecompressor::CompressedBlock::try_read_more()
{
if (m_eof == true)
return false;
auto const symbol = TRY(m_literal_codes.read_symbol(*m_decompressor.m_input_stream));
if (symbol >= 286)
return Error::from_string_literal("Invalid deflate literal/length symbol");
if (symbol < EndOfBlock) {
u8 byte_symbol = symbol;
m_decompressor.m_output_buffer.write({ &byte_symbol, sizeof(byte_symbol) });
return true;
}
if (symbol == EndOfBlock) {
m_eof = true;
return false;
}
if (!m_distance_codes.has_value())
return Error::from_string_literal("Distance codes have not been initialized");
auto const length = TRY(m_decompressor.decode_length(symbol));
auto const distance_symbol = TRY(m_distance_codes.value().read_symbol(*m_decompressor.m_input_stream));
if (distance_symbol >= 30)
return Error::from_string_literal("Invalid deflate distance symbol");
auto const distance = TRY(m_decompressor.decode_distance(distance_symbol));
auto copied_length = TRY(m_decompressor.m_output_buffer.copy_from_seekback(distance, length));
// TODO: What should we do if the output buffer is full?
VERIFY(copied_length == length);
return true;
}
DeflateDecompressor::UncompressedBlock::UncompressedBlock(DeflateDecompressor& decompressor, size_t length)
: m_decompressor(decompressor)
, m_bytes_remaining(length)
{
}
ErrorOr<bool> DeflateDecompressor::UncompressedBlock::try_read_more()
{
if (m_bytes_remaining == 0)
return false;
if (m_decompressor.m_input_stream->is_eof())
return Error::from_string_literal("Input data ends in the middle of an uncompressed DEFLATE block");
Array<u8, 4096> temporary_buffer;
auto readable_bytes = temporary_buffer.span().trim(min(m_bytes_remaining, m_decompressor.m_output_buffer.empty_space()));
auto read_bytes = TRY(m_decompressor.m_input_stream->read_some(readable_bytes));
auto written_bytes = m_decompressor.m_output_buffer.write(read_bytes);
VERIFY(read_bytes.size() == written_bytes);
m_bytes_remaining -= written_bytes;
return true;
}
ErrorOr<NonnullOwnPtr<DeflateDecompressor>> DeflateDecompressor::construct(MaybeOwned<LittleEndianInputBitStream> stream)
{
auto output_buffer = TRY(CircularBuffer::create_empty(32 * KiB));
return TRY(adopt_nonnull_own_or_enomem(new (nothrow) DeflateDecompressor(move(stream), move(output_buffer))));
}
DeflateDecompressor::DeflateDecompressor(MaybeOwned<LittleEndianInputBitStream> stream, CircularBuffer output_buffer)
: m_input_stream(move(stream))
, m_output_buffer(move(output_buffer))
{
}
DeflateDecompressor::~DeflateDecompressor()
{
if (m_state == State::ReadingCompressedBlock)
m_compressed_block.~CompressedBlock();
if (m_state == State::ReadingUncompressedBlock)
m_uncompressed_block.~UncompressedBlock();
}
ErrorOr<Bytes> DeflateDecompressor::read_some(Bytes bytes)
{
size_t total_read = 0;
while (total_read < bytes.size()) {
auto slice = bytes.slice(total_read);
if (m_state == State::Idle) {
if (m_read_final_block)
break;
m_read_final_block = TRY(m_input_stream->read_bit());
auto const block_type = TRY(m_input_stream->read_bits(2));
if (block_type == 0b00) {
m_input_stream->align_to_byte_boundary();
u16 length = TRY(m_input_stream->read_value<LittleEndian<u16>>());
u16 negated_length = TRY(m_input_stream->read_value<LittleEndian<u16>>());
if ((length ^ 0xffff) != negated_length)
return Error::from_string_literal("Calculated negated length does not equal stored negated length");
m_state = State::ReadingUncompressedBlock;
new (&m_uncompressed_block) UncompressedBlock(*this, length);
continue;
}
if (block_type == 0b01) {
m_state = State::ReadingCompressedBlock;
new (&m_compressed_block) CompressedBlock(*this, CanonicalCode::fixed_literal_codes(), CanonicalCode::fixed_distance_codes());
continue;
}
if (block_type == 0b10) {
CanonicalCode literal_codes;
Optional<CanonicalCode> distance_codes;
TRY(decode_codes(literal_codes, distance_codes));
m_state = State::ReadingCompressedBlock;
new (&m_compressed_block) CompressedBlock(*this, literal_codes, distance_codes);
continue;
}
return Error::from_string_literal("Unhandled block type for Idle state");
}
if (m_state == State::ReadingCompressedBlock) {
auto nread = m_output_buffer.read(slice).size();
while (nread < slice.size() && TRY(m_compressed_block.try_read_more())) {
nread += m_output_buffer.read(slice.slice(nread)).size();
}
total_read += nread;
if (nread == slice.size())
break;
m_compressed_block.~CompressedBlock();
m_state = State::Idle;
continue;
}
if (m_state == State::ReadingUncompressedBlock) {
auto nread = m_output_buffer.read(slice).size();
while (nread < slice.size() && TRY(m_uncompressed_block.try_read_more())) {
nread += m_output_buffer.read(slice.slice(nread)).size();
}
total_read += nread;
if (nread == slice.size())
break;
m_uncompressed_block.~UncompressedBlock();
m_state = State::Idle;
continue;
}
VERIFY_NOT_REACHED();
}
return bytes.slice(0, total_read);
}
bool DeflateDecompressor::is_eof() const { return m_state == State::Idle && m_read_final_block; }
ErrorOr<size_t> DeflateDecompressor::write_some(ReadonlyBytes)
{
return Error::from_errno(EBADF);
}
bool DeflateDecompressor::is_open() const
{
return true;
}
void DeflateDecompressor::close()
ErrorOr<NonnullOwnPtr<DeflateDecompressor>> DeflateDecompressor::create(MaybeOwned<Stream> stream)
{
auto buffer = TRY(AK::FixedArray<u8>::create(16 * 1024));
auto zstream = TRY(GenericZlibDecompressor::new_z_stream(-MAX_WBITS));
return adopt_nonnull_own_or_enomem(new (nothrow) DeflateDecompressor(move(buffer), move(stream), zstream));
}
ErrorOr<ByteBuffer> DeflateDecompressor::decompress_all(ReadonlyBytes bytes)
{
FixedMemoryStream memory_stream { bytes };
LittleEndianInputBitStream bit_stream { MaybeOwned<Stream>(memory_stream) };
auto deflate_stream = TRY(DeflateDecompressor::construct(MaybeOwned<LittleEndianInputBitStream>(bit_stream)));
return deflate_stream->read_until_eof(4096);
return ::Compress::decompress_all<DeflateDecompressor>(bytes);
}
ErrorOr<u32> DeflateDecompressor::decode_length(u32 symbol)
ErrorOr<NonnullOwnPtr<DeflateCompressor>> DeflateCompressor::create(MaybeOwned<Stream> stream, GenericZlibCompressionLevel compression_level)
{
if (symbol <= 264)
return symbol - 254;
if (symbol <= 284) {
auto extra_bits = (symbol - 261) / 4;
return (((symbol - 265) % 4 + 4) << extra_bits) + 3 + TRY(m_input_stream->read_bits(extra_bits));
}
if (symbol == 285)
return DeflateDecompressor::max_back_reference_length;
VERIFY_NOT_REACHED();
auto buffer = TRY(AK::FixedArray<u8>::create(16 * 1024));
auto zstream = TRY(GenericZlibCompressor::new_z_stream(-MAX_WBITS, compression_level));
return adopt_nonnull_own_or_enomem(new (nothrow) DeflateCompressor(move(buffer), move(stream), zstream));
}
ErrorOr<u32> DeflateDecompressor::decode_distance(u32 symbol)
ErrorOr<ByteBuffer> DeflateCompressor::compress_all(ReadonlyBytes bytes, GenericZlibCompressionLevel compression_level)
{
if (symbol <= 3)
return symbol + 1;
if (symbol <= 29) {
auto extra_bits = (symbol / 2) - 1;
return ((symbol % 2 + 2) << extra_bits) + 1 + TRY(m_input_stream->read_bits(extra_bits));
}
VERIFY_NOT_REACHED();
}
ErrorOr<void> DeflateDecompressor::decode_codes(CanonicalCode& literal_code, Optional<CanonicalCode>& distance_code)
{
auto literal_code_count = TRY(m_input_stream->read_bits(5)) + 257;
auto distance_code_count = TRY(m_input_stream->read_bits(5)) + 1;
auto code_length_count = TRY(m_input_stream->read_bits(4)) + 4;
// First we have to extract the code lengths of the code that was used to encode the code lengths of
// the code that was used to encode the block.
u8 code_lengths_code_lengths[19] = { 0 };
for (size_t i = 0; i < code_length_count; ++i) {
code_lengths_code_lengths[code_lengths_code_lengths_order[i]] = TRY(m_input_stream->read_bits(3));
}
// Now we can extract the code that was used to encode the code lengths of the code that was used to
// encode the block.
auto const code_length_code = TRY(CanonicalCode::from_bytes({ code_lengths_code_lengths, sizeof(code_lengths_code_lengths) }));
// Next we extract the code lengths of the code that was used to encode the block.
Vector<u8, 286> code_lengths;
while (code_lengths.size() < literal_code_count + distance_code_count) {
auto symbol = TRY(code_length_code.read_symbol(*m_input_stream));
if (symbol < deflate_special_code_length_copy) {
code_lengths.append(static_cast<u8>(symbol));
} else if (symbol == deflate_special_code_length_copy) {
if (code_lengths.is_empty())
return Error::from_string_literal("Found no codes to copy before a copy block");
auto nrepeat = 3 + TRY(m_input_stream->read_bits(2));
for (size_t j = 0; j < nrepeat; ++j)
code_lengths.append(code_lengths.last());
} else if (symbol == deflate_special_code_length_zeros) {
auto nrepeat = 3 + TRY(m_input_stream->read_bits(3));
for (size_t j = 0; j < nrepeat; ++j)
code_lengths.append(0);
} else {
VERIFY(symbol == deflate_special_code_length_long_zeros);
auto nrepeat = 11 + TRY(m_input_stream->read_bits(7));
for (size_t j = 0; j < nrepeat; ++j)
code_lengths.append(0);
}
}
if (code_lengths.size() != literal_code_count + distance_code_count)
return Error::from_string_literal("Number of code lengths does not match the sum of codes");
// Now we extract the code that was used to encode literals and lengths in the block.
literal_code = TRY(CanonicalCode::from_bytes(code_lengths.span().trim(literal_code_count)));
// Now we extract the code that was used to encode distances in the block.
if (distance_code_count == 1) {
auto length = code_lengths[literal_code_count];
if (length == 0)
return {};
else if (length != 1)
return Error::from_string_literal("Length for a single distance code is longer than 1");
}
distance_code = TRY(CanonicalCode::from_bytes(code_lengths.span().slice(literal_code_count)));
return {};
}
ErrorOr<NonnullOwnPtr<DeflateCompressor>> DeflateCompressor::construct(MaybeOwned<Stream> stream, CompressionLevel compression_level)
{
auto bit_stream = TRY(try_make<LittleEndianOutputBitStream>(move(stream)));
auto deflate_compressor = TRY(adopt_nonnull_own_or_enomem(new (nothrow) DeflateCompressor(move(bit_stream), compression_level)));
return deflate_compressor;
}
DeflateCompressor::DeflateCompressor(NonnullOwnPtr<LittleEndianOutputBitStream> stream, CompressionLevel compression_level)
: m_compression_level(compression_level)
, m_compression_constants(compression_constants[static_cast<int>(m_compression_level)])
, m_output_stream(move(stream))
{
m_symbol_frequencies.fill(0);
m_distance_frequencies.fill(0);
}
DeflateCompressor::~DeflateCompressor() = default;
ErrorOr<Bytes> DeflateCompressor::read_some(Bytes)
{
return Error::from_errno(EBADF);
}
ErrorOr<size_t> DeflateCompressor::write_some(ReadonlyBytes bytes)
{
VERIFY(!m_finished);
size_t total_written = 0;
while (!bytes.is_empty()) {
auto n_written = bytes.copy_trimmed_to(pending_block().slice(m_pending_block_size));
m_pending_block_size += n_written;
if (m_pending_block_size == block_size)
TRY(flush());
bytes = bytes.slice(n_written);
total_written += n_written;
}
return total_written;
}
bool DeflateCompressor::is_eof() const
{
return true;
}
bool DeflateCompressor::is_open() const
{
return m_output_stream->is_open();
}
void DeflateCompressor::close()
{
}
// Knuth's multiplicative hash on 4 bytes
u16 DeflateCompressor::hash_sequence(u8 const* bytes)
{
constexpr u32 const knuth_constant = 2654435761; // shares no common factors with 2^32
return ((bytes[0] | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24) * knuth_constant) >> (32 - hash_bits);
}
size_t DeflateCompressor::compare_match_candidate(size_t start, size_t candidate, size_t previous_match_length, size_t maximum_match_length)
{
VERIFY(previous_match_length < maximum_match_length);
// We firstly check that the match is at least (prev_match_length + 1) long, we check backwards as there's a higher chance the end mismatches
for (ssize_t i = previous_match_length; i >= 0; i--) {
if (m_rolling_window[start + i] != m_rolling_window[candidate + i])
return 0;
}
// Find the actual length
auto match_length = previous_match_length + 1;
while (match_length < maximum_match_length && m_rolling_window[start + match_length] == m_rolling_window[candidate + match_length]) {
match_length++;
}
VERIFY(match_length > previous_match_length);
VERIFY(match_length <= maximum_match_length);
return match_length;
}
size_t DeflateCompressor::find_back_match(size_t start, u16 hash, size_t previous_match_length, size_t maximum_match_length, size_t& match_position)
{
auto max_chain_length = m_compression_constants.max_chain;
if (previous_match_length == 0)
previous_match_length = min_match_length - 1; // we only care about matches that are at least min_match_length long
if (previous_match_length >= maximum_match_length)
return 0; // we can't improve a maximum length match
if (previous_match_length >= m_compression_constants.max_lazy_length)
return 0; // the previous match is already pretty, we shouldn't waste another full search
if (previous_match_length >= m_compression_constants.good_match_length)
max_chain_length /= 4; // we already have a pretty good much, so do a shorter search
auto candidate = m_hash_head[hash];
auto match_found = false;
while (max_chain_length--) {
if (candidate == empty_slot)
break; // no remaining candidates
VERIFY(candidate < start);
if (start - candidate > window_size)
break; // outside the window
auto match_length = compare_match_candidate(start, candidate, previous_match_length, maximum_match_length);
if (match_length != 0) {
match_found = true;
match_position = candidate;
previous_match_length = match_length;
if (match_length == maximum_match_length)
return match_length; // bail if we got the maximum possible length
}
candidate = m_hash_prev[candidate % window_size];
}
if (!match_found)
return 0; // we didn't find any matches
return previous_match_length; // we found matches, but they were at most previous_match_length long
}
ALWAYS_INLINE u8 DeflateCompressor::distance_to_base(u16 distance)
{
return (distance <= 256) ? distance_to_base_lo[distance - 1] : distance_to_base_hi[(distance - 1) >> 7];
}
void DeflateCompressor::lz77_compress_block()
{
for (auto& slot : m_hash_head) { // initialize chained hash table
slot = empty_slot;
}
auto insert_hash = [&](auto pos, auto hash) {
auto window_pos = pos % window_size;
m_hash_prev[window_pos] = m_hash_head[hash];
m_hash_head[hash] = window_pos;
};
auto emit_literal = [&](auto literal) {
VERIFY(m_pending_symbol_size <= block_size + 1);
auto index = m_pending_symbol_size++;
m_symbol_buffer[index].distance = 0;
m_symbol_buffer[index].literal = literal;
m_symbol_frequencies[literal]++;
};
auto emit_back_reference = [&](auto distance, auto length) {
VERIFY(m_pending_symbol_size <= block_size + 1);
auto index = m_pending_symbol_size++;
m_symbol_buffer[index].distance = distance;
m_symbol_buffer[index].length = length;
m_symbol_frequencies[length_to_symbol[length]]++;
m_distance_frequencies[distance_to_base(distance)]++;
};
size_t previous_match_length = 0;
size_t previous_match_position = 0;
VERIFY(m_compression_constants.great_match_length <= max_match_length);
// our block starts at block_size and is m_pending_block_size in length
auto block_end = block_size + m_pending_block_size;
size_t current_position;
for (current_position = block_size; current_position < block_end - min_match_length + 1; current_position++) {
auto hash = hash_sequence(&m_rolling_window[current_position]);
size_t match_position;
auto match_length = find_back_match(current_position, hash, previous_match_length,
min(m_compression_constants.great_match_length, block_end - current_position), match_position);
insert_hash(current_position, hash);
// if the previous match is as good as the new match, just use it
if (previous_match_length != 0 && previous_match_length >= match_length) {
emit_back_reference((current_position - 1) - previous_match_position, previous_match_length);
// skip all the bytes that are included in this match
for (size_t j = current_position + 1; j < min(current_position - 1 + previous_match_length, block_end - min_match_length + 1); j++) {
insert_hash(j, hash_sequence(&m_rolling_window[j]));
}
current_position = (current_position - 1) + previous_match_length - 1;
previous_match_length = 0;
continue;
}
if (match_length == 0) {
VERIFY(previous_match_length == 0);
emit_literal(m_rolling_window[current_position]);
continue;
}
// if this is a lazy match, and the new match is better than the old one, output previous as literal
if (previous_match_length != 0) {
emit_literal(m_rolling_window[current_position - 1]);
}
previous_match_length = match_length;
previous_match_position = match_position;
}
// clean up leftover lazy match
if (previous_match_length != 0) {
emit_back_reference((current_position - 1) - previous_match_position, previous_match_length);
current_position = (current_position - 1) + previous_match_length;
}
// output remaining literals
while (current_position < block_end) {
emit_literal(m_rolling_window[current_position++]);
}
}
size_t DeflateCompressor::huffman_block_length(Array<u8, max_huffman_literals> const& literal_bit_lengths, Array<u8, max_huffman_distances> const& distance_bit_lengths)
{
size_t length = 0;
for (size_t i = 0; i < 286; i++) {
auto frequency = m_symbol_frequencies[i];
length += literal_bit_lengths[i] * frequency;
if (i >= 257) // back reference length symbols
length += packed_length_symbols[i - 257].extra_bits * frequency;
}
for (size_t i = 0; i < 30; i++) {
auto frequency = m_distance_frequencies[i];
length += distance_bit_lengths[i] * frequency;
length += packed_distances[i].extra_bits * frequency;
}
return length;
}
size_t DeflateCompressor::uncompressed_block_length()
{
auto padding = 8 - ((m_output_stream->bit_offset() + 3) % 8);
// 3 bit block header + align to byte + 2 * 16 bit length fields + block contents
return 3 + padding + (2 * 16) + m_pending_block_size * 8;
}
size_t DeflateCompressor::fixed_block_length()
{
// block header + fixed huffman encoded block contents
return 3 + huffman_block_length(fixed_literal_bit_lengths, fixed_distance_bit_lengths);
}
size_t DeflateCompressor::dynamic_block_length(Array<u8, max_huffman_literals> const& literal_bit_lengths, Array<u8, max_huffman_distances> const& distance_bit_lengths, Array<u8, 19> const& code_lengths_bit_lengths, Array<u16, 19> const& code_lengths_frequencies, size_t code_lengths_count)
{
// block header + literal code count + distance code count + code length count
auto length = 3 + 5 + 5 + 4;
// 3 bits per code_length
length += 3 * code_lengths_count;
for (size_t i = 0; i < code_lengths_frequencies.size(); i++) {
auto frequency = code_lengths_frequencies[i];
length += code_lengths_bit_lengths[i] * frequency;
if (i == deflate_special_code_length_copy) {
length += 2 * frequency;
} else if (i == deflate_special_code_length_zeros) {
length += 3 * frequency;
} else if (i == deflate_special_code_length_long_zeros) {
length += 7 * frequency;
}
}
return length + huffman_block_length(literal_bit_lengths, distance_bit_lengths);
}
ErrorOr<void> DeflateCompressor::write_huffman(CanonicalCode const& literal_code, Optional<CanonicalCode> const& distance_code)
{
auto has_distances = distance_code.has_value();
for (size_t i = 0; i < m_pending_symbol_size; i++) {
if (m_symbol_buffer[i].distance == 0) {
TRY(literal_code.write_symbol(*m_output_stream, m_symbol_buffer[i].literal));
continue;
}
VERIFY(has_distances);
auto symbol = length_to_symbol[m_symbol_buffer[i].length];
TRY(literal_code.write_symbol(*m_output_stream, symbol));
// Emit extra bits if needed
TRY(m_output_stream->write_bits<u16>(m_symbol_buffer[i].length - packed_length_symbols[symbol - 257].base_length, packed_length_symbols[symbol - 257].extra_bits));
auto base_distance = distance_to_base(m_symbol_buffer[i].distance);
TRY(distance_code.value().write_symbol(*m_output_stream, base_distance));
// Emit extra bits if needed
TRY(m_output_stream->write_bits<u16>(m_symbol_buffer[i].distance - packed_distances[base_distance].base_distance, packed_distances[base_distance].extra_bits));
}
return {};
}
size_t DeflateCompressor::encode_huffman_lengths(ReadonlyBytes lengths, Array<code_length_symbol, max_huffman_literals + max_huffman_distances>& encoded_lengths)
{
size_t encoded_count = 0;
size_t i = 0;
while (i < lengths.size()) {
if (lengths[i] == 0) {
auto zero_count = 0;
for (size_t j = i; j < min(lengths.size(), i + 138) && lengths[j] == 0; j++)
zero_count++;
if (zero_count < 3) { // below minimum repeated zero count
encoded_lengths[encoded_count++].symbol = 0;
i++;
continue;
}
if (zero_count <= 10) {
encoded_lengths[encoded_count].symbol = deflate_special_code_length_zeros;
encoded_lengths[encoded_count++].count = zero_count;
} else {
encoded_lengths[encoded_count].symbol = deflate_special_code_length_long_zeros;
encoded_lengths[encoded_count++].count = zero_count;
}
i += zero_count;
continue;
}
encoded_lengths[encoded_count++].symbol = lengths[i++];
auto copy_count = 0;
for (size_t j = i; j < min(lengths.size(), i + 6) && lengths[j] == lengths[i - 1]; j++)
copy_count++;
if (copy_count >= 3) {
encoded_lengths[encoded_count].symbol = deflate_special_code_length_copy;
encoded_lengths[encoded_count++].count = copy_count;
i += copy_count;
continue;
}
}
return encoded_count;
}
size_t DeflateCompressor::encode_block_lengths(Array<u8, max_huffman_literals> const& literal_bit_lengths, Array<u8, max_huffman_distances> const& distance_bit_lengths, Array<code_length_symbol, max_huffman_literals + max_huffman_distances>& encoded_lengths, size_t& literal_code_count, size_t& distance_code_count)
{
literal_code_count = max_huffman_literals;
distance_code_count = max_huffman_distances;
VERIFY(literal_bit_lengths[EndOfBlock] != 0); // Make sure at least the EndOfBlock marker is present
while (literal_bit_lengths[literal_code_count - 1] == 0)
literal_code_count--;
// Drop trailing zero lengths, keeping at least one
while (distance_bit_lengths[distance_code_count - 1] == 0 && distance_code_count > 1)
distance_code_count--;
Array<u8, max_huffman_literals + max_huffman_distances> all_lengths {};
for (size_t i = 0; i < literal_code_count; i++)
all_lengths[i] = literal_bit_lengths[i];
for (size_t i = 0; i < distance_code_count; i++)
all_lengths[literal_code_count + i] = distance_bit_lengths[i];
return encode_huffman_lengths(all_lengths.span().trim(literal_code_count + distance_code_count), encoded_lengths);
}
ErrorOr<void> DeflateCompressor::write_dynamic_huffman(CanonicalCode const& literal_code, size_t literal_code_count, Optional<CanonicalCode> const& distance_code, size_t distance_code_count, Array<u8, 19> const& code_lengths_bit_lengths, size_t code_length_count, Array<code_length_symbol, max_huffman_literals + max_huffman_distances> const& encoded_lengths, size_t encoded_lengths_count)
{
TRY(m_output_stream->write_bits(literal_code_count - 257, 5));
TRY(m_output_stream->write_bits(distance_code_count - 1, 5));
TRY(m_output_stream->write_bits(code_length_count - 4, 4));
for (size_t i = 0; i < code_length_count; i++) {
TRY(m_output_stream->write_bits(code_lengths_bit_lengths[code_lengths_code_lengths_order[i]], 3));
}
auto code_lengths_code = MUST(CanonicalCode::from_bytes(code_lengths_bit_lengths));
for (size_t i = 0; i < encoded_lengths_count; i++) {
auto encoded_length = encoded_lengths[i];
TRY(code_lengths_code.write_symbol(*m_output_stream, encoded_length.symbol));
if (encoded_length.symbol == deflate_special_code_length_copy) {
TRY(m_output_stream->write_bits<u8>(encoded_length.count - 3, 2));
} else if (encoded_length.symbol == deflate_special_code_length_zeros) {
TRY(m_output_stream->write_bits<u8>(encoded_length.count - 3, 3));
} else if (encoded_length.symbol == deflate_special_code_length_long_zeros) {
TRY(m_output_stream->write_bits<u8>(encoded_length.count - 11, 7));
}
}
TRY(write_huffman(literal_code, distance_code));
return {};
}
ErrorOr<void> DeflateCompressor::flush()
{
TRY(m_output_stream->write_bits(m_finished, 1));
// if this is just an empty block to signify the end of the deflate stream use the smallest block possible (10 bits total)
if (m_pending_block_size == 0) {
VERIFY(m_finished); // we shouldn't be writing empty blocks unless this is the final one
TRY(m_output_stream->write_bits(0b01u, 2)); // fixed huffman codes
TRY(m_output_stream->write_bits(0b0000000u, 7)); // end of block symbol
TRY(m_output_stream->align_to_byte_boundary());
return {};
}
auto write_uncompressed = [&]() -> ErrorOr<void> {
TRY(m_output_stream->write_bits(0b00u, 2)); // no compression
TRY(m_output_stream->align_to_byte_boundary());
TRY(m_output_stream->write_value<LittleEndian<u16>>(m_pending_block_size));
TRY(m_output_stream->write_value<LittleEndian<u16>>(~m_pending_block_size));
TRY(m_output_stream->write_until_depleted(pending_block().slice(0, m_pending_block_size)));
return {};
};
if (m_compression_level == CompressionLevel::STORE) { // disabled compression fast path
TRY(write_uncompressed());
m_pending_block_size = 0;
return {};
}
// The following implementation of lz77 compression and huffman encoding is based on the reference implementation by Hans Wennborg https://www.hanshq.net/zip.html
// this reads from the pending block and writes to m_symbol_buffer
lz77_compress_block();
// insert EndOfBlock marker to the symbol buffer
m_symbol_buffer[m_pending_symbol_size].distance = 0;
m_symbol_buffer[m_pending_symbol_size++].literal = EndOfBlock;
m_symbol_frequencies[EndOfBlock]++;
// generate optimal dynamic huffman code lengths
Array<u8, max_huffman_literals> dynamic_literal_bit_lengths {};
Array<u8, max_huffman_distances> dynamic_distance_bit_lengths {};
generate_huffman_lengths(dynamic_literal_bit_lengths, m_symbol_frequencies, 15); // deflate data huffman can use up to 15 bits per symbol
generate_huffman_lengths(dynamic_distance_bit_lengths, m_distance_frequencies, 15);
// encode literal and distance lengths together in deflate format
Array<code_length_symbol, max_huffman_literals + max_huffman_distances> encoded_lengths {};
size_t literal_code_count;
size_t distance_code_count;
auto encoded_lengths_count = encode_block_lengths(dynamic_literal_bit_lengths, dynamic_distance_bit_lengths, encoded_lengths, literal_code_count, distance_code_count);
// count code length frequencies
Array<u16, 19> code_lengths_frequencies { 0 };
for (size_t i = 0; i < encoded_lengths_count; i++) {
code_lengths_frequencies[encoded_lengths[i].symbol]++;
}
// generate optimal huffman code lengths code lengths
Array<u8, 19> code_lengths_bit_lengths {};
generate_huffman_lengths(code_lengths_bit_lengths, code_lengths_frequencies, 7); // deflate code length huffman can use up to 7 bits per symbol
// calculate actual code length code lengths count (without trailing zeros)
auto code_lengths_count = code_lengths_bit_lengths.size();
while (code_lengths_bit_lengths[code_lengths_code_lengths_order[code_lengths_count - 1]] == 0)
code_lengths_count--;
auto uncompressed_size = uncompressed_block_length();
auto fixed_huffman_size = fixed_block_length();
auto dynamic_huffman_size = dynamic_block_length(dynamic_literal_bit_lengths, dynamic_distance_bit_lengths, code_lengths_bit_lengths, code_lengths_frequencies, code_lengths_count);
// If the compression somehow didn't reduce the size enough, just write out the block uncompressed as it allows for much faster decompression
if (uncompressed_size <= min(fixed_huffman_size, dynamic_huffman_size)) {
TRY(write_uncompressed());
} else if (fixed_huffman_size <= dynamic_huffman_size) {
// If the fixed and dynamic huffman codes come out the same size, prefer the fixed version, as it takes less time to decode fixed huffman codes.
TRY(m_output_stream->write_bits(0b01u, 2));
TRY(write_huffman(CanonicalCode::fixed_literal_codes(), CanonicalCode::fixed_distance_codes()));
} else {
// dynamic huffman codes
TRY(m_output_stream->write_bits(0b10u, 2));
auto literal_code = MUST(CanonicalCode::from_bytes(dynamic_literal_bit_lengths));
auto distance_code_or_error = CanonicalCode::from_bytes(dynamic_distance_bit_lengths);
Optional<CanonicalCode> distance_code;
if (!distance_code_or_error.is_error())
distance_code = distance_code_or_error.release_value();
TRY(write_dynamic_huffman(literal_code, literal_code_count, distance_code, distance_code_count, code_lengths_bit_lengths, code_lengths_count, encoded_lengths, encoded_lengths_count));
}
if (m_finished)
TRY(m_output_stream->align_to_byte_boundary());
// reset all block specific members
m_pending_block_size = 0;
m_pending_symbol_size = 0;
m_symbol_frequencies.fill(0);
m_distance_frequencies.fill(0);
// On the final block this copy will potentially produce an invalid search window, but since its the final block we dont care
pending_block().copy_trimmed_to({ m_rolling_window, block_size });
return {};
}
ErrorOr<void> DeflateCompressor::final_flush()
{
VERIFY(!m_finished);
m_finished = true;
TRY(flush());
TRY(m_output_stream->flush_buffer_to_stream());
return {};
}
ErrorOr<ByteBuffer> DeflateCompressor::compress_all(ReadonlyBytes bytes, CompressionLevel compression_level)
{
auto output_stream = TRY(try_make<AllocatingMemoryStream>());
auto deflate_stream = TRY(DeflateCompressor::construct(MaybeOwned<Stream>(*output_stream), compression_level));
TRY(deflate_stream->write_until_depleted(bytes));
TRY(deflate_stream->final_flush());
auto buffer = TRY(ByteBuffer::create_uninitialized(output_stream->used_buffer_size()));
TRY(output_stream->read_until_filled(buffer));
return buffer;
return ::Compress::compress_all<DeflateCompressor>(bytes, compression_level);
}
}

View file

@ -1,6 +1,7 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -8,14 +9,8 @@
#pragma once
#include <AK/BitStream.h>
#include <AK/ByteBuffer.h>
#include <AK/CircularBuffer.h>
#include <AK/Endian.h>
#include <AK/Forward.h>
#include <AK/MaybeOwned.h>
#include <AK/Stream.h>
#include <AK/Vector.h>
#include <LibCompress/DeflateTables.h>
#include <LibCompress/Zlib.h>
namespace Compress {
@ -60,172 +55,28 @@ ALWAYS_INLINE ErrorOr<void> CanonicalCode::write_symbol(LittleEndianOutputBitStr
return {};
}
class DeflateDecompressor final : public Stream {
private:
class CompressedBlock {
public:
CompressedBlock(DeflateDecompressor&, CanonicalCode literal_codes, Optional<CanonicalCode> distance_codes);
ErrorOr<bool> try_read_more();
private:
bool m_eof { false };
DeflateDecompressor& m_decompressor;
CanonicalCode m_literal_codes;
Optional<CanonicalCode> m_distance_codes;
};
class UncompressedBlock {
public:
UncompressedBlock(DeflateDecompressor&, size_t);
ErrorOr<bool> try_read_more();
private:
DeflateDecompressor& m_decompressor;
size_t m_bytes_remaining;
};
enum class State {
Idle,
ReadingCompressedBlock,
ReadingUncompressedBlock
};
class DeflateDecompressor final : public GenericZlibDecompressor {
public:
friend CompressedBlock;
friend UncompressedBlock;
static ErrorOr<NonnullOwnPtr<DeflateDecompressor>> construct(MaybeOwned<LittleEndianInputBitStream> stream);
~DeflateDecompressor();
virtual ErrorOr<Bytes> read_some(Bytes) override;
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
virtual bool is_eof() const override;
virtual bool is_open() const override;
virtual void close() override;
static ErrorOr<NonnullOwnPtr<DeflateDecompressor>> create(MaybeOwned<Stream>);
static ErrorOr<ByteBuffer> decompress_all(ReadonlyBytes);
private:
DeflateDecompressor(MaybeOwned<LittleEndianInputBitStream> stream, CircularBuffer buffer);
ErrorOr<u32> decode_length(u32);
ErrorOr<u32> decode_distance(u32);
ErrorOr<void> decode_codes(CanonicalCode& literal_code, Optional<CanonicalCode>& distance_code);
static constexpr u16 max_back_reference_length = 258;
bool m_read_final_block { false };
State m_state { State::Idle };
union {
CompressedBlock m_compressed_block;
UncompressedBlock m_uncompressed_block;
};
MaybeOwned<LittleEndianInputBitStream> m_input_stream;
CircularBuffer m_output_buffer;
DeflateDecompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
: GenericZlibDecompressor(move(buffer), move(stream), zstream)
{
}
};
class DeflateCompressor final : public Stream {
class DeflateCompressor final : public GenericZlibCompressor {
public:
static constexpr size_t block_size = 32 * KiB - 1; // TODO: this can theoretically be increased to 64 KiB - 2
static constexpr size_t window_size = block_size * 2;
static constexpr size_t hash_bits = 15;
static constexpr size_t max_huffman_literals = 288;
static constexpr size_t max_huffman_distances = 32;
static constexpr size_t min_match_length = 4; // matches smaller than these are not worth the size of the back reference
static constexpr size_t max_match_length = 258; // matches longer than these cannot be encoded using huffman codes
static constexpr u16 empty_slot = UINT16_MAX;
struct CompressionConstants {
size_t good_match_length; // Once we find a match of at least this length (a good enough match) we reduce max_chain to lower processing time
size_t max_lazy_length; // If the match is at least this long we dont defer matching to the next byte (which takes time) as its good enough
size_t great_match_length; // Once we find a match of at least this length (a great match) we can just stop searching for longer ones
size_t max_chain; // We only check the actual length of the max_chain closest matches
};
// These constants were shamelessly "borrowed" from zlib
static constexpr CompressionConstants compression_constants[] = {
{ 0, 0, 0, 0 },
{ 4, 4, 8, 4 },
{ 8, 16, 128, 128 },
{ 32, 258, 258, 4096 },
{ max_match_length, max_match_length, max_match_length, 1 << hash_bits } // disable all limits
};
enum class CompressionLevel : int {
STORE = 0,
FAST,
GOOD,
GREAT,
BEST // WARNING: this one can take an unreasonable amount of time!
};
static ErrorOr<NonnullOwnPtr<DeflateCompressor>> construct(MaybeOwned<Stream>, CompressionLevel = CompressionLevel::GOOD);
~DeflateCompressor();
virtual ErrorOr<Bytes> read_some(Bytes) override;
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
virtual bool is_eof() const override;
virtual bool is_open() const override;
virtual void close() override;
ErrorOr<void> final_flush();
static ErrorOr<ByteBuffer> compress_all(ReadonlyBytes bytes, CompressionLevel = CompressionLevel::GOOD);
static ErrorOr<NonnullOwnPtr<DeflateCompressor>> create(MaybeOwned<Stream>, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default);
static ErrorOr<ByteBuffer> compress_all(ReadonlyBytes, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default);
private:
DeflateCompressor(NonnullOwnPtr<LittleEndianOutputBitStream>, CompressionLevel = CompressionLevel::GOOD);
Bytes pending_block() { return { m_rolling_window + block_size, block_size }; }
// LZ77 Compression
static u16 hash_sequence(u8 const* bytes);
size_t compare_match_candidate(size_t start, size_t candidate, size_t prev_match_length, size_t max_match_length);
size_t find_back_match(size_t start, u16 hash, size_t previous_match_length, size_t max_match_length, size_t& match_position);
void lz77_compress_block();
// Huffman Coding
struct code_length_symbol {
u8 symbol;
u8 count; // used for special symbols 16-18
};
static u8 distance_to_base(u16 distance);
size_t huffman_block_length(Array<u8, max_huffman_literals> const& literal_bit_lengths, Array<u8, max_huffman_distances> const& distance_bit_lengths);
ErrorOr<void> write_huffman(CanonicalCode const& literal_code, Optional<CanonicalCode> const& distance_code);
static size_t encode_huffman_lengths(ReadonlyBytes lengths, Array<code_length_symbol, max_huffman_literals + max_huffman_distances>& encoded_lengths);
size_t encode_block_lengths(Array<u8, max_huffman_literals> const& literal_bit_lengths, Array<u8, max_huffman_distances> const& distance_bit_lengths, Array<code_length_symbol, max_huffman_literals + max_huffman_distances>& encoded_lengths, size_t& literal_code_count, size_t& distance_code_count);
ErrorOr<void> write_dynamic_huffman(CanonicalCode const& literal_code, size_t literal_code_count, Optional<CanonicalCode> const& distance_code, size_t distance_code_count, Array<u8, 19> const& code_lengths_bit_lengths, size_t code_length_count, Array<code_length_symbol, max_huffman_literals + max_huffman_distances> const& encoded_lengths, size_t encoded_lengths_count);
size_t uncompressed_block_length();
size_t fixed_block_length();
size_t dynamic_block_length(Array<u8, max_huffman_literals> const& literal_bit_lengths, Array<u8, max_huffman_distances> const& distance_bit_lengths, Array<u8, 19> const& code_lengths_bit_lengths, Array<u16, 19> const& code_lengths_frequencies, size_t code_lengths_count);
ErrorOr<void> flush();
bool m_finished { false };
CompressionLevel m_compression_level;
CompressionConstants m_compression_constants;
NonnullOwnPtr<LittleEndianOutputBitStream> m_output_stream;
u8 m_rolling_window[window_size];
size_t m_pending_block_size { 0 };
struct [[gnu::packed]] {
u16 distance; // back reference length
union {
u16 literal; // literal byte or on of block symbol
u16 length; // back reference length (if distance != 0)
};
} m_symbol_buffer[block_size + 1];
size_t m_pending_symbol_size { 0 };
Array<u16, max_huffman_literals> m_symbol_frequencies; // there are 286 valid symbol values (symbols 286-287 never occur)
Array<u16, max_huffman_distances> m_distance_frequencies; // there are 30 valid distance values (distances 30-31 never occur)
// LZ77 Chained hash table
u16 m_hash_head[1 << hash_bits];
u16 m_hash_prev[window_size];
DeflateCompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
: GenericZlibCompressor(move(buffer), move(stream), zstream)
{
}
};
}

View file

@ -0,0 +1,219 @@
/*
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCompress/GenericZlib.h>
#include <zlib.h>
namespace Compress {
static Error handle_zlib_error(int ret)
{
switch (ret) {
case Z_ERRNO:
return Error::from_errno(errno);
case Z_DATA_ERROR:
// Z_DATA_ERROR if the input data was corrupted
return Error::from_string_literal("zlib data error");
case Z_STREAM_ERROR:
// Z_STREAM_ERROR if the parameters are invalid, such as a null pointer to the structure
return Error::from_string_literal("zlib stream error");
case Z_VERSION_ERROR:
// Z_VERSION_ERROR if the zlib library version is incompatible with the version assumed by the caller
return Error::from_string_literal("zlib version mismatch");
case Z_MEM_ERROR:
// Z_MEM_ERROR if there was not enough memory
return Error::from_errno(ENOMEM);
default:
VERIFY_NOT_REACHED();
}
}
GenericZlibDecompressor::GenericZlibDecompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
: m_stream(move(stream))
, m_zstream(zstream)
, m_buffer(move(buffer))
{
}
ErrorOr<z_stream*> GenericZlibDecompressor::new_z_stream(int window_bits)
{
auto zstream = new (nothrow) z_stream {};
if (!zstream)
return Error::from_errno(ENOMEM);
// The fields next_in, avail_in, zalloc, zfree and opaque must be initialized before by the caller.
zstream->next_in = nullptr;
zstream->avail_in = 0;
zstream->zalloc = nullptr;
zstream->zfree = nullptr;
zstream->opaque = nullptr;
if (auto ret = inflateInit2(zstream, window_bits); ret != Z_OK)
return handle_zlib_error(ret);
return zstream;
}
GenericZlibDecompressor::~GenericZlibDecompressor()
{
inflateEnd(m_zstream);
delete m_zstream;
}
ErrorOr<Bytes> GenericZlibDecompressor::read_some(Bytes bytes)
{
m_zstream->avail_out = bytes.size();
m_zstream->next_out = bytes.data();
if (m_zstream->avail_in == 0) {
auto in = TRY(m_stream->read_some(m_buffer.span()));
m_zstream->avail_in = in.size();
m_zstream->next_in = m_buffer.data();
}
auto ret = inflate(m_zstream, Z_NO_FLUSH);
if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR)
return handle_zlib_error(ret);
if (ret == Z_STREAM_END) {
inflateReset(m_zstream);
if (m_zstream->avail_in == 0)
m_eof = true;
}
return bytes.slice(0, bytes.size() - m_zstream->avail_out);
}
ErrorOr<size_t> GenericZlibDecompressor::write_some(ReadonlyBytes)
{
return Error::from_errno(EBADF);
}
bool GenericZlibDecompressor::is_eof() const
{
return m_eof;
}
bool GenericZlibDecompressor::is_open() const
{
return m_stream->is_open();
}
void GenericZlibDecompressor::close()
{
}
GenericZlibCompressor::GenericZlibCompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
: m_stream(move(stream))
, m_zstream(zstream)
, m_buffer(move(buffer))
{
}
ErrorOr<z_stream*> GenericZlibCompressor::new_z_stream(int window_bits, GenericZlibCompressionLevel compression_level)
{
auto zstream = new (nothrow) z_stream {};
if (!zstream)
return Error::from_errno(ENOMEM);
// The fields zalloc, zfree and opaque must be initialized before by the caller.
zstream->zalloc = nullptr;
zstream->zfree = nullptr;
zstream->opaque = nullptr;
int level = [&] {
switch (compression_level) {
case GenericZlibCompressionLevel::Fastest:
return Z_BEST_SPEED;
case GenericZlibCompressionLevel::Default:
return Z_DEFAULT_COMPRESSION;
case GenericZlibCompressionLevel::Best:
return Z_BEST_COMPRESSION;
default:
VERIFY_NOT_REACHED();
}
}();
if (auto ret = deflateInit2(zstream, level, Z_DEFLATED, window_bits, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); ret != Z_OK)
return handle_zlib_error(ret);
return zstream;
}
GenericZlibCompressor::~GenericZlibCompressor()
{
deflateEnd(m_zstream);
delete m_zstream;
}
ErrorOr<Bytes> GenericZlibCompressor::read_some(Bytes)
{
return Error::from_errno(EBADF);
}
ErrorOr<size_t> GenericZlibCompressor::write_some(ReadonlyBytes bytes)
{
m_zstream->avail_in = bytes.size();
m_zstream->next_in = const_cast<u8*>(bytes.data());
// If deflate returns with avail_out == 0, this function must be called again with the same value of the flush parameter
// and more output space (updated avail_out), until the flush is complete (deflate returns with non-zero avail_out).
do {
m_zstream->avail_out = m_buffer.size();
m_zstream->next_out = m_buffer.data();
auto ret = deflate(m_zstream, Z_NO_FLUSH);
if (ret != Z_OK && ret != Z_BUF_ERROR)
return handle_zlib_error(ret);
auto have = m_buffer.size() - m_zstream->avail_out;
TRY(m_stream->write_until_depleted(m_buffer.span().slice(0, have)));
} while (m_zstream->avail_out == 0);
VERIFY(m_zstream->avail_in == 0);
return bytes.size();
}
bool GenericZlibCompressor::is_eof() const
{
return false;
}
bool GenericZlibCompressor::is_open() const
{
return m_stream->is_open();
}
void GenericZlibCompressor::close()
{
}
ErrorOr<void> GenericZlibCompressor::finish()
{
VERIFY(m_zstream->avail_in == 0);
// If the parameter flush is set to Z_FINISH, pending input is processed, pending output is flushed and deflate returns with Z_STREAM_END
// if there was enough output space. If deflate returns with Z_OK or Z_BUF_ERROR, this function must be called again with Z_FINISH
// and more output space (updated avail_out) but no more input data, until it returns with Z_STREAM_END or an error.
while (true) {
m_zstream->avail_out = m_buffer.size();
m_zstream->next_out = m_buffer.data();
auto ret = deflate(m_zstream, Z_FINISH);
if (ret == Z_STREAM_END || ret == Z_BUF_ERROR || ret == Z_OK) {
auto have = m_buffer.size() - m_zstream->avail_out;
TRY(m_stream->write_until_depleted(m_buffer.span().slice(0, have)));
if (ret == Z_STREAM_END)
return {};
} else {
return handle_zlib_error(ret);
}
}
}
}

View file

@ -0,0 +1,101 @@
/*
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/FixedArray.h>
#include <AK/MaybeOwned.h>
#include <AK/MemoryStream.h>
#include <AK/Stream.h>
extern "C" {
typedef struct z_stream_s z_stream;
}
namespace Compress {
enum class GenericZlibCompressionLevel : u8 {
Fastest,
Default,
Best,
};
class GenericZlibDecompressor : public Stream {
AK_MAKE_NONCOPYABLE(GenericZlibDecompressor);
public:
~GenericZlibDecompressor() override;
virtual ErrorOr<Bytes> read_some(Bytes) override;
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
virtual bool is_eof() const override;
virtual bool is_open() const override;
virtual void close() override;
protected:
GenericZlibDecompressor(AK::FixedArray<u8>, MaybeOwned<Stream>, z_stream*);
static ErrorOr<z_stream*> new_z_stream(int window_bits);
private:
MaybeOwned<Stream> m_stream;
z_stream* m_zstream;
bool m_eof { false };
AK::FixedArray<u8> m_buffer;
};
class GenericZlibCompressor : public Stream {
AK_MAKE_NONCOPYABLE(GenericZlibCompressor);
public:
~GenericZlibCompressor() override;
virtual ErrorOr<Bytes> read_some(Bytes) override;
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
virtual bool is_eof() const override;
virtual bool is_open() const override;
virtual void close() override;
ErrorOr<void> finish();
protected:
GenericZlibCompressor(AK::FixedArray<u8>, MaybeOwned<Stream>, z_stream*);
static ErrorOr<z_stream*> new_z_stream(int window_bits, GenericZlibCompressionLevel compression_level);
private:
MaybeOwned<Stream> m_stream;
z_stream* m_zstream;
AK::FixedArray<u8> m_buffer;
};
template<class T>
ErrorOr<ByteBuffer> decompress_all(ReadonlyBytes bytes)
{
auto input_stream = make<AK::FixedMemoryStream>(bytes);
auto deflate_stream = TRY(T::create(MaybeOwned<Stream>(move(input_stream))));
return TRY(deflate_stream->read_until_eof(4096));
}
template<class T>
ErrorOr<ByteBuffer> compress_all(ReadonlyBytes bytes, GenericZlibCompressionLevel compression_level)
{
auto output_stream = TRY(try_make<AllocatingMemoryStream>());
auto gzip_stream = TRY(T::create(MaybeOwned { *output_stream }, compression_level));
TRY(gzip_stream->write_until_depleted(bytes));
TRY(gzip_stream->finish());
auto buffer = TRY(ByteBuffer::create_uninitialized(output_stream->used_buffer_size()));
TRY(output_stream->read_until_filled(buffer.bytes()));
return buffer;
}
}

View file

@ -1,267 +1,39 @@
/*
* Copyright (c) 2020-2022, the SerenityOS developers.
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCompress/Gzip.h>
#include <AK/BitStream.h>
#include <AK/MemoryStream.h>
#include <AK/String.h>
#include <LibCore/DateTime.h>
#include <zlib.h>
namespace Compress {
bool GzipDecompressor::is_likely_compressed(ReadonlyBytes bytes)
ErrorOr<NonnullOwnPtr<GzipDecompressor>> GzipDecompressor::create(MaybeOwned<Stream> stream)
{
return bytes.size() >= 2 && bytes[0] == gzip_magic_1 && bytes[1] == gzip_magic_2;
}
bool BlockHeader::valid_magic_number() const
{
return identification_1 == gzip_magic_1 && identification_2 == gzip_magic_2;
}
bool BlockHeader::supported_by_implementation() const
{
if (compression_method != 0x08) {
// RFC 1952 does not define any compression methods other than deflate.
return false;
}
if (flags > Flags::MAX) {
// RFC 1952 does not define any more flags.
return false;
}
return true;
}
ErrorOr<NonnullOwnPtr<GzipDecompressor::Member>> GzipDecompressor::Member::construct(BlockHeader header, LittleEndianInputBitStream& stream)
{
auto deflate_stream = TRY(DeflateDecompressor::construct(MaybeOwned<LittleEndianInputBitStream>(stream)));
return TRY(adopt_nonnull_own_or_enomem(new (nothrow) Member(header, move(deflate_stream))));
}
GzipDecompressor::Member::Member(BlockHeader header, NonnullOwnPtr<DeflateDecompressor> stream)
: m_header(header)
, m_stream(move(stream))
{
}
GzipDecompressor::GzipDecompressor(MaybeOwned<Stream> stream)
: m_input_stream(make<LittleEndianInputBitStream>(move(stream)))
{
}
GzipDecompressor::~GzipDecompressor()
{
m_current_member.clear();
}
ErrorOr<Bytes> GzipDecompressor::read_some(Bytes bytes)
{
size_t total_read = 0;
while (total_read < bytes.size()) {
if (is_eof())
break;
auto slice = bytes.slice(total_read);
if (m_current_member) {
auto current_slice = TRY(current_member().m_stream->read_some(slice));
current_member().m_checksum.update(current_slice);
current_member().m_nread += current_slice.size();
if (current_slice.size() < slice.size()) {
u32 crc32 = TRY(m_input_stream->read_value<LittleEndian<u32>>());
u32 input_size = TRY(m_input_stream->read_value<LittleEndian<u32>>());
if (crc32 != current_member().m_checksum.digest())
return Error::from_string_literal("Stored CRC32 does not match the calculated CRC32 of the current member");
if (input_size != current_member().m_nread)
return Error::from_string_literal("Input size does not match the number of read bytes");
m_current_member.clear();
total_read += current_slice.size();
continue;
}
total_read += current_slice.size();
continue;
} else {
auto current_partial_header_slice = Bytes { m_partial_header, sizeof(BlockHeader) }.slice(m_partial_header_offset);
auto current_partial_header_data = TRY(m_input_stream->read_some(current_partial_header_slice));
m_partial_header_offset += current_partial_header_data.size();
if (is_eof())
break;
if (m_partial_header_offset < sizeof(BlockHeader)) {
break; // partial header read
}
m_partial_header_offset = 0;
BlockHeader header = *(reinterpret_cast<BlockHeader*>(m_partial_header));
if (!header.valid_magic_number())
return Error::from_string_literal("Header does not have a valid magic number");
if (!header.supported_by_implementation())
return Error::from_string_literal("Header is not supported by implementation");
if (header.flags & Flags::FEXTRA) {
u16 subfield_id = TRY(m_input_stream->read_value<LittleEndian<u16>>());
u16 length = TRY(m_input_stream->read_value<LittleEndian<u16>>());
TRY(m_input_stream->discard(length));
(void)subfield_id;
}
auto discard_string = [&]() -> ErrorOr<void> {
char next_char;
do {
next_char = TRY(m_input_stream->read_value<char>());
} while (next_char);
return {};
};
if (header.flags & Flags::FNAME)
TRY(discard_string());
if (header.flags & Flags::FCOMMENT)
TRY(discard_string());
if (header.flags & Flags::FHCRC) {
u16 crc = TRY(m_input_stream->read_value<LittleEndian<u16>>());
// FIXME: we should probably verify this instead of just assuming it matches
(void)crc;
}
m_current_member = TRY(Member::construct(header, *m_input_stream));
continue;
}
}
return bytes.slice(0, total_read);
}
ErrorOr<Optional<String>> GzipDecompressor::describe_header(ReadonlyBytes bytes)
{
if (bytes.size() < sizeof(BlockHeader))
return OptionalNone {};
auto& header = *(reinterpret_cast<BlockHeader const*>(bytes.data()));
if (!header.valid_magic_number() || !header.supported_by_implementation())
return OptionalNone {};
LittleEndian<u32> original_size = *reinterpret_cast<u32 const*>(bytes.offset(bytes.size() - sizeof(u32)));
return TRY(String::formatted("last modified: {}, original size {}", Core::DateTime::from_timestamp(header.modification_time), (u32)original_size));
auto buffer = TRY(AK::FixedArray<u8>::create(16 * 1024));
auto zstream = TRY(GenericZlibDecompressor::new_z_stream(MAX_WBITS | 16));
return adopt_nonnull_own_or_enomem(new (nothrow) GzipDecompressor(move(buffer), move(stream), zstream));
}
ErrorOr<ByteBuffer> GzipDecompressor::decompress_all(ReadonlyBytes bytes)
{
auto memory_stream = TRY(try_make<FixedMemoryStream>(bytes));
auto gzip_stream = make<GzipDecompressor>(move(memory_stream));
AllocatingMemoryStream output_stream;
auto buffer = TRY(ByteBuffer::create_uninitialized(4096));
while (!gzip_stream->is_eof()) {
auto const data = TRY(gzip_stream->read_some(buffer));
TRY(output_stream.write_until_depleted(data));
}
auto output_buffer = TRY(ByteBuffer::create_uninitialized(output_stream.used_buffer_size()));
TRY(output_stream.read_until_filled(output_buffer));
return output_buffer;
return ::Compress::decompress_all<GzipDecompressor>(bytes);
}
bool GzipDecompressor::is_eof() const { return m_input_stream->is_eof(); }
ErrorOr<size_t> GzipDecompressor::write_some(ReadonlyBytes)
ErrorOr<NonnullOwnPtr<GzipCompressor>> GzipCompressor::create(MaybeOwned<Stream> stream, GenericZlibCompressionLevel compression_level)
{
return Error::from_errno(EBADF);
auto buffer = TRY(AK::FixedArray<u8>::create(16 * 1024));
auto zstream = TRY(GenericZlibCompressor::new_z_stream(MAX_WBITS | 16, compression_level));
return adopt_nonnull_own_or_enomem(new (nothrow) GzipCompressor(move(buffer), move(stream), zstream));
}
ErrorOr<NonnullOwnPtr<GzipCompressor>> GzipCompressor::create(MaybeOwned<Stream> output_stream)
ErrorOr<ByteBuffer> GzipCompressor::compress_all(ReadonlyBytes bytes, GenericZlibCompressionLevel compression_level)
{
BlockHeader header;
header.identification_1 = 0x1f;
header.identification_2 = 0x8b;
header.compression_method = 0x08;
header.flags = 0;
header.modification_time = 0;
header.extra_flags = 3; // DEFLATE sets 2 for maximum compression and 4 for minimum compression
header.operating_system = 3; // unix
TRY(output_stream->write_until_depleted({ &header, sizeof(header) }));
auto deflate_compressor = TRY(DeflateCompressor::construct(MaybeOwned(*output_stream)));
return adopt_own(*new GzipCompressor { move(output_stream), move(deflate_compressor) });
}
GzipCompressor::GzipCompressor(MaybeOwned<Stream> output_stream, NonnullOwnPtr<DeflateCompressor> deflate_compressor)
: m_output_stream(move(output_stream))
, m_deflate_compressor(move(deflate_compressor))
{
}
ErrorOr<Bytes> GzipCompressor::read_some(Bytes)
{
return Error::from_errno(EBADF);
}
ErrorOr<size_t> GzipCompressor::write_some(ReadonlyBytes bytes)
{
VERIFY(!m_finished);
TRY(m_deflate_compressor->write_until_depleted(bytes));
m_total_bytes += bytes.size();
m_crc32.update(bytes);
return bytes.size();
}
ErrorOr<void> GzipCompressor::finish()
{
VERIFY(!m_finished);
m_finished = true;
TRY(m_deflate_compressor->final_flush());
TRY(m_output_stream->write_value<LittleEndian<u32>>(m_crc32.digest()));
TRY(m_output_stream->write_value<LittleEndian<u32>>(m_total_bytes));
return {};
}
bool GzipCompressor::is_eof() const
{
return true;
}
bool GzipCompressor::is_open() const
{
return m_output_stream->is_open();
}
void GzipCompressor::close()
{
}
ErrorOr<ByteBuffer> GzipCompressor::compress_all(ReadonlyBytes bytes)
{
auto output_stream = TRY(try_make<AllocatingMemoryStream>());
auto gzip_stream = TRY(GzipCompressor::create(MaybeOwned { *output_stream }));
TRY(gzip_stream->write_until_depleted(bytes));
TRY(gzip_stream->finish());
auto buffer = TRY(ByteBuffer::create_uninitialized(output_stream->used_buffer_size()));
TRY(output_stream->read_until_filled(buffer.bytes()));
return buffer;
return ::Compress::compress_all<GzipCompressor>(bytes, compression_level);
}
}

View file

@ -1,109 +1,41 @@
/*
* Copyright (c) 2020-2022, the SerenityOS developers.
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullOwnPtr.h>
#include <AK/OwnPtr.h>
#include <AK/Stream.h>
#include <LibCompress/Deflate.h>
#include <LibCrypto/Checksum/CRC32.h>
#include <LibCompress/Zlib.h>
namespace Compress {
constexpr u8 gzip_magic_1 = 0x1f;
constexpr u8 gzip_magic_2 = 0x8b;
struct [[gnu::packed]] BlockHeader {
u8 identification_1;
u8 identification_2;
u8 compression_method;
u8 flags;
LittleEndian<u32> modification_time;
u8 extra_flags;
u8 operating_system;
bool valid_magic_number() const;
bool supported_by_implementation() const;
};
struct Flags {
static constexpr u8 FTEXT = 1 << 0;
static constexpr u8 FHCRC = 1 << 1;
static constexpr u8 FEXTRA = 1 << 2;
static constexpr u8 FNAME = 1 << 3;
static constexpr u8 FCOMMENT = 1 << 4;
static constexpr u8 MAX = FTEXT | FHCRC | FEXTRA | FNAME | FCOMMENT;
};
class GzipDecompressor final : public Stream {
class GzipDecompressor final : public GenericZlibDecompressor {
public:
GzipDecompressor(MaybeOwned<Stream>);
~GzipDecompressor();
virtual ErrorOr<Bytes> read_some(Bytes) override;
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
virtual bool is_eof() const override;
virtual bool is_open() const override { return true; }
virtual void close() override { }
bool is_likely_compressed(ReadonlyBytes bytes);
static ErrorOr<NonnullOwnPtr<GzipDecompressor>> create(MaybeOwned<Stream>);
static ErrorOr<ByteBuffer> decompress_all(ReadonlyBytes);
static ErrorOr<Optional<String>> describe_header(ReadonlyBytes);
static bool is_likely_compressed(ReadonlyBytes bytes);
private:
class Member {
public:
static ErrorOr<NonnullOwnPtr<Member>> construct(BlockHeader header, LittleEndianInputBitStream&);
BlockHeader m_header;
NonnullOwnPtr<DeflateDecompressor> m_stream;
Crypto::Checksum::CRC32 m_checksum;
size_t m_nread { 0 };
private:
Member(BlockHeader, NonnullOwnPtr<DeflateDecompressor>);
};
Member const& current_member() const { return *m_current_member; }
Member& current_member() { return *m_current_member; }
NonnullOwnPtr<LittleEndianInputBitStream> m_input_stream;
u8 m_partial_header[sizeof(BlockHeader)];
size_t m_partial_header_offset { 0 };
OwnPtr<Member> m_current_member {};
bool m_eof { false };
GzipDecompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
: GenericZlibDecompressor(move(buffer), move(stream), zstream)
{
}
};
class GzipCompressor final : public Stream {
class GzipCompressor final : public GenericZlibCompressor {
public:
static ErrorOr<NonnullOwnPtr<GzipCompressor>> create(MaybeOwned<Stream>);
virtual ErrorOr<Bytes> read_some(Bytes) override;
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
virtual bool is_eof() const override;
virtual bool is_open() const override;
virtual void close() override;
static ErrorOr<ByteBuffer> compress_all(ReadonlyBytes bytes);
ErrorOr<void> finish();
static ErrorOr<NonnullOwnPtr<GzipCompressor>> create(MaybeOwned<Stream>, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default);
static ErrorOr<ByteBuffer> compress_all(ReadonlyBytes, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default);
private:
GzipCompressor(MaybeOwned<Stream>, NonnullOwnPtr<DeflateCompressor>);
MaybeOwned<Stream> m_output_stream;
NonnullOwnPtr<DeflateCompressor> m_deflate_compressor;
Crypto::Checksum::CRC32 m_crc32;
size_t m_total_bytes { 0 };
bool m_finished { false };
GzipCompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
: GenericZlibCompressor(move(buffer), move(stream), zstream)
{
}
};
}

View file

@ -1,175 +1,38 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/BitStream.h>
#include <AK/IntegralMath.h>
#include <AK/MemoryStream.h>
#include <AK/Span.h>
#include <AK/TypeCasts.h>
#include <AK/Types.h>
#include <LibCompress/Deflate.h>
#include <LibCompress/Zlib.h>
#include <zlib.h>
namespace Compress {
ErrorOr<NonnullOwnPtr<ZlibDecompressor>> ZlibDecompressor::create(MaybeOwned<Stream> stream)
{
return adopt_nonnull_own_or_enomem(new (nothrow) ZlibDecompressor(move(stream)));
auto buffer = TRY(AK::FixedArray<u8>::create(16 * 1024));
auto zstream = TRY(GenericZlibDecompressor::new_z_stream(MAX_WBITS));
return adopt_nonnull_own_or_enomem(new (nothrow) ZlibDecompressor(move(buffer), move(stream), zstream));
}
ZlibDecompressor::ZlibDecompressor(MaybeOwned<Stream> stream)
: m_has_seen_header(false)
, m_stream(move(stream))
ErrorOr<ByteBuffer> ZlibDecompressor::decompress_all(ReadonlyBytes bytes)
{
return ::Compress::decompress_all<ZlibDecompressor>(bytes);
}
ErrorOr<Bytes> ZlibDecompressor::read_some(Bytes bytes)
ErrorOr<NonnullOwnPtr<ZlibCompressor>> ZlibCompressor::create(MaybeOwned<Stream> stream, GenericZlibCompressionLevel compression_level)
{
if (!m_has_seen_header) {
auto header = TRY(m_stream->read_value<ZlibHeader>());
if (header.compression_method != ZlibCompressionMethod::Deflate || header.compression_info > 7)
return Error::from_string_literal("Non-DEFLATE compression inside Zlib is not supported");
if (header.present_dictionary)
return Error::from_string_literal("Zlib compression with a pre-defined dictionary is currently not supported");
if (header.as_u16 % 31 != 0)
return Error::from_string_literal("Zlib error correction code does not match");
auto bit_stream = make<LittleEndianInputBitStream>(move(m_stream));
auto deflate_stream = TRY(Compress::DeflateDecompressor::construct(move(bit_stream)));
m_stream = move(deflate_stream);
m_has_seen_header = true;
}
return m_stream->read_some(bytes);
auto buffer = TRY(AK::FixedArray<u8>::create(16 * 1024));
auto zstream = TRY(GenericZlibCompressor::new_z_stream(MAX_WBITS, compression_level));
return adopt_nonnull_own_or_enomem(new (nothrow) ZlibCompressor(move(buffer), move(stream), zstream));
}
ErrorOr<size_t> ZlibDecompressor::write_some(ReadonlyBytes)
ErrorOr<ByteBuffer> ZlibCompressor::compress_all(ReadonlyBytes bytes, GenericZlibCompressionLevel compression_level)
{
return Error::from_errno(EBADF);
}
bool ZlibDecompressor::is_eof() const
{
return m_stream->is_eof();
}
bool ZlibDecompressor::is_open() const
{
return m_stream->is_open();
}
void ZlibDecompressor::close()
{
}
ErrorOr<NonnullOwnPtr<ZlibCompressor>> ZlibCompressor::construct(MaybeOwned<Stream> stream, ZlibCompressionLevel compression_level)
{
// Zlib only defines Deflate as a compression method.
auto compression_method = ZlibCompressionMethod::Deflate;
// FIXME: Find a way to compress with Deflate's "Best" compression level.
auto compressor_stream = TRY(DeflateCompressor::construct(MaybeOwned(*stream), static_cast<DeflateCompressor::CompressionLevel>(compression_level)));
auto zlib_compressor = TRY(adopt_nonnull_own_or_enomem(new (nothrow) ZlibCompressor(move(stream), move(compressor_stream))));
TRY(zlib_compressor->write_header(compression_method, compression_level));
return zlib_compressor;
}
ZlibCompressor::ZlibCompressor(MaybeOwned<Stream> stream, NonnullOwnPtr<Stream> compressor_stream)
: m_output_stream(move(stream))
, m_compressor(move(compressor_stream))
{
}
ZlibCompressor::~ZlibCompressor() = default;
ErrorOr<void> ZlibCompressor::write_header(ZlibCompressionMethod compression_method, ZlibCompressionLevel compression_level)
{
u8 compression_info = 0;
if (compression_method == ZlibCompressionMethod::Deflate) {
compression_info = AK::log2(DeflateCompressor::window_size) - 8;
VERIFY(compression_info <= 7);
}
ZlibHeader header {
.compression_method = compression_method,
.compression_info = compression_info,
.check_bits = 0,
.present_dictionary = false,
.compression_level = compression_level,
};
header.check_bits = 0b11111 - header.as_u16 % 31;
// FIXME: Support pre-defined dictionaries.
TRY(m_output_stream->write_value(header.as_u16));
return {};
}
ErrorOr<Bytes> ZlibCompressor::read_some(Bytes)
{
return Error::from_errno(EBADF);
}
ErrorOr<size_t> ZlibCompressor::write_some(ReadonlyBytes bytes)
{
VERIFY(!m_finished);
size_t n_written = TRY(m_compressor->write_some(bytes));
m_adler32_checksum.update(bytes.trim(n_written));
return n_written;
}
bool ZlibCompressor::is_eof() const
{
return false;
}
bool ZlibCompressor::is_open() const
{
return m_output_stream->is_open();
}
void ZlibCompressor::close()
{
}
ErrorOr<void> ZlibCompressor::finish()
{
VERIFY(!m_finished);
if (is<DeflateCompressor>(m_compressor.ptr()))
TRY(static_cast<DeflateCompressor*>(m_compressor.ptr())->final_flush());
NetworkOrdered<u32> adler_sum = m_adler32_checksum.digest();
TRY(m_output_stream->write_value(adler_sum));
m_finished = true;
return {};
}
ErrorOr<ByteBuffer> ZlibCompressor::compress_all(ReadonlyBytes bytes, ZlibCompressionLevel compression_level)
{
auto output_stream = TRY(try_make<AllocatingMemoryStream>());
auto zlib_stream = TRY(ZlibCompressor::construct(MaybeOwned<Stream>(*output_stream), compression_level));
TRY(zlib_stream->write_until_depleted(bytes));
TRY(zlib_stream->finish());
auto buffer = TRY(ByteBuffer::create_uninitialized(output_stream->used_buffer_size()));
TRY(output_stream->read_until_filled(buffer.bytes()));
return buffer;
return ::Compress::compress_all<ZlibCompressor>(bytes, compression_level);
}
}

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,87 +8,34 @@
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/Endian.h>
#include <AK/MaybeOwned.h>
#include <AK/Optional.h>
#include <AK/OwnPtr.h>
#include <AK/Span.h>
#include <AK/Stream.h>
#include <AK/Types.h>
#include <LibCrypto/Checksum/Adler32.h>
#include <LibCompress/GenericZlib.h>
namespace Compress {
enum class ZlibCompressionMethod : u8 {
Deflate = 8,
};
enum class ZlibCompressionLevel : u8 {
Fastest,
Fast,
Default,
Best,
};
struct ZlibHeader {
union {
struct {
ZlibCompressionMethod compression_method : 4;
u8 compression_info : 4;
u8 check_bits : 5;
bool present_dictionary : 1;
ZlibCompressionLevel compression_level : 2;
};
NetworkOrdered<u16> as_u16;
};
};
static_assert(sizeof(ZlibHeader) == sizeof(u16));
class ZlibDecompressor : public Stream {
class ZlibDecompressor final : public GenericZlibDecompressor {
public:
static ErrorOr<NonnullOwnPtr<ZlibDecompressor>> create(MaybeOwned<Stream>);
virtual ErrorOr<Bytes> read_some(Bytes) override;
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
virtual bool is_eof() const override;
virtual bool is_open() const override;
virtual void close() override;
static ErrorOr<ByteBuffer> decompress_all(ReadonlyBytes);
private:
ZlibDecompressor(MaybeOwned<Stream>);
bool m_has_seen_header { false };
MaybeOwned<Stream> m_stream;
ZlibDecompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
: GenericZlibDecompressor(move(buffer), move(stream), zstream)
{
}
};
class ZlibCompressor : public Stream {
class ZlibCompressor final : public GenericZlibCompressor {
public:
static ErrorOr<NonnullOwnPtr<ZlibCompressor>> construct(MaybeOwned<Stream>, ZlibCompressionLevel = ZlibCompressionLevel::Default);
~ZlibCompressor();
virtual ErrorOr<Bytes> read_some(Bytes) override;
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
virtual bool is_eof() const override;
virtual bool is_open() const override;
virtual void close() override;
ErrorOr<void> finish();
static ErrorOr<ByteBuffer> compress_all(ReadonlyBytes bytes, ZlibCompressionLevel = ZlibCompressionLevel::Default);
static ErrorOr<NonnullOwnPtr<ZlibCompressor>> create(MaybeOwned<Stream>, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default);
static ErrorOr<ByteBuffer> compress_all(ReadonlyBytes, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default);
private:
ZlibCompressor(MaybeOwned<Stream> stream, NonnullOwnPtr<Stream> compressor_stream);
ErrorOr<void> write_header(ZlibCompressionMethod, ZlibCompressionLevel);
bool m_finished { false };
MaybeOwned<Stream> m_output_stream;
NonnullOwnPtr<Stream> m_compressor;
Crypto::Checksum::Adler32 m_adler32_checksum;
ZlibCompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
: GenericZlibCompressor(move(buffer), move(stream), zstream)
{
}
};
}
template<>
struct AK::Traits<Compress::ZlibHeader> : public AK::DefaultTraits<Compress::ZlibHeader> {
static constexpr bool is_trivially_serializable() { return true; }
};

View file

@ -13,10 +13,13 @@ set(SOURCES
if (WIN32)
list(APPEND SOURCES
ProcessWindows.cpp
SocketpairWindows.cpp
SystemWindows.cpp)
else()
list(APPEND SOURCES System.cpp)
list(APPEND SOURCES
Process.cpp
System.cpp)
endif()
serenity_lib(LibCoreMinimal coreminimal)
@ -52,14 +55,12 @@ set(SOURCES
if (WIN32)
# FIXME: Support UDPServer and TCPServer on Windows
list(APPEND SOURCES
ProcessWindows.cpp
SocketWindows.cpp
AnonymousBufferWindows.cpp
EventLoopImplementationWindows.cpp)
else()
list(APPEND SOURCES
Command.cpp
Process.cpp
Socket.cpp
AnonymousBuffer.cpp
EventLoopImplementationUnix.cpp
@ -122,12 +123,20 @@ if (ENABLE_SWIFT)
else()
list(APPEND SWIFT_EXCLUDE_HEADERS "EventLoopImplementationWindows.h")
endif()
if (NOT APPLE)
list(APPEND SWIFT_EXCLUDE_HEADERS
IOSurface.h
MachPort.h
MachMessageTypes.h
ProcessStatisticsMach.h
)
endif()
generate_clang_module_map(LibCore EXCLUDE_FILES ${SWIFT_EXCLUDE_HEADERS})
target_sources(LibCore PRIVATE
EventSwift.mm
EventLoopExecutor.swift)
set_source_files_properties(EventSwift.mm PRIVATE PROPERTIES COMPILE_FLAGS -fobjc-arc)
set_source_files_properties(EventSwift.mm PRIVATE PROPERTIES COMPILE_FLAGS -fblocks)
target_link_libraries(LibCore PRIVATE AK)
add_swift_target_properties(LibCore LAGOM_LIBRARIES AK)
endif()

View file

@ -7,16 +7,10 @@
#include <LibCore/EventLoop.h>
#include <LibCore/EventSwift.h>
#if !__has_feature(objc_arc)
# error "This file requires ARC"
#endif
namespace Core {
void deferred_invoke_block(EventLoop& event_loop, void (^invokee)(void))
{
event_loop.deferred_invoke([invokee = move(invokee)] {
invokee();
});
event_loop.deferred_invoke(invokee);
}
}

View file

@ -11,6 +11,7 @@
#include <AK/ByteString.h>
#include <AK/Forward.h>
#include <AK/String.h>
#include <LibCore/File.h>
namespace Core {

View file

@ -193,15 +193,7 @@ ErrorOr<void> PosixSocketHelper::set_blocking(bool enabled)
ErrorOr<void> PosixSocketHelper::set_close_on_exec(bool enabled)
{
int flags = TRY(System::fcntl(m_fd, F_GETFD));
if (enabled)
flags |= FD_CLOEXEC;
else
flags &= ~FD_CLOEXEC;
TRY(System::fcntl(m_fd, F_SETFD, flags));
return {};
return System::set_close_on_exec(m_fd, enabled);
}
ErrorOr<void> PosixSocketHelper::set_receive_timeout(AK::Duration timeout)

View file

@ -106,9 +106,7 @@ ErrorOr<void> PosixSocketHelper::set_blocking(bool)
ErrorOr<void> PosixSocketHelper::set_close_on_exec(bool enabled)
{
if (!SetHandleInformation(to_handle(m_fd), HANDLE_FLAG_INHERIT, enabled ? 0 : HANDLE_FLAG_INHERIT))
return Error::from_windows_error();
return {};
return System::set_close_on_exec(m_fd, enabled);
}
ErrorOr<size_t> PosixSocketHelper::pending_bytes() const

View file

@ -157,8 +157,7 @@ ByteString StandardPaths::config_directory()
ByteString StandardPaths::user_data_directory()
{
#ifdef AK_OS_WINDOWS
dbgln("Core::StandardPaths::user_data_directory() is not implemented");
VERIFY_NOT_REACHED();
return ByteString::formatted("{}/Ladybird"sv, getenv("LOCALAPPDATA"));
#endif
if (auto data_directory = get_environment_if_not_empty("XDG_DATA_HOME"sv); data_directory.has_value())
return LexicalPath::canonicalized_path(*data_directory);

View file

@ -1017,4 +1017,17 @@ ErrorOr<void> sleep_ms(u32 milliseconds)
return {};
}
ErrorOr<void> set_close_on_exec(int fd, bool enabled)
{
int flags = TRY(fcntl(fd, F_GETFD));
if (enabled)
flags |= FD_CLOEXEC;
else
flags &= ~FD_CLOEXEC;
TRY(fcntl(fd, F_SETFD, flags));
return {};
}
}

View file

@ -184,5 +184,6 @@ ErrorOr<void> set_resource_limits(int resource, rlim_t limit);
int getpid();
bool is_socket(int fd);
ErrorOr<void> sleep_ms(u32 milliseconds);
ErrorOr<void> set_close_on_exec(int fd, bool enabled);
}

View file

@ -11,6 +11,7 @@
#include <AK/ByteString.h>
#include <AK/ScopeGuard.h>
#include <LibCore/Process.h>
#include <LibCore/System.h>
#include <direct.h>
#include <sys/mman.h>
@ -263,4 +264,16 @@ u64 physical_memory_bytes()
return ms.ullTotalPhys;
}
ErrorOr<ByteString> current_executable_path()
{
return TRY(Process::get_name()).to_byte_string();
}
ErrorOr<void> set_close_on_exec(int handle, bool enabled)
{
if (!SetHandleInformation(to_handle(handle), HANDLE_FLAG_INHERIT, enabled ? 0 : HANDLE_FLAG_INHERIT))
return Error::from_windows_error();
return {};
}
}

View file

@ -131,7 +131,7 @@ FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_xor_without_allocation(
/**
* Complexity: O(N) where N is the number of words
*/
FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_without_allocation(
FLATTEN ErrorOr<void> UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_without_allocation(
UnsignedBigInteger const& right,
size_t index,
UnsignedBigInteger& output)
@ -139,16 +139,16 @@ FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_w
// If the value is invalid, the output value is invalid as well.
if (right.is_invalid()) {
output.invalidate();
return;
return {};
}
if (index == 0) {
output.set_to_0();
return;
return {};
}
size_t size = (index + UnsignedBigInteger::BITS_IN_WORD - 1) / UnsignedBigInteger::BITS_IN_WORD;
output.m_words.resize_and_keep_capacity(size);
TRY(output.m_words.try_resize_and_keep_capacity(size));
VERIFY(size > 0);
for (size_t i = 0; i < size - 1; ++i)
output.m_words[i] = ~(i < right.length() ? right.words()[i] : 0);
@ -158,6 +158,8 @@ FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_w
auto last_word = last_word_index < right.length() ? right.words()[last_word_index] : 0;
output.m_words[last_word_index] = (NumericLimits<UnsignedBigInteger::Word>::max() >> (UnsignedBigInteger::BITS_IN_WORD - index)) & ~last_word;
return {};
}
FLATTEN void UnsignedBigIntegerAlgorithms::shift_left_without_allocation(

View file

@ -20,13 +20,13 @@ public:
static void bitwise_or_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& output);
static void bitwise_and_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& output);
static void bitwise_xor_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& output);
static void bitwise_not_fill_to_one_based_index_without_allocation(UnsignedBigInteger const& left, size_t, UnsignedBigInteger& output);
static void shift_left_without_allocation(UnsignedBigInteger const& number, size_t bits_to_shift_by, UnsignedBigInteger& temp_result, UnsignedBigInteger& temp_plus, UnsignedBigInteger& output);
static void shift_right_without_allocation(UnsignedBigInteger const& number, size_t num_bits, UnsignedBigInteger& output);
static void multiply_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& temp_shift_result, UnsignedBigInteger& temp_shift_plus, UnsignedBigInteger& temp_shift, UnsignedBigInteger& output);
static void divide_without_allocation(UnsignedBigInteger const& numerator, UnsignedBigInteger const& denominator, UnsignedBigInteger& quotient, UnsignedBigInteger& remainder);
static void divide_u16_without_allocation(UnsignedBigInteger const& numerator, UnsignedBigInteger::Word denominator, UnsignedBigInteger& quotient, UnsignedBigInteger& remainder);
static ErrorOr<void> bitwise_not_fill_to_one_based_index_without_allocation(UnsignedBigInteger const& left, size_t, UnsignedBigInteger& output);
static ErrorOr<void> try_shift_left_without_allocation(UnsignedBigInteger const& number, size_t bits_to_shift_by, UnsignedBigInteger& temp_result, UnsignedBigInteger& temp_plus, UnsignedBigInteger& output);
static void extended_GCD_without_allocation(UnsignedBigInteger const& a, UnsignedBigInteger const& b, UnsignedBigInteger& x, UnsignedBigInteger& y, UnsignedBigInteger& gcd, UnsignedBigInteger& temp_quotient, UnsignedBigInteger& temp_1, UnsignedBigInteger& temp_2, UnsignedBigInteger& temp_shift_result, UnsignedBigInteger& temp_shift_plus, UnsignedBigInteger& temp_shift, UnsignedBigInteger& temp_r, UnsignedBigInteger& temp_s, UnsignedBigInteger& temp_t);

View file

@ -291,6 +291,17 @@ FLATTEN SignedBigInteger SignedBigInteger::shift_right(size_t num_bits) const
return SignedBigInteger { m_unsigned_data.shift_right(num_bits), m_sign };
}
FLATTEN ErrorOr<SignedBigInteger> SignedBigInteger::mod_power_of_two(size_t power_of_two) const
{
auto const lower_bits = m_unsigned_data.as_n_bits(power_of_two);
if (is_positive())
return SignedBigInteger(lower_bits);
// twos encode lower bits
return SignedBigInteger(TRY(lower_bits.try_bitwise_not_fill_to_one_based_index(power_of_two)).plus(1).as_n_bits(power_of_two));
}
FLATTEN SignedBigInteger SignedBigInteger::multiplied_by(SignedBigInteger const& other) const
{
bool result_sign = m_sign ^ other.m_sign;

View file

@ -120,6 +120,7 @@ public:
[[nodiscard]] SignedBigInteger multiplied_by(SignedBigInteger const& other) const;
[[nodiscard]] SignedDivisionResult divided_by(SignedBigInteger const& divisor) const;
[[nodiscard]] ErrorOr<SignedBigInteger> mod_power_of_two(size_t power_of_two) const;
[[nodiscard]] ErrorOr<SignedBigInteger> try_shift_left(size_t num_bits) const;
[[nodiscard]] SignedBigInteger plus(UnsignedBigInteger const& other) const;

View file

@ -473,10 +473,15 @@ FLATTEN UnsignedBigInteger UnsignedBigInteger::bitwise_xor(UnsignedBigInteger co
}
FLATTEN UnsignedBigInteger UnsignedBigInteger::bitwise_not_fill_to_one_based_index(size_t size) const
{
return MUST(try_bitwise_not_fill_to_one_based_index(size));
}
FLATTEN ErrorOr<UnsignedBigInteger> UnsignedBigInteger::try_bitwise_not_fill_to_one_based_index(size_t size) const
{
UnsignedBigInteger result;
UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_without_allocation(*this, size, result);
TRY(UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_without_allocation(*this, size, result));
return result;
}
@ -506,6 +511,33 @@ FLATTEN UnsignedBigInteger UnsignedBigInteger::shift_right(size_t num_bits) cons
return output;
}
FLATTEN UnsignedBigInteger UnsignedBigInteger::as_n_bits(size_t n) const
{
if (auto const num_bits = one_based_index_of_highest_set_bit(); n >= num_bits)
return *this;
UnsignedBigInteger output;
output.set_to(*this);
auto const word_index = n / BITS_IN_WORD;
auto const bits_to_keep = n % BITS_IN_WORD;
auto const bits_to_discard = BITS_IN_WORD - bits_to_keep;
output.m_words.resize(word_index + 1);
auto const last_word = output.m_words[word_index];
Word new_last_word = 0;
// avoid UB from a 32 bit shift on a u32
if (bits_to_keep != 0)
new_last_word = last_word << bits_to_discard >> bits_to_discard;
output.m_words[word_index] = new_last_word;
return output;
}
FLATTEN UnsignedBigInteger UnsignedBigInteger::multiplied_by(UnsignedBigInteger const& other) const
{
UnsignedBigInteger result;

View file

@ -117,9 +117,11 @@ public:
[[nodiscard]] UnsignedBigInteger bitwise_not_fill_to_one_based_index(size_t) const;
[[nodiscard]] UnsignedBigInteger shift_left(size_t num_bits) const;
[[nodiscard]] UnsignedBigInteger shift_right(size_t num_bits) const;
[[nodiscard]] UnsignedBigInteger as_n_bits(size_t n) const;
[[nodiscard]] UnsignedBigInteger multiplied_by(UnsignedBigInteger const& other) const;
[[nodiscard]] UnsignedDivisionResult divided_by(UnsignedBigInteger const& divisor) const;
[[nodiscard]] ErrorOr<UnsignedBigInteger> try_bitwise_not_fill_to_one_based_index(size_t) const;
[[nodiscard]] ErrorOr<UnsignedBigInteger> try_shift_left(size_t num_bits) const;
[[nodiscard]] u32 hash() const;

View file

@ -17,9 +17,6 @@ set(SOURCES
BigInt/SignedBigInteger.cpp
BigInt/UnsignedBigInteger.cpp
Certificate/Certificate.cpp
Checksum/Adler32.cpp
Checksum/cksum.cpp
Checksum/CRC32.cpp
Cipher/AES.cpp
Curves/EdwardsCurve.cpp
Curves/SECPxxxr1.cpp

View file

@ -1,48 +0,0 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Span.h>
#include <AK/Types.h>
#include <LibCrypto/Checksum/Adler32.h>
namespace Crypto::Checksum {
void Adler32::update(ReadonlyBytes data)
{
// See https://github.com/SerenityOS/serenity/pull/24408#discussion_r1609051678
constexpr size_t iterations_without_overflow = 380368439;
u64 state_a = m_state_a;
u64 state_b = m_state_b;
while (data.size()) {
// You can verify that no overflow will happen here during at least
// `iterations_without_overflow` iterations using the following Python script:
//
// state_a = 65520
// state_b = 65520
// for i in range(380368439):
// state_a += 255
// state_b += state_a
// print(state_b < 2 ** 64)
auto chunk = data.slice(0, min(data.size(), iterations_without_overflow));
for (u8 byte : chunk) {
state_a += byte;
state_b += state_a;
}
state_a %= 65521;
state_b %= 65521;
data = data.slice(chunk.size());
}
m_state_a = state_a;
m_state_b = state_b;
}
u32 Adler32::digest()
{
return (m_state_b << 16) | m_state_a;
}
}

View file

@ -1,38 +0,0 @@
/*
* Copyright (c) 2020-2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Span.h>
#include <AK/Types.h>
#include <LibCrypto/Checksum/ChecksumFunction.h>
namespace Crypto::Checksum {
class Adler32 : public ChecksumFunction<u32> {
public:
Adler32() = default;
Adler32(ReadonlyBytes data)
{
update(data);
}
Adler32(u32 initial_a, u32 initial_b, ReadonlyBytes data)
: m_state_a(initial_a)
, m_state_b(initial_b)
{
update(data);
}
virtual void update(ReadonlyBytes data) override;
virtual u32 digest() override;
private:
u32 m_state_a { 1 };
u32 m_state_b { 0 };
};
}

View file

@ -1,82 +0,0 @@
/*
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Endian.h>
#include <AK/Format.h>
#include <AK/Types.h>
#include <LibCrypto/Checksum/ChecksumFunction.h>
namespace Crypto::Checksum {
// A generic 16-bit Cyclic Redundancy Check.
// Just like CRC32, this class receives its polynomial little-endian.
// For example, the polynomial x¹⁶ + x¹² + x⁵ + 1 is represented as 0x8408.
template<u16 polynomial>
class CRC16 : public ChecksumFunction<u16> {
public:
static constexpr u16 be_polynomial = bitswap(polynomial);
// This is a big endian table, while CRC-32 uses a little endian table.
static constexpr auto generate_table()
{
Array<u16, 256> data {};
data[0] = 0;
u16 value = 0x8000;
auto i = 1u;
do {
if ((value & 0x8000) != 0) {
value = be_polynomial ^ (value << 1);
} else {
value = value << 1;
}
for (auto j = 0u; j < i; ++j) {
data[i + j] = value ^ data[j];
}
i <<= 1;
} while (i < 256);
return data;
}
static constexpr auto table = generate_table();
virtual ~CRC16() = default;
CRC16() = default;
CRC16(ReadonlyBytes data)
{
update(data);
}
CRC16(u16 initial_state, ReadonlyBytes data)
: m_state(initial_state)
{
update(data);
}
// FIXME: This implementation is naive and slow.
// Figure out how to adopt the slicing-by-8 algorithm (see CRC32) for 16-bit polynomials.
virtual void update(ReadonlyBytes data) override
{
for (size_t i = 0; i < data.size(); i++) {
size_t table_index = ((m_state >> 8) ^ data.at(i)) & 0xFF;
m_state = (table[table_index] ^ (static_cast<u32>(m_state) << 8)) & 0xFFFF;
}
}
virtual u16 digest() override
{
return m_state;
}
private:
u16 m_state { 0 };
};
}

View file

@ -1,176 +0,0 @@
/*
* Copyright (c) 2020-2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Array.h>
#include <AK/Span.h>
#include <AK/Types.h>
#include <LibCrypto/Checksum/CRC32.h>
#ifdef __ARM_ACLE
# include <arm_acle.h>
#endif
namespace Crypto::Checksum {
#if __ARM_ARCH >= 8 && defined(__ARM_FEATURE_CRC32) && defined(__ARM_ACLE)
void CRC32::update(ReadonlyBytes span)
{
// FIXME: Does this require runtime checking on rpi?
// (Maybe the instruction is present on the rpi4 but not on the rpi3?)
u8 const* data = span.data();
size_t size = span.size();
while (size > 0 && (reinterpret_cast<FlatPtr>(data) & 7) != 0) {
m_state = __crc32b(m_state, *data);
++data;
--size;
}
auto* data64 = reinterpret_cast<u64 const*>(data);
while (size >= 8) {
m_state = __crc32d(m_state, *data64);
++data64;
size -= 8;
}
data = reinterpret_cast<u8 const*>(data64);
while (size > 0) {
m_state = __crc32b(m_state, *data);
++data;
--size;
}
}
// FIXME: On Intel, use _mm_crc32_u8 / _mm_crc32_u64 if available (SSE 4.2).
#else
static constexpr size_t ethernet_polynomial = 0xEDB88320;
# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
// This implements Intel's slicing-by-8 algorithm. Their original paper is no longer on their website,
// but their source code is still available for reference:
// https://sourceforge.net/projects/slicing-by-8/
static constexpr auto generate_table()
{
Array<Array<u32, 256>, 8> data {};
for (u32 i = 0; i < 256; ++i) {
auto value = i;
for (size_t j = 0; j < 8; ++j)
value = (value >> 1) ^ ((value & 1) * ethernet_polynomial);
data[0][i] = value;
}
for (u32 i = 0; i < 256; ++i) {
for (size_t j = 1; j < 8; ++j)
data[j][i] = (data[j - 1][i] >> 8) ^ data[0][data[j - 1][i] & 0xff];
}
return data;
}
static constexpr auto table = generate_table();
struct AlignmentData {
ReadonlyBytes misaligned;
ReadonlyBytes aligned;
};
static AlignmentData split_bytes_for_alignment(ReadonlyBytes data, size_t alignment)
{
auto address = reinterpret_cast<uintptr_t>(data.data());
auto offset = alignment - address % alignment;
if (offset == alignment)
return { {}, data };
if (data.size() < alignment)
return { data, {} };
return { data.trim(offset), data.slice(offset) };
}
static constexpr u32 single_byte_crc(u32 crc, u8 byte)
{
return (crc >> 8) ^ table[0][(crc & 0xff) ^ byte];
}
void CRC32::update(ReadonlyBytes data)
{
// The provided data may not be aligned to a 4-byte boundary, required to reinterpret its address
// into a u32 in the loop below. So we split the bytes into two segments: the misaligned bytes
// (which undergo the standard 1-byte-at-a-time algorithm) and remaining aligned bytes.
auto [misaligned_data, aligned_data] = split_bytes_for_alignment(data, alignof(u32));
for (auto byte : misaligned_data)
m_state = single_byte_crc(m_state, byte);
while (aligned_data.size() >= 8) {
auto const* segment = reinterpret_cast<u32 const*>(aligned_data.data());
auto low = *segment ^ m_state;
auto high = *(++segment);
m_state = table[0][(high >> 24) & 0xff]
^ table[1][(high >> 16) & 0xff]
^ table[2][(high >> 8) & 0xff]
^ table[3][high & 0xff]
^ table[4][(low >> 24) & 0xff]
^ table[5][(low >> 16) & 0xff]
^ table[6][(low >> 8) & 0xff]
^ table[7][low & 0xff];
aligned_data = aligned_data.slice(8);
}
for (auto byte : aligned_data)
m_state = single_byte_crc(m_state, byte);
}
# else
// FIXME: Implement the slicing-by-8 algorithm for big endian CPUs.
static constexpr auto generate_table()
{
Array<u32, 256> data {};
for (auto i = 0u; i < data.size(); i++) {
u32 value = i;
for (auto j = 0; j < 8; j++) {
if (value & 1) {
value = ethernet_polynomial ^ (value >> 1);
} else {
value = value >> 1;
}
}
data[i] = value;
}
return data;
}
static constexpr auto table = generate_table();
void CRC32::update(ReadonlyBytes data)
{
for (size_t i = 0; i < data.size(); i++) {
m_state = table[(m_state ^ data.at(i)) & 0xFF] ^ (m_state >> 8);
}
}
# endif
#endif
u32 CRC32::digest()
{
return ~m_state;
}
}

View file

@ -1,36 +0,0 @@
/*
* Copyright (c) 2020-2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Span.h>
#include <AK/Types.h>
#include <LibCrypto/Checksum/ChecksumFunction.h>
namespace Crypto::Checksum {
class CRC32 : public ChecksumFunction<u32> {
public:
CRC32() = default;
CRC32(ReadonlyBytes data)
{
update(data);
}
CRC32(u32 initial_state, ReadonlyBytes data)
: m_state(initial_state)
{
update(data);
}
virtual void update(ReadonlyBytes data) override;
virtual u32 digest() override;
private:
u32 m_state { ~0u };
};
}

View file

@ -1,76 +0,0 @@
/*
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
#include <LibCrypto/Checksum/ChecksumFunction.h>
namespace Crypto::Checksum {
// A generic 8-bit Cyclic Redundancy Check.
// Note that as opposed to CRC32, this class operates with MSB first, so the polynomial must not be reversed.
// For example, the polynomial x⁸ + x² + x + 1 is represented as 0x07 and not 0xE0.
template<u8 polynomial>
class CRC8 : public ChecksumFunction<u8> {
public:
// This is a big endian table, while CRC-32 uses a little endian table.
static constexpr auto generate_table()
{
Array<u8, 256> data {};
u8 value = 0x80;
auto i = 1u;
do {
if ((value & 0x80) != 0) {
value = polynomial ^ (value << 1);
} else {
value = value << 1;
}
for (auto j = 0u; j < i; ++j) {
data[i + j] = value ^ data[j];
}
i <<= 1;
} while (i < 256);
return data;
}
static constexpr auto table = generate_table();
virtual ~CRC8() = default;
CRC8() = default;
CRC8(ReadonlyBytes data)
{
update(data);
}
CRC8(u8 initial_state, ReadonlyBytes data)
: m_state(initial_state)
{
update(data);
}
// FIXME: This implementation is naive and slow.
// Figure out how to adopt the slicing-by-8 algorithm (see CRC32) for 8 bit polynomials.
virtual void update(ReadonlyBytes data) override
{
for (size_t i = 0; i < data.size(); i++) {
size_t table_index = (m_state ^ data.at(i)) & 0xFF;
m_state = (table[table_index] ^ (static_cast<u32>(m_state) << 8)) & 0xFF;
}
}
virtual u8 digest() override
{
return m_state;
}
private:
u8 m_state { 0 };
};
}

View file

@ -1,25 +0,0 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Span.h>
namespace Crypto::Checksum {
template<typename ChecksumT>
class ChecksumFunction {
public:
using ChecksumType = ChecksumT;
virtual void update(ReadonlyBytes data) = 0;
virtual ChecksumType digest() = 0;
protected:
virtual ~ChecksumFunction() = default;
};
}

View file

@ -1,81 +0,0 @@
/*
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/Concepts.h>
#include <AK/MaybeOwned.h>
#include <AK/Stream.h>
#include <LibCrypto/Checksum/ChecksumFunction.h>
namespace Crypto::Checksum {
// A stream wrapper type which passes all read and written data through a checksum function.
template<typename ChecksumFunctionType, typename ChecksumType = typename ChecksumFunctionType::ChecksumType>
requires(
IsBaseOf<ChecksumFunction<ChecksumType>, ChecksumFunctionType>,
// Require checksum function to be constructible without arguments, since we have no initial data.
requires() {
ChecksumFunctionType {};
})
class ChecksummingStream : public Stream {
public:
virtual ~ChecksummingStream() = default;
ChecksummingStream(MaybeOwned<Stream> stream)
: m_stream(move(stream))
{
}
virtual ErrorOr<Bytes> read_some(Bytes bytes) override
{
auto const written_bytes = TRY(m_stream->read_some(bytes));
update(written_bytes);
return written_bytes;
}
virtual ErrorOr<void> read_until_filled(Bytes bytes) override
{
TRY(m_stream->read_until_filled(bytes));
update(bytes);
return {};
}
virtual ErrorOr<size_t> write_some(ReadonlyBytes bytes) override
{
auto bytes_written = TRY(m_stream->write_some(bytes));
// Only update with the bytes that were actually written
update(bytes.trim(bytes_written));
return bytes_written;
}
virtual ErrorOr<void> write_until_depleted(ReadonlyBytes bytes) override
{
update(bytes);
return m_stream->write_until_depleted(bytes);
}
virtual bool is_eof() const override { return m_stream->is_eof(); }
virtual bool is_open() const override { return m_stream->is_open(); }
virtual void close() override { m_stream->close(); }
ChecksumType digest()
{
return m_checksum.digest();
}
private:
ALWAYS_INLINE void update(ReadonlyBytes bytes)
{
m_checksum.update(bytes);
}
MaybeOwned<Stream> m_stream;
ChecksumFunctionType m_checksum {};
};
}

View file

@ -1,88 +0,0 @@
/*
* Copyright (c) 2024, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCrypto/Checksum/cksum.h>
namespace Crypto::Checksum {
static constexpr u32 table[256] = {
0x00000000,
0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6,
0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac,
0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f,
0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a,
0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58,
0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033,
0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe,
0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4,
0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5,
0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07,
0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c,
0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1,
0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b,
0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698,
0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d,
0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f,
0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80,
0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a,
0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629,
0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c,
0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e,
0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65,
0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8,
0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2,
0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74,
0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21,
0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a,
0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087,
0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d,
0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce,
0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb,
0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09,
0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf,
0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
};
void cksum::update(ReadonlyBytes data)
{
for (u8 byte : data)
m_state = (m_state << 8) ^ table[((m_state >> 24) ^ byte) & 0xFF];
m_size += data.size();
}
u32 cksum::digest()
{
u32 size = m_size;
u32 state = m_state;
while (size) {
state = (state << 8) ^ table[((state >> 24) ^ size) & 0xFF];
size >>= 8;
}
state = ~state & 0xFFFFFFFF;
return state;
}
}

View file

@ -1,31 +0,0 @@
/*
* Copyright (c) 2024, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Span.h>
#include <AK/Types.h>
#include <LibCrypto/Checksum/ChecksumFunction.h>
namespace Crypto::Checksum {
class cksum : public ChecksumFunction<u32> {
public:
cksum() = default;
cksum(ReadonlyBytes data)
{
update(data);
}
virtual void update(ReadonlyBytes data) override;
virtual u32 digest() override;
private:
u32 m_state { 0 };
off_t m_size { 0 };
};
}

View file

@ -66,6 +66,7 @@ void FrameActor::handle_message(Message const& message)
if (message.type == "detach"sv) {
if (auto tab = m_tab.strong_ref()) {
devtools().delegate().stop_listening_for_dom_properties(tab->description());
devtools().delegate().stop_listening_for_dom_mutations(tab->description());
devtools().delegate().stop_listening_for_console_messages(tab->description());
devtools().delegate().stop_listening_for_style_sheet_sources(tab->description());

View file

@ -4,6 +4,8 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/JsonValue.h>
#include <LibDevTools/Actors/InspectorActor.h>
#include <LibDevTools/Actors/PageStyleActor.h>
@ -14,47 +16,47 @@
namespace DevTools {
static void received_layout(JsonObject& response, JsonObject const& computed_style, JsonObject const& node_box_sizing)
static void received_layout(JsonObject& response, JsonObject const& node_box_sizing)
{
response.set("autoMargins"sv, JsonObject {});
auto pixel_value = [&](auto const& object, auto key) {
return object.get_double_with_precision_loss(key).value_or(0);
auto pixel_value = [&](auto key) {
return node_box_sizing.get_double_with_precision_loss(key).value_or(0);
};
auto set_pixel_value_from = [&](auto const& object, auto object_key, auto message_key) {
response.set(message_key, MUST(String::formatted("{}px", pixel_value(object, object_key))));
auto set_pixel_value = [&](auto key) {
response.set(key, MUST(String::formatted("{}px", pixel_value(key))));
};
auto set_computed_value_from = [&](auto const& object, auto key) {
response.set(key, object.get_string(key).value_or(String {}));
auto set_computed_value = [&](auto key) {
response.set(key, node_box_sizing.get_string(key).value_or(String {}));
};
response.set("width"sv, pixel_value(node_box_sizing, "content_width"sv));
response.set("height"sv, pixel_value(node_box_sizing, "content_height"sv));
// FIXME: This response should also contain "top", "right", "bottom", and "left", but our box model metrics in
// WebContent do not provide this information.
set_pixel_value_from(node_box_sizing, "border_top"sv, "border-top-width"sv);
set_pixel_value_from(node_box_sizing, "border_right"sv, "border-right-width"sv);
set_pixel_value_from(node_box_sizing, "border_bottom"sv, "border-bottom-width"sv);
set_pixel_value_from(node_box_sizing, "border_left"sv, "border-left-width"sv);
set_computed_value("width"sv);
set_computed_value("height"sv);
set_pixel_value_from(node_box_sizing, "margin_top"sv, "margin-top"sv);
set_pixel_value_from(node_box_sizing, "margin_right"sv, "margin-right"sv);
set_pixel_value_from(node_box_sizing, "margin_bottom"sv, "margin-bottom"sv);
set_pixel_value_from(node_box_sizing, "margin_left"sv, "margin-left"sv);
set_pixel_value("border-top-width"sv);
set_pixel_value("border-right-width"sv);
set_pixel_value("border-bottom-width"sv);
set_pixel_value("border-left-width"sv);
set_pixel_value_from(node_box_sizing, "padding_top"sv, "padding-top"sv);
set_pixel_value_from(node_box_sizing, "padding_right"sv, "padding-right"sv);
set_pixel_value_from(node_box_sizing, "padding_bottom"sv, "padding-bottom"sv);
set_pixel_value_from(node_box_sizing, "padding_left"sv, "padding-left"sv);
set_pixel_value("margin-top"sv);
set_pixel_value("margin-right"sv);
set_pixel_value("margin-bottom"sv);
set_pixel_value("margin-left"sv);
set_computed_value_from(computed_style, "box-sizing"sv);
set_computed_value_from(computed_style, "display"sv);
set_computed_value_from(computed_style, "float"sv);
set_computed_value_from(computed_style, "line-height"sv);
set_computed_value_from(computed_style, "position"sv);
set_computed_value_from(computed_style, "z-index"sv);
set_pixel_value("padding-top"sv);
set_pixel_value("padding-right"sv);
set_pixel_value("padding-bottom"sv);
set_pixel_value("padding-left"sv);
set_computed_value("box-sizing"sv);
set_computed_value("display"sv);
set_computed_value("float"sv);
set_computed_value("line-height"sv);
set_computed_value("position"sv);
set_computed_value("z-index"sv);
}
static void received_computed_style(JsonObject& response, JsonObject const& computed_style)
@ -111,9 +113,20 @@ PageStyleActor::PageStyleActor(DevToolsServer& devtools, String name, WeakPtr<In
: Actor(devtools, move(name))
, m_inspector(move(inspector))
{
if (auto tab = InspectorActor::tab_for(m_inspector)) {
devtools.delegate().listen_for_dom_properties(tab->description(),
[weak_self = make_weak_ptr<PageStyleActor>()](WebView::DOMNodeProperties const& properties) {
if (auto self = weak_self.strong_ref())
self->received_dom_node_properties(properties);
});
}
}
PageStyleActor::~PageStyleActor() = default;
PageStyleActor::~PageStyleActor()
{
if (auto tab = InspectorActor::tab_for(m_inspector))
devtools().delegate().stop_listening_for_dom_properties(tab->description());
}
void PageStyleActor::handle_message(Message const& message)
{
@ -134,38 +147,17 @@ void PageStyleActor::handle_message(Message const& message)
}
if (message.type == "getComputed"sv) {
auto node = get_required_parameter<String>(message, "node"sv);
if (!node.has_value())
return;
inspect_dom_node(message, *node, [](auto& response, auto const& properties) {
received_computed_style(response, properties.computed_style);
});
inspect_dom_node(message, WebView::DOMNodeProperties::Type::ComputedStyle);
return;
}
if (message.type == "getLayout"sv) {
auto node = get_required_parameter<String>(message, "node"sv);
if (!node.has_value())
return;
inspect_dom_node(message, *node, [](auto& response, auto const& properties) {
received_layout(response, properties.computed_style, properties.node_box_sizing);
});
inspect_dom_node(message, WebView::DOMNodeProperties::Type::Layout);
return;
}
if (message.type == "getUsedFontFaces"sv) {
auto node = get_required_parameter<String>(message, "node"sv);
if (!node.has_value())
return;
inspect_dom_node(message, *node, [](auto& response, auto const& properties) {
received_fonts(response, properties.fonts);
});
inspect_dom_node(message, WebView::DOMNodeProperties::Type::UsedFonts);
return;
}
@ -192,19 +184,46 @@ JsonValue PageStyleActor::serialize_style() const
return style;
}
template<typename Callback>
void PageStyleActor::inspect_dom_node(Message const& message, StringView node_actor, Callback&& callback)
void PageStyleActor::inspect_dom_node(Message const& message, WebView::DOMNodeProperties::Type property_type)
{
auto dom_node = WalkerActor::dom_node_for(InspectorActor::walker_for(m_inspector), node_actor);
auto node = get_required_parameter<String>(message, "node"sv);
if (!node.has_value())
return;
auto dom_node = WalkerActor::dom_node_for(InspectorActor::walker_for(m_inspector), *node);
if (!dom_node.has_value()) {
send_unknown_actor_error(message, node_actor);
send_unknown_actor_error(message, *node);
return;
}
devtools().delegate().inspect_dom_node(dom_node->tab->description(), dom_node->identifier.id, dom_node->identifier.pseudo_element,
async_handler(message, [callback = forward<Callback>(callback)](auto&, auto properties, auto& response) {
callback(response, properties);
}));
devtools().delegate().inspect_dom_node(dom_node->tab->description(), property_type, dom_node->identifier.id, dom_node->identifier.pseudo_element);
m_pending_inspect_requests.append({ .id = message.id });
}
void PageStyleActor::received_dom_node_properties(WebView::DOMNodeProperties const& properties)
{
if (m_pending_inspect_requests.is_empty())
return;
JsonObject response;
switch (properties.type) {
case WebView::DOMNodeProperties::Type::ComputedStyle:
if (properties.properties.is_object())
received_computed_style(response, properties.properties.as_object());
break;
case WebView::DOMNodeProperties::Type::Layout:
if (properties.properties.is_object())
received_layout(response, properties.properties.as_object());
break;
case WebView::DOMNodeProperties::Type::UsedFonts:
if (properties.properties.is_array())
received_fonts(response, properties.properties.as_array());
break;
}
auto message = m_pending_inspect_requests.take_first();
send_response(message, move(response));
}
}

View file

@ -6,19 +6,12 @@
#pragma once
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/NonnullRefPtr.h>
#include <LibDevTools/Actor.h>
#include <LibWebView/DOMNodeProperties.h>
namespace DevTools {
struct DOMNodeProperties {
JsonObject computed_style;
JsonObject node_box_sizing;
JsonArray fonts;
};
class PageStyleActor final : public Actor {
public:
static constexpr auto base_name = "page-style"sv;
@ -33,10 +26,12 @@ private:
virtual void handle_message(Message const&) override;
template<typename Callback>
void inspect_dom_node(Message const&, StringView node_actor, Callback&&);
void inspect_dom_node(Message const&, WebView::DOMNodeProperties::Type);
void received_dom_node_properties(WebView::DOMNodeProperties const&);
WeakPtr<InspectorActor> m_inspector;
Vector<Message, 1> m_pending_inspect_requests;
};
}

View file

@ -17,6 +17,7 @@
#include <LibWeb/CSS/Selector.h>
#include <LibWeb/CSS/StyleSheetIdentifier.h>
#include <LibWeb/Forward.h>
#include <LibWebView/DOMNodeProperties.h>
#include <LibWebView/Forward.h>
namespace DevTools {
@ -31,8 +32,10 @@ public:
using OnTabInspectionComplete = Function<void(ErrorOr<JsonValue>)>;
virtual void inspect_tab(TabDescription const&, OnTabInspectionComplete) const { }
using OnDOMNodeInspectionComplete = Function<void(ErrorOr<DOMNodeProperties>)>;
virtual void inspect_dom_node(TabDescription const&, Web::UniqueNodeID, Optional<Web::CSS::Selector::PseudoElement::Type>, OnDOMNodeInspectionComplete) const { }
using OnDOMNodePropertiesReceived = Function<void(WebView::DOMNodeProperties)>;
virtual void listen_for_dom_properties(TabDescription const&, OnDOMNodePropertiesReceived) const { }
virtual void stop_listening_for_dom_properties(TabDescription const&) const { }
virtual void inspect_dom_node(TabDescription const&, WebView::DOMNodeProperties::Type, Web::UniqueNodeID, Optional<Web::CSS::Selector::PseudoElement::Type>) const { }
virtual void clear_inspected_dom_node(TabDescription const&) const { }
virtual void highlight_dom_node(TabDescription const&, Web::UniqueNodeID, Optional<Web::CSS::Selector::PseudoElement::Type>) const { }

View file

@ -468,29 +468,31 @@ size_t GIFImageDecoderPlugin::first_animated_frame_index()
ErrorOr<ImageFrameDescriptor> GIFImageDecoderPlugin::frame(size_t index, Optional<IntSize>)
{
if (m_context->error_state >= GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame) {
if (m_context->error_state == GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame) {
return Error::from_string_literal("GIFImageDecoderPlugin: Decoding failed");
}
if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) {
if (auto result = load_gif_frame_descriptors(*m_context); result.is_error()) {
m_context->error_state = GIFLoadingContext::ErrorState::FailedToLoadFrameDescriptors;
return result.release_error();
// If we failed to load frame descriptors but we have some images, we can still try to decode them.
if (m_context->images.is_empty()) {
return result.release_error();
}
}
}
if (m_context->error_state == GIFLoadingContext::ErrorState::NoError) {
if (auto result = decode_frame(*m_context, index); result.is_error()) {
if (m_context->state < GIFLoadingContext::State::FrameComplete) {
m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame;
return result.release_error();
}
if (auto result = decode_frame(*m_context, 0); result.is_error()) {
m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame;
return result.release_error();
}
m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAllFrames;
if (auto result = decode_frame(*m_context, index); result.is_error()) {
if (m_context->state < GIFLoadingContext::State::FrameComplete) {
m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame;
return result.release_error();
}
if (auto result = decode_frame(*m_context, 0); result.is_error()) {
m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame;
return result.release_error();
}
m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAllFrames;
}
ImageFrameDescriptor frame {};

View file

@ -8,6 +8,7 @@
#include <AK/BitStream.h>
#include <AK/Debug.h>
#include <AK/HashMap.h>
#include <AK/HashTable.h>
#include <AK/MemoryStream.h>
#include <AK/QuickSort.h>

View file

@ -11,25 +11,11 @@
namespace IPC {
// FIXME: IPC::Files transferred over the wire are always set O_CLOEXEC during decoding.
// Perhaps we should add an option to IPC::File to allow the receiver to decide whether to
// make it O_CLOEXEC or not. Or an attribute in the .ipc file?
ErrorOr<void> File::clear_close_on_exec()
{
auto fd_flags = TRY(Core::System::fcntl(m_fd, F_GETFD));
fd_flags &= ~FD_CLOEXEC;
TRY(Core::System::fcntl(m_fd, F_SETFD, fd_flags));
return {};
}
template<>
ErrorOr<File> decode(Decoder& decoder)
{
auto file = TRY(decoder.files().try_dequeue());
auto fd = file.fd();
auto fd_flags = TRY(Core::System::fcntl(fd, F_GETFD));
TRY(Core::System::fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC));
TRY(Core::System::set_close_on_exec(file.fd(), true));
return file;
}

View file

@ -63,7 +63,13 @@ public:
return exchange(m_fd, -1);
}
ErrorOr<void> clear_close_on_exec();
// FIXME: IPC::Files transferred over the wire are always set O_CLOEXEC during decoding.
// Perhaps we should add an option to IPC::File to allow the receiver to decide whether to
// make it O_CLOEXEC or not. Or an attribute in the .ipc file?
ErrorOr<void> clear_close_on_exec()
{
return Core::System::set_close_on_exec(m_fd, false);
}
private:
explicit File(int fd)

View file

@ -12,13 +12,6 @@
namespace IPC {
ErrorOr<void> File::clear_close_on_exec()
{
if (!SetHandleInformation(to_handle(m_fd), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
return Error::from_windows_error();
return {};
}
template<>
ErrorOr<File> decode(Decoder& decoder)
{

View file

@ -1616,9 +1616,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> VariableDeclaration::ge
} else if (m_declaration_kind != DeclarationKind::Var) {
(void)TRY(assign_value_to_variable_declarator(generator, declarator, *this, generator.add_constant(js_undefined())));
}
}
for (auto& declarator : m_declarations) {
if (auto const* identifier = declarator->target().get_pointer<NonnullRefPtr<Identifier const>>()) {
if ((*identifier)->is_local()) {
generator.set_local_initialized((*identifier)->local_variable_index());

View file

@ -31,7 +31,9 @@ struct PropertyLookupCache {
struct GlobalVariableCache : public PropertyLookupCache {
u64 environment_serial_number { 0 };
Optional<u32> environment_binding_index;
u32 environment_binding_index { 0 };
bool has_environment_binding_index { false };
bool in_module_environment { false };
};
struct SourceRecord {

View file

@ -33,7 +33,15 @@ Generator::Generator(VM& vm, GC::Ptr<ECMAScriptFunctionObject const> function, M
CodeGenerationErrorOr<void> Generator::emit_function_declaration_instantiation(ECMAScriptFunctionObject const& function)
{
if (function.m_has_parameter_expressions) {
emit<Op::CreateLexicalEnvironment>();
bool has_non_local_parameters = false;
for (auto const& parameter_name : function.m_parameter_names) {
if (parameter_name.value == ECMAScriptFunctionObject::ParameterIsLocal::No) {
has_non_local_parameters = true;
break;
}
}
if (has_non_local_parameters)
emit<Op::CreateLexicalEnvironment>();
}
for (auto const& parameter_name : function.m_parameter_names) {
@ -128,7 +136,19 @@ CodeGenerationErrorOr<void> Generator::emit_function_declaration_instantiation(E
}
}
} else {
emit<Op::CreateVariableEnvironment>(function.m_var_environment_bindings_count);
bool has_non_local_parameters = false;
if (scope_body) {
for (auto const& variable_to_initialize : function.m_var_names_to_initialize_binding) {
auto const& id = variable_to_initialize.identifier;
if (!id.is_local()) {
has_non_local_parameters = true;
break;
}
}
}
if (has_non_local_parameters)
emit<Op::CreateVariableEnvironment>(function.m_var_environment_bindings_count);
if (scope_body) {
for (auto const& variable_to_initialize : function.m_var_names_to_initialize_binding) {
@ -164,8 +184,8 @@ CodeGenerationErrorOr<void> Generator::emit_function_declaration_instantiation(E
}
if (!function.m_strict) {
bool can_elide_declarative_environment = !function.m_contains_direct_call_to_eval && (!scope_body || !scope_body->has_non_local_lexical_declarations());
if (!can_elide_declarative_environment) {
bool can_elide_lexical_environment = !scope_body || !scope_body->has_non_local_lexical_declarations();
if (!can_elide_lexical_environment) {
emit<Op::CreateLexicalEnvironment>(function.m_lex_environment_bindings_count);
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2024, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -26,6 +26,7 @@
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Iterator.h>
#include <LibJS/Runtime/MathObject.h>
#include <LibJS/Runtime/ModuleEnvironment.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/ObjectEnvironment.h>
#include <LibJS/Runtime/Realm.h>
@ -36,6 +37,30 @@
#include <LibJS/Runtime/ValueInlines.h>
#include <LibJS/SourceTextModule.h>
namespace JS {
struct PropertyKeyAndEnumerableFlag {
JS::PropertyKey key;
bool enumerable { false };
};
}
namespace AK {
template<>
struct Traits<JS::PropertyKeyAndEnumerableFlag> : public DefaultTraits<JS::PropertyKeyAndEnumerableFlag> {
static unsigned hash(JS::PropertyKeyAndEnumerableFlag const& entry)
{
return Traits<JS::PropertyKey>::hash(entry.key);
}
static bool equals(JS::PropertyKeyAndEnumerableFlag const& a, JS::PropertyKeyAndEnumerableFlag const& b)
{
return Traits<JS::PropertyKey>::equals(a.key, b.key);
}
};
}
namespace JS::Bytecode {
bool g_dump_bytecode = false;
@ -1124,20 +1149,31 @@ inline ThrowCompletionOr<Value> get_global(Interpreter& interpreter, IdentifierT
// OPTIMIZATION: For global lexical bindings, if the global declarative environment hasn't changed,
// we can use the cached environment binding index.
if (cache.environment_binding_index.has_value())
return declarative_record.get_binding_value_direct(vm, cache.environment_binding_index.value());
if (cache.has_environment_binding_index) {
if (cache.in_module_environment) {
auto module = vm.running_execution_context().script_or_module.get_pointer<GC::Ref<Module>>();
return (*module)->environment()->get_binding_value_direct(vm, cache.environment_binding_index);
}
return declarative_record.get_binding_value_direct(vm, cache.environment_binding_index);
}
}
cache.environment_serial_number = declarative_record.environment_serial_number();
auto& identifier = interpreter.current_executable().get_identifier(identifier_index);
if (vm.running_execution_context().script_or_module.has<GC::Ref<Module>>()) {
if (auto* module = vm.running_execution_context().script_or_module.get_pointer<GC::Ref<Module>>()) {
// NOTE: GetGlobal is used to access variables stored in the module environment and global environment.
// The module environment is checked first since it precedes the global environment in the environment chain.
auto& module_environment = *vm.running_execution_context().script_or_module.get<GC::Ref<Module>>()->environment();
if (TRY(module_environment.has_binding(identifier))) {
// TODO: Cache offset of binding value
auto& module_environment = *(*module)->environment();
Optional<size_t> index;
if (TRY(module_environment.has_binding(identifier, &index))) {
if (index.has_value()) {
cache.environment_binding_index = static_cast<u32>(index.value());
cache.has_environment_binding_index = true;
cache.in_module_environment = true;
return TRY(module_environment.get_binding_value_direct(vm, index.value()));
}
return TRY(module_environment.get_binding_value(vm, identifier, vm.in_strict_mode()));
}
}
@ -1145,6 +1181,8 @@ inline ThrowCompletionOr<Value> get_global(Interpreter& interpreter, IdentifierT
Optional<size_t> offset;
if (TRY(declarative_record.has_binding(identifier, &offset))) {
cache.environment_binding_index = static_cast<u32>(offset.value());
cache.has_environment_binding_index = true;
cache.in_module_environment = false;
return TRY(declarative_record.get_binding_value(vm, identifier, vm.in_strict_mode()));
}
@ -1699,8 +1737,8 @@ inline ThrowCompletionOr<Object*> get_object_property_iterator(VM& vm, Value val
auto object = TRY(value.to_object(vm));
// Note: While the spec doesn't explicitly require these to be ordered, it says that the values should be retrieved via OwnPropertyKeys,
// so we just keep the order consistent anyway.
OrderedHashTable<PropertyKey> properties;
OrderedHashTable<PropertyKey> non_enumerable_properties;
OrderedHashTable<PropertyKeyAndEnumerableFlag> properties;
HashTable<GC::Ref<Object>> seen_objects;
// Collect all keys immediately (invariant no. 5)
for (auto object_to_check = GC::Ptr { object.ptr() }; object_to_check && !seen_objects.contains(*object_to_check); object_to_check = TRY(object_to_check->internal_get_prototype_of())) {
@ -1708,50 +1746,57 @@ inline ThrowCompletionOr<Object*> get_object_property_iterator(VM& vm, Value val
for (auto& key : TRY(object_to_check->internal_own_property_keys())) {
if (key.is_symbol())
continue;
auto property_key = TRY(PropertyKey::from_value(vm, key));
// If there is a non-enumerable property higher up the prototype chain with the same key,
// we mustn't include this property even if it's enumerable (invariant no. 5 and 6)
if (non_enumerable_properties.contains(property_key))
continue;
if (properties.contains(property_key))
// NOTE: If there is a non-enumerable property higher up the prototype chain with the same key,
// we mustn't include this property even if it's enumerable (invariant no. 5 and 6)
// This is achieved with the PropertyKeyAndEnumerableFlag struct, which doesn't consider
// the enumerable flag when comparing keys.
PropertyKeyAndEnumerableFlag new_entry {
.key = TRY(PropertyKey::from_value(vm, key)),
.enumerable = false,
};
if (properties.contains(new_entry))
continue;
auto descriptor = TRY(object_to_check->internal_get_own_property(property_key));
if (!*descriptor->enumerable)
non_enumerable_properties.set(move(property_key));
else
properties.set(move(property_key));
auto descriptor = TRY(object_to_check->internal_get_own_property(new_entry.key));
if (!descriptor.has_value())
continue;
new_entry.enumerable = *descriptor->enumerable;
properties.set(move(new_entry), AK::HashSetExistingEntryBehavior::Keep);
}
}
auto& realm = *vm.current_realm();
auto callback = NativeFunction::create(
*vm.current_realm(), [items = move(properties)](VM& vm) mutable -> ThrowCompletionOr<Value> {
auto& realm = *vm.current_realm();
auto iterated_object_value = vm.this_value();
if (!iterated_object_value.is_object())
return vm.throw_completion<InternalError>("Invalid state for GetObjectPropertyIterator.next"sv);
auto& iterated_object = iterated_object_value.as_object();
auto result_object = Object::create(realm, nullptr);
properties.remove_all_matching([&](auto& entry) { return !entry.enumerable; });
auto& realm = *vm.current_realm();
auto result_object = Object::create_with_premade_shape(realm.intrinsics().iterator_result_object_shape());
auto value_offset = realm.intrinsics().iterator_result_object_value_offset();
auto done_offset = realm.intrinsics().iterator_result_object_done_offset();
auto callback = NativeFunction::create(
*vm.current_realm(), [items = move(properties), result_object, value_offset, done_offset](VM& vm) mutable -> ThrowCompletionOr<Value> {
auto& iterated_object = vm.this_value().as_object();
while (true) {
if (items.is_empty()) {
result_object->define_direct_property(vm.names.done, JS::Value(true), default_attributes);
result_object->put_direct(done_offset, JS::Value(true));
return result_object;
}
auto key = items.take_first();
auto key = move(items.take_first().key);
// If the property is deleted, don't include it (invariant no. 2)
if (!TRY(iterated_object.has_property(key)))
continue;
result_object->define_direct_property(vm.names.done, JS::Value(false), default_attributes);
result_object->put_direct(done_offset, JS::Value(false));
if (key.is_number())
result_object->define_direct_property(vm.names.value, PrimitiveString::create(vm, String::number(key.as_number())), default_attributes);
result_object->put_direct(value_offset, PrimitiveString::create(vm, String::number(key.as_number())));
else if (key.is_string())
result_object->define_direct_property(vm.names.value, PrimitiveString::create(vm, key.as_string()), default_attributes);
result_object->put_direct(value_offset, PrimitiveString::create(vm, key.as_string()));
else
VERIFY_NOT_REACHED(); // We should not have non-string/number keys.

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2021, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2020-2025, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2020-2022, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
@ -189,6 +189,7 @@ class Intrinsics;
class IteratorRecord;
class MemberExpression;
class MetaProperty;
class ModuleEnvironment;
class Module;
struct ModuleRequest;
class NativeFunction;

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
* Copyright (c) 2023, networkException <networkexception@serenityos.org>
*
@ -8,6 +8,7 @@
#include <LibJS/CyclicModule.h>
#include <LibJS/Module.h>
#include <LibJS/Runtime/ModuleEnvironment.h>
#include <LibJS/Runtime/ModuleNamespaceObject.h>
#include <LibJS/Runtime/ModuleRequest.h>
#include <LibJS/Runtime/Promise.h>

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
@ -100,7 +100,7 @@ public:
StringView filename() const { return m_filename; }
Environment* environment() { return m_environment; }
GC::Ptr<ModuleEnvironment> environment() { return m_environment; }
Script::HostDefined* host_defined() const { return m_host_defined; }
@ -122,7 +122,7 @@ protected:
virtual void visit_edges(Cell::Visitor&) override;
void set_environment(Environment* environment)
void set_environment(GC::Ref<ModuleEnvironment> environment)
{
m_environment = environment;
}
@ -136,7 +136,7 @@ private:
// stores modules with a RefPtr we cannot just store the VM as that leads to
// cycles.
GC::Ptr<Realm> m_realm; // [[Realm]]
GC::Ptr<Environment> m_environment; // [[Environment]]
GC::Ptr<ModuleEnvironment> m_environment; // [[Environment]]
GC::Ptr<Object> m_namespace; // [[Namespace]]
Script::HostDefined* m_host_defined { nullptr }; // [[HostDefined]]

View file

@ -743,19 +743,19 @@ ErrorOr<void> print_intl_plural_rules(JS::PrintContext& print_context, JS::Intl:
ErrorOr<void> print_intl_collator(JS::PrintContext& print_context, JS::Intl::Collator const& collator, HashTable<JS::Object*>& seen_objects)
{
TRY(print_type(print_context, "Intl.Collator"sv));
out("\n locale: ");
TRY(js_out(print_context, "\n locale: "));
TRY(print_value(print_context, JS::PrimitiveString::create(collator.vm(), collator.locale()), seen_objects));
out("\n usage: ");
TRY(js_out(print_context, "\n usage: "));
TRY(print_value(print_context, JS::PrimitiveString::create(collator.vm(), collator.usage_string()), seen_objects));
out("\n sensitivity: ");
TRY(js_out(print_context, "\n sensitivity: "));
TRY(print_value(print_context, JS::PrimitiveString::create(collator.vm(), collator.sensitivity_string()), seen_objects));
out("\n caseFirst: ");
TRY(js_out(print_context, "\n caseFirst: "));
TRY(print_value(print_context, JS::PrimitiveString::create(collator.vm(), collator.case_first_string()), seen_objects));
out("\n collation: ");
TRY(js_out(print_context, "\n collation: "));
TRY(print_value(print_context, JS::PrimitiveString::create(collator.vm(), collator.collation()), seen_objects));
out("\n ignorePunctuation: ");
TRY(js_out(print_context, "\n ignorePunctuation: "));
TRY(print_value(print_context, JS::Value(collator.ignore_punctuation()), seen_objects));
out("\n numeric: ");
TRY(js_out(print_context, "\n numeric: "));
TRY(print_value(print_context, JS::Value(collator.numeric()), seen_objects));
return {};
}
@ -763,9 +763,9 @@ ErrorOr<void> print_intl_collator(JS::PrintContext& print_context, JS::Intl::Col
ErrorOr<void> print_intl_segmenter(JS::PrintContext& print_context, JS::Intl::Segmenter const& segmenter, HashTable<JS::Object*>& seen_objects)
{
TRY(print_type(print_context, "Intl.Segmenter"sv));
out("\n locale: ");
TRY(js_out(print_context, "\n locale: "));
TRY(print_value(print_context, JS::PrimitiveString::create(segmenter.vm(), segmenter.locale()), seen_objects));
out("\n granularity: ");
TRY(js_out(print_context, "\n granularity: "));
TRY(print_value(print_context, JS::PrimitiveString::create(segmenter.vm(), segmenter.segmenter_granularity_string()), seen_objects));
return {};
}
@ -775,62 +775,47 @@ ErrorOr<void> print_intl_segments(JS::PrintContext& print_context, JS::Intl::Seg
auto segments_string = JS::Utf16String::create(segments.segments_string());
TRY(print_type(print_context, "Segments"sv));
out("\n string: ");
TRY(js_out(print_context, "\n string: "));
TRY(print_value(print_context, JS::PrimitiveString::create(segments.vm(), move(segments_string)), seen_objects));
return {};
}
ErrorOr<void> print_intl_duration_format(JS::PrintContext& print_context, JS::Intl::DurationFormat const& duration_format, HashTable<JS::Object*>& seen_objects)
{
auto print_style_and_display = [&](StringView style_name, StringView display_name, JS::Intl::DurationFormat::DurationUnitOptions options) -> ErrorOr<void> {
auto style = JS::Intl::DurationFormat::value_style_to_string(options.style);
auto display = JS::Intl::DurationFormat::display_to_string(options.display);
TRY(js_out(print_context, "\n {}: ", style_name));
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), style), seen_objects));
TRY(js_out(print_context, "\n {}: ", display_name));
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), display), seen_objects));
return {};
};
TRY(print_type(print_context, "Intl.DurationFormat"sv));
out("\n locale: ");
TRY(js_out(print_context, "\n locale: "));
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.locale()), seen_objects));
out("\n numberingSystem: ");
TRY(js_out(print_context, "\n numberingSystem: "));
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.numbering_system()), seen_objects));
out("\n style: ");
TRY(js_out(print_context, "\n style: "));
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.style_string()), seen_objects));
out("\n years: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.years_style_string()), seen_objects));
out("\n yearsDisplay: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.years_display_string()), seen_objects));
out("\n months: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.months_style_string()), seen_objects));
out("\n monthsDisplay: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.months_display_string()), seen_objects));
out("\n weeks: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.weeks_style_string()), seen_objects));
out("\n weeksDisplay: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.weeks_display_string()), seen_objects));
out("\n days: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.days_style_string()), seen_objects));
out("\n daysDisplay: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.days_display_string()), seen_objects));
out("\n hours: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.hours_style_string()), seen_objects));
out("\n hoursDisplay: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.hours_display_string()), seen_objects));
out("\n minutes: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.minutes_style_string()), seen_objects));
out("\n minutesDisplay: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.minutes_display_string()), seen_objects));
out("\n seconds: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.seconds_style_string()), seen_objects));
out("\n secondsDisplay: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.seconds_display_string()), seen_objects));
out("\n milliseconds: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.milliseconds_style_string()), seen_objects));
out("\n millisecondsDisplay: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.milliseconds_display_string()), seen_objects));
out("\n microseconds: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.microseconds_style_string()), seen_objects));
out("\n microsecondsDisplay: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.microseconds_display_string()), seen_objects));
out("\n nanoseconds: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.nanoseconds_style_string()), seen_objects));
out("\n nanosecondsDisplay: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.nanoseconds_display_string()), seen_objects));
TRY(print_style_and_display("years"sv, "yearsDisplay"sv, duration_format.years_options()));
TRY(print_style_and_display("months"sv, "monthsDisplay"sv, duration_format.months_options()));
TRY(print_style_and_display("weeks"sv, "weeksDisplay"sv, duration_format.weeks_options()));
TRY(print_style_and_display("days"sv, "daysDisplay"sv, duration_format.days_options()));
TRY(print_style_and_display("hours"sv, "hoursDisplay"sv, duration_format.hours_options()));
TRY(print_style_and_display("minutes"sv, "minutesDisplay"sv, duration_format.minutes_options()));
TRY(print_style_and_display("seconds"sv, "secondsDisplay"sv, duration_format.seconds_options()));
TRY(print_style_and_display("milliseconds"sv, "millisecondsDisplay"sv, duration_format.milliseconds_options()));
TRY(print_style_and_display("microseconds"sv, "microsecondsDisplay"sv, duration_format.microseconds_options()));
TRY(print_style_and_display("nanoseconds"sv, "nanosecondsDisplay"sv, duration_format.nanoseconds_options()));
if (duration_format.has_fractional_digits()) {
out("\n fractionalDigits: ");
TRY(js_out(print_context, "\n fractionalDigits: "));
TRY(print_value(print_context, JS::Value(duration_format.fractional_digits()), seen_objects));
}
return {};

View file

@ -18,6 +18,7 @@ namespace JS {
GC_DEFINE_ALLOCATOR(BigIntConstructor);
static Crypto::SignedBigInteger const BIGINT_ONE { 1 };
static Crypto::SignedBigInteger const BIGINT_ZERO { 0 };
BigIntConstructor::BigIntConstructor(Realm& realm)
: NativeFunction(realm.vm().names.BigInt.as_string(), realm.intrinsics().function_prototype())
@ -72,20 +73,25 @@ JS_DEFINE_NATIVE_FUNCTION(BigIntConstructor::as_int_n)
// 2. Set bigint to ? ToBigInt(bigint).
auto bigint = TRY(vm.argument(1).to_bigint(vm));
// OPTIMIZATION: mod = bigint (mod 2^0) = 0 < 2^(0-1) = 0.5
if (bits == 0)
return BigInt::create(vm, BIGINT_ZERO);
// 3. Let mod be (bigint) modulo 2^bits.
// FIXME: For large values of `bits`, this can likely be improved with a SignedBigInteger API to
// drop the most significant bits.
auto bits_shift_left = TRY_OR_THROW_OOM(vm, BIGINT_ONE.try_shift_left(bits));
auto mod = modulo(bigint->big_integer(), bits_shift_left);
auto const mod = TRY_OR_THROW_OOM(vm, bigint->big_integer().mod_power_of_two(bits));
// 4. If mod ≥ 2^(bits-1), return (mod - 2^bits); otherwise, return (mod).
// NOTE: Some of the below conditionals are non-standard, but are to protect SignedBigInteger from
// allocating an absurd amount of memory if `bits - 1` overflows to NumericLimits<size_t>::max.
if ((bits == 0) && (mod >= BIGINT_ONE))
return BigInt::create(vm, mod.minus(bits_shift_left));
if ((bits > 0) && (mod >= BIGINT_ONE.shift_left(bits - 1)))
return BigInt::create(vm, mod.minus(bits_shift_left));
// OPTIMIZATION: mod < 2^(bits-1)
if (mod.is_zero())
return BigInt::create(vm, BIGINT_ZERO);
// 4. If mod ≥ 2^(bits-1), return (mod - 2^bits); ...
if (auto top_bit_index = mod.unsigned_value().one_based_index_of_highest_set_bit(); top_bit_index >= bits) {
// twos complement decode
auto decoded = TRY_OR_THROW_OOM(vm, mod.unsigned_value().try_bitwise_not_fill_to_one_based_index(bits)).plus(1);
return BigInt::create(vm, Crypto::SignedBigInteger { std::move(decoded), true });
}
// ... otherwise, return (mod).
return BigInt::create(vm, mod);
}
@ -98,10 +104,10 @@ JS_DEFINE_NATIVE_FUNCTION(BigIntConstructor::as_uint_n)
// 2. Set bigint to ? ToBigInt(bigint).
auto bigint = TRY(vm.argument(1).to_bigint(vm));
// 3. Return the BigInt value that represents (bigint) modulo 2bits.
// FIXME: For large values of `bits`, this can likely be improved with a SignedBigInteger API to
// drop the most significant bits.
return BigInt::create(vm, modulo(bigint->big_integer(), TRY_OR_THROW_OOM(vm, BIGINT_ONE.try_shift_left(bits))));
// 3. Return the BigInt value that represents (bigint) modulo 2^bits.
auto const mod = TRY_OR_THROW_OOM(vm, bigint->big_integer().mod_power_of_two(bits));
return BigInt::create(vm, mod);
}
}

View file

@ -439,6 +439,17 @@ ThrowCompletionOr<GC::Ref<Object>> ECMAScriptFunctionObject::internal_construct(
{
auto& vm = this->vm();
auto callee_context = ExecutionContext::create();
// Non-standard
callee_context->arguments.ensure_capacity(max(arguments_list.size(), m_formal_parameters.size()));
callee_context->arguments.append(arguments_list.data(), arguments_list.size());
callee_context->passed_argument_count = arguments_list.size();
if (arguments_list.size() < m_formal_parameters.size()) {
for (size_t i = arguments_list.size(); i < m_formal_parameters.size(); ++i)
callee_context->arguments.append(js_undefined());
}
// 1. Let callerContext be the running execution context.
// NOTE: No-op, kept by the VM in its execution context stack.
@ -453,17 +464,6 @@ ThrowCompletionOr<GC::Ref<Object>> ECMAScriptFunctionObject::internal_construct(
this_argument = TRY(ordinary_create_from_constructor<Object>(vm, new_target, &Intrinsics::object_prototype, ConstructWithPrototypeTag::Tag));
}
auto callee_context = ExecutionContext::create();
// Non-standard
callee_context->arguments.ensure_capacity(max(arguments_list.size(), m_formal_parameters.size()));
callee_context->arguments.append(arguments_list.data(), arguments_list.size());
callee_context->passed_argument_count = arguments_list.size();
if (arguments_list.size() < m_formal_parameters.size()) {
for (size_t i = arguments_list.size(); i < m_formal_parameters.size(); ++i)
callee_context->arguments.append(js_undefined());
}
// 4. Let calleeContext be PrepareForOrdinaryCall(F, newTarget).
// NOTE: We throw if the end of the native stack is reached, so unlike in the spec this _does_ need an exception check.
TRY(prepare_for_ordinary_call(*callee_context, &new_target));

View file

@ -77,7 +77,7 @@
M(InvalidEnumerationValue, "Invalid value '{}' for enumeration type '{}'") \
M(InvalidFractionDigits, "Fraction Digits must be an integer no less than 0, and no greater than 100") \
M(InvalidHint, "Invalid hint: \"{}\"") \
M(InvalidIndex, "Index must be a positive integer") \
M(InvalidIndex, "Index must be a positive integer no greater than 2^53-1") \
M(InvalidLeftHandAssignment, "Invalid left-hand side in assignment") \
M(InvalidLength, "Invalid {} length") \
M(InvalidNormalizationForm, "The normalization form must be one of NFC, NFD, NFKC, NFKD. Got '{}'") \

View file

@ -500,6 +500,8 @@ ResolvedLocale resolve_locale(ReadonlySpan<String> requested_locales, LocaleOpti
// 12. Let supportedKeywords be a new empty List.
Vector<Unicode::Keyword> supported_keywords;
Vector<Unicode::Keyword> icu_keywords;
// 13. For each element key of relevantExtensionKeys, do
for (auto const& key : relevant_extension_keys) {
// a. Let keyLocaleData be foundLocaleData.[[<key>]].
@ -574,10 +576,23 @@ ResolvedLocale resolve_locale(ReadonlySpan<String> requested_locales, LocaleOpti
if (supported_keyword.has_value())
supported_keywords.append(supported_keyword.release_value());
if (auto* value_string = value.get_pointer<String>())
icu_keywords.empend(MUST(String::from_utf8(key)), *value_string);
// m. Set result.[[<key>]] to value.
find_key_in_value(result, key) = move(value);
}
// AD-HOC: For ICU, we need to form a locale with all relevant extension keys present.
if (icu_keywords.is_empty()) {
result.icu_locale = found_locale;
} else {
auto locale_id = Unicode::parse_unicode_locale_id(found_locale);
VERIFY(locale_id.has_value());
result.icu_locale = insert_unicode_extension_and_canonicalize(locale_id.release_value(), {}, move(icu_keywords));
}
// 14. If supportedKeywords is not empty, then
if (!supported_keywords.is_empty()) {
auto locale_id = Unicode::parse_unicode_locale_id(found_locale);

View file

@ -38,6 +38,7 @@ struct MatchedLocale {
struct ResolvedLocale {
String locale;
String icu_locale;
LocaleKey ca; // [[Calendar]]
LocaleKey co; // [[Collation]]
LocaleKey hc; // [[HourCycle]]

View file

@ -47,32 +47,32 @@ static Optional<Unicode::DateTimeFormat const&> get_or_create_formatter(StringVi
Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_date_formatter()
{
return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_date_formatter, m_temporal_plain_date_format);
return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_date_formatter, m_temporal_plain_date_format);
}
Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_year_month_formatter()
{
return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_year_month_formatter, m_temporal_plain_year_month_format);
return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_year_month_formatter, m_temporal_plain_year_month_format);
}
Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_month_day_formatter()
{
return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_month_day_formatter, m_temporal_plain_month_day_format);
return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_month_day_formatter, m_temporal_plain_month_day_format);
}
Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_time_formatter()
{
return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_time_formatter, m_temporal_plain_time_format);
return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_time_formatter, m_temporal_plain_time_format);
}
Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_date_time_formatter()
{
return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_date_time_formatter, m_temporal_plain_date_time_format);
return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_date_time_formatter, m_temporal_plain_date_time_format);
}
Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_instant_formatter()
{
return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_instant_formatter, m_temporal_instant_format);
return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_instant_formatter, m_temporal_instant_format);
}
// 11.5.5 FormatDateTimePattern ( dateTimeFormat, patternParts, x, rangeFormatOptions ), https://tc39.es/ecma402/#sec-formatdatetimepattern

View file

@ -38,6 +38,9 @@ public:
String const& locale() const { return m_locale; }
void set_locale(String locale) { m_locale = move(locale); }
String const& icu_locale() const { return m_icu_locale; }
void set_icu_locale(String icu_locale) { m_icu_locale = move(icu_locale); }
String const& calendar() const { return m_calendar; }
void set_calendar(String calendar) { m_calendar = move(calendar); }
@ -107,6 +110,7 @@ private:
GC::Ptr<NativeFunction> m_bound_format; // [[BoundFormat]]
// Non-standard. Stores the ICU date-time formatters for the Intl object's formatting options.
String m_icu_locale;
OwnPtr<Unicode::DateTimeFormat> m_formatter;
OwnPtr<Unicode::DateTimeFormat> m_temporal_plain_date_formatter;
OwnPtr<Unicode::DateTimeFormat> m_temporal_plain_year_month_formatter;

View file

@ -149,6 +149,7 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
// 18. Set dateTimeFormat.[[Locale]] to r.[[Locale]].
date_time_format->set_locale(move(result.locale));
date_time_format->set_icu_locale(move(result.icu_locale));
// 19. Let resolvedCalendar be r.[[ca]].
// 20. Set dateTimeFormat.[[Calendar]] to resolvedCalendar.
@ -355,7 +356,7 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
// d. Let styles be resolvedLocaleData.[[styles]].[[<resolvedCalendar>]].
// e. Let bestFormat be DateTimeStyleFormat(dateStyle, timeStyle, styles).
formatter = Unicode::DateTimeFormat::create_for_date_and_time_style(
date_time_format->locale(),
date_time_format->icu_locale(),
time_zone,
format_options.hour_cycle,
format_options.hour12,
@ -443,7 +444,7 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
}
formatter = Unicode::DateTimeFormat::create_for_pattern_options(
date_time_format->locale(),
date_time_format->icu_locale(),
time_zone,
best_format);
}

View file

@ -208,7 +208,7 @@ static ThrowCompletionOr<void> validate_duration_unit_style(VM& vm, PropertyKey
}
// 13.5.6 GetDurationUnitOptions ( unit, options, baseStyle, stylesList, digitalBase, prevStyle, twoDigitHours ), https://tc39.es/ecma402/#sec-getdurationunitoptions
ThrowCompletionOr<DurationUnitOptions> get_duration_unit_options(VM& vm, DurationFormat::Unit unit, Object const& options, DurationFormat::Style base_style, ReadonlySpan<StringView> styles_list, DurationFormat::ValueStyle digital_base, Optional<DurationFormat::ValueStyle> previous_style, bool two_digit_hours)
ThrowCompletionOr<DurationFormat::DurationUnitOptions> get_duration_unit_options(VM& vm, DurationFormat::Unit unit, Object const& options, DurationFormat::Style base_style, ReadonlySpan<StringView> styles_list, DurationFormat::ValueStyle digital_base, Optional<DurationFormat::ValueStyle> previous_style, bool two_digit_hours)
{
auto const& unit_property_key = unit_to_property_key(vm, unit);
@ -251,8 +251,8 @@ ThrowCompletionOr<DurationUnitOptions> get_duration_unit_options(VM& vm, Duratio
style = DurationFormat::value_style_from_string(style_value.as_string().utf8_string_view());
}
// 4. If style is "numeric" and unit is one of "milliseconds", "microseconds", or "nanoseconds", then
if (style == DurationFormat::ValueStyle::Numeric && first_is_one_of(unit, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds, DurationFormat::Unit::Nanoseconds)) {
// 4. If style is "numeric" and IsFractionalSecondUnitName(unit) is true, then
if (style == DurationFormat::ValueStyle::Numeric && is_fractional_second_unit_name(unit)) {
// a. Set style to "fractional".
style = DurationFormat::ValueStyle::Fractional;
@ -280,8 +280,8 @@ ThrowCompletionOr<DurationUnitOptions> get_duration_unit_options(VM& vm, Duratio
style = DurationFormat::ValueStyle::TwoDigit;
}
// 10. Return the Record { [[Style]]: style, [[Display]]: display }.
return DurationUnitOptions { .style = style, .display = display };
// 10. Return the Duration Unit Options Record { [[Style]]: style, [[Display]]: display }.
return DurationFormat::DurationUnitOptions { .style = style, .display = display };
}
// 13.5.7 ComputeFractionalDigits ( durationFormat, duration ), https://tc39.es/ecma402/#sec-computefractionaldigits
@ -294,25 +294,25 @@ Crypto::BigFraction compute_fractional_digits(DurationFormat const& duration_for
// 2. Let exponent be 3.
double exponent = 3;
// 3. For each row of Table 23, except the header row, in table order, do
// 3. For each row of Table 24, except the header row, in table order, do
for (auto const& duration_instances_component : duration_instances_components) {
// a. Let style be the value of durationFormat's internal slot whose name is the Style Slot value of the current row.
auto style = (duration_format.*duration_instances_component.get_style_slot)();
// b. If style is "fractional", then
if (style == DurationFormat::ValueStyle::Fractional) {
// i. Assert: The Unit value of the current row is "milliseconds", "microseconds", or "nanoseconds".
VERIFY(first_is_one_of(duration_instances_component.unit, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds, DurationFormat::Unit::Nanoseconds));
// a. Let unitOptions be the value of durationFormat's internal slot whose name is the Internal Slot value of the current row.
auto unit_options = (duration_format.*duration_instances_component.get_internal_slot)();
// ii. Let value be the value of duration's field whose name is the Value Field value of the current row.
// iii. Set value to value / 10**exponent.
Crypto::BigFraction value {
Crypto::SignedBigInteger { (duration.*duration_instances_component.value_slot)() },
Crypto::UnsignedBigInteger { pow(10, exponent) }
};
// b. If unitOptions.[[Style]] is "fractional", then
if (unit_options.style == DurationFormat::ValueStyle::Fractional) {
// i. Let unit be the Unit value of the current row.
auto unit = duration_instances_component.unit;
// iv. Set result to result + value.
result = result + value;
// ii. Assert: IsFractionalSecondUnitName(unit) is true.
VERIFY(is_fractional_second_unit_name(unit));
// iii. Let value be the value of duration's field whose name is the Value Field value of the current row.
auto value = (duration.*duration_instances_component.value_slot)();
// iv. Set result to result + (value / 10**exponent).
result = result + Crypto::BigFraction { Crypto::SignedBigInteger { value }, Crypto::UnsignedBigInteger { pow(10, exponent) } };
// v. Set exponent to exponent + 3.
exponent += 3;
@ -326,22 +326,19 @@ Crypto::BigFraction compute_fractional_digits(DurationFormat const& duration_for
// 13.5.8 NextUnitFractional ( durationFormat, unit ), https://tc39.es/ecma402/#sec-nextunitfractional
bool next_unit_fractional(DurationFormat const& duration_format, DurationFormat::Unit unit)
{
// 1. Assert: unit is "seconds", "milliseconds", or "microseconds".
VERIFY(first_is_one_of(unit, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds, DurationFormat::Unit::Nanoseconds));
// 2. If unit is "seconds" and durationFormat.[[MillisecondsStyle]] is "fractional", return true.
if (unit == DurationFormat::Unit::Seconds && duration_format.milliseconds_style() == DurationFormat::ValueStyle::Fractional)
// 1. If unit is "seconds" and durationFormat.[[MillisecondsOptions]].[[Style]] is "fractional", return true.
if (unit == DurationFormat::Unit::Seconds && duration_format.milliseconds_options().style == DurationFormat::ValueStyle::Fractional)
return true;
// 3. Else if unit is "milliseconds" and durationFormat.[[MicrosecondsStyle]] is "fractional", return true.
if (unit == DurationFormat::Unit::Milliseconds && duration_format.microseconds_style() == DurationFormat::ValueStyle::Fractional)
// 2. If unit is "milliseconds" and durationFormat.[[MicrosecondsOptions]].[[Style]] is "fractional", return true.
if (unit == DurationFormat::Unit::Milliseconds && duration_format.microseconds_options().style == DurationFormat::ValueStyle::Fractional)
return true;
// 4. Else if unit is "microseconds" and durationFormat.[[NanosecondsStyle]] is "fractional", return true.
if (unit == DurationFormat::Unit::Microseconds && duration_format.nanoseconds_style() == DurationFormat::ValueStyle::Fractional)
// 3. If unit is "microseconds" and durationFormat.[[NanosecondsOptions]].[[Style]] is "fractional", return true.
if (unit == DurationFormat::Unit::Microseconds && duration_format.nanoseconds_options().style == DurationFormat::ValueStyle::Fractional)
return true;
// 5. Return false.
// 4. Return false.
return false;
}
@ -353,8 +350,8 @@ Vector<DurationFormatPart> format_numeric_hours(VM& vm, DurationFormat const& du
// 1. Let result be a new empty List.
Vector<DurationFormatPart> result;
// 2. Let hoursStyle be durationFormat.[[HoursStyle]].
auto hours_style = duration_format.hours_style();
// 2. Let hoursStyle be durationFormat.[[HoursOptions]].[[Style]].
auto hours_style = duration_format.hours_options().style;
// 3. Assert: hoursStyle is "numeric" or hoursStyle is "2-digit".
VERIFY(hours_style == DurationFormat::ValueStyle::Numeric || hours_style == DurationFormat::ValueStyle::TwoDigit);
@ -418,8 +415,8 @@ Vector<DurationFormatPart> format_numeric_minutes(VM& vm, DurationFormat const&
result.append({ .type = "literal"sv, .value = move(separator), .unit = {} });
}
// 3. Let minutesStyle be durationFormat.[[MinutesStyle]].
auto minutes_style = duration_format.minutes_style();
// 3. Let minutesStyle be durationFormat.[[MinutesOptions]].[[Style]].
auto minutes_style = duration_format.minutes_options().style;
// 4. Assert: minutesStyle is "numeric" or minutesStyle is "2-digit".
VERIFY(minutes_style == DurationFormat::ValueStyle::Numeric || minutes_style == DurationFormat::ValueStyle::TwoDigit);
@ -483,8 +480,8 @@ Vector<DurationFormatPart> format_numeric_seconds(VM& vm, DurationFormat const&
result.append({ .type = "literal"sv, .value = move(separator), .unit = {} });
}
// 3. Let secondsStyle be durationFormat.[[SecondsStyle]].
auto seconds_style = duration_format.seconds_style();
// 3. Let secondsStyle be durationFormat.[[SecondsOptions]].[[Style]].
auto seconds_style = duration_format.seconds_options().style;
// 4. Assert: secondsStyle is "numeric" or secondsStyle is "2-digit".
VERIFY(seconds_style == DurationFormat::ValueStyle::Numeric || seconds_style == DurationFormat::ValueStyle::TwoDigit);
@ -567,14 +564,14 @@ Vector<DurationFormatPart> format_numeric_units(VM& vm, DurationFormat const& du
// 3. Let hoursValue be duration.[[Hours]].
auto hours_value = duration.hours();
// 4. Let hoursDisplay be durationFormat.[[HoursDisplay]].
auto hours_display = duration_format.hours_display();
// 4. Let hoursDisplay be durationFormat.[[HoursOptions]].[[Display]].
auto hours_display = duration_format.hours_options().display;
// 5. Let minutesValue be duration.[[Minutes]].
// 5. Let minutesDisplay be durationFormat.[[MinutesOptions]].[[Display]].
auto minutes_value = duration.minutes();
// 6. Let minutesDisplay be durationFormat.[[MinutesDisplay]].
auto minutes_display = duration_format.minutes_display();
auto minutes_display = duration_format.minutes_options().display;
// 7. Let secondsValue be duration.[[Seconds]].
Crypto::BigFraction seconds_value { duration.seconds() };
@ -585,8 +582,8 @@ Vector<DurationFormatPart> format_numeric_units(VM& vm, DurationFormat const& du
seconds_value = seconds_value + compute_fractional_digits(duration_format, duration);
}
// 9. Let secondsDisplay be durationFormat.[[SecondsDisplay]].
auto seconds_display = duration_format.seconds_display();
// 9. Let secondsDisplay be durationFormat.[[SecondsOptions]].[[Display]].
auto seconds_display = duration_format.seconds_options().display;
// 10. Let hoursFormatted be false.
auto hours_formatted = false;
@ -682,7 +679,15 @@ Vector<DurationFormatPart> format_numeric_units(VM& vm, DurationFormat const& du
return numeric_parts_list;
}
// 13.5.13 ListFormatParts ( durationFormat, partitionedPartsList ), https://tc39.es/ecma402/#sec-listformatparts
// 13.5.13 IsFractionalSecondUnitName ( unit ), https://tc39.es/ecma402/#sec-isfractionalsecondunitname
bool is_fractional_second_unit_name(DurationFormat::Unit unit)
{
// 1. If unit is one of "milliseconds", "microseconds", or "nanoseconds", return true.
// 2. Return false.
return first_is_one_of(unit, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds, DurationFormat::Unit::Nanoseconds);
}
// 13.5.14 ListFormatParts ( durationFormat, partitionedPartsList ), https://tc39.es/ecma402/#sec-listformatparts
Vector<DurationFormatPart> list_format_parts(VM& vm, DurationFormat const& duration_format, Vector<Vector<DurationFormatPart>>& partitioned_parts_list)
{
auto& realm = *vm.current_realm();
@ -773,7 +778,7 @@ Vector<DurationFormatPart> list_format_parts(VM& vm, DurationFormat const& durat
return flattened_parts_list;
}
// 13.5.14 PartitionDurationFormatPattern ( durationFormat, duration ), https://tc39.es/ecma402/#sec-partitiondurationformatpattern
// 13.5.15 PartitionDurationFormatPattern ( durationFormat, duration ), https://tc39.es/ecma402/#sec-partitiondurationformatpattern
// 15.9.8 PartitionDurationFormatPattern ( durationFormat, duration ), https://tc39.es/proposal-temporal/#sec-formatnumericunits
Vector<DurationFormatPart> partition_duration_format_pattern(VM& vm, DurationFormat const& duration_format, Temporal::Duration const& duration)
{
@ -788,23 +793,25 @@ Vector<DurationFormatPart> partition_duration_format_pattern(VM& vm, DurationFor
// 3. Let numericUnitFound be false.
auto numeric_unit_found = false;
// 4. While numericUnitFound is false, repeat for each row in Table 23 in table order, except the header row:
// 4. While numericUnitFound is false, repeat for each row in Table 24 in table order, except the header row:
for (size_t i = 0; !numeric_unit_found && i < duration_instances_components.size(); ++i) {
auto const& duration_instances_component = duration_instances_components[i];
// a. Let value be the value of duration's field whose name is the Value Field value of the current row.
Crypto::BigFraction value { (duration.*duration_instances_component.value_slot)() };
// b. Let style be the value of durationFormat's internal slot whose name is the Style Slot value of the current row.
auto style = (duration_format.*duration_instances_component.get_style_slot)();
// b. Let unitOptions be the value of durationFormat's internal slot whose name is the Internal Slot value of the current row.
// c. Let style be unitOptions.[[Style]].
// d. Let display be unitOptions.[[Display]].
auto [style, display] = (duration_format.*duration_instances_component.get_internal_slot)();
// c. Let display be the value of durationFormat's internal slot whose name is the Display Slot value of the current row.
auto display = (duration_format.*duration_instances_component.get_display_slot)();
// d. Let unit be the Unit value of the current row.
// e. Let unit be the Unit value of the current row.
auto unit = duration_instances_component.unit;
// e. If style is "numeric" or "2-digit", then
// f. Let numberFormatUnit be the NumberFormat Unit value of the current row.
auto const& number_format_unit = unit_to_number_format_property_key(vm, duration_instances_component.unit);
// g. If style is "numeric" or "2-digit", then
if (style == DurationFormat::ValueStyle::Numeric || style == DurationFormat::ValueStyle::TwoDigit) {
// i. Let numericPartsList be FormatNumericUnits(durationFormat, duration, unit, signDisplayed).
auto numeric_parts_list = format_numeric_units(vm, duration_format, duration, unit, sign_displayed);
@ -816,13 +823,13 @@ Vector<DurationFormatPart> partition_duration_format_pattern(VM& vm, DurationFor
// iii. Set numericUnitFound to true.
numeric_unit_found = true;
}
// f. Else,
// h. Else,
else {
// i. Let nfOpts be OrdinaryObjectCreate(null).
auto number_format_options = Object::create(realm, nullptr);
// ii. If unit is one of "seconds", "milliseconds", or "microseconds", and NextUnitFractional(durationFormat, unit) is true, then
if (first_is_one_of(unit, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds, DurationFormat::Unit::Nanoseconds) && next_unit_fractional(duration_format, unit)) {
// ii. If NextUnitFractional(durationFormat, unit) is true, then
if (next_unit_fractional(duration_format, unit)) {
// 1. Set value to value + ComputeFractionalDigits(durationFormat, duration).
value = value + compute_fractional_digits(duration_format, duration);
@ -875,29 +882,26 @@ Vector<DurationFormatPart> partition_duration_format_pattern(VM& vm, DurationFor
MUST(number_format_options->create_data_property_or_throw(vm.names.signDisplay, PrimitiveString::create(vm, "never"sv)));
}
// 4. Let numberFormatUnit be the NumberFormat Unit value of the current row.
auto const& number_format_unit = unit_to_number_format_property_key(vm, duration_instances_component.unit);
// 5. Perform ! CreateDataPropertyOrThrow(nfOpts, "style", "unit").
// 3. Perform ! CreateDataPropertyOrThrow(nfOpts, "style", "unit").
MUST(number_format_options->create_data_property_or_throw(vm.names.style, PrimitiveString::create(vm, "unit"sv)));
// 6. Perform ! CreateDataPropertyOrThrow(nfOpts, "unit", numberFormatUnit).
// 4. Perform ! CreateDataPropertyOrThrow(nfOpts, "unit", numberFormatUnit).
MUST(number_format_options->create_data_property_or_throw(vm.names.unit, PrimitiveString::create(vm, number_format_unit.as_string())));
// 7. Perform ! CreateDataPropertyOrThrow(nfOpts, "unitDisplay", style).
// 5. Perform ! CreateDataPropertyOrThrow(nfOpts, "unitDisplay", style).
auto locale_style = Unicode::style_to_string(static_cast<Unicode::Style>(style));
MUST(number_format_options->create_data_property_or_throw(vm.names.unitDisplay, PrimitiveString::create(vm, locale_style)));
// 8. Let nf be ! Construct(%Intl.NumberFormat%, « durationFormat.[[Locale]], nfOpts »).
// 6. Let nf be ! Construct(%Intl.NumberFormat%, « durationFormat.[[Locale]], nfOpts »).
auto number_format = construct_number_format(vm, duration_format, number_format_options);
// 9. Let parts be PartitionNumberPattern(nf, value).
// 7. Let parts be PartitionNumberPattern(nf, value).
auto parts = partition_number_pattern(number_format, value_mv);
// 10. Let list be a new empty List.
// 8. Let list be a new empty List.
Vector<DurationFormatPart> list;
// 11. For each Record { [[Type]], [[Value]] } part of parts, do
// 10. For each Record { [[Type]], [[Value]] } part of parts, do
list.ensure_capacity(parts.size());
for (auto& part : parts) {
@ -905,7 +909,7 @@ Vector<DurationFormatPart> partition_duration_format_pattern(VM& vm, DurationFor
list.unchecked_append({ .type = part.type, .value = move(part.value), .unit = number_format_unit.as_string() });
}
// 12. Append list to result.
// 11. Append list to result.
result.append(list);
}
}

View file

@ -66,6 +66,12 @@ public:
Nanoseconds,
};
// 13.5.6.1 Duration Unit Options Records, https://tc39.es/ecma402/#sec-durationformat-unit-options-record
struct DurationUnitOptions {
ValueStyle style { ValueStyle::Long };
Display display { Display::Auto };
};
static constexpr auto relevant_extension_keys()
{
// 13.2.3 Internal slots, https://tc39.es/ecma402/#sec-Intl.DurationFormat-internal-slots
@ -91,85 +97,35 @@ public:
Style style() const { return m_style; }
StringView style_string() const { return style_to_string(m_style); }
void set_years_style(ValueStyle years_style) { m_years_style = years_style; }
ValueStyle years_style() const { return m_years_style; }
StringView years_style_string() const { return value_style_to_string(m_years_style); }
void set_years_options(DurationUnitOptions years_options) { m_years_options = years_options; }
DurationUnitOptions years_options() const { return m_years_options; }
void set_years_display(Display years_display) { m_years_display = years_display; }
Display years_display() const { return m_years_display; }
StringView years_display_string() const { return display_to_string(m_years_display); }
void set_months_options(DurationUnitOptions months_options) { m_months_options = months_options; }
DurationUnitOptions months_options() const { return m_months_options; }
void set_months_style(ValueStyle months_style) { m_months_style = months_style; }
ValueStyle months_style() const { return m_months_style; }
StringView months_style_string() const { return value_style_to_string(m_months_style); }
void set_weeks_options(DurationUnitOptions weeks_options) { m_weeks_options = weeks_options; }
DurationUnitOptions weeks_options() const { return m_weeks_options; }
void set_months_display(Display months_display) { m_months_display = months_display; }
Display months_display() const { return m_months_display; }
StringView months_display_string() const { return display_to_string(m_months_display); }
void set_days_options(DurationUnitOptions days_options) { m_days_options = days_options; }
DurationUnitOptions days_options() const { return m_days_options; }
void set_weeks_style(ValueStyle weeks_style) { m_weeks_style = weeks_style; }
ValueStyle weeks_style() const { return m_weeks_style; }
StringView weeks_style_string() const { return value_style_to_string(m_weeks_style); }
void set_hours_options(DurationUnitOptions hours_options) { m_hours_options = hours_options; }
DurationUnitOptions hours_options() const { return m_hours_options; }
void set_weeks_display(Display weeks_display) { m_weeks_display = weeks_display; }
Display weeks_display() const { return m_weeks_display; }
StringView weeks_display_string() const { return display_to_string(m_weeks_display); }
void set_minutes_options(DurationUnitOptions minutes_options) { m_minutes_options = minutes_options; }
DurationUnitOptions minutes_options() const { return m_minutes_options; }
void set_days_style(ValueStyle days_style) { m_days_style = days_style; }
ValueStyle days_style() const { return m_days_style; }
StringView days_style_string() const { return value_style_to_string(m_days_style); }
void set_seconds_options(DurationUnitOptions seconds_options) { m_seconds_options = seconds_options; }
DurationUnitOptions seconds_options() const { return m_seconds_options; }
void set_days_display(Display days_display) { m_days_display = days_display; }
Display days_display() const { return m_days_display; }
StringView days_display_string() const { return display_to_string(m_days_display); }
void set_milliseconds_options(DurationUnitOptions milliseconds_options) { m_milliseconds_options = milliseconds_options; }
DurationUnitOptions milliseconds_options() const { return m_milliseconds_options; }
void set_hours_style(ValueStyle hours_style) { m_hours_style = hours_style; }
ValueStyle hours_style() const { return m_hours_style; }
StringView hours_style_string() const { return value_style_to_string(m_hours_style); }
void set_microseconds_options(DurationUnitOptions microseconds_options) { m_microseconds_options = microseconds_options; }
DurationUnitOptions microseconds_options() const { return m_microseconds_options; }
void set_hours_display(Display hours_display) { m_hours_display = hours_display; }
Display hours_display() const { return m_hours_display; }
StringView hours_display_string() const { return display_to_string(m_hours_display); }
void set_minutes_style(ValueStyle minutes_style) { m_minutes_style = minutes_style; }
ValueStyle minutes_style() const { return m_minutes_style; }
StringView minutes_style_string() const { return value_style_to_string(m_minutes_style); }
void set_minutes_display(Display minutes_display) { m_minutes_display = minutes_display; }
Display minutes_display() const { return m_minutes_display; }
StringView minutes_display_string() const { return display_to_string(m_minutes_display); }
void set_seconds_style(ValueStyle seconds_style) { m_seconds_style = seconds_style; }
ValueStyle seconds_style() const { return m_seconds_style; }
StringView seconds_style_string() const { return value_style_to_string(m_seconds_style); }
void set_seconds_display(Display seconds_display) { m_seconds_display = seconds_display; }
Display seconds_display() const { return m_seconds_display; }
StringView seconds_display_string() const { return display_to_string(m_seconds_display); }
void set_milliseconds_style(ValueStyle milliseconds_style) { m_milliseconds_style = milliseconds_style; }
ValueStyle milliseconds_style() const { return m_milliseconds_style; }
StringView milliseconds_style_string() const { return value_style_to_string(m_milliseconds_style); }
void set_milliseconds_display(Display milliseconds_display) { m_milliseconds_display = milliseconds_display; }
Display milliseconds_display() const { return m_milliseconds_display; }
StringView milliseconds_display_string() const { return display_to_string(m_milliseconds_display); }
void set_microseconds_style(ValueStyle microseconds_style) { m_microseconds_style = microseconds_style; }
ValueStyle microseconds_style() const { return m_microseconds_style; }
StringView microseconds_style_string() const { return value_style_to_string(m_microseconds_style); }
void set_microseconds_display(Display microseconds_display) { m_microseconds_display = microseconds_display; }
Display microseconds_display() const { return m_microseconds_display; }
StringView microseconds_display_string() const { return display_to_string(m_microseconds_display); }
void set_nanoseconds_style(ValueStyle nanoseconds_style) { m_nanoseconds_style = nanoseconds_style; }
ValueStyle nanoseconds_style() const { return m_nanoseconds_style; }
StringView nanoseconds_style_string() const { return value_style_to_string(m_nanoseconds_style); }
void set_nanoseconds_display(Display nanoseconds_display) { m_nanoseconds_display = nanoseconds_display; }
Display nanoseconds_display() const { return m_nanoseconds_display; }
StringView nanoseconds_display_string() const { return display_to_string(m_nanoseconds_display); }
void set_nanoseconds_options(DurationUnitOptions nanoseconds_options) { m_nanoseconds_options = nanoseconds_options; }
DurationUnitOptions nanoseconds_options() const { return m_nanoseconds_options; }
void set_fractional_digits(Optional<u8> fractional_digits) { m_fractional_digits = move(fractional_digits); }
bool has_fractional_digits() const { return m_fractional_digits.has_value(); }
@ -183,78 +139,62 @@ private:
String m_hour_minute_separator; // [[HourMinutesSeparator]]
String m_minute_second_separator; // [[MinutesSecondsSeparator]]
Style m_style { Style::Long }; // [[Style]]
ValueStyle m_years_style { ValueStyle::Long }; // [[YearsStyle]]
Display m_years_display { Display::Auto }; // [[YearsDisplay]]
ValueStyle m_months_style { ValueStyle::Long }; // [[MonthsStyle]]
Display m_months_display { Display::Auto }; // [[MonthsDisplay]]
ValueStyle m_weeks_style { ValueStyle::Long }; // [[WeeksStyle]]
Display m_weeks_display { Display::Auto }; // [[WeeksDisplay]]
ValueStyle m_days_style { ValueStyle::Long }; // [[DaysStyle]]
Display m_days_display { Display::Auto }; // [[DaysDisplay]]
ValueStyle m_hours_style { ValueStyle::Long }; // [[HoursStyle]]
Display m_hours_display { Display::Auto }; // [[HoursDisplay]]
ValueStyle m_minutes_style { ValueStyle::Long }; // [[MinutesStyle]]
Display m_minutes_display { Display::Auto }; // [[MinutesDisplay]]
ValueStyle m_seconds_style { ValueStyle::Long }; // [[SecondsStyle]]
Display m_seconds_display { Display::Auto }; // [[SecondsDisplay]]
ValueStyle m_milliseconds_style { ValueStyle::Long }; // [[MillisecondsStyle]]
Display m_milliseconds_display { Display::Auto }; // [[MillisecondsDisplay]]
ValueStyle m_microseconds_style { ValueStyle::Long }; // [[MicrosecondsStyle]]
Display m_microseconds_display { Display::Auto }; // [[MicrosecondsDisplay]]
ValueStyle m_nanoseconds_style { ValueStyle::Long }; // [[NanosecondsStyle]]
Display m_nanoseconds_display { Display::Auto }; // [[NanosecondsDisplay]]
Optional<u8> m_fractional_digits; // [[FractionalDigits]]
Style m_style { Style::Long }; // [[Style]]
DurationUnitOptions m_years_options; // [[YearsOptions]]
DurationUnitOptions m_months_options; // [[MonthsOptions]]
DurationUnitOptions m_weeks_options; // [[WeeksOptions]]
DurationUnitOptions m_days_options; // [[DaysOptions]]
DurationUnitOptions m_hours_options; // [[HoursOptions]]
DurationUnitOptions m_minutes_options; // [[MinutesOptions]]
DurationUnitOptions m_seconds_options; // [[SecondsOptions]]
DurationUnitOptions m_milliseconds_options; // [[MillisecondsOptions]]
DurationUnitOptions m_microseconds_options; // [[MicrosecondsOptions]]
DurationUnitOptions m_nanoseconds_options; // [[NanosecondsOptions]]
Optional<u8> m_fractional_digits; // [[FractionalDigits]]
};
struct DurationInstanceComponent {
double (Temporal::Duration::*value_slot)() const;
DurationFormat::ValueStyle (DurationFormat::*get_style_slot)() const;
void (DurationFormat::*set_style_slot)(DurationFormat::ValueStyle);
DurationFormat::Display (DurationFormat::*get_display_slot)() const;
void (DurationFormat::*set_display_slot)(DurationFormat::Display);
DurationFormat::DurationUnitOptions (DurationFormat::*get_internal_slot)() const;
void (DurationFormat::*set_internal_slot)(DurationFormat::DurationUnitOptions);
DurationFormat::Unit unit;
ReadonlySpan<StringView> values;
ReadonlySpan<StringView> styles;
DurationFormat::ValueStyle digital_default;
};
// Table 20: Internal slots and property names of DurationFormat instances relevant to Intl.DurationFormat constructor, https://tc39.es/ecma402/#table-durationformat
// Table 23: DurationFormat instance internal slots and properties relevant to PartitionDurationFormatPattern, https://tc39.es/ecma402/#table-partition-duration-format-pattern
static constexpr auto date_values = AK::Array { "long"sv, "short"sv, "narrow"sv };
static constexpr auto time_values = AK::Array { "long"sv, "short"sv, "narrow"sv, "numeric"sv, "2-digit"sv };
static constexpr auto sub_second_values = AK::Array { "long"sv, "short"sv, "narrow"sv, "numeric"sv };
// Table 24: DurationFormat instance internal slots and properties relevant to PartitionDurationFormatPattern, https://tc39.es/ecma402/#table-partition-duration-format-pattern
static constexpr auto date_styles = AK::Array { "long"sv, "short"sv, "narrow"sv };
static constexpr auto time_styles = AK::Array { "long"sv, "short"sv, "narrow"sv, "numeric"sv, "2-digit"sv };
static constexpr auto sub_second_styles = AK::Array { "long"sv, "short"sv, "narrow"sv, "numeric"sv };
static constexpr auto duration_instances_components = to_array<DurationInstanceComponent>({
{ &Temporal::Duration::years, &DurationFormat::years_style, &DurationFormat::set_years_style, &DurationFormat::years_display, &DurationFormat::set_years_display, DurationFormat::Unit::Years, date_values, DurationFormat::ValueStyle::Short },
{ &Temporal::Duration::months, &DurationFormat::months_style, &DurationFormat::set_months_style, &DurationFormat::months_display, &DurationFormat::set_months_display, DurationFormat::Unit::Months, date_values, DurationFormat::ValueStyle::Short },
{ &Temporal::Duration::weeks, &DurationFormat::weeks_style, &DurationFormat::set_weeks_style, &DurationFormat::weeks_display, &DurationFormat::set_weeks_display, DurationFormat::Unit::Weeks, date_values, DurationFormat::ValueStyle::Short },
{ &Temporal::Duration::days, &DurationFormat::days_style, &DurationFormat::set_days_style, &DurationFormat::days_display, &DurationFormat::set_days_display, DurationFormat::Unit::Days, date_values, DurationFormat::ValueStyle::Short },
{ &Temporal::Duration::hours, &DurationFormat::hours_style, &DurationFormat::set_hours_style, &DurationFormat::hours_display, &DurationFormat::set_hours_display, DurationFormat::Unit::Hours, time_values, DurationFormat::ValueStyle::Numeric },
{ &Temporal::Duration::minutes, &DurationFormat::minutes_style, &DurationFormat::set_minutes_style, &DurationFormat::minutes_display, &DurationFormat::set_minutes_display, DurationFormat::Unit::Minutes, time_values, DurationFormat::ValueStyle::Numeric },
{ &Temporal::Duration::seconds, &DurationFormat::seconds_style, &DurationFormat::set_seconds_style, &DurationFormat::seconds_display, &DurationFormat::set_seconds_display, DurationFormat::Unit::Seconds, time_values, DurationFormat::ValueStyle::Numeric },
{ &Temporal::Duration::milliseconds, &DurationFormat::milliseconds_style, &DurationFormat::set_milliseconds_style, &DurationFormat::milliseconds_display, &DurationFormat::set_milliseconds_display, DurationFormat::Unit::Milliseconds, sub_second_values, DurationFormat::ValueStyle::Numeric },
{ &Temporal::Duration::microseconds, &DurationFormat::microseconds_style, &DurationFormat::set_microseconds_style, &DurationFormat::microseconds_display, &DurationFormat::set_microseconds_display, DurationFormat::Unit::Microseconds, sub_second_values, DurationFormat::ValueStyle::Numeric },
{ &Temporal::Duration::nanoseconds, &DurationFormat::nanoseconds_style, &DurationFormat::set_nanoseconds_style, &DurationFormat::nanoseconds_display, &DurationFormat::set_nanoseconds_display, DurationFormat::Unit::Nanoseconds, sub_second_values, DurationFormat::ValueStyle::Numeric },
{ &Temporal::Duration::years, &DurationFormat::years_options, &DurationFormat::set_years_options, DurationFormat::Unit::Years, date_styles, DurationFormat::ValueStyle::Short },
{ &Temporal::Duration::months, &DurationFormat::months_options, &DurationFormat::set_months_options, DurationFormat::Unit::Months, date_styles, DurationFormat::ValueStyle::Short },
{ &Temporal::Duration::weeks, &DurationFormat::weeks_options, &DurationFormat::set_weeks_options, DurationFormat::Unit::Weeks, date_styles, DurationFormat::ValueStyle::Short },
{ &Temporal::Duration::days, &DurationFormat::days_options, &DurationFormat::set_days_options, DurationFormat::Unit::Days, date_styles, DurationFormat::ValueStyle::Short },
{ &Temporal::Duration::hours, &DurationFormat::hours_options, &DurationFormat::set_hours_options, DurationFormat::Unit::Hours, time_styles, DurationFormat::ValueStyle::Numeric },
{ &Temporal::Duration::minutes, &DurationFormat::minutes_options, &DurationFormat::set_minutes_options, DurationFormat::Unit::Minutes, time_styles, DurationFormat::ValueStyle::Numeric },
{ &Temporal::Duration::seconds, &DurationFormat::seconds_options, &DurationFormat::set_seconds_options, DurationFormat::Unit::Seconds, time_styles, DurationFormat::ValueStyle::Numeric },
{ &Temporal::Duration::milliseconds, &DurationFormat::milliseconds_options, &DurationFormat::set_milliseconds_options, DurationFormat::Unit::Milliseconds, sub_second_styles, DurationFormat::ValueStyle::Numeric },
{ &Temporal::Duration::microseconds, &DurationFormat::microseconds_options, &DurationFormat::set_microseconds_options, DurationFormat::Unit::Microseconds, sub_second_styles, DurationFormat::ValueStyle::Numeric },
{ &Temporal::Duration::nanoseconds, &DurationFormat::nanoseconds_options, &DurationFormat::set_nanoseconds_options, DurationFormat::Unit::Nanoseconds, sub_second_styles, DurationFormat::ValueStyle::Numeric },
});
struct DurationUnitOptions {
DurationFormat::ValueStyle style;
DurationFormat::Display display;
};
struct DurationFormatPart {
StringView type;
String value;
StringView unit;
};
ThrowCompletionOr<DurationUnitOptions> get_duration_unit_options(VM&, DurationFormat::Unit unit, Object const& options, DurationFormat::Style base_style, ReadonlySpan<StringView> styles_list, DurationFormat::ValueStyle digital_base, Optional<DurationFormat::ValueStyle> previous_style, bool two_digit_hours);
ThrowCompletionOr<DurationFormat::DurationUnitOptions> get_duration_unit_options(VM&, DurationFormat::Unit unit, Object const& options, DurationFormat::Style base_style, ReadonlySpan<StringView> styles_list, DurationFormat::ValueStyle digital_base, Optional<DurationFormat::ValueStyle> previous_style, bool two_digit_hours);
Crypto::BigFraction compute_fractional_digits(DurationFormat const&, Temporal::Duration const&);
bool next_unit_fractional(DurationFormat const&, DurationFormat::Unit unit);
Vector<DurationFormatPart> format_numeric_hours(VM&, DurationFormat const&, MathematicalValue const& hours_value, bool sign_displayed);
Vector<DurationFormatPart> format_numeric_minutes(VM&, DurationFormat const&, MathematicalValue const& minutes_value, bool hours_displayed, bool sign_displayed);
Vector<DurationFormatPart> format_numeric_seconds(VM&, DurationFormat const&, MathematicalValue const& seconds_value, bool minutes_displayed, bool sign_displayed);
Vector<DurationFormatPart> format_numeric_units(VM&, DurationFormat const&, Temporal::Duration const&, DurationFormat::Unit first_numeric_unit, bool sign_displayed);
bool is_fractional_second_unit_name(DurationFormat::Unit);
Vector<DurationFormatPart> list_format_parts(VM&, DurationFormat const&, Vector<Vector<DurationFormatPart>>& partitioned_parts_list);
Vector<DurationFormatPart> partition_duration_format_pattern(VM&, DurationFormat const&, Temporal::Duration const&);

View file

@ -53,7 +53,7 @@ ThrowCompletionOr<GC::Ref<Object>> DurationFormatConstructor::construct(Function
auto locales = vm.argument(0);
auto options_value = vm.argument(1);
// 2. Let durationFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%Intl.DurationFormatPrototype%", « [[InitializedDurationFormat]], [[Locale]], [[NumberingSystem]], [[Style]], [[YearsStyle]], [[YearsDisplay]], [[MonthsStyle]], [[MonthsDisplay]], [[WeeksStyle]], [[WeeksDisplay]], [[DaysStyle]], [[DaysDisplay]], [[HoursStyle]], [[HoursDisplay]], [[MinutesStyle]], [[MinutesDisplay]], [[SecondsStyle]], [[SecondsDisplay]], [[MillisecondsStyle]], [[MillisecondsDisplay]], [[MicrosecondsStyle]], [[MicrosecondsDisplay]], [[NanosecondsStyle]], [[NanosecondsDisplay]], [[HourMinuteSeparator]], [[MinuteSecondSeparator]], [[FractionalDigits]] »).
// 2. Let durationFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%Intl.DurationFormatPrototype%", « [[InitializedDurationFormat]], [[Locale]], [[NumberingSystem]], [[Style]], [[YearsOptions]], [[MonthsOptions]], [[WeeksOptions]], [[DaysOptions]], [[HoursOptions]], [[MinutesOptions]], [[SecondsOptions]], [[MillisecondsOptions]], [[MicrosecondsOptions]], [[NanosecondsOptions]], [[HourMinuteSeparator]], [[MinuteSecondSeparator]], [[FractionalDigits]] »).
auto duration_format = TRY(ordinary_create_from_constructor<DurationFormat>(vm, new_target, &Intrinsics::intl_duration_format_prototype));
// 3. Let requestedLocales be ? CanonicalizeLocaleList(locales).
@ -89,7 +89,7 @@ ThrowCompletionOr<GC::Ref<Object>> DurationFormatConstructor::construct(Function
// 11. Let resolvedLocaleData be r.[[LocaleData]].
// 12. Let digitalFormat be resolvedLocaleData.[[DigitalFormat]].
auto digital_format = Unicode::digital_format(duration_format->locale());
auto digital_format = Unicode::digital_format(result.icu_locale);
// 13. Set durationFormat.[[HourMinuteSeparator]] to digitalFormat.[[HourMinuteSeparator]].
duration_format->set_hour_minute_separator(move(digital_format.hours_minutes_separator));
@ -112,31 +112,25 @@ ThrowCompletionOr<GC::Ref<Object>> DurationFormatConstructor::construct(Function
// 19. For each row of Table 20, except the header row, in table order, do
for (auto const& duration_instances_component : duration_instances_components) {
// a. Let styleSlot be the Style Slot value of the current row.
auto style_slot = duration_instances_component.set_style_slot;
// a. Let slot be the Internal Slot value of the current row.
auto slot = duration_instances_component.set_internal_slot;
// b. Let displaySlot be the Display Slot value of the current row.
auto display_slot = duration_instances_component.set_display_slot;
// c. Let unit be the Unit value of the current row.
// b. Let unit be the Unit value of the current row.
auto unit = duration_instances_component.unit;
// d. Let valueList be the Values value of the current row.
auto value_list = duration_instances_component.values;
// c. Let styles be the Styles value of the current row.
auto styles = duration_instances_component.styles;
// e. Let digitalBase be the Digital Default value of the current row.
// d. Let digitalBase be the Digital Default value of the current row.
auto digital_base = duration_instances_component.digital_default;
// f. Let unitOptions be ? GetDurationUnitOptions(unit, options, style, valueList, digitalBase, prevStyle, digitalFormat.[[TwoDigitHours]]).
auto unit_options = TRY(get_duration_unit_options(vm, unit, *options, duration_format->style(), value_list, digital_base, previous_style, digital_format.uses_two_digit_hours));
// e. Let unitOptions be ? GetDurationUnitOptions(unit, options, style, styles, digitalBase, prevStyle, digitalFormat.[[TwoDigitHours]]).
auto unit_options = TRY(get_duration_unit_options(vm, unit, *options, duration_format->style(), styles, digital_base, previous_style, digital_format.uses_two_digit_hours));
// g. Set the value of the styleSlot slot of durationFormat to unitOptions.[[Style]].
(duration_format->*style_slot)(unit_options.style);
// f. Set the value of durationFormat's internal slot whose name is slot to unitOptions.
(duration_format->*slot)(unit_options);
// h. Set the value of the displaySlot slot of durationFormat to unitOptions.[[Display]].
(duration_format->*display_slot)(unit_options.display);
// i. If unit is one of "hours", "minutes", "seconds", "milliseconds", or "microseconds", then
// g. If unit is one of "hours", "minutes", "seconds", "milliseconds", or "microseconds", then
if (first_is_one_of(unit, DurationFormat::Unit::Hours, DurationFormat::Unit::Minutes, DurationFormat::Unit::Seconds, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds)) {
// i. Set prevStyle to unitOptions.[[Style]].
previous_style = unit_options.style;

View file

@ -50,49 +50,57 @@ JS_DEFINE_NATIVE_FUNCTION(DurationFormatPrototype::resolved_options)
auto options = Object::create(realm, realm.intrinsics().object_prototype());
// 4. For each row of Table 21, except the header row, in table order, do
auto create_option = [&](PropertyKey const& property, StringView value) {
auto create_option = [&]<typename T>(PropertyKey const& property, Optional<PropertyKey const&> display_property, T value) {
// a. Let p be the Property value of the current row.
// b. Let v be the value of df's internal slot whose name is the Internal Slot value of the current row.
// c. If p is "fractionalDigits", then
// i. If v is not undefined, perform ! CreateDataPropertyOrThrow(options, p, 𝔽(v)).
// NOTE: This case is handled separately below.
// c. If v is not undefined, then
// i. If there is a Conversion value in the current row, let conversion be that value; else let conversion be empty.
// ii. If conversion is number, then
// 1. Set v to 𝔽(v).
// NOTE: This case is for fractionalDigits and is handled separately below.
// d. Else,
// i. Assert: v is not undefined.
// ii. If v is "fractional", then
if (value == "fractional"sv) {
// 1. Assert: The Internal Slot value of the current row is [[MillisecondsStyle]], [[MicrosecondsStyle]], or [[NanosecondsStyle]] .
// 2. Set v to "numeric".
value = "numeric"sv;
// iii. Else if conversion is not empty, then
if constexpr (IsSame<T, DurationFormat::DurationUnitOptions>) {
// 1. Assert: conversion is STYLE+DISPLAY and v is a Duration Unit Options Record.
// 2. NOTE: v.[[Style]] will be represented with a property named p (a plural Temporal unit), then v.[[Display]] will be represented with a property whose name suffixes p with "Display".
VERIFY(display_property.has_value());
// 3. Let style be v.[[Style]].
auto style = value.style;
// 4. If style is "fractional", then
if (style == DurationFormat::ValueStyle::Fractional) {
// a. Assert: IsFractionalSecondUnitName(p) is true.
// b. Set style to "numeric".
style = DurationFormat::ValueStyle::Numeric;
}
// 5. Perform ! CreateDataPropertyOrThrow(options, p, style).
MUST(options->create_data_property_or_throw(property, PrimitiveString::create(vm, DurationFormat::value_style_to_string(style))));
// 6. Set p to the string-concatenation of p and "Display".
// 7. Set v to v.[[Display]].
MUST(options->create_data_property_or_throw(*display_property, PrimitiveString::create(vm, DurationFormat::display_to_string(value.display))));
} else {
// iv. Perform ! CreateDataPropertyOrThrow(options, p, v).
MUST(options->create_data_property_or_throw(property, PrimitiveString::create(vm, move(value))));
}
// iii. Perform ! CreateDataPropertyOrThrow(options, p, v).
MUST(options->create_data_property_or_throw(property, PrimitiveString::create(vm, value)));
};
create_option(vm.names.locale, duration_format->locale());
create_option(vm.names.numberingSystem, duration_format->numbering_system());
create_option(vm.names.style, duration_format->style_string());
create_option(vm.names.years, duration_format->years_style_string());
create_option(vm.names.yearsDisplay, duration_format->years_display_string());
create_option(vm.names.months, duration_format->months_style_string());
create_option(vm.names.monthsDisplay, duration_format->months_display_string());
create_option(vm.names.weeks, duration_format->weeks_style_string());
create_option(vm.names.weeksDisplay, duration_format->weeks_display_string());
create_option(vm.names.days, duration_format->days_style_string());
create_option(vm.names.daysDisplay, duration_format->days_display_string());
create_option(vm.names.hours, duration_format->hours_style_string());
create_option(vm.names.hoursDisplay, duration_format->hours_display_string());
create_option(vm.names.minutes, duration_format->minutes_style_string());
create_option(vm.names.minutesDisplay, duration_format->minutes_display_string());
create_option(vm.names.seconds, duration_format->seconds_style_string());
create_option(vm.names.secondsDisplay, duration_format->seconds_display_string());
create_option(vm.names.milliseconds, duration_format->milliseconds_style_string());
create_option(vm.names.millisecondsDisplay, duration_format->milliseconds_display_string());
create_option(vm.names.microseconds, duration_format->microseconds_style_string());
create_option(vm.names.microsecondsDisplay, duration_format->microseconds_display_string());
create_option(vm.names.nanoseconds, duration_format->nanoseconds_style_string());
create_option(vm.names.nanosecondsDisplay, duration_format->nanoseconds_display_string());
create_option(vm.names.locale, {}, duration_format->locale());
create_option(vm.names.numberingSystem, {}, duration_format->numbering_system());
create_option(vm.names.style, {}, duration_format->style_string());
create_option(vm.names.years, vm.names.yearsDisplay, duration_format->years_options());
create_option(vm.names.months, vm.names.monthsDisplay, duration_format->months_options());
create_option(vm.names.weeks, vm.names.weeksDisplay, duration_format->weeks_options());
create_option(vm.names.days, vm.names.daysDisplay, duration_format->days_options());
create_option(vm.names.hours, vm.names.hoursDisplay, duration_format->hours_options());
create_option(vm.names.minutes, vm.names.minutesDisplay, duration_format->minutes_options());
create_option(vm.names.seconds, vm.names.secondsDisplay, duration_format->seconds_options());
create_option(vm.names.milliseconds, vm.names.millisecondsDisplay, duration_format->milliseconds_options());
create_option(vm.names.microseconds, vm.names.microsecondsDisplay, duration_format->microseconds_options());
create_option(vm.names.nanoseconds, vm.names.nanosecondsDisplay, duration_format->nanoseconds_options());
if (duration_format->has_fractional_digits())
MUST(options->create_data_property_or_throw(vm.names.fractionalDigits, Value(duration_format->fractional_digits())));

View file

@ -47,7 +47,7 @@ JS_DEFINE_NATIVE_FUNCTION(ListFormatPrototype::resolved_options)
// 3. Let options be OrdinaryObjectCreate(%Object.prototype%).
auto options = Object::create(realm, realm.intrinsics().object_prototype());
// 4. For each row of Table 24, except the header row, in table order, do
// 4. For each row of Table 25, except the header row, in table order, do
// a. Let p be the Property value of the current row.
// b. Let v be the value of lf's internal slot whose name is the Internal Slot value of the current row.
// c. Assert: v is not undefined.

View file

@ -63,13 +63,13 @@ ThrowCompletionOr<GC::Ref<Object>> NumberFormatConstructor::construct(FunctionOb
// 5. Let opt be a new Record.
LocaleOptions opt {};
// 6. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit").
// 6. Let matcher be ? GetOption(options, "localeMatcher", STRING, « "lookup", "best fit" », "best fit").
auto matcher = TRY(get_option(vm, *options, vm.names.localeMatcher, OptionType::String, { "lookup"sv, "best fit"sv }, "best fit"sv));
// 7. Set opt.[[localeMatcher]] to matcher.
opt.locale_matcher = matcher;
// 8. Let numberingSystem be ? GetOption(options, "numberingSystem", string, empty, undefined).
// 8. Let numberingSystem be ? GetOption(options, "numberingSystem", STRING, EMPTY, undefined).
auto numbering_system = TRY(get_option(vm, *options, vm.names.numberingSystem, OptionType::String, {}, Empty {}));
// 9. If numberingSystem is not undefined, then
@ -100,11 +100,17 @@ ThrowCompletionOr<GC::Ref<Object>> NumberFormatConstructor::construct(FunctionOb
// 16. Let style be numberFormat.[[Style]].
auto style = number_format->style();
// 17. Let notation be ? GetOption(options, "notation", STRING, « "standard", "scientific", "engineering", "compact" », "standard").
auto notation = TRY(get_option(vm, *options, vm.names.notation, OptionType::String, { "standard"sv, "scientific"sv, "engineering"sv, "compact"sv }, "standard"sv));
// 18. Set numberFormat.[[Notation]] to notation.
number_format->set_notation(notation.as_string().utf8_string_view());
int default_min_fraction_digits = 0;
int default_max_fraction_digits = 0;
// 17. If style is "currency", then
if (style == Unicode::NumberFormatStyle::Currency) {
// 19. If style is "currency" and notation is "standard", then
if (style == Unicode::NumberFormatStyle::Currency && number_format->notation() == Unicode::Notation::Standard) {
// a. Let currency be numberFormat.[[Currency]].
auto const& currency = number_format->currency();
@ -117,7 +123,7 @@ ThrowCompletionOr<GC::Ref<Object>> NumberFormatConstructor::construct(FunctionOb
// d. Let mxfdDefault be cDigits.
default_max_fraction_digits = digits;
}
// 18. Else,
// 20. Else,
else {
// a. Let mnfdDefault be 0.
default_min_fraction_digits = 0;
@ -129,16 +135,10 @@ ThrowCompletionOr<GC::Ref<Object>> NumberFormatConstructor::construct(FunctionOb
default_max_fraction_digits = style == Unicode::NumberFormatStyle::Percent ? 0 : 3;
}
// 19. Let notation be ? GetOption(options, "notation", string, « "standard", "scientific", "engineering", "compact" », "standard").
auto notation = TRY(get_option(vm, *options, vm.names.notation, OptionType::String, { "standard"sv, "scientific"sv, "engineering"sv, "compact"sv }, "standard"sv));
// 20. Set numberFormat.[[Notation]] to notation.
number_format->set_notation(notation.as_string().utf8_string_view());
// 21. Perform ? SetNumberFormatDigitOptions(numberFormat, options, mnfdDefault, mxfdDefault, notation).
TRY(set_number_format_digit_options(vm, number_format, *options, default_min_fraction_digits, default_max_fraction_digits, number_format->notation()));
// 22. Let compactDisplay be ? GetOption(options, "compactDisplay", string, « "short", "long" », "short").
// 22. Let compactDisplay be ? GetOption(options, "compactDisplay", STRING, « "short", "long" », "short").
auto compact_display = TRY(get_option(vm, *options, vm.names.compactDisplay, OptionType::String, { "short"sv, "long"sv }, "short"sv));
// 23. Let defaultUseGrouping be "auto".
@ -172,7 +172,7 @@ ThrowCompletionOr<GC::Ref<Object>> NumberFormatConstructor::construct(FunctionOb
// 29. Set numberFormat.[[UseGrouping]] to useGrouping.
number_format->set_use_grouping(use_grouping);
// 30. Let signDisplay be ? GetOption(options, "signDisplay", string, « "auto", "never", "always", "exceptZero", "negative" », "auto").
// 30. Let signDisplay be ? GetOption(options, "signDisplay", STRING, « "auto", "never", "always", "exceptZero", "negative" », "auto").
auto sign_display = TRY(get_option(vm, *options, vm.names.signDisplay, OptionType::String, { "auto"sv, "never"sv, "always"sv, "exceptZero"sv, "negative"sv }, "auto"sv));
// 31. Set numberFormat.[[SignDisplay]] to signDisplay.
@ -184,8 +184,7 @@ ThrowCompletionOr<GC::Ref<Object>> NumberFormatConstructor::construct(FunctionOb
// Non-standard, create an ICU number formatter for this Intl object.
auto formatter = Unicode::NumberFormat::create(
number_format->locale(),
number_format->numbering_system(),
result.icu_locale,
number_format->display_options(),
number_format->rounding_options());
number_format->set_formatter(move(formatter));
@ -224,14 +223,14 @@ ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase
if (!sanctioned_rounding_increments.span().contains_slow(*rounding_increment))
return vm.throw_completion<RangeError>(ErrorType::IntlInvalidRoundingIncrement, *rounding_increment);
// 9. Let roundingMode be ? GetOption(options, "roundingMode", string, « "ceil", "floor", "expand", "trunc", "halfCeil", "halfFloor", "halfExpand", "halfTrunc", "halfEven" », "halfExpand").
// 9. Let roundingMode be ? GetOption(options, "roundingMode", STRING, « "ceil", "floor", "expand", "trunc", "halfCeil", "halfFloor", "halfExpand", "halfTrunc", "halfEven" », "halfExpand").
auto rounding_mode = TRY(get_option(vm, options, vm.names.roundingMode, OptionType::String, { "ceil"sv, "floor"sv, "expand"sv, "trunc"sv, "halfCeil"sv, "halfFloor"sv, "halfExpand"sv, "halfTrunc"sv, "halfEven"sv }, "halfExpand"sv));
// 10. Let roundingPriority be ? GetOption(options, "roundingPriority", string, « "auto", "morePrecision", "lessPrecision" », "auto").
// 10. Let roundingPriority be ? GetOption(options, "roundingPriority", STRING, « "auto", "morePrecision", "lessPrecision" », "auto").
auto rounding_priority_option = TRY(get_option(vm, options, vm.names.roundingPriority, OptionType::String, { "auto"sv, "morePrecision"sv, "lessPrecision"sv }, "auto"sv));
auto rounding_priority = rounding_priority_option.as_string().utf8_string_view();
// 11. Let trailingZeroDisplay be ? GetOption(options, "trailingZeroDisplay", string, « "auto", "stripIfInteger" », "auto").
// 11. Let trailingZeroDisplay be ? GetOption(options, "trailingZeroDisplay", STRING, « "auto", "stripIfInteger" », "auto").
auto trailing_zero_display = TRY(get_option(vm, options, vm.names.trailingZeroDisplay, OptionType::String, { "auto"sv, "stripIfInteger"sv }, "auto"sv));
// 12. NOTE: All fields required by SetNumberFormatDigitOptions have now been read from options. The remainder of this AO interprets the options and may throw exceptions.
@ -266,8 +265,8 @@ ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase
// a. Set needSd to hasSd.
need_significant_digits = has_significant_digits;
// b. If hasSd is true, or hasFd is false and notation is "compact", then
if (has_significant_digits || (!has_fraction_digits && notation == Unicode::Notation::Compact)) {
// b. If needSd is true, or hasFd is false and notation is "compact", then
if (need_significant_digits || (!has_fraction_digits && notation == Unicode::Notation::Compact)) {
// i. Set needFd to false.
need_fraction_digits = false;
}
@ -390,7 +389,7 @@ ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase
if (intl_object.rounding_type() != Unicode::RoundingType::FractionDigits)
return vm.throw_completion<TypeError>(ErrorType::IntlInvalidRoundingIncrementForRoundingType, *rounding_increment, intl_object.rounding_type_string());
// b. If intlObj.[[MaximumFractionDigits]] is not equal to intlObj.[[MinimumFractionDigits]], throw a RangeError exception.
// b. If intlObj.[[MaximumFractionDigits]] is not intlObj.[[MinimumFractionDigits]], throw a RangeError exception.
if (intl_object.max_fraction_digits() != intl_object.min_fraction_digits())
return vm.throw_completion<RangeError>(ErrorType::IntlInvalidRoundingIncrementForFractionDigits, *rounding_increment);
}
@ -402,13 +401,13 @@ ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase
// 16.1.3 SetNumberFormatUnitOptions ( intlObj, options ), https://tc39.es/ecma402/#sec-setnumberformatunitoptions
ThrowCompletionOr<void> set_number_format_unit_options(VM& vm, NumberFormat& intl_object, Object const& options)
{
// 1. Let style be ? GetOption(options, "style", string, « "decimal", "percent", "currency", "unit" », "decimal").
// 1. Let style be ? GetOption(options, "style", STRING, « "decimal", "percent", "currency", "unit" », "decimal").
auto style = TRY(get_option(vm, options, vm.names.style, OptionType::String, { "decimal"sv, "percent"sv, "currency"sv, "unit"sv }, "decimal"sv));
// 2. Set intlObj.[[Style]] to style.
intl_object.set_style(style.as_string().utf8_string_view());
// 3. Let currency be ? GetOption(options, "currency", string, empty, undefined).
// 3. Let currency be ? GetOption(options, "currency", STRING, EMPTY, undefined).
auto currency = TRY(get_option(vm, options, vm.names.currency, OptionType::String, {}, Empty {}));
// 4. If currency is undefined, then
@ -423,13 +422,13 @@ ThrowCompletionOr<void> set_number_format_unit_options(VM& vm, NumberFormat& int
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, currency, "currency"sv);
}
// 6. Let currencyDisplay be ? GetOption(options, "currencyDisplay", string, « "code", "symbol", "narrowSymbol", "name" », "symbol").
// 6. Let currencyDisplay be ? GetOption(options, "currencyDisplay", STRING, « "code", "symbol", "narrowSymbol", "name" », "symbol").
auto currency_display = TRY(get_option(vm, options, vm.names.currencyDisplay, OptionType::String, { "code"sv, "symbol"sv, "narrowSymbol"sv, "name"sv }, "symbol"sv));
// 7. Let currencySign be ? GetOption(options, "currencySign", string, « "standard", "accounting" », "standard").
// 7. Let currencySign be ? GetOption(options, "currencySign", STRING, « "standard", "accounting" », "standard").
auto currency_sign = TRY(get_option(vm, options, vm.names.currencySign, OptionType::String, { "standard"sv, "accounting"sv }, "standard"sv));
// 8. Let unit be ? GetOption(options, "unit", string, empty, undefined).
// 8. Let unit be ? GetOption(options, "unit", STRING, EMPTY, undefined).
auto unit = TRY(get_option(vm, options, vm.names.unit, OptionType::String, {}, Empty {}));
// 9. If unit is undefined, then
@ -444,7 +443,7 @@ ThrowCompletionOr<void> set_number_format_unit_options(VM& vm, NumberFormat& int
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, unit, "unit"sv);
}
// 11. Let unitDisplay be ? GetOption(options, "unitDisplay", string, « "short", "narrow", "long" », "short").
// 11. Let unitDisplay be ? GetOption(options, "unitDisplay", STRING, « "short", "narrow", "long" », "short").
auto unit_display = TRY(get_option(vm, options, vm.names.unitDisplay, OptionType::String, { "short"sv, "narrow"sv, "long"sv }, "short"sv));
// 12. If style is "currency", then

View file

@ -53,7 +53,7 @@ JS_DEFINE_NATIVE_FUNCTION(NumberFormatPrototype::resolved_options)
// 4. Let options be OrdinaryObjectCreate(%Object.prototype%).
auto options = Object::create(realm, realm.intrinsics().object_prototype());
// 5. For each row of Table 25, except the header row, in table order, do
// 5. For each row of Table 26, except the header row, in table order, do
// a. Let p be the Property value of the current row.
// b. Let v be the value of nf's internal slot whose name is the Internal Slot value of the current row.
// c. If v is not undefined, then

View file

@ -87,8 +87,7 @@ ThrowCompletionOr<GC::Ref<Object>> PluralRulesConstructor::construct(FunctionObj
// Non-standard, create an ICU number formatter for this Intl object.
auto formatter = Unicode::NumberFormat::create(
plural_rules->locale(),
{},
result.icu_locale,
{},
plural_rules->rounding_options());

View file

@ -56,7 +56,7 @@ JS_DEFINE_NATIVE_FUNCTION(PluralRulesPrototype::resolved_options)
return PrimitiveString::create(vm, Unicode::plural_category_to_string(category));
});
// 5. For each row of Table 29, except the header row, in table order, do
// 5. For each row of Table 30, except the header row, in table order, do
// a. Let p be the Property value of the current row.
// b. If p is "pluralCategories", then
// i. Let v be CreateArrayFromList(pluralCategories).

View file

@ -63,13 +63,13 @@ ThrowCompletionOr<GC::Ref<Object>> RelativeTimeFormatConstructor::construct(Func
// 5. Let opt be a new Record.
LocaleOptions opt {};
// 6. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit").
// 6. Let matcher be ? GetOption(options, "localeMatcher", STRING, « "lookup", "best fit" », "best fit").
auto matcher = TRY(get_option(vm, *options, vm.names.localeMatcher, OptionType::String, AK::Array { "lookup"sv, "best fit"sv }, "best fit"sv));
// 7. Set opt.[[LocaleMatcher]] to matcher.
opt.locale_matcher = matcher;
// 8. Let numberingSystem be ? GetOption(options, "numberingSystem", string, empty, undefined).
// 8. Let numberingSystem be ? GetOption(options, "numberingSystem", STRING, EMPTY, undefined).
auto numbering_system = TRY(get_option(vm, *options, vm.names.numberingSystem, OptionType::String, {}, Empty {}));
// 9. If numberingSystem is not undefined, then
@ -97,13 +97,13 @@ ThrowCompletionOr<GC::Ref<Object>> RelativeTimeFormatConstructor::construct(Func
if (auto* resolved_numbering_system = result.nu.get_pointer<String>())
relative_time_format->set_numbering_system(move(*resolved_numbering_system));
// 16. Let style be ? GetOption(options, "style", string, « "long", "short", "narrow" », "long").
// 16. Let style be ? GetOption(options, "style", STRING, « "long", "short", "narrow" », "long").
auto style = TRY(get_option(vm, *options, vm.names.style, OptionType::String, { "long"sv, "short"sv, "narrow"sv }, "long"sv));
// 17. Set relativeTimeFormat.[[Style]] to style.
relative_time_format->set_style(style.as_string().utf8_string_view());
// 18. Let numeric be ? GetOption(options, "numeric", string, « "always", "auto" », "always").
// 18. Let numeric be ? GetOption(options, "numeric", STRING, « "always", "auto" », "always").
auto numeric = TRY(get_option(vm, *options, vm.names.numeric, OptionType::String, { "always"sv, "auto"sv }, "always"sv));
// 19. Set relativeTimeFormat.[[Numeric]] to numeric.
@ -112,7 +112,7 @@ ThrowCompletionOr<GC::Ref<Object>> RelativeTimeFormatConstructor::construct(Func
// 20. Let relativeTimeFormat.[[NumberFormat]] be ! Construct(%Intl.NumberFormat%, « locale »).
// 21. Let relativeTimeFormat.[[PluralRules]] be ! Construct(%Intl.PluralRules%, « locale »).
auto formatter = Unicode::RelativeTimeFormat::create(
relative_time_format->locale(),
result.icu_locale,
relative_time_format->style());
relative_time_format->set_formatter(move(formatter));

View file

@ -46,7 +46,7 @@ JS_DEFINE_NATIVE_FUNCTION(RelativeTimeFormatPrototype::resolved_options)
// 3. Let options be OrdinaryObjectCreate(%Object.prototype%).
auto options = Object::create(realm, realm.intrinsics().object_prototype());
// 4. For each row of Table 30, except the header row, in table order, do
// 4. For each row of Table 31, except the header row, in table order, do
// a. Let p be the Property value of the current row.
// b. Let v be the value of relativeTimeFormat's internal slot whose name is the Internal Slot value of the current row.
// c. Assert: v is not undefined.

View file

@ -45,7 +45,7 @@ JS_DEFINE_NATIVE_FUNCTION(SegmenterPrototype::resolved_options)
// 3. Let options be OrdinaryObjectCreate(%Object.prototype%).
auto options = Object::create(realm, realm.intrinsics().object_prototype());
// 4. For each row of Table 31, except the header row, in table order, do
// 4. For each row of Table 32, except the header row, in table order, do
// a. Let p be the Property value of the current row.
// b. Let v be the value of segmenter's internal slot whose name is the Internal Slot value of the current row.
// c. Assert: v is not undefined.

View file

@ -34,7 +34,7 @@ ThrowCompletionOr<Value> ModuleEnvironment::get_binding_value(VM& vm, Deprecated
// a. Let M and N2 be the indirection values provided when this binding for N was created.
// b. Let targetEnv be M.[[Environment]].
auto* target_env = indirect_binding->module->environment();
auto target_env = indirect_binding->module->environment();
// c. If targetEnv is empty, throw a ReferenceError exception.
if (!target_env)
@ -97,11 +97,10 @@ Optional<ModuleEnvironment::BindingAndIndex> ModuleEnvironment::find_binding_and
{
auto* indirect_binding = get_indirect_binding(name);
if (indirect_binding != nullptr) {
auto* target_env = indirect_binding->module->environment();
auto target_env = indirect_binding->module->environment();
if (!target_env)
return {};
VERIFY(is<ModuleEnvironment>(target_env));
auto& target_module_environment = static_cast<ModuleEnvironment&>(*target_env);
auto result = target_module_environment.find_binding_and_index(indirect_binding->binding_name);
if (!result.has_value())

View file

@ -6,6 +6,7 @@
#include <AK/QuickSort.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/ModuleEnvironment.h>
#include <LibJS/Runtime/ModuleNamespaceObject.h>
namespace JS {
@ -173,7 +174,7 @@ ThrowCompletionOr<Value> ModuleNamespaceObject::internal_get(PropertyKey const&
}
// 10. Let targetEnv be targetModule.[[Environment]].
auto* target_environment = target_module->environment();
auto target_environment = target_module->environment();
// 11. If targetEnv is empty, throw a ReferenceError exception.
if (!target_environment)

View file

@ -22,12 +22,6 @@ describe("errors", () => {
BigInt.asIntN(1, "foo");
}).toThrowWithMessage(SyntaxError, "Invalid value for BigInt: foo");
});
test("large allocation", () => {
expect(() => {
BigInt.asIntN(0x4000000000000, 1n);
}).toThrowWithMessage(InternalError, "Out of memory");
});
});
describe("correct behavior", () => {
@ -82,4 +76,23 @@ describe("correct behavior", () => {
expect(BigInt.asIntN(128, -extremelyBigInt)).toBe(99061374399389259395070030194384019691n);
expect(BigInt.asIntN(256, -extremelyBigInt)).toBe(-extremelyBigInt);
});
test("large bit values", () => {
expect(BigInt.asIntN(0x4000000000000, 1n)).toBe(1n);
expect(BigInt.asIntN(0x8ffffffffffff, 1n)).toBe(1n);
expect(BigInt.asIntN(2 ** 53 - 1, 2n)).toBe(2n);
// These incur large intermediate values that 00M. For now, ensure they don't crash
expect(() => {
BigInt.asIntN(0x4000000000000, -1n);
}).toThrowWithMessage(InternalError, "Out of memory");
expect(() => {
BigInt.asIntN(0x8ffffffffffff, -1n);
}).toThrowWithMessage(InternalError, "Out of memory");
expect(() => {
BigInt.asIntN(2 ** 53 - 1, -2n);
}).toThrowWithMessage(InternalError, "Out of memory");
});
});

View file

@ -22,12 +22,6 @@ describe("errors", () => {
BigInt.asUintN(1, "foo");
}).toThrowWithMessage(SyntaxError, "Invalid value for BigInt: foo");
});
test("large allocation", () => {
expect(() => {
BigInt.asUintN(0x4000000000000, 1n);
}).toThrowWithMessage(InternalError, "Out of memory");
});
});
describe("correct behavior", () => {
@ -80,4 +74,20 @@ describe("correct behavior", () => {
115792089237316195423570861551898784396480861208851440582668460551124006183147n
);
});
test("large bit values", () => {
const INDEX_MAX = 2 ** 53 - 1;
const LAST_8_DIGITS = 10n ** 8n;
expect(BigInt.asUintN(0x400000000, 1n)).toBe(1n);
expect(BigInt.asUintN(0x4000, -1n) % LAST_8_DIGITS).toBe(64066815n);
expect(BigInt.asUintN(0x400000000, 2n)).toBe(2n);
expect(BigInt.asUintN(0x4000, -2n) % LAST_8_DIGITS).toBe(64066814n);
expect(BigInt.asUintN(INDEX_MAX, 2n)).toBe(2n);
expect(() => {
BigInt.asUintN(INDEX_MAX, -2n);
}).toThrowWithMessage(InternalError, "Out of memory");
});
});

View file

@ -652,17 +652,17 @@ describe("Temporal objects", () => {
test("Temporal.PlainDate", () => {
const plainDate = new Temporal.PlainDate(1989, 1, 23);
expect(formatter.format(plainDate)).toBe("1/23/1989");
expect(formatter.format(plainDate)).toBe("1989-01-23");
});
test("Temporal.PlainYearMonth", () => {
const plainYearMonth = new Temporal.PlainYearMonth(1989, 1);
expect(formatter.format(plainYearMonth)).toBe("1/1989");
expect(formatter.format(plainYearMonth)).toBe("1989-01");
});
test("Temporal.PlainMonthDay", () => {
const plainMonthDay = new Temporal.PlainMonthDay(1, 23);
expect(formatter.format(plainMonthDay)).toBe("1/23");
expect(formatter.format(plainMonthDay)).toBe("01-23");
});
test("Temporal.PlainTime", () => {
@ -672,6 +672,6 @@ describe("Temporal objects", () => {
test("Temporal.Instant", () => {
const instant = new Temporal.Instant(1732740069000000000n);
expect(formatter.format(instant)).toBe("11/27/2024, 8:41:09 PM");
expect(formatter.format(instant)).toBe("2024-11-27, 8:41:09 PM");
});
});

View file

@ -322,19 +322,19 @@ describe("Temporal objects", () => {
test("Temporal.PlainDate", () => {
const plainDate1 = new Temporal.PlainDate(1989, 1, 23);
const plainDate2 = new Temporal.PlainDate(2024, 11, 27);
expect(formatter.formatRange(plainDate1, plainDate2)).toBe("1/23/1989 11/27/2024");
expect(formatter.formatRange(plainDate1, plainDate2)).toBe("1989-01-23 2024-11-27");
});
test("Temporal.PlainYearMonth", () => {
const plainYearMonth1 = new Temporal.PlainYearMonth(1989, 1);
const plainYearMonth2 = new Temporal.PlainYearMonth(2024, 11);
expect(formatter.formatRange(plainYearMonth1, plainYearMonth2)).toBe("1/1989 11/2024");
expect(formatter.formatRange(plainYearMonth1, plainYearMonth2)).toBe("1989-01 2024-11");
});
test("Temporal.PlainMonthDay", () => {
const plainMonthDay1 = new Temporal.PlainMonthDay(1, 23);
const plainMonthDay2 = new Temporal.PlainMonthDay(11, 27);
expect(formatter.formatRange(plainMonthDay1, plainMonthDay2)).toBe("1/23 11/27");
expect(formatter.formatRange(plainMonthDay1, plainMonthDay2)).toBe("01-23 11-27");
});
test("Temporal.PlainTime", () => {
@ -347,7 +347,7 @@ describe("Temporal objects", () => {
const instant1 = new Temporal.Instant(601546251000000000n);
const instant2 = new Temporal.Instant(1732740069000000000n);
expect(formatter.formatRange(instant1, instant2)).toBe(
"1/23/1989, 8:10:51 AM 11/27/2024, 8:41:09 PM"
"1989-01-23, 8:10:51 AM 2024-11-27, 8:41:09 PM"
);
});
});

View file

@ -736,17 +736,17 @@ describe("Temporal objects", () => {
const plainDate1 = new Temporal.PlainDate(1989, 1, 23);
const plainDate2 = new Temporal.PlainDate(2024, 11, 27);
expect(formatter.formatRangeToParts(plainDate1, plainDate2)).toEqual([
{ type: "month", value: "1", source: "startRange" },
{ type: "literal", value: "/", source: "startRange" },
{ type: "day", value: "23", source: "startRange" },
{ type: "literal", value: "/", source: "startRange" },
{ type: "year", value: "1989", source: "startRange" },
{ type: "literal", value: "-", source: "startRange" },
{ type: "month", value: "01", source: "startRange" },
{ type: "literal", value: "-", source: "startRange" },
{ type: "day", value: "23", source: "startRange" },
{ type: "literal", value: " ", source: "shared" },
{ type: "month", value: "11", source: "endRange" },
{ type: "literal", value: "/", source: "endRange" },
{ type: "day", value: "27", source: "endRange" },
{ type: "literal", value: "/", source: "endRange" },
{ type: "year", value: "2024", source: "endRange" },
{ type: "literal", value: "-", source: "endRange" },
{ type: "month", value: "11", source: "endRange" },
{ type: "literal", value: "-", source: "endRange" },
{ type: "day", value: "27", source: "endRange" },
]);
});
@ -754,13 +754,13 @@ describe("Temporal objects", () => {
const plainYearMonth1 = new Temporal.PlainYearMonth(1989, 1);
const plainYearMonth2 = new Temporal.PlainYearMonth(2024, 11);
expect(formatter.formatRangeToParts(plainYearMonth1, plainYearMonth2)).toEqual([
{ type: "month", value: "1", source: "startRange" },
{ type: "literal", value: "/", source: "startRange" },
{ type: "year", value: "1989", source: "startRange" },
{ type: "literal", value: "-", source: "startRange" },
{ type: "month", value: "01", source: "startRange" },
{ type: "literal", value: " ", source: "shared" },
{ type: "month", value: "11", source: "endRange" },
{ type: "literal", value: "/", source: "endRange" },
{ type: "year", value: "2024", source: "endRange" },
{ type: "literal", value: "-", source: "endRange" },
{ type: "month", value: "11", source: "endRange" },
]);
});
@ -768,12 +768,12 @@ describe("Temporal objects", () => {
const plainMonthDay1 = new Temporal.PlainMonthDay(1, 23);
const plainMonthDay2 = new Temporal.PlainMonthDay(11, 27);
expect(formatter.formatRangeToParts(plainMonthDay1, plainMonthDay2)).toEqual([
{ type: "month", value: "1", source: "startRange" },
{ type: "literal", value: "/", source: "startRange" },
{ type: "month", value: "01", source: "startRange" },
{ type: "literal", value: "-", source: "startRange" },
{ type: "day", value: "23", source: "startRange" },
{ type: "literal", value: " ", source: "shared" },
{ type: "month", value: "11", source: "endRange" },
{ type: "literal", value: "/", source: "endRange" },
{ type: "literal", value: "-", source: "endRange" },
{ type: "day", value: "27", source: "endRange" },
]);
});
@ -804,11 +804,11 @@ describe("Temporal objects", () => {
const instant1 = new Temporal.Instant(601546251000000000n);
const instant2 = new Temporal.Instant(1732740069000000000n);
expect(formatter.formatRangeToParts(instant1, instant2)).toEqual([
{ type: "month", value: "1", source: "startRange" },
{ type: "literal", value: "/", source: "startRange" },
{ type: "day", value: "23", source: "startRange" },
{ type: "literal", value: "/", source: "startRange" },
{ type: "year", value: "1989", source: "startRange" },
{ type: "literal", value: "-", source: "startRange" },
{ type: "month", value: "01", source: "startRange" },
{ type: "literal", value: "-", source: "startRange" },
{ type: "day", value: "23", source: "startRange" },
{ type: "literal", value: ", ", source: "startRange" },
{ type: "hour", value: "8", source: "startRange" },
{ type: "literal", value: ":", source: "startRange" },
@ -818,11 +818,11 @@ describe("Temporal objects", () => {
{ type: "literal", value: " ", source: "startRange" },
{ type: "dayPeriod", value: "AM", source: "startRange" },
{ type: "literal", value: " ", source: "shared" },
{ type: "month", value: "11", source: "endRange" },
{ type: "literal", value: "/", source: "endRange" },
{ type: "day", value: "27", source: "endRange" },
{ type: "literal", value: "/", source: "endRange" },
{ type: "year", value: "2024", source: "endRange" },
{ type: "literal", value: "-", source: "endRange" },
{ type: "month", value: "11", source: "endRange" },
{ type: "literal", value: "-", source: "endRange" },
{ type: "day", value: "27", source: "endRange" },
{ type: "literal", value: ", ", source: "endRange" },
{ type: "hour", value: "8", source: "endRange" },
{ type: "literal", value: ":", source: "endRange" },

View file

@ -350,28 +350,28 @@ describe("Temporal objects", () => {
test("Temporal.PlainDate", () => {
const plainDate = new Temporal.PlainDate(1989, 1, 23);
expect(formatter.formatToParts(plainDate)).toEqual([
{ type: "month", value: "1" },
{ type: "literal", value: "/" },
{ type: "day", value: "23" },
{ type: "literal", value: "/" },
{ type: "year", value: "1989" },
{ type: "literal", value: "-" },
{ type: "month", value: "01" },
{ type: "literal", value: "-" },
{ type: "day", value: "23" },
]);
});
test("Temporal.PlainYearMonth", () => {
const plainYearMonth = new Temporal.PlainYearMonth(1989, 1);
expect(formatter.formatToParts(plainYearMonth)).toEqual([
{ type: "month", value: "1" },
{ type: "literal", value: "/" },
{ type: "year", value: "1989" },
{ type: "literal", value: "-" },
{ type: "month", value: "01" },
]);
});
test("Temporal.PlainMonthDay", () => {
const plainMonthDay = new Temporal.PlainMonthDay(1, 23);
expect(formatter.formatToParts(plainMonthDay)).toEqual([
{ type: "month", value: "1" },
{ type: "literal", value: "/" },
{ type: "month", value: "01" },
{ type: "literal", value: "-" },
{ type: "day", value: "23" },
]);
});
@ -392,11 +392,11 @@ describe("Temporal objects", () => {
test("Temporal.Instant", () => {
const instant = new Temporal.Instant(1732740069000000000n);
expect(formatter.formatToParts(instant)).toEqual([
{ type: "month", value: "11" },
{ type: "literal", value: "/" },
{ type: "day", value: "27" },
{ type: "literal", value: "/" },
{ type: "year", value: "2024" },
{ type: "literal", value: "-" },
{ type: "month", value: "11" },
{ type: "literal", value: "-" },
{ type: "day", value: "27" },
{ type: "literal", value: ", " },
{ type: "hour", value: "8" },
{ type: "literal", value: ":" },

View file

@ -95,6 +95,26 @@ describe("correct behavior", () => {
expect(en2.resolvedOptions().maximumFractionDigits).toBe(2);
});
test("currency digits are not used for non-standard notation", () => {
const en1 = new Intl.NumberFormat("en", {
style: "currency",
currency: "USD",
notation: "engineering",
});
expect(en1.resolvedOptions().style).toBe("currency");
expect(en1.resolvedOptions().minimumFractionDigits).toBe(0);
expect(en1.resolvedOptions().maximumFractionDigits).toBe(3);
const en2 = new Intl.NumberFormat("en", {
style: "currency",
currency: "USD",
notation: "compact",
});
expect(en2.resolvedOptions().style).toBe("currency");
expect(en2.resolvedOptions().minimumFractionDigits).toBe(0);
expect(en2.resolvedOptions().maximumFractionDigits).toBe(0);
});
test("other style options default to min fraction digits of 0 and max fraction digits of 0 or 3", () => {
const en1 = new Intl.NumberFormat("en", { style: "decimal" });
expect(en1.resolvedOptions().style).toBe("decimal");

View file

@ -129,6 +129,11 @@ describe("second", () => {
runTest("second", "narrow", "auto", en, ar, pl);
});
test("numberingSystem set via locale options", () => {
const formatter = new Intl.RelativeTimeFormat("en", { numberingSystem: "arab" });
expect(formatter.format(1, "second")).toBe("in ١ second");
});
});
describe("minute", () => {

View file

@ -0,0 +1,12 @@
test("iterate over bogus proxy", () => {
expect(() => {
let proxy = new Proxy([123], {
getOwnPropertyDescriptor: function (p) {
return undefined;
},
});
for (const p in proxy) {
}
}).toThrow();
});

View file

@ -0,0 +1,33 @@
test("Proxied constructor should handle argument_buffer reallocation during prototype get()", () => {
function foo() {}
let handler = {
get() {
// prettier-ignore
foo(
// make extra sure we trigger a reallocation
0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41
);
return null;
},
};
function Construct() {
// later use dangling pointer
console.log(arguments);
}
let ConstructProxy = new Proxy(Construct, handler);
new ConstructProxy(0x1);
});

View file

@ -218,6 +218,15 @@ ALWAYS_INLINE ExecutionResult OpCode_FailForks::execute(MatchInput const& input,
return ExecutionResult::Failed_ExecuteLowPrioForks;
}
ALWAYS_INLINE ExecutionResult OpCode_PopSaved::execute(MatchInput const& input, MatchState&) const
{
if (input.saved_positions.is_empty() || input.saved_code_unit_positions.is_empty())
return ExecutionResult::Failed_ExecuteLowPrioForks;
input.saved_positions.take_last();
input.saved_code_unit_positions.take_last();
return ExecutionResult::Failed_ExecuteLowPrioForks;
}
ALWAYS_INLINE ExecutionResult OpCode_Jump::execute(MatchInput const&, MatchState& state) const
{
state.instruction_position += offset();

View file

@ -31,6 +31,7 @@ using ByteCodeValueType = u64;
__ENUMERATE_OPCODE(ForkReplaceJump) \
__ENUMERATE_OPCODE(ForkReplaceStay) \
__ENUMERATE_OPCODE(FailForks) \
__ENUMERATE_OPCODE(PopSaved) \
__ENUMERATE_OPCODE(SaveLeftCaptureGroup) \
__ENUMERATE_OPCODE(SaveRightCaptureGroup) \
__ENUMERATE_OPCODE(SaveRightNamedCaptureGroup) \
@ -266,9 +267,15 @@ public:
switch (type) {
case LookAroundType::LookAhead: {
// SAVE
// FORKJUMP _BODY
// POPSAVED
// LABEL _BODY
// REGEXP BODY
// RESTORE
empend((ByteCodeValueType)OpCodeId::Save);
empend((ByteCodeValueType)OpCodeId::ForkJump);
empend((ByteCodeValueType)1);
empend((ByteCodeValueType)OpCodeId::PopSaved);
extend(move(lookaround_body));
empend((ByteCodeValueType)OpCodeId::Restore);
return;
@ -613,6 +620,14 @@ public:
ByteString arguments_string() const override { return ByteString::empty(); }
};
class OpCode_PopSaved final : public OpCode {
public:
ExecutionResult execute(MatchInput const& input, MatchState& state) const override;
ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::PopSaved; }
ALWAYS_INLINE size_t size() const override { return 1; }
ByteString arguments_string() const override { return ByteString::empty(); }
};
class OpCode_Save final : public OpCode {
public:
ExecutionResult execute(MatchInput const& input, MatchState& state) const override;

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2021, Max Wipfli <mail@maxwipfli.ch>
* Copyright (c) 2023-2024, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2023-2025, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -1558,23 +1558,32 @@ Optional<URL> Parser::basic_parse(StringView raw_input, Optional<URL const&> bas
buffer.clear();
state = State::Fragment;
}
// 3. Otherwise:
else {
// 1. If c is not the EOF code point, not a URL code point, and not U+0025 (%), invalid-URL-unit validation error.
if (code_point != end_of_file && !is_url_code_point(code_point) && code_point != '%')
// 3. Otherwise, if c is U+0020 SPACE:
else if (code_point == ' ') {
// 1. If remaining starts with U+003F (?) or U+003F (#), then append "%20" to urls path.
if (auto remaining = get_remaining(); remaining.starts_with('?') || remaining.starts_with('#')) {
buffer.append("%20"sv);
}
// 2. Otherwise, append U+0020 SPACE to urls path.
else {
buffer.append(' ');
}
}
// 4. Otherwise, if c is not the EOF code point:
else if (code_point != end_of_file) {
// 1. If c is not a URL code point and not U+0025 (%), invalid-URL-unit validation error.
if (!is_url_code_point(code_point) && code_point != '%')
report_validation_error();
// 2. If c is U+0025 (%) and remaining does not start with two ASCII hex digits, validation error.
// 2. If c is U+0025 (%) and remaining does not start with two ASCII hex digits, invalid-URL-unit validation error.
if (code_point == '%' && !remaining_starts_with_two_ascii_hex_digits())
report_validation_error();
// 3. If c is not the EOF code point, UTF-8 percent-encode c using the C0 control percent-encode set and append the result to urls path.
if (code_point != end_of_file) {
append_percent_encoded_if_necessary(buffer, code_point, PercentEncodeSet::C0Control);
} else {
url->m_data->paths[0] = buffer.to_string_without_validation();
buffer.clear();
}
// 3. UTF-8 percent-encode c using the C0 control percent-encode set and append the result to urls path.
append_percent_encoded_if_necessary(buffer, code_point, PercentEncodeSet::C0Control);
} else {
url->m_data->paths[0] = buffer.to_string_without_validation();
buffer.clear();
}
break;
// -> query state, https://url.spec.whatwg.org/#query-state

Some files were not shown because too many files have changed in this diff Show more