AK: Don't implicitly convert Optional<T&> to Optional<T>

C++ will jovially select the implicit conversion operator, even if it's
complete bogus, such as for unknown-size types or non-destructible
types. Therefore, all such conversions (which incur a copy) must
(unfortunately) be explicit so that non-copyable types continue to work.

NOTE: We make an exception for trivially copyable types, since they
are, well, trivially copyable.

Co-authored-by: kleines Filmröllchen <filmroellchen@serenityos.org>
This commit is contained in:
Jonne Ransijn 2024-11-25 13:23:31 +01:00 committed by Ali Mohammad Pur
commit d7596a0a61
Notes: github-actions[bot] 2024-12-04 00:59:23 +00:00
22 changed files with 118 additions and 40 deletions

View file

@ -210,7 +210,7 @@ public:
} }
template<typename U> template<typename U>
requires(IsConstructible<T, U const&> && !IsSpecializationOf<T, Optional> && !IsSpecializationOf<U, Optional>) ALWAYS_INLINE explicit Optional(Optional<U> const& other) requires(IsConstructible<T, U const&> && !IsSpecializationOf<T, Optional> && !IsSpecializationOf<U, Optional> && (!IsLvalueReference<U> || IsTriviallyCopyConstructible<U>)) ALWAYS_INLINE explicit Optional(Optional<U> const& other)
: m_has_value(other.has_value()) : m_has_value(other.has_value())
{ {
if (other.has_value()) if (other.has_value())
@ -218,7 +218,7 @@ public:
} }
template<typename U> template<typename U>
requires(IsConstructible<T, U &&> && !IsSpecializationOf<T, Optional> && !IsSpecializationOf<U, Optional>) ALWAYS_INLINE explicit Optional(Optional<U>&& other) requires(IsConstructible<T, U &&> && !IsSpecializationOf<T, Optional> && !IsSpecializationOf<U, Optional> && (!IsLvalueReference<U> || IsTriviallyMoveConstructible<U>)) ALWAYS_INLINE explicit Optional(Optional<U>&& other)
: m_has_value(other.has_value()) : m_has_value(other.has_value())
{ {
if (other.has_value()) if (other.has_value())
@ -503,14 +503,29 @@ public:
ALWAYS_INLINE RawPtr<AddConst<RemoveReference<T>>> operator->() const { return &value(); } ALWAYS_INLINE RawPtr<AddConst<RemoveReference<T>>> operator->() const { return &value(); }
ALWAYS_INLINE RawPtr<RemoveReference<T>> operator->() { return &value(); } ALWAYS_INLINE RawPtr<RemoveReference<T>> operator->() { return &value(); }
// Conversion operators from Optional<T&> -> Optional<T> // Conversion operators from Optional<T&> -> Optional<T>, implicit when T is trivially copyable.
ALWAYS_INLINE operator Optional<RemoveCVReference<T>>() const ALWAYS_INLINE operator Optional<RemoveCVReference<T>>() const
requires(IsTriviallyCopyable<RemoveCVReference<T>>)
{ {
if (has_value()) if (has_value())
return Optional<RemoveCVReference<T>>(value()); return Optional<RemoveCVReference<T>>(value());
return {}; return {};
} }
// Conversion operators from Optional<T&> -> Optional<T>, explicit when T is not trivially copyable, since this is usually a mistake.
ALWAYS_INLINE explicit operator Optional<RemoveCVReference<T>>() const
requires(!IsTriviallyCopyable<RemoveCVReference<T>>)
{
if (has_value())
return Optional<RemoveCVReference<T>>(value());
return {};
}
ALWAYS_INLINE constexpr Optional<RemoveCVReference<T>> copy() const
{
return static_cast<Optional<RemoveCVReference<T>>>(*this);
}
template<typename Callback> template<typename Callback>
[[nodiscard]] ALWAYS_INLINE T value_or_lazy_evaluated(Callback callback) const [[nodiscard]] ALWAYS_INLINE T value_or_lazy_evaluated(Callback callback) const
{ {

View file

@ -50,17 +50,17 @@ public:
return m_members.try_set(move(key), move(value)); return m_members.try_set(move(key), move(value));
} }
Optional<String> get(StringView key) const Optional<String const&> get(StringView key) const
{ {
return m_members.get(key); return m_members.get(key);
} }
Optional<String> get(ASN1::AttributeType key) const Optional<String const&> get(ASN1::AttributeType key) const
{ {
return m_members.get(enum_value(key)); return m_members.get(enum_value(key));
} }
Optional<String> get(ASN1::ObjectClass key) const Optional<String const&> get(ASN1::ObjectClass key) const
{ {
return m_members.get(enum_value(key)); return m_members.get(enum_value(key));
} }

View file

@ -39,7 +39,7 @@ public:
return m_map.contains(name); return m_map.contains(name);
} }
[[nodiscard]] Optional<ByteString> get(ByteString const& name) const [[nodiscard]] Optional<ByteString const&> get(ByteString const& name) const
{ {
return m_map.get(name); return m_map.get(name);
} }

