Coverage Report

Created: 2025-11-16 07:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibJS/Runtime/PropertyKey.h
Line
Count
Source
1
/*
2
 * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#pragma once
8
9
#include <AK/DeprecatedFlyString.h>
10
#include <AK/FlyString.h>
11
#include <LibJS/Heap/Handle.h>
12
#include <LibJS/Runtime/Completion.h>
13
#include <LibJS/Runtime/StringOrSymbol.h>
14
15
namespace JS {
16
17
class PropertyKey {
18
public:
19
    enum class Type : u8 {
20
        Invalid,
21
        Number,
22
        String,
23
        Symbol,
24
    };
25
26
    enum class StringMayBeNumber {
27
        Yes,
28
        No,
29
    };
30
31
    static ThrowCompletionOr<PropertyKey> from_value(VM& vm, Value value)
32
0
    {
33
0
        if (value.is_empty())
34
0
            return PropertyKey {};
35
0
        if (value.is_symbol())
36
0
            return PropertyKey { value.as_symbol() };
37
0
        if (value.is_integral_number() && value.as_double() >= 0 && value.as_double() < NumericLimits<u32>::max())
38
0
            return static_cast<u32>(value.as_double());
39
0
        return TRY(value.to_byte_string(vm));
40
0
    }
41
42
0
    PropertyKey() = default;
43
44
    template<Integral T>
45
    PropertyKey(T index)
46
0
    {
47
        // FIXME: Replace this with requires(IsUnsigned<T>)?
48
        //        Needs changes in various places using `int` (but not actually being in the negative range)
49
0
        VERIFY(index >= 0);
50
0
        if constexpr (NumericLimits<T>::max() >= NumericLimits<u32>::max()) {
51
0
            if (index >= NumericLimits<u32>::max()) {
52
0
                m_string = ByteString::number(index);
53
0
                m_type = Type::String;
54
0
                m_string_may_be_number = false;
55
0
                return;
56
0
            }
57
0
        }
58
59
0
        m_type = Type::Number;
60
0
        m_number = index;
61
0
    }
Unexecuted instantiation: _ZN2JS11PropertyKeyC2ITkN2AK8Concepts8IntegralEmEET_
Unexecuted instantiation: _ZN2JS11PropertyKeyC2ITkN2AK8Concepts8IntegralEjEET_
Unexecuted instantiation: _ZN2JS11PropertyKeyC2ITkN2AK8Concepts8IntegralEiEET_
Unexecuted instantiation: _ZN2JS11PropertyKeyC2ITkN2AK8Concepts8IntegralElEET_
62
63
    PropertyKey(char const* chars)
64
0
        : m_type(Type::String)
65
0
        , m_string(DeprecatedFlyString(chars))
66
0
    {
67
0
    }
68
69
    PropertyKey(ByteString const& string)
70
0
        : m_type(Type::String)
71
0
        , m_string(DeprecatedFlyString(string))
72
0
    {
73
0
    }
74
75
    PropertyKey(FlyString const& string)
76
        : m_type(Type::String)
77
        , m_string(string.to_deprecated_fly_string())
78
0
    {
79
0
    }
80
81
    PropertyKey(DeprecatedFlyString string, StringMayBeNumber string_may_be_number = StringMayBeNumber::Yes)
82
0
        : m_string_may_be_number(string_may_be_number == StringMayBeNumber::Yes)
83
0
        , m_type(Type::String)
84
0
        , m_string(move(string))
85
0
    {
86
0
    }
87
88
    PropertyKey(NonnullGCPtr<Symbol> symbol)
89
0
        : m_type(Type::Symbol)
90
0
        , m_symbol(symbol)
91
0
    {
92
0
    }
93
94
    PropertyKey(StringOrSymbol const& string_or_symbol)
95
0
    {
96
0
        if (string_or_symbol.is_string()) {
97
0
            m_string = string_or_symbol.as_string();
98
0
            m_type = Type::String;
99
0
        } else if (string_or_symbol.is_symbol()) {
100
0
            m_symbol = const_cast<Symbol*>(string_or_symbol.as_symbol());
101
0
            m_type = Type::Symbol;
102
0
        }
103
0
    }
104
105
0
    ALWAYS_INLINE Type type() const { return m_type; }
106
107
0
    bool is_valid() const { return m_type != Type::Invalid; }
108
    bool is_number() const
109
0
    {
110
0
        if (m_type == Type::Number)
111
0
            return true;
112
0
        if (m_type != Type::String || !m_string_may_be_number)
113
0
            return false;
114
115
0
        return const_cast<PropertyKey*>(this)->try_coerce_into_number();
116
0
    }
117
    bool is_string() const
118
0
    {
119
0
        if (m_type != Type::String)
120
0
            return false;
121
0
        if (!m_string_may_be_number)
122
0
            return true;
123
124
0
        return !const_cast<PropertyKey*>(this)->try_coerce_into_number();
125
0
    }
126
0
    bool is_symbol() const { return m_type == Type::Symbol; }
127
128
    bool try_coerce_into_number()
129
0
    {
130
0
        VERIFY(m_string_may_be_number);
131
0
        if (m_string.is_empty()) {
132
0
            m_string_may_be_number = false;
133
0
            return false;
134
0
        }
135
136
0
        if (char first = m_string.characters()[0]; first < '0' || first > '9') {
137
0
            m_string_may_be_number = false;
138
0
            return false;
139
0
        } else if (m_string.length() > 1 && first == '0') {
140
0
            m_string_may_be_number = false;
141
0
            return false;
142
0
        }
143
144
0
        auto property_index = m_string.to_number<unsigned>(TrimWhitespace::No);
145
0
        if (!property_index.has_value() || property_index.value() == NumericLimits<u32>::max()) {
146
0
            m_string_may_be_number = false;
147
0
            return false;
148
0
        }
149
0
        m_type = Type::Number;
150
0
        m_number = *property_index;
151
0
        return true;
152
0
    }
153
154
    u32 as_number() const
155
0
    {
156
0
        VERIFY(is_number());
157
0
        return m_number;
158
0
    }
159
160
    DeprecatedFlyString const& as_string() const
161
0
    {
162
0
        VERIFY(is_string());
163
0
        return m_string;
164
0
    }
165
166
    Symbol const* as_symbol() const
167
0
    {
168
0
        VERIFY(is_symbol());
169
0
        return m_symbol;
170
0
    }
171
172
    ByteString to_string() const
173
0
    {
174
0
        VERIFY(is_valid());
175
0
        VERIFY(!is_symbol());
176
0
        if (is_string())
177
0
            return as_string();
178
0
        return ByteString::number(as_number());
179
0
    }
180
181
    StringOrSymbol to_string_or_symbol() const
182
0
    {
183
0
        VERIFY(is_valid());
184
0
        VERIFY(!is_number());
185
0
        if (is_string())
186
0
            return StringOrSymbol(as_string());
187
0
        return StringOrSymbol(as_symbol());
188
0
    }
189
190
private:
191
    bool m_string_may_be_number { true };
192
    Type m_type { Type::Invalid };
193
    u32 m_number { 0 };
194
    DeprecatedFlyString m_string;
195
    Handle<Symbol> m_symbol;
196
};
197
198
}
199
200
namespace AK {
201
202
template<>
203
struct Traits<JS::PropertyKey> : public DefaultTraits<JS::PropertyKey> {
204
    static unsigned hash(JS::PropertyKey const& name)
205
0
    {
206
0
        VERIFY(name.is_valid());
207
0
        if (name.is_string())
208
0
            return name.as_string().hash();
209
0
        if (name.is_number())
210
0
            return int_hash(name.as_number());
211
0
        return ptr_hash(name.as_symbol());
212
0
    }
213
214
    static bool equals(JS::PropertyKey const& a, JS::PropertyKey const& b)
215
0
    {
216
0
        if (a.type() != b.type())
217
0
            return false;
218
219
0
        switch (a.type()) {
220
0
        case JS::PropertyKey::Type::Number:
221
0
            return a.as_number() == b.as_number();
222
0
        case JS::PropertyKey::Type::String:
223
0
            return a.as_string() == b.as_string();
224
0
        case JS::PropertyKey::Type::Symbol:
225
0
            return a.as_symbol() == b.as_symbol();
226
0
        default:
227
0
            VERIFY_NOT_REACHED();
228
0
        }
229
0
    }
230
};
231
232
template<>
233
struct Formatter<JS::PropertyKey> : Formatter<StringView> {
234
    ErrorOr<void> format(FormatBuilder& builder, JS::PropertyKey const& property_key)
235
0
    {
236
0
        if (!property_key.is_valid())
237
0
            return builder.put_string("<invalid PropertyKey>"sv);
238
0
        if (property_key.is_number())
239
0
            return builder.put_u64(property_key.as_number());
240
0
        return builder.put_string(property_key.to_string_or_symbol().to_display_string());
241
0
    }
242
};
243
244
}