diff --git a/AK/Assertions.h b/AK/Assertions.h index 53766c2994d125..9d77a4090a871d 100644 --- a/AK/Assertions.h +++ b/AK/Assertions.h @@ -21,4 +21,9 @@ static constexpr bool TODO = false; # define TODO() VERIFY(TODO) /* NOLINT(cert-dcl03-c,misc-static-assert) No, this can't be static_assert, it's a runtime check */ # define TODO_AARCH64() VERIFY(TODO) /* NOLINT(cert-dcl03-c,misc-static-assert) No, this can't be static_assert, it's a runtime check */ # define TODO_RISCV64() VERIFY(TODO) /* NOLINT(cert-dcl03-c,misc-static-assert) No, this can't be static_assert, it's a runtime check */ + +// For LB compatibility +// Note: Assertions in LB are removed in release builds +# define ASSERT(expr) VERIFY(expr) +# define ASSERT_NOT_REACHED() VERIFY_NOT_REACHED() #endif diff --git a/AK/ByteBuffer.h b/AK/ByteBuffer.h index f1d57e3e33221f..defb51e315af33 100644 --- a/AK/ByteBuffer.h +++ b/AK/ByteBuffer.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include #include @@ -298,6 +299,23 @@ class ByteBuffer { operator ReadonlyBytes() const { return bytes(); } ALWAYS_INLINE size_t capacity() const { return m_inline ? inline_capacity : m_outline_capacity; } + ALWAYS_INLINE bool is_inline() const { return m_inline; } + + struct OutlineBuffer { + Bytes buffer; + size_t capacity { 0 }; + }; + Optional leak_outline_buffer(Badge) + { + if (m_inline) + return {}; + + auto buffer = bytes(); + m_inline = true; + m_size = 0; + + return OutlineBuffer { buffer, capacity() }; + } private: void move_from(ByteBuffer&& other) diff --git a/AK/FlyString.cpp b/AK/FlyString.cpp index b7cd4d0e5dcf2c..80800fc4e54850 100644 --- a/AK/FlyString.cpp +++ b/AK/FlyString.cpp @@ -50,6 +50,8 @@ FlyString FlyString::from_utf8_without_validation(ReadonlyBytes string) FlyString::FlyString(String const& string) { + ASSERT(!string.is_invalid()); + if (string.is_short_string()) { m_data = string; return; diff --git a/AK/FlyString.h b/AK/FlyString.h index 90ca5d16694b97..ce54a352b01857 100644 --- a/AK/FlyString.h +++ b/AK/FlyString.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -77,12 +78,129 @@ class FlyString { } private: + friend class Optional; + + explicit FlyString(nullptr_t) + : m_data(Detail::StringBase(nullptr)) + { + } + explicit FlyString(Detail::StringBase data) : m_data(move(data)) { } Detail::StringBase m_data; + + bool is_invalid() const { return m_data.is_invalid(); } +}; + +template<> +class Optional : public OptionalBase { + template + friend class Optional; + +public: + using ValueType = FlyString; + + Optional() = default; + + template V> + Optional(V) { } + + Optional(Optional const& other) + { + if (other.has_value()) + m_value = other.m_value; + } + + Optional(Optional&& other) + : m_value(other.m_value) + { + } + + template + requires(!IsSame>) + explicit(!IsConvertible) Optional(U&& value) + requires(!IsSame, Optional> && IsConstructible) + : m_value(forward(value)) + { + } + + template V> + Optional& operator=(V) + { + clear(); + return *this; + } + + Optional& operator=(Optional const& other) + { + if (this != &other) { + clear(); + m_value = other.m_value; + } + return *this; + } + + Optional& operator=(Optional&& other) + { + if (this != &other) { + clear(); + m_value = other.m_value; + } + return *this; + } + + template + ALWAYS_INLINE bool operator==(Optional const& other) const + { + return has_value() == other.has_value() && (!has_value() || value() == other.value()); + } + + template + ALWAYS_INLINE bool operator==(O const& other) const + { + return has_value() && value() == other; + } + + void clear() + { + m_value = FlyString(nullptr); + } + + [[nodiscard]] bool has_value() const + { + return !m_value.is_invalid(); + } + + [[nodiscard]] FlyString& value() & + { + VERIFY(has_value()); + return m_value; + } + + [[nodiscard]] FlyString const& value() const& + { + VERIFY(has_value()); + return m_value; + } + + [[nodiscard]] FlyString value() && + { + return release_value(); + } + + [[nodiscard]] FlyString release_value() + { + VERIFY(has_value()); + FlyString released_value = m_value; + clear(); + return released_value; + } + +private: + FlyString m_value = FlyString(nullptr); }; template<> diff --git a/AK/Forward.h b/AK/Forward.h index ad8b42c8349a1b..3ef4b04b0824fb 100644 --- a/AK/Forward.h +++ b/AK/Forward.h @@ -16,6 +16,8 @@ namespace AK { namespace Detail { template class ByteBuffer; + +class StringData; } enum class TrailingCodePointTransformation : u8; @@ -128,6 +130,16 @@ class NonnullOwnPtr; template class Optional; +#ifndef KERNEL + +template<> +class Optional; + +template<> +class Optional; + +#endif + #ifdef KERNEL template class NonnullLockRefPtr; diff --git a/AK/Noncopyable.h b/AK/Noncopyable.h index 9c08a9b3f9fd71..c9bd8d5bd36750 100644 --- a/AK/Noncopyable.h +++ b/AK/Noncopyable.h @@ -25,3 +25,46 @@ public: \ public: \ c(c const&) = default; \ c& operator=(c const&) = default + +#define AK_MAKE_CONDITIONALLY_NONMOVABLE(c, ...) \ +public: \ + c(c&&) \ + requires(!(AK::Detail::IsMoveConstructible __VA_ARGS__)) \ + = delete; \ + c& operator=(c&&) \ + requires(!(AK::Detail::IsMoveConstructible __VA_ARGS__) \ + || !(AK::Detail::IsDestructible __VA_ARGS__)) \ + = delete + +#define AK_MAKE_CONDITIONALLY_MOVABLE(c, T) \ + AK_MAKE_CONDITIONALLY_NONMOVABLE(c, T); \ + c(c&&) = default; \ + c& operator=(c&&) = default + +#define AK_MAKE_CONDITIONALLY_NONCOPYABLE(c, ...) \ +public: \ + c(c const&) \ + requires(!(AK::Detail::IsCopyConstructible __VA_ARGS__)) \ + = delete; \ + c& operator=(c const&) \ + requires(!(AK::Detail::IsCopyConstructible __VA_ARGS__) \ + || !(AK::Detail::IsDestructible __VA_ARGS__)) \ + = delete + +#define AK_MAKE_CONDITIONALLY_COPYABLE(c, ...) \ + AK_MAKE_CONDITIONALLY_NONCOPYABLE(c, __VA_ARGS__); \ + c(c const&) = default; \ + c& operator=(c const&) = default + +#define AK_MAKE_CONDITIONALLY_NONDESTRUCTIBLE(c, ...) \ +public: \ + ~c() \ + requires(!(AK::Detail::IsDestructible __VA_ARGS__)) \ + = delete + +#define AK_MAKE_CONDITIONALLY_DESTRUCTIBLE(c, ...) \ +public: \ + ~c() \ + requires(!(AK::Detail::IsDestructible __VA_ARGS__)) \ + = delete; \ + ~c() = default diff --git a/AK/Optional.h b/AK/Optional.h index 18968d6e11fb2d..b1cc8351a27d56 100644 --- a/AK/Optional.h +++ b/AK/Optional.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include #include @@ -48,8 +49,127 @@ struct OptionalNone { explicit OptionalNone() = default; }; +template> +requires(!IsLvalueReference) class [[nodiscard]] OptionalBase { +public: + using ValueType = T; + + template V> + Self& operator=(V) + { + static_cast(*this).clear(); + return static_cast(*this); + } + + [[nodiscard]] ALWAYS_INLINE T* ptr() & + { + return static_cast(*this).has_value() ? __builtin_launder(reinterpret_cast(&static_cast(*this).value())) : nullptr; + } + + [[nodiscard]] ALWAYS_INLINE T const* ptr() const& + { + return static_cast(*this).has_value() ? __builtin_launder(reinterpret_cast(&static_cast(*this).value())) : nullptr; + } + + [[nodiscard]] ALWAYS_INLINE T value_or(T const& fallback) const& + { + if (static_cast(*this).has_value()) + return static_cast(*this).value(); + return fallback; + } + + [[nodiscard]] ALWAYS_INLINE T value_or(T&& fallback) && + { + if (static_cast(*this).has_value()) + return move(static_cast(*this).value()); + return move(fallback); + } + + template + [[nodiscard]] ALWAYS_INLINE O value_or_lazy_evaluated(Callback callback) const + { + if (static_cast(*this).has_value()) + return static_cast(*this).value(); + return callback(); + } + + template + [[nodiscard]] ALWAYS_INLINE Optional value_or_lazy_evaluated_optional(Callback callback) const + { + if (static_cast(*this).has_value()) + return static_cast(*this).value(); + return callback(); + } + + template + [[nodiscard]] ALWAYS_INLINE ErrorOr try_value_or_lazy_evaluated(Callback callback) const + { + if (static_cast(*this).has_value()) + return static_cast(*this).value(); + return TRY(callback()); + } + + template + [[nodiscard]] ALWAYS_INLINE ErrorOr> try_value_or_lazy_evaluated_optional(Callback callback) const + { + if (static_cast(*this).has_value()) + return static_cast(*this).value(); + return TRY(callback()); + } + + template + ALWAYS_INLINE bool operator==(Optional const& other) const + { + return static_cast(*this).has_value() == (other).has_value() + && (!static_cast(*this).has_value() || static_cast(*this).value() == (other).value()); + } + + template + requires(!Detail::IsBaseOf, O>) + ALWAYS_INLINE bool operator==(O const& other) const + { + return static_cast(*this).has_value() && static_cast(*this).value() == other; + } + + [[nodiscard]] ALWAYS_INLINE T const& operator*() const { return static_cast(*this).value(); } + [[nodiscard]] ALWAYS_INLINE T& operator*() { return static_cast(*this).value(); } + + ALWAYS_INLINE T const* operator->() const { return &static_cast(*this).value(); } + ALWAYS_INLINE T* operator->() { return &static_cast(*this).value(); } + + template()(declval())), auto IsErrorOr = IsSpecializationOf, typename OptionalType = Optional>> + ALWAYS_INLINE Conditional, OptionalType> map(F&& mapper) + { + if constexpr (IsErrorOr) { + if (static_cast(*this).has_value()) + return OptionalType { TRY(mapper(static_cast(*this).value())) }; + return OptionalType {}; + } else { + if (static_cast(*this).has_value()) + return OptionalType { mapper(static_cast(*this).value()) }; + + return OptionalType {}; + } + } + + template()(declval())), auto IsErrorOr = IsSpecializationOf, typename OptionalType = Optional>> + ALWAYS_INLINE Conditional, OptionalType> map(F&& mapper) const + { + if constexpr (IsErrorOr) { + if (static_cast(*this).has_value()) + return OptionalType { TRY(mapper(static_cast(*this).value())) }; + return OptionalType {}; + } else { + if (static_cast(*this).has_value()) + return OptionalType { mapper(static_cast(*this).value()) }; + + return OptionalType {}; + } + } +}; + template -requires(!IsLvalueReference) class [[nodiscard]] Optional { +requires(!IsLvalueReference) class [[nodiscard]] Optional : public OptionalBase> { template friend class Optional; @@ -70,28 +190,9 @@ requires(!IsLvalueReference) class [[nodiscard]] Optional { return *this; } - Optional(Optional const& other) - requires(!IsCopyConstructible) - = delete; - Optional(Optional const& other) = default; - - Optional(Optional&& other) - requires(!IsMoveConstructible) - = delete; - - Optional& operator=(Optional const&) - requires(!IsCopyConstructible || !IsDestructible) - = delete; - Optional& operator=(Optional const&) = default; - - Optional& operator=(Optional&& other) - requires(!IsMoveConstructible || !IsDestructible) - = delete; - - ~Optional() - requires(!IsDestructible) - = delete; - ~Optional() = default; + AK_MAKE_CONDITIONALLY_COPYABLE(Optional, ); + AK_MAKE_CONDITIONALLY_NONMOVABLE(Optional, ); + AK_MAKE_CONDITIONALLY_DESTRUCTIBLE(Optional, ); ALWAYS_INLINE Optional(Optional const& other) requires(!IsTriviallyCopyConstructible) @@ -110,7 +211,7 @@ requires(!IsLvalueReference) class [[nodiscard]] Optional { template requires(IsConstructible && !IsSpecializationOf && !IsSpecializationOf && !IsLvalueReference) ALWAYS_INLINE explicit Optional(Optional const& other) - : m_has_value(other.m_has_value) + : m_has_value(other.has_value()) { if (other.has_value()) new (&m_storage) T(other.value()); @@ -118,7 +219,7 @@ requires(!IsLvalueReference) class [[nodiscard]] Optional { template requires(IsConstructible && !IsSpecializationOf && !IsSpecializationOf && !IsLvalueReference) ALWAYS_INLINE explicit Optional(Optional&& other) - : m_has_value(other.m_has_value) + : m_has_value(other.has_value()) { if (other.has_value()) new (&m_storage) T(other.release_value()); @@ -228,88 +329,6 @@ requires(!IsLvalueReference) class [[nodiscard]] Optional { return released_value; } - [[nodiscard]] ALWAYS_INLINE T value_or(T const& fallback) const& - { - if (m_has_value) - return value(); - return fallback; - } - - [[nodiscard]] ALWAYS_INLINE T value_or(T&& fallback) && - { - if (m_has_value) - return move(value()); - return move(fallback); - } - - template - [[nodiscard]] ALWAYS_INLINE T value_or_lazy_evaluated(Callback callback) const - { - if (m_has_value) - return value(); - return callback(); - } - - template - [[nodiscard]] ALWAYS_INLINE Optional value_or_lazy_evaluated_optional(Callback callback) const - { - if (m_has_value) - return value(); - return callback(); - } - - template - [[nodiscard]] ALWAYS_INLINE ErrorOr try_value_or_lazy_evaluated(Callback callback) const - { - if (m_has_value) - return value(); - return TRY(callback()); - } - - template - [[nodiscard]] ALWAYS_INLINE ErrorOr> try_value_or_lazy_evaluated_optional(Callback callback) const - { - if (m_has_value) - return value(); - return TRY(callback()); - } - - ALWAYS_INLINE T const& operator*() const { return value(); } - ALWAYS_INLINE T& operator*() { return value(); } - - ALWAYS_INLINE T const* operator->() const { return &value(); } - ALWAYS_INLINE T* operator->() { return &value(); } - - template()(declval())), auto IsErrorOr = IsSpecializationOf, typename OptionalType = Optional>> - ALWAYS_INLINE Conditional, OptionalType> map(F&& mapper) - { - if constexpr (IsErrorOr) { - if (m_has_value) - return OptionalType { TRY(mapper(value())) }; - return OptionalType {}; - } else { - if (m_has_value) - return OptionalType { mapper(value()) }; - - return OptionalType {}; - } - } - - template()(declval())), auto IsErrorOr = IsSpecializationOf, typename OptionalType = Optional>> - ALWAYS_INLINE Conditional, OptionalType> map(F&& mapper) const - { - if constexpr (IsErrorOr) { - if (m_has_value) - return OptionalType { TRY(mapper(value())) }; - return OptionalType {}; - } else { - if (m_has_value) - return OptionalType { mapper(value()) }; - - return OptionalType {}; - } - } - private: alignas(T) u8 m_storage[sizeof(T)]; bool m_has_value { false }; diff --git a/AK/String.cpp b/AK/String.cpp index 8e6c552df5d8b8..4734dfd810d0df 100644 --- a/AK/String.cpp +++ b/AK/String.cpp @@ -67,6 +67,23 @@ ErrorOr String::from_stream(Stream& stream, size_t byte_count) return result; } +ErrorOr String::from_string_builder(Badge, StringBuilder& builder) +{ + if (!Utf8View { builder.string_view() }.validate()) + return Error::from_string_literal("String::from_string_builder: Input was not valid UTF-8"); + + String result; + result.replace_with_string_builder(builder); + return result; +} + +String String::from_string_builder_without_validation(Badge, StringBuilder& builder) +{ + String result; + result.replace_with_string_builder(builder); + return result; +} + ErrorOr String::repeated(u32 code_point, size_t count) { VERIFY(is_unicode(code_point)); diff --git a/AK/String.h b/AK/String.h index 07da894c70c6b2..999b9c4e858b25 100644 --- a/AK/String.h +++ b/AK/String.h @@ -64,6 +64,9 @@ class String : public Detail::StringBase { [[nodiscard]] static String from_utf8_without_validation(ReadonlyBytes); + static ErrorOr from_string_builder(Badge, StringBuilder&); + [[nodiscard]] static String from_string_builder_without_validation(Badge, StringBuilder&); + // Creates a new String by reading byte_count bytes from a UTF-8 encoded Stream. static ErrorOr from_stream(Stream&, size_t byte_count); @@ -200,6 +203,7 @@ class String : public Detail::StringBase { private: friend class ::AK::FlyString; + friend class Optional; using ShortString = Detail::ShortString; @@ -207,6 +211,117 @@ class String : public Detail::StringBase { : StringBase(move(base)) { } + + explicit constexpr String(nullptr_t) + : StringBase(nullptr) + { + } +}; + +template<> +class Optional : public OptionalBase { + template + friend class Optional; + +public: + using ValueType = String; + + Optional() = default; + + template V> + Optional(V) { } + + Optional(Optional const& other) + { + if (other.has_value()) + m_value = other.m_value; + } + + Optional(Optional&& other) + : m_value(move(other.m_value)) + { + } + + template + requires(!IsSame>) + explicit(!IsConvertible) Optional(U&& value) + requires(!IsSame, Optional> && IsConstructible) + : m_value(forward(value)) + { + } + + template V> + Optional& operator=(V) + { + clear(); + return *this; + } + + Optional& operator=(Optional const& other) + { + if (this != &other) { + m_value = other.m_value; + } + return *this; + } + + Optional& operator=(Optional&& other) + { + if (this != &other) { + m_value = move(other.m_value); + } + return *this; + } + + template + ALWAYS_INLINE bool operator==(Optional const& other) const + { + return has_value() == other.has_value() && (!has_value() || value() == other.value()); + } + + template + ALWAYS_INLINE bool operator==(O const& other) const + { + return has_value() && value() == other; + } + + void clear() + { + m_value = String(nullptr); + } + + [[nodiscard]] bool has_value() const + { + return !m_value.is_invalid(); + } + + [[nodiscard]] String& value() & + { + VERIFY(has_value()); + return m_value; + } + + [[nodiscard]] String const& value() const& + { + VERIFY(has_value()); + return m_value; + } + + [[nodiscard]] String value() && + { + return release_value(); + } + + [[nodiscard]] String release_value() + { + VERIFY(has_value()); + String released_value = m_value; + clear(); + return released_value; + } + +private: + String m_value { nullptr }; }; template<> diff --git a/AK/StringBase.cpp b/AK/StringBase.cpp index 62746b91a58495..dd453bc4cf08f5 100644 --- a/AK/StringBase.cpp +++ b/AK/StringBase.cpp @@ -58,6 +58,7 @@ StringBase& StringBase::operator=(StringBase const& other) ReadonlyBytes StringBase::bytes() const { + ASSERT(!is_invalid()); if (is_short_string()) return m_short_string.bytes(); return m_data->bytes(); @@ -65,6 +66,7 @@ ReadonlyBytes StringBase::bytes() const u32 StringBase::hash() const { + ASSERT(!is_invalid()); if (is_short_string()) { auto bytes = this->bytes(); return string_hash(reinterpret_cast(bytes.data()), bytes.size()); @@ -74,6 +76,7 @@ u32 StringBase::hash() const size_t StringBase::byte_count() const { + ASSERT(!is_invalid()); if (is_short_string()) return m_short_string.byte_count_and_short_string_flag >> 1; return m_data->byte_count(); @@ -81,6 +84,7 @@ size_t StringBase::byte_count() const bool StringBase::operator==(StringBase const& other) const { + ASSERT(!is_invalid()); if (is_short_string()) return m_data == other.m_data; if (other.is_short_string()) @@ -90,8 +94,23 @@ bool StringBase::operator==(StringBase const& other) const return bytes() == other.bytes(); } +void StringBase::replace_with_string_builder(StringBuilder& builder) +{ + ASSERT(!is_invalid()); + if (builder.length() <= MAX_SHORT_STRING_BYTE_COUNT) { + return replace_with_new_short_string(builder.length(), [&](Bytes buffer) { + builder.string_view().bytes().copy_to(buffer); + }); + } + + destroy_string(); + + m_data = &StringData::create_from_string_builder(builder).leak_ref(); +} + ErrorOr StringBase::replace_with_uninitialized_buffer(size_t byte_count) { + ASSERT(!is_invalid()); if (byte_count <= MAX_SHORT_STRING_BYTE_COUNT) return replace_with_uninitialized_short_string(byte_count); @@ -103,6 +122,7 @@ ErrorOr StringBase::replace_with_uninitialized_buffer(size_t byte_count) ErrorOr StringBase::substring_from_byte_offset_with_shared_superstring(size_t start, size_t length) const { + ASSERT(!is_invalid()); VERIFY(start + length <= byte_count()); if (length == 0) diff --git a/AK/StringBase.h b/AK/StringBase.h index 541f7b8bdded4d..f138ccc8c0027a 100644 --- a/AK/StringBase.h +++ b/AK/StringBase.h @@ -72,6 +72,8 @@ class StringBase { [[nodiscard]] ALWAYS_INLINE FlatPtr raw(Badge) const { return bit_cast(m_data); } protected: + bool is_invalid() const { return m_invalid_tag == UINTPTR_MAX; } + template ErrorOr replace_with_new_string(size_t byte_count, Func&& callback) { @@ -89,6 +91,8 @@ class StringBase { callback(buffer); } + void replace_with_string_builder(StringBuilder&); + // This is not a trivial operation with storage, so it does not belong here. Unfortunately, it // is impossible to implement it without access to StringData. ErrorOr substring_from_byte_offset_with_shared_superstring(size_t start, size_t byte_count) const; @@ -102,6 +106,11 @@ class StringBase { explicit StringBase(NonnullRefPtr); + explicit constexpr StringBase(nullptr_t) + : m_invalid_tag(UINTPTR_MAX) + { + } + explicit constexpr StringBase(ShortString short_string) : m_short_string(short_string) { @@ -124,6 +133,7 @@ class StringBase { union { ShortString m_short_string; Detail::StringData const* m_data { nullptr }; + uintptr_t m_invalid_tag; }; }; diff --git a/AK/StringBuilder.cpp b/AK/StringBuilder.cpp index cdf8d2f5f6291d..52c018a15a8698 100644 --- a/AK/StringBuilder.cpp +++ b/AK/StringBuilder.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -22,57 +23,58 @@ namespace AK { -inline ErrorOr StringBuilder::will_append(size_t size) +static constexpr auto STRING_BASE_PREFIX_SIZE = sizeof(Detail::StringData); + +static ErrorOr create_buffer(size_t capacity) { - if (m_use_inline_capacity_only == UseInlineCapacityOnly::Yes) { - VERIFY(m_buffer.capacity() == StringBuilder::inline_capacity); - Checked current_pointer = m_buffer.size(); - current_pointer += size; - VERIFY(!current_pointer.has_overflow()); - if (current_pointer <= StringBuilder::inline_capacity) { - return {}; - } - return Error::from_errno(ENOMEM); - } + StringBuilder::Buffer buffer; - Checked needed_capacity = m_buffer.size(); - needed_capacity += size; - VERIFY(!needed_capacity.has_overflow()); - // Prefer to completely use the existing capacity first - if (needed_capacity <= m_buffer.capacity()) - return {}; - Checked expanded_capacity = needed_capacity; - expanded_capacity *= 2; - VERIFY(!expanded_capacity.has_overflow()); - TRY(m_buffer.try_ensure_capacity(expanded_capacity.value())); - return {}; + if (capacity > StringBuilder::inline_capacity) + TRY(buffer.try_ensure_capacity(STRING_BASE_PREFIX_SIZE + capacity)); + + TRY(buffer.try_resize(STRING_BASE_PREFIX_SIZE)); + return buffer; } ErrorOr StringBuilder::create(size_t initial_capacity) { - StringBuilder builder; - TRY(builder.m_buffer.try_ensure_capacity(initial_capacity)); - return builder; + auto buffer = TRY(create_buffer(initial_capacity)); + return StringBuilder { move(buffer) }; } StringBuilder::StringBuilder(size_t initial_capacity) + : m_buffer(MUST(create_buffer(initial_capacity))) { - m_buffer.ensure_capacity(initial_capacity); } -StringBuilder::StringBuilder(UseInlineCapacityOnly use_inline_capacity_only) - : m_use_inline_capacity_only(use_inline_capacity_only) +StringBuilder::StringBuilder(Buffer buffer) + : m_buffer(move(buffer)) { } +inline ErrorOr StringBuilder::will_append(size_t size) +{ + Checked needed_capacity = m_buffer.size(); + needed_capacity += size; + VERIFY(!needed_capacity.has_overflow()); + // Prefer to completely use the existing capacity first + if (needed_capacity <= m_buffer.capacity()) + return {}; + Checked expanded_capacity = needed_capacity; + expanded_capacity *= 2; + VERIFY(!expanded_capacity.has_overflow()); + TRY(m_buffer.try_ensure_capacity(expanded_capacity.value())); + return {}; +} + size_t StringBuilder::length() const { - return m_buffer.size(); + return m_buffer.size() - STRING_BASE_PREFIX_SIZE; } bool StringBuilder::is_empty() const { - return m_buffer.is_empty(); + return length() == 0; } void StringBuilder::trim(size_t count) @@ -151,14 +153,18 @@ ByteString StringBuilder::to_byte_string() const return ByteString((char const*)data(), length()); } -ErrorOr StringBuilder::to_string() const +ErrorOr StringBuilder::to_string() { - return String::from_utf8(string_view()); + if (m_buffer.is_inline()) + return String::from_utf8(string_view()); + return String::from_string_builder({}, *this); } -String StringBuilder::to_string_without_validation() const +String StringBuilder::to_string_without_validation() { - return String::from_utf8_without_validation(string_view().bytes()); + if (m_buffer.is_inline()) + return String::from_utf8_without_validation(string_view().bytes()); + return String::from_string_builder_without_validation({}, *this); } FlyString StringBuilder::to_fly_string_without_validation() const @@ -174,22 +180,22 @@ ErrorOr StringBuilder::to_fly_string() const u8* StringBuilder::data() { - return m_buffer.data(); + return m_buffer.data() + STRING_BASE_PREFIX_SIZE; } u8 const* StringBuilder::data() const { - return m_buffer.data(); + return m_buffer.data() + STRING_BASE_PREFIX_SIZE; } StringView StringBuilder::string_view() const { - return StringView { data(), m_buffer.size() }; + return m_buffer.span().slice(STRING_BASE_PREFIX_SIZE); } void StringBuilder::clear() { - m_buffer.clear(); + m_buffer.resize(STRING_BASE_PREFIX_SIZE); } ErrorOr StringBuilder::try_append_code_point(u32 code_point) @@ -304,4 +310,14 @@ ErrorOr StringBuilder::try_append_escaped_for_json(StringView string) return {}; } +auto StringBuilder::leak_buffer_for_string_construction(Badge) -> Optional +{ + if (auto buffer = m_buffer.leak_outline_buffer({}); buffer.has_value()) { + clear(); + return buffer; + } + + return {}; +} + } diff --git a/AK/StringBuilder.h b/AK/StringBuilder.h index 923adfedb626b2..3f43e576f1fe93 100644 --- a/AK/StringBuilder.h +++ b/AK/StringBuilder.h @@ -18,17 +18,12 @@ class StringBuilder { public: static constexpr size_t inline_capacity = 256; + using Buffer = Detail::ByteBuffer; using OutputType = ByteString; static ErrorOr create(size_t initial_capacity = inline_capacity); explicit StringBuilder(size_t initial_capacity = inline_capacity); - - enum class UseInlineCapacityOnly { - Yes, - No, - }; - explicit StringBuilder(UseInlineCapacityOnly use_inline_capacity_only); ~StringBuilder() = default; ErrorOr try_append(StringView); @@ -73,8 +68,8 @@ class StringBuilder { [[nodiscard]] ByteString to_byte_string() const; #endif - [[nodiscard]] String to_string_without_validation() const; - ErrorOr to_string() const; + [[nodiscard]] String to_string_without_validation(); + ErrorOr to_string(); [[nodiscard]] FlyString to_fly_string_without_validation() const; ErrorOr to_fly_string() const; @@ -107,13 +102,16 @@ class StringBuilder { return {}; } + Optional leak_buffer_for_string_construction(Badge); + private: + explicit StringBuilder(Buffer); + ErrorOr will_append(size_t); u8* data(); u8 const* data() const; - UseInlineCapacityOnly m_use_inline_capacity_only { UseInlineCapacityOnly::No }; - Detail::ByteBuffer m_buffer; + Buffer m_buffer; }; } diff --git a/AK/StringData.h b/AK/StringData.h index a871c2871a35bd..c029afd641369e 100644 --- a/AK/StringData.h +++ b/AK/StringData.h @@ -6,7 +6,12 @@ #pragma once +#include +#include +#include #include +#include +#include #include namespace AK::Detail { @@ -16,25 +21,39 @@ class StringData final : public RefCounted { static ErrorOr> create_uninitialized(size_t byte_count, u8*& buffer) { VERIFY(byte_count); - void* slot = malloc(allocation_size_for_string_data(byte_count)); - if (!slot) { + + auto capacity = allocation_size_for_string_data(byte_count); + void* slot = malloc(capacity); + if (!slot) return Error::from_errno(ENOMEM); - } - auto new_string_data = adopt_ref(*new (slot) StringData(byte_count)); + + auto new_string_data = adopt_ref(*new (slot) StringData(byte_count, capacity)); buffer = const_cast(new_string_data->bytes().data()); return new_string_data; } + static NonnullRefPtr create_from_string_builder(StringBuilder& builder) + { + auto byte_count = builder.length(); + VERIFY(byte_count > MAX_SHORT_STRING_BYTE_COUNT); + + auto buffer = builder.leak_buffer_for_string_construction({}); + VERIFY(buffer.has_value()); // We should only arrive here if the buffer is outlined. + + return adopt_ref(*new (buffer->buffer.data()) StringData(byte_count, buffer->capacity)); + } + static ErrorOr> create_substring(StringData const& superstring, size_t start, size_t byte_count) { // Strings of MAX_SHORT_STRING_BYTE_COUNT bytes or less should be handled by the String short string optimization. VERIFY(byte_count > MAX_SHORT_STRING_BYTE_COUNT); - void* slot = malloc(sizeof(StringData) + sizeof(StringData::SubstringData)); - if (!slot) { + auto capacity = sizeof(StringData) + sizeof(StringData::SubstringData); + void* slot = malloc(capacity); + if (!slot) return Error::from_errno(ENOMEM); - } - return adopt_ref(*new (slot) StringData(superstring, start, byte_count)); + + return adopt_ref(*new (slot) StringData(superstring, start, byte_count, capacity)); } struct SubstringData { @@ -44,7 +63,7 @@ class StringData final : public RefCounted { void operator delete(void* ptr) { - kfree_sized(ptr, allocation_size_for_string_data(static_cast(ptr)->m_byte_count)); + kfree_sized(ptr, static_cast(ptr)->m_capacity); } ~StringData() @@ -95,13 +114,15 @@ class StringData final : public RefCounted { return sizeof(StringData) + (sizeof(char) * length); } - explicit StringData(size_t byte_count) + StringData(size_t byte_count, size_t capacity) : m_byte_count(byte_count) + , m_capacity(capacity) { } - StringData(StringData const& superstring, size_t start, size_t byte_count) + StringData(StringData const& superstring, size_t start, size_t byte_count, size_t capacity) : m_byte_count(byte_count) + , m_capacity(capacity) , m_substring(true) { auto& data = const_cast(substring_data()); @@ -121,6 +142,8 @@ class StringData final : public RefCounted { } u32 m_byte_count { 0 }; + u32 m_capacity { 0 }; + mutable unsigned m_hash { 0 }; mutable bool m_has_hash { false }; bool m_substring { false }; diff --git a/AK/Variant.h b/AK/Variant.h index 885f89570eaf45..7ae5619ef636d6 100644 --- a/AK/Variant.h +++ b/AK/Variant.h @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -267,30 +268,9 @@ struct Variant { } - Variant(Variant const&) - requires(!(IsCopyConstructible && ...)) - = delete; - Variant(Variant const&) = default; - - Variant(Variant&&) - requires(!(IsMoveConstructible && ...)) - = delete; - Variant(Variant&&) = default; - - ~Variant() - requires(!(IsDestructible && ...)) - = delete; - ~Variant() = default; - - Variant& operator=(Variant const&) - requires(!(IsCopyConstructible && ...) || !(IsDestructible && ...)) - = delete; - Variant& operator=(Variant const&) = default; - - Variant& operator=(Variant&&) - requires(!(IsMoveConstructible && ...) || !(IsDestructible && ...)) - = delete; - Variant& operator=(Variant&&) = default; + AK_MAKE_CONDITIONALLY_COPYABLE(Variant, &&...); + AK_MAKE_CONDITIONALLY_MOVABLE(Variant, &&...); + AK_MAKE_CONDITIONALLY_DESTRUCTIBLE(Variant, &&...); ALWAYS_INLINE Variant(Variant const& old) requires(!(IsTriviallyCopyConstructible && ...)) diff --git a/Tests/AK/TestOptional.cpp b/Tests/AK/TestOptional.cpp index ffac011f31d568..1aac3e05511190 100644 --- a/Tests/AK/TestOptional.cpp +++ b/Tests/AK/TestOptional.cpp @@ -8,7 +8,9 @@ #include #include +#include #include +#include #include TEST_CASE(basic_optional) @@ -300,3 +302,173 @@ TEST_CASE(comparison_reference) EXPECT_EQ(opt1, opt2); EXPECT_NE(opt1, opt3); } + +TEST_CASE(string_specialization) +{ + EXPECT_EQ(sizeof(Optional), sizeof(String)); + + { + Optional foo; + + EXPECT(!foo.has_value()); + + foo = "long_enough_to_be_allocated"_string; + + EXPECT(foo.has_value()); + EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv); + } + + { + Optional foo = "initial_value"_string; + + EXPECT(foo.has_value()); + EXPECT_EQ(foo.value(), "initial_value"sv); + + foo = "long_enough_to_be_allocated"_string; + + EXPECT(foo.has_value()); + EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv); + } + + { + Optional foo; + + EXPECT(!foo.has_value()); + + String bar = "long_enough_to_be_allocated"_string; + foo = bar; + + EXPECT(foo.has_value()); + EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv); + } + + { + Optional foo; + + EXPECT(!foo.has_value()); + + Optional bar = "long_enough_to_be_allocated"_string; + foo = bar; + + EXPECT(foo.has_value()); + EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv); + EXPECT(bar.has_value()); + EXPECT_EQ(bar.value(), "long_enough_to_be_allocated"sv); + } + + { + Optional foo; + + EXPECT(!foo.has_value()); + + foo = Optional { "long_enough_to_be_allocated"_string }; + + EXPECT(foo.has_value()); + EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv); + } + + { + Optional foo = "long_enough_to_be_allocated"_string; + + EXPECT_EQ(foo.value_or("fallback_value"_string), "long_enough_to_be_allocated"sv); + } + + { + Optional foo; + + EXPECT_EQ(foo.value_or("fallback_value"_string), "fallback_value"sv); + } + + { + EXPECT_EQ((Optional { "long_enough_to_be_allocated"_string }).value_or("fallback_value"_string), "long_enough_to_be_allocated"sv); + } + + { + EXPECT_EQ((Optional {}).value_or("fallback_value"_string), "fallback_value"sv); + } +} + +TEST_CASE(flystring_specialization) +{ + EXPECT_EQ(sizeof(Optional), sizeof(FlyString)); + + { + Optional foo; + + EXPECT(!foo.has_value()); + + foo = "long_enough_to_be_allocated"_fly_string; + + EXPECT(foo.has_value()); + EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv); + } + + { + Optional foo = "initial_value"_fly_string; + + EXPECT(foo.has_value()); + EXPECT_EQ(foo.value(), "initial_value"sv); + + foo = "long_enough_to_be_allocated"_fly_string; + + EXPECT(foo.has_value()); + EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv); + } + + { + Optional foo; + + EXPECT(!foo.has_value()); + + FlyString bar = "long_enough_to_be_allocated"_fly_string; + foo = bar; + + EXPECT(foo.has_value()); + EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv); + } + + { + Optional foo; + + EXPECT(!foo.has_value()); + + Optional bar = "long_enough_to_be_allocated"_fly_string; + foo = bar; + + EXPECT(bar.has_value()); + EXPECT_EQ(bar.value(), "long_enough_to_be_allocated"sv); + EXPECT(foo.has_value()); + EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv); + } + + { + Optional foo; + + EXPECT(!foo.has_value()); + + foo = Optional { "long_enough_to_be_allocated"_fly_string }; + + EXPECT(foo.has_value()); + EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv); + } + + { + Optional foo = "long_enough_to_be_allocated"_fly_string; + + EXPECT_EQ(foo.value_or("fallback_value"_fly_string), "long_enough_to_be_allocated"sv); + } + + { + Optional foo; + + EXPECT_EQ(foo.value_or("fallback_value"_fly_string), "fallback_value"sv); + } + + { + EXPECT_EQ((Optional { "long_enough_to_be_allocated"_fly_string }).value_or("fallback_value"_fly_string), "long_enough_to_be_allocated"sv); + } + + { + EXPECT_EQ((Optional {}).value_or("fallback_value"_fly_string), "fallback_value"sv); + } +} diff --git a/Userland/Libraries/LibJS/Runtime/Completion.h b/Userland/Libraries/LibJS/Runtime/Completion.h index 02ede17f010249..944c33cf89dd1f 100644 --- a/Userland/Libraries/LibJS/Runtime/Completion.h +++ b/Userland/Libraries/LibJS/Runtime/Completion.h @@ -157,7 +157,7 @@ class [[nodiscard]] Completion { namespace AK { template<> -class Optional { +class Optional : public OptionalBase { template friend class Optional; @@ -237,26 +237,6 @@ class Optional { return released_value; } - JS::Completion value_or(JS::Completion const& fallback) const& - { - if (has_value()) - return value(); - return fallback; - } - - [[nodiscard]] JS::Completion value_or(JS::Completion&& fallback) && - { - if (has_value()) - return value(); - return fallback; - } - - JS::Completion const& operator*() const { return value(); } - JS::Completion& operator*() { return value(); } - - JS::Completion const* operator->() const { return &value(); } - JS::Completion* operator->() { return &value(); } - private: JS::Completion m_value { JS::Completion::EmptyTag {} }; }; diff --git a/Userland/Libraries/LibJS/Runtime/PrimitiveString.cpp b/Userland/Libraries/LibJS/Runtime/PrimitiveString.cpp index 1d7ba55dfe937e..3cc32c4ba44460 100644 --- a/Userland/Libraries/LibJS/Runtime/PrimitiveString.cpp +++ b/Userland/Libraries/LibJS/Runtime/PrimitiveString.cpp @@ -262,6 +262,7 @@ void PrimitiveString::resolve_rope_if_needed(EncodingPreference preference) cons // This vector will hold all the pieces of the rope that need to be assembled // into the resolved string. Vector pieces; + size_t approximate_length = 0; // NOTE: We traverse the rope tree without using recursion, since we'd run out of // stack space quickly when handling a long sequence of unresolved concatenations. @@ -275,6 +276,9 @@ void PrimitiveString::resolve_rope_if_needed(EncodingPreference preference) cons stack.append(current->m_lhs); continue; } + + if (current->has_utf8_string()) + approximate_length += current->utf8_string_view().length(); pieces.append(current); } @@ -294,7 +298,7 @@ void PrimitiveString::resolve_rope_if_needed(EncodingPreference preference) cons } // Now that we have all the pieces, we can concatenate them using a StringBuilder. - StringBuilder builder; + StringBuilder builder(approximate_length); // We keep track of the previous piece in order to handle surrogate pairs spread across two pieces. PrimitiveString const* previous = nullptr; diff --git a/Userland/Libraries/LibJS/Runtime/Value.h b/Userland/Libraries/LibJS/Runtime/Value.h index 9edc9a23c406e8..6a481f42c8a899 100644 --- a/Userland/Libraries/LibJS/Runtime/Value.h +++ b/Userland/Libraries/LibJS/Runtime/Value.h @@ -596,7 +596,7 @@ namespace AK { static_assert(sizeof(JS::Value) == sizeof(double)); template<> -class Optional { +class Optional : public OptionalBase { template friend class Optional; @@ -699,26 +699,6 @@ class Optional { return released_value; } - JS::Value value_or(JS::Value const& fallback) const& - { - if (has_value()) - return value(); - return fallback; - } - - [[nodiscard]] JS::Value value_or(JS::Value&& fallback) && - { - if (has_value()) - return value(); - return fallback; - } - - JS::Value const& operator*() const { return value(); } - JS::Value& operator*() { return value(); } - - JS::Value const* operator->() const { return &value(); } - JS::Value* operator->() { return &value(); } - private: JS::Value m_value; }; diff --git a/Userland/Libraries/LibWeb/DOMURL/URLSearchParams.cpp b/Userland/Libraries/LibWeb/DOMURL/URLSearchParams.cpp index 3a78274ae664f3..4b531c033a4fd9 100644 --- a/Userland/Libraries/LibWeb/DOMURL/URLSearchParams.cpp +++ b/Userland/Libraries/LibWeb/DOMURL/URLSearchParams.cpp @@ -228,7 +228,7 @@ void URLSearchParams::update() serialized_query = {}; // 4. Set query’s URL object’s URL’s query to serializedQuery. - m_url->set_query({}, move(serialized_query)); + m_url->set_query({}, serialized_query); // 5. If serializedQuery is null, then potentially strip trailing spaces from an opaque path with query’s URL object. if (!serialized_query.has_value())