diff --git a/AK/ByteString.cpp b/AK/ByteString.cpp index 4705e2d16ed..ff12f3e62b3 100644 --- a/AK/ByteString.cpp +++ b/AK/ByteString.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include #include @@ -16,6 +16,11 @@ namespace AK { +bool ByteString::operator==(DeprecatedFlyString const& fly_string) const +{ + return m_impl == fly_string.impl() || view() == fly_string.view(); +} + bool ByteString::operator==(ByteString const& other) const { return m_impl == other.impl() || view() == other.view(); @@ -332,8 +337,8 @@ ByteString escape_html_entities(StringView html) return builder.to_byte_string(); } -ByteString::ByteString(FlyString const& string) - : m_impl(*StringImpl::create(string.bytes())) +ByteString::ByteString(DeprecatedFlyString const& string) + : m_impl(string.impl()) { } diff --git a/AK/ByteString.h b/AK/ByteString.h index c0b0607c617..e528985c855 100644 --- a/AK/ByteString.h +++ b/AK/ByteString.h @@ -86,7 +86,7 @@ public: { } - ByteString(FlyString const&); + ByteString(DeprecatedFlyString const&); static ErrorOr from_utf8(ReadonlyBytes); static ErrorOr from_utf8(StringView string) { return from_utf8(string.bytes()); } @@ -228,6 +228,8 @@ public: bool operator==(StringView) const; + bool operator==(DeprecatedFlyString const&) const; + bool operator<(ByteString const&) const; bool operator>=(ByteString const& other) const { return !(*this < other); } bool operator>=(char const* other) const { return !(*this < other); } diff --git a/AK/CMakeLists.txt b/AK/CMakeLists.txt index 475f46e6253..5ba6b17b94b 100644 --- a/AK/CMakeLists.txt +++ b/AK/CMakeLists.txt @@ -5,6 +5,7 @@ set(SOURCES ConstrainedStream.cpp CountingStream.cpp DOSPackedTime.cpp + DeprecatedFlyString.cpp ByteString.cpp Error.cpp FloatingPointStringConversions.cpp diff --git a/AK/DeprecatedFlyString.cpp b/AK/DeprecatedFlyString.cpp new file mode 100644 index 00000000000..d5479b3c26d --- /dev/null +++ b/AK/DeprecatedFlyString.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2020, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace AK { + +struct DeprecatedFlyStringImplTraits : public Traits { + static unsigned hash(StringImpl const* s) { return s->hash(); } + static bool equals(StringImpl const* a, StringImpl const* b) + { + return *a == *b; + } +}; + +static Singleton> s_table; + +static HashTable& fly_impls() +{ + return *s_table; +} + +void DeprecatedFlyString::did_destroy_impl(Badge, StringImpl& impl) +{ + fly_impls().remove(&impl); +} + +DeprecatedFlyString::DeprecatedFlyString(ByteString const& string) + : m_impl(string.impl()) +{ + if (string.impl()->is_fly()) + return; + + auto it = fly_impls().find(string.impl()); + if (it == fly_impls().end()) { + fly_impls().set(string.impl()); + string.impl()->set_fly({}, true); + m_impl = string.impl(); + } else { + VERIFY((*it)->is_fly()); + m_impl = **it; + } +} + +DeprecatedFlyString::DeprecatedFlyString(StringView string) + : m_impl(StringImpl::the_empty_stringimpl()) +{ + if (string.is_null()) + return; + auto it = fly_impls().find(string.hash(), [&](auto& candidate) { + return string == *candidate; + }); + if (it == fly_impls().end()) { + auto new_string = string.to_byte_string(); + fly_impls().set(new_string.impl()); + new_string.impl()->set_fly({}, true); + m_impl = new_string.impl(); + } else { + VERIFY((*it)->is_fly()); + m_impl = **it; + } +} + +bool DeprecatedFlyString::equals_ignoring_ascii_case(StringView other) const +{ + return StringUtils::equals_ignoring_ascii_case(view(), other); +} + +bool DeprecatedFlyString::starts_with(StringView str, CaseSensitivity case_sensitivity) const +{ + return StringUtils::starts_with(view(), str, case_sensitivity); +} + +bool DeprecatedFlyString::ends_with(StringView str, CaseSensitivity case_sensitivity) const +{ + return StringUtils::ends_with(view(), str, case_sensitivity); +} + +DeprecatedFlyString DeprecatedFlyString::to_lowercase() const +{ + return ByteString(*m_impl).to_lowercase(); +} + +bool DeprecatedFlyString::operator==(ByteString const& other) const +{ + return m_impl == other.impl() || view() == other.view(); +} + +bool DeprecatedFlyString::operator==(StringView string) const +{ + return view() == string; +} + +bool DeprecatedFlyString::operator==(char const* string) const +{ + return view() == string; +} + +} diff --git a/AK/DeprecatedFlyString.h b/AK/DeprecatedFlyString.h new file mode 100644 index 00000000000..80087aa48f7 --- /dev/null +++ b/AK/DeprecatedFlyString.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2020, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace AK { + +class DeprecatedFlyString { +public: + DeprecatedFlyString() + : m_impl(StringImpl::the_empty_stringimpl()) + { + } + DeprecatedFlyString(DeprecatedFlyString const& other) + : m_impl(other.impl()) + { + } + DeprecatedFlyString(DeprecatedFlyString&& other) + : m_impl(move(other.m_impl)) + { + } + DeprecatedFlyString(ByteString const&); + DeprecatedFlyString(StringView); + DeprecatedFlyString(char const* string) + : DeprecatedFlyString(static_cast(string)) + { + } + + static DeprecatedFlyString from_fly_impl(NonnullRefPtr impl) + { + VERIFY(impl->is_fly()); + DeprecatedFlyString string; + string.m_impl = move(impl); + return string; + } + + DeprecatedFlyString& operator=(DeprecatedFlyString const& other) + { + m_impl = other.m_impl; + return *this; + } + + DeprecatedFlyString& operator=(DeprecatedFlyString&& other) + { + m_impl = move(other.m_impl); + return *this; + } + + bool is_empty() const { return !m_impl->length(); } + + bool operator==(DeprecatedFlyString const& other) const { return m_impl == other.m_impl; } + + bool operator==(ByteString const&) const; + + bool operator==(StringView) const; + + bool operator==(char const*) const; + + NonnullRefPtr impl() const { return m_impl; } + char const* characters() const { return m_impl->characters(); } + size_t length() const { return m_impl->length(); } + + ALWAYS_INLINE u32 hash() const { return m_impl->existing_hash(); } + ALWAYS_INLINE StringView view() const { return m_impl->view(); } + + DeprecatedFlyString to_lowercase() const; + + template + Optional to_number(TrimWhitespace trim_whitespace = TrimWhitespace::Yes) const + { + return view().to_number(trim_whitespace); + } + + bool equals_ignoring_ascii_case(StringView) const; + bool starts_with(StringView, CaseSensitivity = CaseSensitivity::CaseSensitive) const; + bool ends_with(StringView, CaseSensitivity = CaseSensitivity::CaseSensitive) const; + + static void did_destroy_impl(Badge, StringImpl&); + + template + [[nodiscard]] ALWAYS_INLINE constexpr bool is_one_of(Ts&&... strings) const + { + return (... || this->operator==(forward(strings))); + } + +private: + NonnullRefPtr m_impl; +}; + +template<> +struct Traits : public DefaultTraits { + static unsigned hash(DeprecatedFlyString const& s) { return s.hash(); } +}; + +} + +#if USING_AK_GLOBALLY +using AK::DeprecatedFlyString; +#endif diff --git a/AK/Error.h b/AK/Error.h index 84585ababfb..71db75afa42 100644 --- a/AK/Error.h +++ b/AK/Error.h @@ -40,7 +40,7 @@ public: } static Error from_string_view(StringView string_literal) { return Error(string_literal); } - template T> + template T> static Error from_string_view(T) { // `Error::from_string_view(ByteString::formatted(...))` is a somewhat common mistake, which leads to a UAF situation. diff --git a/AK/FlyString.cpp b/AK/FlyString.cpp index 468f97519c5..db549fb1f0c 100644 --- a/AK/FlyString.cpp +++ b/AK/FlyString.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -119,6 +120,16 @@ size_t FlyString::number_of_fly_strings() return all_fly_strings().size(); } +DeprecatedFlyString FlyString::to_deprecated_fly_string() const +{ + return DeprecatedFlyString(bytes_as_string_view()); +} + +ErrorOr FlyString::from_deprecated_fly_string(DeprecatedFlyString const& deprecated_fly_string) +{ + return FlyString::from_utf8(deprecated_fly_string.view()); +} + unsigned Traits::hash(FlyString const& fly_string) { return fly_string.hash(); diff --git a/AK/FlyString.h b/AK/FlyString.h index d304909383f..8fde3482c0b 100644 --- a/AK/FlyString.h +++ b/AK/FlyString.h @@ -26,7 +26,7 @@ public: static ErrorOr from_utf8(StringView); static FlyString from_utf8_without_validation(ReadonlyBytes); template - requires(IsOneOf, ByteString, FlyString, String>) + requires(IsOneOf, ByteString, DeprecatedFlyString, FlyString, String>) static ErrorOr from_utf8(T&&) = delete; FlyString(String const&); @@ -55,6 +55,9 @@ public: // This is primarily interesting to unit tests. [[nodiscard]] static size_t number_of_fly_strings(); + // FIXME: Remove these once all code has been ported to FlyString + [[nodiscard]] DeprecatedFlyString to_deprecated_fly_string() const; + static ErrorOr from_deprecated_fly_string(DeprecatedFlyString const&); template requires(IsSame, StringView>) static ErrorOr from_deprecated_fly_string(T&&) = delete; diff --git a/AK/Format.h b/AK/Format.h index 955471588b3..0c0d4fe7783 100644 --- a/AK/Format.h +++ b/AK/Format.h @@ -496,6 +496,9 @@ struct Formatter : Formatter { template<> struct Formatter : Formatter { }; +template<> +struct Formatter : Formatter { +}; template struct Formatter : StandardFormatter { diff --git a/AK/Forward.h b/AK/Forward.h index d67db621370..e1abf382013 100644 --- a/AK/Forward.h +++ b/AK/Forward.h @@ -29,6 +29,7 @@ using ByteBuffer = Detail::ByteBuffer<32>; class CircularBuffer; class ConstrainedStream; class CountingStream; +class DeprecatedFlyString; class ByteString; class Duration; class Error; @@ -160,6 +161,7 @@ using AK::CircularBuffer; using AK::CircularQueue; using AK::ConstrainedStream; using AK::CountingStream; +using AK::DeprecatedFlyString; using AK::DoublyLinkedList; using AK::Error; using AK::ErrorOr; diff --git a/AK/String.h b/AK/String.h index 9d5d3b5edf3..6c3c38cc31e 100644 --- a/AK/String.h +++ b/AK/String.h @@ -60,7 +60,7 @@ public: [[nodiscard]] static String from_utf8_with_replacement_character(StringView, WithBOMHandling = WithBOMHandling::Yes); template - requires(IsOneOf, ByteString, FlyString, String>) + requires(IsOneOf, ByteString, DeprecatedFlyString, FlyString, String>) static ErrorOr from_utf8(T&&) = delete; [[nodiscard]] static String from_utf8_without_validation(ReadonlyBytes); diff --git a/AK/StringImpl.cpp b/AK/StringImpl.cpp index e245e5e9b7f..f2a62579976 100644 --- a/AK/StringImpl.cpp +++ b/AK/StringImpl.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -27,7 +28,11 @@ StringImpl::StringImpl(ConstructWithInlineBufferTag, size_t length) { } -StringImpl::~StringImpl() = default; +StringImpl::~StringImpl() +{ + if (m_fly) + DeprecatedFlyString::did_destroy_impl({}, *this); +} NonnullRefPtr StringImpl::create_uninitialized(size_t length, char*& buffer) { diff --git a/AK/StringImpl.h b/AK/StringImpl.h index d8999df3701..d1ef18e051a 100644 --- a/AK/StringImpl.h +++ b/AK/StringImpl.h @@ -77,6 +77,9 @@ public: unsigned case_insensitive_hash() const; + bool is_fly() const { return m_fly; } + void set_fly(Badge, bool fly) const { m_fly = fly; } + private: enum ConstructTheEmptyStringImplTag { ConstructTheEmptyStringImpl diff --git a/AK/StringView.cpp b/AK/StringView.cpp index 9db439acdb6..a7b2a9d8669 100644 --- a/AK/StringView.cpp +++ b/AK/StringView.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,12 @@ StringView::StringView(ByteString const& string) { } +StringView::StringView(DeprecatedFlyString const& string) + : m_characters(string.characters()) + , m_length(string.length()) +{ +} + StringView::StringView(ByteBuffer const& buffer) : m_characters((char const*)buffer.data()) , m_length(buffer.size()) diff --git a/AK/StringView.h b/AK/StringView.h index f58827fdd19..96cdeaa2881 100644 --- a/AK/StringView.h +++ b/AK/StringView.h @@ -51,13 +51,15 @@ public: StringView(String const&); StringView(FlyString const&); StringView(ByteString const&); + StringView(DeprecatedFlyString const&); explicit StringView(ByteBuffer&&) = delete; explicit StringView(String&&) = delete; explicit StringView(FlyString&&) = delete; explicit StringView(ByteString&&) = delete; + explicit StringView(DeprecatedFlyString&&) = delete; - template StringType> + template StringType> StringView& operator=(StringType&&) = delete; [[nodiscard]] constexpr bool is_null() const diff --git a/Libraries/LibJS/Runtime/VM.cpp b/Libraries/LibJS/Runtime/VM.cpp index 53e11d7d5b9..9c400fdb3d2 100644 --- a/Libraries/LibJS/Runtime/VM.cpp +++ b/Libraries/LibJS/Runtime/VM.cpp @@ -659,7 +659,7 @@ void VM::load_imported_module(ImportedModuleReferrer referrer, ModuleRequest con }); LexicalPath base_path { base_filename }; - auto filename = LexicalPath::absolute_path(base_path.dirname(), module_request.module_specifier); + auto filename = LexicalPath::absolute_path(base_path.dirname(), module_request.module_specifier.to_deprecated_fly_string()); dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] base path: '{}'", base_path); dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] initial filename: '{}'", filename); diff --git a/Libraries/LibRegex/RegexParser.h b/Libraries/LibRegex/RegexParser.h index ed8d2c75d15..517337426fd 100644 --- a/Libraries/LibRegex/RegexParser.h +++ b/Libraries/LibRegex/RegexParser.h @@ -11,6 +11,7 @@ #include "RegexLexer.h" #include "RegexOptions.h" +#include #include #include #include diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index cd253ed61a3..0eaf8ca60bc 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -2100,7 +2100,7 @@ WebIDL::ExceptionOr> Document::create_element(String const& a_l namespace_ = Namespace::HTML; // 6. Return the result of creating an element given this, localName, namespace, null, is, and with the synchronous custom elements flag set. - return TRY(DOM::create_element(*this, FlyString::from_utf8_without_validation(local_name.bytes()), move(namespace_), {}, move(is_value), true)); + return TRY(DOM::create_element(*this, MUST(FlyString::from_deprecated_fly_string(local_name)), move(namespace_), {}, move(is_value), true)); } // https://dom.spec.whatwg.org/#dom-document-createelementns diff --git a/Libraries/LibWeb/DOM/Node.cpp b/Libraries/LibWeb/DOM/Node.cpp index 9c15b4a898c..8c8c26f47e6 100644 --- a/Libraries/LibWeb/DOM/Node.cpp +++ b/Libraries/LibWeb/DOM/Node.cpp @@ -2206,7 +2206,7 @@ GC::Ref Node::get_root_node(GetRootNodeOptions const& options) String Node::debug_description() const { StringBuilder builder; - builder.append(node_name().to_ascii_lowercase()); + builder.append(node_name().to_deprecated_fly_string().to_lowercase()); if (is_element()) { auto const& element = static_cast(*this); if (element.id().has_value()) diff --git a/Meta/gn/secondary/AK/BUILD.gn b/Meta/gn/secondary/AK/BUILD.gn index 6ad7ee677e4..c9daf40a10c 100644 --- a/Meta/gn/secondary/AK/BUILD.gn +++ b/Meta/gn/secondary/AK/BUILD.gn @@ -61,6 +61,8 @@ shared_library("AK") { "DateConstants.h", "DefaultDelete.h", "Demangle.h", + "DeprecatedFlyString.cpp", + "DeprecatedFlyString.h", "Diagnostics.h", "DisjointChunks.h", "DistinctNumeric.h", diff --git a/Tests/AK/TestByteString.cpp b/Tests/AK/TestByteString.cpp index a62df767d5c..8c86a6476ff 100644 --- a/Tests/AK/TestByteString.cpp +++ b/Tests/AK/TestByteString.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -138,6 +139,26 @@ TEST_CASE(to_uppercase) EXPECT(ByteString("AbC").to_uppercase() == "ABC"); } +TEST_CASE(flystring) +{ + { + DeprecatedFlyString a("foo"); + DeprecatedFlyString b("foo"); + EXPECT_EQ(a.impl(), b.impl()); + } + + { + ByteString a = "foo"; + DeprecatedFlyString b = a; + StringBuilder builder; + builder.append('f'); + builder.append("oo"sv); + DeprecatedFlyString c = builder.to_byte_string(); + EXPECT_EQ(a.impl(), b.impl()); + EXPECT_EQ(a.impl(), c.impl()); + } +} + TEST_CASE(replace) { ByteString test_string = "Well, hello Friends!"; diff --git a/Tests/AK/TestStringUtils.cpp b/Tests/AK/TestStringUtils.cpp index 031d00d1db2..fea8dbc0212 100644 --- a/Tests/AK/TestStringUtils.cpp +++ b/Tests/AK/TestStringUtils.cpp @@ -23,7 +23,11 @@ TEST_CASE(hash_compatible) static_assert(AK::Concepts::HashCompatible); static_assert(AK::Concepts::HashCompatible); + static_assert(AK::Concepts::HashCompatible); static_assert(AK::Concepts::HashCompatible); + static_assert(AK::Concepts::HashCompatible); + static_assert(AK::Concepts::HashCompatible); + static_assert(AK::Concepts::HashCompatible); static_assert(AK::Concepts::HashCompatible); static_assert(AK::Concepts::HashCompatible); diff --git a/Utilities/js.cpp b/Utilities/js.cpp index a9dbe998702..5faef4307f6 100644 --- a/Utilities/js.cpp +++ b/Utilities/js.cpp @@ -795,7 +795,7 @@ ErrorOr serenity_main(Main::Arguments arguments) for (auto const& name : global_environment.declarative_record().bindings()) { if (name.bytes_as_string_view().starts_with(variable_name)) { - results.empend(name); + results.empend(name.to_deprecated_fly_string()); results.last().invariant_offset = variable_name.bytes().size(); } }