View file

@ -101,7 +101,7 @@ Optional<ValueAndAttributes> GenericIndexedPropertyStorage::get(u32 index) const
{ {
if (index >= m_array_size) if (index >= m_array_size)
return {}; return {};
return m_sparse_elements.get(index); return m_sparse_elements.get(index).copy();
} }
void GenericIndexedPropertyStorage::put(u32 index, Value value, PropertyAttributes attributes) void GenericIndexedPropertyStorage::put(u32 index, Value value, PropertyAttributes attributes)

View file

@ -699,7 +699,7 @@ String Parser::percent_encode_after_encoding(TextCodec::Encoder& encoder, String
} }
// https://url.spec.whatwg.org/#concept-basic-url-parser // https://url.spec.whatwg.org/#concept-basic-url-parser
URL Parser::basic_parse(StringView raw_input, Optional<URL> const& base_url, URL* url, Optional<State> state_override, Optional<StringView> encoding) URL Parser::basic_parse(StringView raw_input, Optional<URL const&> base_url, URL* url, Optional<State> state_override, Optional<StringView> encoding)
{ {
dbgln_if(URL_PARSER_DEBUG, "URL::Parser::basic_parse: Parsing '{}'", raw_input); dbgln_if(URL_PARSER_DEBUG, "URL::Parser::basic_parse: Parsing '{}'", raw_input);

View file

@ -58,7 +58,7 @@ public:
} }
// https://url.spec.whatwg.org/#concept-basic-url-parser // https://url.spec.whatwg.org/#concept-basic-url-parser
static URL basic_parse(StringView input, Optional<URL> const& base_url = {}, URL* url = nullptr, Optional<State> state_override = {}, Optional<StringView> encoding = {}); static URL basic_parse(StringView input, Optional<URL const&> base_url = {}, URL* url = nullptr, Optional<State> state_override = {}, Optional<StringView> encoding = {});
// https://url.spec.whatwg.org/#string-percent-encode-after-encoding // https://url.spec.whatwg.org/#string-percent-encode-after-encoding
static String percent_encode_after_encoding(TextCodec::Encoder&, StringView input, PercentEncodeSet percent_encode_set, bool space_as_plus = false); static String percent_encode_after_encoding(TextCodec::Encoder&, StringView input, PercentEncodeSet percent_encode_set, bool space_as_plus = false);

View file

@ -197,7 +197,7 @@ static auto const& ensure_currency_codes()
return currency_codes; return currency_codes;
} }
Optional<CurrencyCode> get_currency_code(StringView currency) Optional<CurrencyCode const&> get_currency_code(StringView currency)
{ {
static auto const& currency_codes = ensure_currency_codes(); static auto const& currency_codes = ensure_currency_codes();
return currency_codes.get(currency); return currency_codes.get(currency);

View file

@ -17,6 +17,6 @@ struct CurrencyCode {
Optional<int> minor_unit {}; Optional<int> minor_unit {};
}; };
Optional<CurrencyCode> get_currency_code(StringView currency); Optional<CurrencyCode const&> get_currency_code(StringView currency);
} }

