Coverage Report

Created: 2026-10-28 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/AK/String.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <AK/Array.h>
8
#include <AK/Checked.h>
9
#include <AK/FlyString.h>
10
#include <AK/Format.h>
11
#include <AK/MemMem.h>
12
#include <AK/Stream.h>
13
#include <AK/String.h>
14
#include <AK/Vector.h>
15
#include <stdlib.h>
16
17
namespace AK {
18
19
String String::from_utf8_with_replacement_character(StringView view, WithBOMHandling with_bom_handling)
20
734
{
21
734
    if (auto bytes = view.bytes(); with_bom_handling == WithBOMHandling::Yes && bytes.size() >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF)
22
1
        view = view.substring_view(3);
23
24
734
    if (Utf8View(view).validate())
25
75
        return String::from_utf8_without_validation(view.bytes());
26
27
659
    StringBuilder builder;
28
29
659
    for (auto c : Utf8View { view })
30
42.8M
        builder.append_code_point(c);
31
32
659
    return builder.to_string_without_validation();
33
734
}
34
35
String String::from_utf8_without_validation(ReadonlyBytes bytes)
36
6.18M
{
37
6.18M
    String result;
38
6.18M
    MUST(result.replace_with_new_string(bytes.size(), [&](Bytes buffer) {
39
6.18M
        bytes.copy_to(buffer);
40
6.18M
        return ErrorOr<void> {};
41
6.18M
    }));
42
6.18M
    return result;
43
6.18M
}
44
45
ErrorOr<String> String::from_utf8(StringView view)
46
52.5M
{
47
52.5M
    if (!Utf8View { view }.validate())
48
286k
        return Error::from_string_literal("String::from_utf8: Input was not valid UTF-8");
49
50
52.2M
    String result;
51
52.2M
    TRY(result.replace_with_new_string(view.length(), [&](Bytes buffer) {
52
52.2M
        view.bytes().copy_to(buffer);
53
52.2M
        return ErrorOr<void> {};
54
52.2M
    }));
55
52.2M
    return result;
56
52.2M
}
57
58
ErrorOr<String> String::from_stream(Stream& stream, size_t byte_count)
59
12.9k
{
60
12.9k
    String result;
61
12.9k
    TRY(result.replace_with_new_string(byte_count, [&](Bytes buffer) -> ErrorOr<void> {
62
12.6k
        TRY(stream.read_until_filled(buffer));
63
12.6k
        if (!Utf8View { StringView { buffer } }.validate())
64
12.6k
            return Error::from_string_literal("String::from_stream: Input was not valid UTF-8");
65
12.6k
        return {};
66
12.6k
    }));
67
12.6k
    return result;
68
12.9k
}
69
70
ErrorOr<String> String::repeated(u32 code_point, size_t count)
71
0
{
72
0
    VERIFY(is_unicode(code_point));
73
74
0
    Array<u8, 4> code_point_as_utf8;
75
0
    size_t i = 0;
76
77
0
    size_t code_point_byte_length = UnicodeUtils::code_point_to_utf8(code_point, [&](auto byte) {
78
0
        code_point_as_utf8[i++] = static_cast<u8>(byte);
79
0
    });
80
81
0
    auto total_byte_count = code_point_byte_length * count;
82
83
0
    String result;
84
0
    TRY(result.replace_with_new_string(total_byte_count, [&](Bytes buffer) {
85
0
        if (code_point_byte_length == 1) {
86
0
            buffer.fill(code_point_as_utf8[0]);
87
0
        } else {
88
0
            for (i = 0; i < count; ++i)
89
0
                memcpy(buffer.data() + (i * code_point_byte_length), code_point_as_utf8.data(), code_point_byte_length);
90
0
        }
91
0
        return ErrorOr<void> {};
92
0
    }));
93
0
    return result;
94
0
}
95
96
StringView String::bytes_as_string_view() const&
97
425M
{
98
425M
    return StringView(bytes());
99
425M
}
100
101
bool String::is_empty() const
102
24.5M
{
103
24.5M
    return bytes().size() == 0;
104
24.5M
}
105
106
ErrorOr<String> String::vformatted(StringView fmtstr, TypeErasedFormatParams& params)
107
3.49M
{
108
3.49M
    StringBuilder builder;
109
3.49M
    TRY(vformat(builder, fmtstr, params));
110
3.49M
    return builder.to_string();
111
3.49M
}
112
113
ErrorOr<Vector<String>> String::split(u32 separator, SplitBehavior split_behavior) const
114
416
{
115
416
    return split_limit(separator, 0, split_behavior);
116
416
}
117
118
ErrorOr<Vector<String>> String::split_limit(u32 separator, size_t limit, SplitBehavior split_behavior) const
119
3.56k
{
120
3.56k
    Vector<String> result;
121
122
3.56k
    if (is_empty())
123
569
        return result;
124
125
3.00k
    bool keep_empty = has_flag(split_behavior, SplitBehavior::KeepEmpty);
126
127
3.00k
    size_t substring_start = 0;
128
27.9k
    for (auto it = code_points().begin(); it != code_points().end() && (result.size() + 1) != limit; ++it) {
129
24.9k
        u32 code_point = *it;
130
24.9k
        if (code_point == separator) {
131
3.34k
            size_t substring_length = code_points().iterator_offset(it) - substring_start;
132
3.34k
            if (substring_length != 0 || keep_empty)
133
2.66k
                TRY(result.try_append(TRY(substring_from_byte_offset_with_shared_superstring(substring_start, substring_length))));
134
3.34k
            substring_start = code_points().iterator_offset(it) + it.underlying_code_point_length_in_bytes();
135
3.34k
        }
136
24.9k
    }
137
3.00k
    size_t tail_length = code_points().byte_length() - substring_start;
138
3.00k
    if (tail_length != 0 || keep_empty)
139
2.86k
        TRY(result.try_append(TRY(substring_from_byte_offset_with_shared_superstring(substring_start, tail_length))));
140
3.00k
    return result;
141
3.00k
}
142
143
Optional<size_t> String::find_byte_offset(u32 code_point, size_t from_byte_offset) const
144
738k
{
145
738k
    auto code_points = this->code_points();
146
738k
    if (from_byte_offset >= code_points.byte_length())
147
0
        return {};
148
149
22.7M
    for (auto it = code_points.iterator_at_byte_offset(from_byte_offset); it != code_points.end(); ++it) {
150
22.7M
        if (*it == code_point)
151
737k
            return code_points.byte_offset_of(it);
152
22.7M
    }
153
154
851
    return {};
155
738k
}
156
157
Optional<size_t> String::find_byte_offset(StringView substring, size_t from_byte_offset) const
158
0
{
159
0
    auto view = bytes_as_string_view();
160
0
    if (from_byte_offset >= view.length())
161
0
        return {};
162
163
0
    auto index = memmem_optional(
164
0
        view.characters_without_null_termination() + from_byte_offset, view.length() - from_byte_offset,
165
0
        substring.characters_without_null_termination(), substring.length());
166
167
0
    if (index.has_value())
168
0
        return *index + from_byte_offset;
169
0
    return {};
170
0
}
171
172
bool String::operator==(FlyString const& other) const
173
0
{
174
0
    return static_cast<StringBase const&>(*this) == other.data({});
175
0
}
176
177
bool String::operator==(StringView other) const
178
382M
{
179
382M
    return bytes_as_string_view() == other;
180
382M
}
181
182
ErrorOr<String> String::substring_from_byte_offset(size_t start, size_t byte_count) const
183
977
{
184
977
    if (!byte_count)
185
477
        return String {};
186
500
    return String::from_utf8(bytes_as_string_view().substring_view(start, byte_count));
187
977
}
188
189
ErrorOr<String> String::substring_from_byte_offset(size_t start) const
190
977
{
191
977
    VERIFY(start <= bytes_as_string_view().length());
192
977
    return substring_from_byte_offset(start, bytes_as_string_view().length() - start);
193
977
}
194
195
ErrorOr<String> String::substring_from_byte_offset_with_shared_superstring(size_t start, size_t byte_count) const
196
482k
{
197
482k
    return String { TRY(StringBase::substring_from_byte_offset_with_shared_superstring(start, byte_count)) };
198
482k
}
199
200
ErrorOr<String> String::substring_from_byte_offset_with_shared_superstring(size_t start) const
201
224k
{
202
224k
    VERIFY(start <= bytes_as_string_view().length());
203
224k
    return substring_from_byte_offset_with_shared_superstring(start, bytes_as_string_view().length() - start);
204
224k
}
205
206
bool String::operator==(char const* c_string) const
207
7.29M
{
208
7.29M
    return bytes_as_string_view() == c_string;
209
7.29M
}
210
211
u32 String::ascii_case_insensitive_hash() const
212
0
{
213
0
    return case_insensitive_string_hash(reinterpret_cast<char const*>(bytes().data()), bytes().size());
214
0
}
215
216
Utf8View String::code_points() const&
217
13.3M
{
218
13.3M
    return Utf8View(bytes_as_string_view());
219
13.3M
}
220
221
ErrorOr<void> Formatter<String>::format(FormatBuilder& builder, String const& utf8_string)
222
252k
{
223
252k
    return Formatter<StringView>::format(builder, utf8_string.bytes_as_string_view());
224
252k
}
225
226
ErrorOr<String> String::replace(StringView needle, StringView replacement, ReplaceMode replace_mode) const
227
0
{
228
0
    return StringUtils::replace(*this, needle, replacement, replace_mode);
229
0
}
230
231
ErrorOr<String> String::reverse() const
232
0
{
233
    // FIXME: This handles multi-byte code points, but not e.g. grapheme clusters.
234
    // FIXME: We could avoid allocating a temporary vector if Utf8View supports reverse iteration.
235
0
    auto code_point_length = code_points().length();
236
237
0
    Vector<u32> code_points;
238
0
    TRY(code_points.try_ensure_capacity(code_point_length));
239
240
0
    for (auto code_point : this->code_points())
241
0
        code_points.unchecked_append(code_point);
242
243
0
    auto builder = TRY(StringBuilder::create(code_point_length * sizeof(u32)));
244
0
    while (!code_points.is_empty())
245
0
        TRY(builder.try_append_code_point(code_points.take_last()));
246
247
0
    return builder.to_string();
248
0
}
249
250
ErrorOr<String> String::trim(Utf8View const& code_points_to_trim, TrimMode mode) const
251
0
{
252
0
    auto trimmed = code_points().trim(code_points_to_trim, mode);
253
0
    return String::from_utf8(trimmed.as_string());
254
0
}
255
256
ErrorOr<String> String::trim(StringView code_points_to_trim, TrimMode mode) const
257
0
{
258
0
    return trim(Utf8View { code_points_to_trim }, mode);
259
0
}
260
261
ErrorOr<String> String::trim_ascii_whitespace(TrimMode mode) const
262
0
{
263
0
    return trim(" \n\t\v\f\r"sv, mode);
264
0
}
265
266
bool String::contains(StringView needle, CaseSensitivity case_sensitivity) const
267
5.52M
{
268
5.52M
    return StringUtils::contains(bytes_as_string_view(), needle, case_sensitivity);
269
5.52M
}
270
271
bool String::contains(u32 needle, CaseSensitivity case_sensitivity) const
272
5.52M
{
273
5.52M
    auto needle_as_string = String::from_code_point(needle);
274
5.52M
    return contains(needle_as_string.bytes_as_string_view(), case_sensitivity);
275
5.52M
}
276
277
bool String::starts_with(u32 code_point) const
278
1.29M
{
279
1.29M
    if (is_empty())
280
0
        return false;
281
282
1.29M
    return *code_points().begin() == code_point;
283
1.29M
}
284
285
bool String::starts_with_bytes(StringView bytes, CaseSensitivity case_sensitivity) const
286
1.29M
{
287
1.29M
    return bytes_as_string_view().starts_with(bytes, case_sensitivity);
288
1.29M
}
289
290
bool String::ends_with(u32 code_point) const
291
361k
{
292
361k
    if (is_empty())
293
0
        return false;
294
295
361k
    u32 last_code_point = 0;
296
10.8M
    for (auto it = code_points().begin(); it != code_points().end(); ++it)
297
10.5M
        last_code_point = *it;
298
299
361k
    return last_code_point == code_point;
300
361k
}
301
302
bool String::ends_with_bytes(StringView bytes, CaseSensitivity case_sensitivity) const
303
0
{
304
0
    return bytes_as_string_view().ends_with(bytes, case_sensitivity);
305
0
}
306
307
unsigned Traits<String>::hash(String const& string)
308
1.07M
{
309
1.07M
    return string.hash();
310
1.07M
}
311
312
ByteString String::to_byte_string() const
313
1
{
314
1
    return ByteString(bytes_as_string_view());
315
1
}
316
317
ErrorOr<String> String::from_byte_string(ByteString const& byte_string)
318
15.9k
{
319
15.9k
    return String::from_utf8(byte_string.view());
320
15.9k
}
321
322
String String::to_ascii_lowercase() const
323
0
{
324
0
    bool const has_ascii_uppercase = [&] {
325
0
        for (u8 const byte : bytes()) {
326
0
            if (AK::is_ascii_upper_alpha(byte))
327
0
                return true;
328
0
        }
329
0
        return false;
330
0
    }();
331
332
0
    if (!has_ascii_uppercase)
333
0
        return *this;
334
335
0
    Vector<u8> lowercase_bytes;
336
0
    lowercase_bytes.ensure_capacity(bytes().size());
337
0
    for (u8 const byte : bytes()) {
338
0
        if (AK::is_ascii_upper_alpha(byte))
339
0
            lowercase_bytes.unchecked_append(AK::to_ascii_lowercase(byte));
340
0
        else
341
0
            lowercase_bytes.unchecked_append(byte);
342
0
    }
343
0
    return String::from_utf8_without_validation(lowercase_bytes);
344
0
}
345
346
String String::to_ascii_uppercase() const
347
0
{
348
0
    bool const has_ascii_lowercase = [&] {
349
0
        for (u8 const byte : bytes()) {
350
0
            if (AK::is_ascii_lower_alpha(byte))
351
0
                return true;
352
0
        }
353
0
        return false;
354
0
    }();
355
356
0
    if (!has_ascii_lowercase)
357
0
        return *this;
358
359
0
    Vector<u8> uppercase_bytes;
360
0
    uppercase_bytes.ensure_capacity(bytes().size());
361
0
    for (u8 const byte : bytes()) {
362
0
        if (AK::is_ascii_lower_alpha(byte))
363
0
            uppercase_bytes.unchecked_append(AK::to_ascii_uppercase(byte));
364
0
        else
365
0
            uppercase_bytes.unchecked_append(byte);
366
0
    }
367
0
    return String::from_utf8_without_validation(uppercase_bytes);
368
0
}
369
370
bool String::equals_ignoring_ascii_case(String const& other) const
371
0
{
372
0
    return StringUtils::equals_ignoring_ascii_case(bytes_as_string_view(), other.bytes_as_string_view());
373
0
}
374
375
bool String::equals_ignoring_ascii_case(StringView other) const
376
0
{
377
0
    return StringUtils::equals_ignoring_ascii_case(bytes_as_string_view(), other);
378
0
}
379
380
ErrorOr<String> String::repeated(String const& input, size_t count)
381
0
{
382
0
    if (Checked<u32>::multiplication_would_overflow(count, input.bytes().size()))
383
0
        return Error::from_errno(EOVERFLOW);
384
385
0
    String result;
386
0
    size_t input_size = input.bytes().size();
387
0
    TRY(result.replace_with_new_string(count * input_size, [&](Bytes buffer) {
388
0
        if (input_size == 1) {
389
0
            buffer.fill(input.bytes().first());
390
0
        } else {
391
0
            for (size_t i = 0; i < count; ++i)
392
0
                input.bytes().copy_to(buffer.slice(i * input_size, input_size));
393
0
        }
394
0
        return ErrorOr<void> {};
395
0
    }));
396
397
0
    return result;
398
0
}
399
400
}