View file

@ -29,7 +29,7 @@ public:
virtual String item(size_t index) const = 0; virtual String item(size_t index) const = 0;
virtual Optional<StyleProperty> property(PropertyID) const = 0; virtual Optional<StyleProperty> property(PropertyID) const = 0;
virtual Optional<StyleProperty> custom_property(FlyString const& custom_property_name) const = 0; virtual Optional<StyleProperty const&> custom_property(FlyString const& custom_property_name) const = 0;
virtual WebIDL::ExceptionOr<void> set_property(PropertyID, StringView css_text, StringView priority = ""sv); virtual WebIDL::ExceptionOr<void> set_property(PropertyID, StringView css_text, StringView priority = ""sv);
virtual WebIDL::ExceptionOr<String> remove_property(PropertyID); virtual WebIDL::ExceptionOr<String> remove_property(PropertyID);
@ -76,7 +76,7 @@ public:
virtual String item(size_t index) const override; virtual String item(size_t index) const override;
virtual Optional<StyleProperty> property(PropertyID) const override; virtual Optional<StyleProperty> property(PropertyID) const override;
virtual Optional<StyleProperty> custom_property(FlyString const& custom_property_name) const override { return m_custom_properties.get(custom_property_name); } virtual Optional<StyleProperty const&> custom_property(FlyString const& custom_property_name) const override { return m_custom_properties.get(custom_property_name); }
virtual WebIDL::ExceptionOr<void> set_property(StringView property_name, StringView css_text, StringView priority) override; virtual WebIDL::ExceptionOr<void> set_property(StringView property_name, StringView css_text, StringView priority) override;
virtual WebIDL::ExceptionOr<String> remove_property(StringView property_name) override; virtual WebIDL::ExceptionOr<String> remove_property(StringView property_name) override;

View file

@ -601,7 +601,7 @@ Optional<StyleProperty> ResolvedCSSStyleDeclaration::property(PropertyID propert
}; };
} }
Optional<StyleProperty> ResolvedCSSStyleDeclaration::custom_property(FlyString const& name) const Optional<StyleProperty const&> ResolvedCSSStyleDeclaration::custom_property(FlyString const& name) const
{ {
const_cast<DOM::Document&>(m_element->document()).update_style(); const_cast<DOM::Document&>(m_element->document()).update_style();

View file

@ -24,7 +24,7 @@ public:
virtual String item(size_t index) const override; virtual String item(size_t index) const override;
virtual Optional<StyleProperty> property(PropertyID) const override; virtual Optional<StyleProperty> property(PropertyID) const override;
virtual Optional<StyleProperty> custom_property(FlyString const& custom_property_name) const override; virtual Optional<StyleProperty const&> custom_property(FlyString const& custom_property_name) const override;
virtual WebIDL::ExceptionOr<void> set_property(PropertyID, StringView css_text, StringView priority) override; virtual WebIDL::ExceptionOr<void> set_property(PropertyID, StringView css_text, StringView priority) override;
virtual WebIDL::ExceptionOr<void> set_property(StringView property_name, StringView css_text, StringView priority) override; virtual WebIDL::ExceptionOr<void> set_property(StringView property_name, StringView css_text, StringView priority) override;
virtual WebIDL::ExceptionOr<String> remove_property(PropertyID) override; virtual WebIDL::ExceptionOr<String> remove_property(PropertyID) override;

View file

@ -506,7 +506,7 @@ void strip_trailing_spaces_from_an_opaque_path(DOMURL& url)
} }
// https://url.spec.whatwg.org/#concept-url-parser // https://url.spec.whatwg.org/#concept-url-parser
URL::URL parse(StringView input, Optional<URL::URL> const& base_url, Optional<StringView> encoding) URL::URL parse(StringView input, Optional<URL::URL const&> base_url, Optional<StringView> encoding)
{ {
// FIXME: We should probably have an extended version of URL::URL for LibWeb instead of standalone functions like this. // FIXME: We should probably have an extended version of URL::URL for LibWeb instead of standalone functions like this.

View file

@ -98,6 +98,6 @@ bool host_is_domain(URL::Host const&);
void strip_trailing_spaces_from_an_opaque_path(DOMURL& url); void strip_trailing_spaces_from_an_opaque_path(DOMURL& url);
// https://url.spec.whatwg.org/#concept-url-parser // https://url.spec.whatwg.org/#concept-url-parser
URL::URL parse(StringView input, Optional<URL::URL> const& base_url = {}, Optional<StringView> encoding = {}); URL::URL parse(StringView input, Optional<URL::URL const&> base_url = {}, Optional<StringView> encoding = {});
} }

View file

@ -467,7 +467,7 @@ WebIDL::ExceptionOr<GC::Ref<Request>> Request::construct_impl(JS::Realm& realm,
} }
// 38. Let inputOrInitBody be initBody if it is non-null; otherwise inputBody. // 38. Let inputOrInitBody be initBody if it is non-null; otherwise inputBody.
Optional<Infrastructure::Request::BodyType> input_or_init_body = init_body Optional<Infrastructure::Request::BodyType const&> input_or_init_body = init_body
? Infrastructure::Request::BodyType { *init_body } ? Infrastructure::Request::BodyType { *init_body }
: input_body; : input_body;

View file

@ -108,7 +108,7 @@ void run_unloading_cleanup_steps(GC::Ref<DOM::Document> document)
} }
// https://w3c.github.io/FileAPI/#blob-url-resolve // https://w3c.github.io/FileAPI/#blob-url-resolve
Optional<BlobURLEntry> resolve_a_blob_url(URL::URL const& url) Optional<BlobURLEntry const&> resolve_a_blob_url(URL::URL const& url)
{ {
// 1. Assert: urls scheme is "blob". // 1. Assert: urls scheme is "blob".
VERIFY(url.scheme() == "blob"sv); VERIFY(url.scheme() == "blob"sv);

View file

@ -28,7 +28,7 @@ BlobURLStore& blob_url_store();
ErrorOr<String> generate_new_blob_url(); ErrorOr<String> generate_new_blob_url();
ErrorOr<String> add_entry_to_blob_url_store(GC::Ref<Blob> object); ErrorOr<String> add_entry_to_blob_url_store(GC::Ref<Blob> object);
ErrorOr<void> remove_entry_from_blob_url_store(StringView url); ErrorOr<void> remove_entry_from_blob_url_store(StringView url);
Optional<BlobURLEntry> resolve_a_blob_url(URL::URL const&); Optional<BlobURLEntry const&> resolve_a_blob_url(URL::URL const&);
void run_unloading_cleanup_steps(GC::Ref<DOM::Document>); void run_unloading_cleanup_steps(GC::Ref<DOM::Document>);

View file

@ -38,7 +38,7 @@ ConnectionQueue& ConnectionQueueHandler::for_key_and_name(StorageAPI::StorageKey
}); });
} }
Optional<GC::Root<Database>> Database::for_key_and_name(StorageAPI::StorageKey& key, String& name) Optional<GC::Root<Database> const&> Database::for_key_and_name(StorageAPI::StorageKey& key, String& name)
{ {
return m_databases.ensure(key, [] { return m_databases.ensure(key, [] {
return HashMap<String, GC::Root<Database>>(); return HashMap<String, GC::Root<Database>>();

View file

@ -39,7 +39,7 @@ public:
return connections; return connections;
} }
[[nodiscard]] static Optional<GC::Root<Database>> for_key_and_name(StorageAPI::StorageKey&, String&); [[nodiscard]] static Optional<GC::Root<Database> const&> for_key_and_name(StorageAPI::StorageKey&, String&);
[[nodiscard]] static ErrorOr<GC::Root<Database>> create_for_key_and_name(JS::Realm&, StorageAPI::StorageKey&, String&); [[nodiscard]] static ErrorOr<GC::Root<Database>> create_for_key_and_name(JS::Realm&, StorageAPI::StorageKey&, String&);
[[nodiscard]] static ErrorOr<void> delete_for_key_and_name(StorageAPI::StorageKey&, String&); [[nodiscard]] static ErrorOr<void> delete_for_key_and_name(StorageAPI::StorageKey&, String&);

View file

@ -618,7 +618,7 @@ void CookieJar::TransientStorage::set_cookie(CookieStorageKey key, Web::Cookie::
m_dirty_cookies.set(move(key), move(cookie)); m_dirty_cookies.set(move(key), move(cookie));
} }
Optional<Web::Cookie::Cookie> CookieJar::TransientStorage::get_cookie(CookieStorageKey const& key) Optional<Web::Cookie::Cookie const&> CookieJar::TransientStorage::get_cookie(CookieStorageKey const& key)
{ {
return m_cookies.get(key); return m_cookies.get(key);
} }

View file

@ -43,7 +43,7 @@ class CookieJar {
void set_cookies(Cookies); void set_cookies(Cookies);
void set_cookie(CookieStorageKey, Web::Cookie::Cookie); void set_cookie(CookieStorageKey, Web::Cookie::Cookie);
Optional<Web::Cookie::Cookie> get_cookie(CookieStorageKey const&); Optional<Web::Cookie::Cookie const&> get_cookie(CookieStorageKey const&);
size_t size() const { return m_cookies.size(); } size_t size() const { return m_cookies.size(); }

View file

@ -265,7 +265,7 @@ void add_@global_object_snake_name@_exposed_interfaces(JS::Object& global)
static constexpr u8 attr = JS::Attribute::Writable | JS::Attribute::Configurable; static constexpr u8 attr = JS::Attribute::Writable | JS::Attribute::Configurable;
)~~~"); )~~~");
auto add_interface = [](SourceGenerator& gen, StringView name, StringView prototype_class, Optional<LegacyConstructor> const& legacy_constructor, Optional<ByteString> const& legacy_alias_name) { auto add_interface = [](SourceGenerator& gen, StringView name, StringView prototype_class, Optional<LegacyConstructor> const& legacy_constructor, Optional<ByteString const&> legacy_alias_name) {
gen.set("interface_name", name); gen.set("interface_name", name);
gen.set("prototype_class", prototype_class); gen.set("prototype_class", prototype_class);

View file

@ -13,6 +13,39 @@
#include <AK/String.h> #include <AK/String.h>
#include <AK/Vector.h> #include <AK/Vector.h>
class NonCopyable {
AK_MAKE_NONCOPYABLE(NonCopyable);
AK_MAKE_DEFAULT_MOVABLE(NonCopyable);
public:
NonCopyable() { }
~NonCopyable() = default;
int x { 13 };
};
class NonTriviallyCopyable {
AK_MAKE_DEFAULT_COPYABLE(NonTriviallyCopyable);
AK_MAKE_DEFAULT_MOVABLE(NonTriviallyCopyable);
public:
NonTriviallyCopyable() = default;
~NonTriviallyCopyable() = default;
ByteString x { "13" };
};
class TriviallyCopyable {
AK_MAKE_DEFAULT_COPYABLE(TriviallyCopyable);
AK_MAKE_DEFAULT_MOVABLE(TriviallyCopyable);
public:
TriviallyCopyable() = default;
~TriviallyCopyable() = default;
int x { 13 };
};
TEST_CASE(basic_optional) TEST_CASE(basic_optional)
{ {
Optional<int> x; Optional<int> x;
@ -39,23 +72,12 @@ TEST_CASE(move_optional)
TEST_CASE(optional_rvalue_ref_qualified_getters) TEST_CASE(optional_rvalue_ref_qualified_getters)
{ {
struct DontCopyMe { auto make_an_optional = []() -> Optional<NonCopyable> {
DontCopyMe() { } return NonCopyable {};
~DontCopyMe() = default;
DontCopyMe(DontCopyMe&&) = default;
DontCopyMe& operator=(DontCopyMe&&) = default;
DontCopyMe(DontCopyMe const&) = delete;
DontCopyMe& operator=(DontCopyMe const&) = delete;
int x { 13 };
};
auto make_an_optional = []() -> Optional<DontCopyMe> {
return DontCopyMe {};
}; };
EXPECT_EQ(make_an_optional().value().x, 13); EXPECT_EQ(make_an_optional().value().x, 13);
EXPECT_EQ(make_an_optional().value_or(DontCopyMe {}).x, 13); EXPECT_EQ(make_an_optional().value_or(NonCopyable {}).x, 13);
} }
TEST_CASE(optional_leak_1) TEST_CASE(optional_leak_1)
@ -299,6 +321,47 @@ static_assert(CheckAssignments<Optional<int const&>, int const&>::allowed);
static_assert(CheckAssignments<Optional<int const&>, int&&>::allowed); // Lifetime extension static_assert(CheckAssignments<Optional<int const&>, int&&>::allowed); // Lifetime extension
static_assert(CheckAssignments<Optional<int const&>, int const&&>::allowed); // Lifetime extension static_assert(CheckAssignments<Optional<int const&>, int const&&>::allowed); // Lifetime extension
static_assert(CheckAssignments<Optional<NonTriviallyCopyable const&>, NonTriviallyCopyable&>::allowed);
static_assert(CheckAssignments<Optional<NonTriviallyCopyable const&>, NonTriviallyCopyable const&>::allowed);
static_assert(CheckAssignments<Optional<NonTriviallyCopyable const&>, NonTriviallyCopyable&&>::allowed); // Lifetime extension
static_assert(CheckAssignments<Optional<NonTriviallyCopyable const&>, NonTriviallyCopyable const&&>::allowed); // Lifetime extension
static_assert(CheckAssignments<Optional<TriviallyCopyable const&>, TriviallyCopyable>::allowed);
static_assert(CheckAssignments<Optional<TriviallyCopyable const&>, TriviallyCopyable const&>::allowed);
static_assert(CheckAssignments<Optional<TriviallyCopyable const&>, Optional<TriviallyCopyable>>::allowed);
static_assert(CheckAssignments<Optional<TriviallyCopyable const&>, Optional<TriviallyCopyable const&>>::allowed);
static_assert(CheckAssignments<Optional<TriviallyCopyable>, Optional<TriviallyCopyable const&>>::allowed);
static_assert(CheckAssignments<Optional<NonTriviallyCopyable const&>, NonTriviallyCopyable>::allowed);
static_assert(CheckAssignments<Optional<NonTriviallyCopyable const&>, NonTriviallyCopyable const&>::allowed);
static_assert(CheckAssignments<Optional<NonTriviallyCopyable const&>, Optional<NonTriviallyCopyable>>::allowed);
static_assert(CheckAssignments<Optional<NonTriviallyCopyable const&>, Optional<NonTriviallyCopyable const&>>::allowed);
static_assert(!CheckAssignments<Optional<NonTriviallyCopyable>, Optional<NonTriviallyCopyable const&>>::allowed);
TEST_CASE(nontrivially_copyable_assignment)
{
{
TriviallyCopyable x {};
Optional<TriviallyCopyable const&> y = x;
Optional<TriviallyCopyable> z = y; // Can copy implicitly
EXPECT_EQ(z->x, 13);
}
{
NonTriviallyCopyable x {};
Optional<NonTriviallyCopyable const&> y = x;
Optional<NonTriviallyCopyable> z = y.copy(); // Have to copy explicitly
EXPECT_EQ(z->x, "13");
}
{
NonTriviallyCopyable x {};
Optional<NonTriviallyCopyable const&> y = x;
Optional<NonTriviallyCopyable> z = Optional<NonTriviallyCopyable>(y); // Explicit copy constructor is still defined
EXPECT_EQ(z->x, "13");
}
}
TEST_CASE(string_specialization) TEST_CASE(string_specialization)
{ {
EXPECT_EQ(sizeof(Optional<String>), sizeof(String)); EXPECT_EQ(sizeof(Optional<String>), sizeof(String));