• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / 515

09 Sep 2026 01:22PM UTC coverage: 78.323% (+0.02%) from 78.306%
515

push

buildkite

web-flow
Merge pull request #62995 from JuliaLang/tb/abi_fixes

codegen: Correct native ABI lowering on RISC-V, x86-64, and AArch64

69493 of 88726 relevant lines covered (78.32%)

23636525.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.56
/base/strings/string.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
"""
4
    StringView{T <: AbstractVector{UInt8}} <: AbstractString
5

6
An `AbstractString` representation of any `vector` of `UInt8` data,
7
interpreted as UTF-8 encoded Unicode.
8
Similar to `String`, the underlying data may be invalid UTF-8.
9

10
`StringView(v::AbstractVector{UInt8})::StringView` does not make a copy of
11
or modify the `v`. Use `codeunits` to get `v` from the `StringView`.
12
After construction, `v` may be mutated, which will be reflected in
13
the resulting `StringView`.
14

15
!!! compat "Julia 1.14"
16
    The `StringView` type requires at least Julia 1.14.
17

18
# Examples
19
```jldoctest
20
julia> arr = [0x61, 0xf0, 0x63, 0x64];
21

22
julia> s = StringView(arr)
23
"a\\xf0cd"
24

25
julia> codeunits(s) === arr
26
true
27

28
julia> arr[2] = Int('b'); s
29
"abcd"
30
```
31
"""
32
struct StringView{T <: AbstractVector{UInt8}} <: AbstractString
33
    data::T
34

35
    function StringView{T}(data::T) where {T <: AbstractVector{UInt8}}
2✔
36
        # For now, StringViews code assumes one-based indexing
37
        require_one_based_indexing(data)
424✔
38

39
        # Prevent someone constructing e.g. a `StringView{AbstractVector{UInt8}}`,
40
        # the existence of which will complicate the implementation and provide
41
        # no usability benefit.
42
        if !isconcretetype(T)
424✔
43
            throw(ArgumentError("StringView must be parameterized with a concrete type"))
2✔
44
        end
45

46
        new{T}(data)
422✔
47
    end
48
end
49

50

51
"""
52
    StringIndexError(str, i)
53

54
An error occurred when trying to access `str` at index `i` that is not valid.
55
"""
56
struct StringIndexError <: Exception
57
    string::AbstractString
10✔
58
    index::Int
59
end
60
@noinline string_index_err((@nospecialize s::AbstractString), i::Integer) =
2✔
61
    throw(StringIndexError(s, Int(i)))
62
function showerror(io::IO, exc::StringIndexError)
8✔
63
    s = exc.string
8✔
64
    print(io, "StringIndexError: ", "invalid index [$(exc.index)]")
8✔
65
    if firstindex(s) <= exc.index <= ncodeunits(s)
8✔
66
        iprev = thisind(s, exc.index)
8✔
67
        inext = nextind(s, iprev)
8✔
68
        escprev = escape_string(s[iprev:iprev])
8✔
69
        if inext <= ncodeunits(s)
8✔
70
            escnext = escape_string(s[inext:inext])
6✔
71
            print(io, ", valid nearby indices [$iprev]=>'$escprev', [$inext]=>'$escnext'")
6✔
72
        else
73
            print(io, ", valid nearby index [$iprev]=>'$escprev'")
2✔
74
        end
75
    end
76
end
77

78
@inline between(b::T, lo::T, hi::T) where {T<:Integer} = (lo ≤ b) & (b ≤ hi)
1,020,345,111✔
79

80
"""
81
    String <: AbstractString
82

83
The default string type in Julia, used by e.g. string literals.
84

85
`String`s are immutable sequences of `Char`s. A `String` is stored internally as
86
a contiguous byte array, and while they are interpreted as being UTF-8 encoded,
87
they can be composed of any byte sequence. Use [`isvalid`](@ref) to validate
88
that the underlying byte sequence is valid as UTF-8.
89
"""
90
String
91

92
## constructors and conversions ##
93

94
# String constructor docstring from boot.jl, workaround for #16730
95
# and the unavailability of @doc in boot.jl context.
96
"""
97
    String(v::AbstractVector{UInt8})
98

99
Create a new `String` object using the data buffer from byte vector `v`.
100
If `v` is a `Vector{UInt8}` it will be truncated to zero length and future
101
modification of `v` cannot affect the contents of the resulting string.
102
To avoid truncation of `Vector{UInt8}` data, use `String(copy(v))`; for other
103
`AbstractVector` types, `String(v)` already makes a copy.
104

105
When possible, the memory of `v` will be used without copying when the `String`
106
object is created. This is guaranteed to be the case for byte vectors returned
107
by [`take!`](@ref) on a writable [`IOBuffer`](@ref) and by calls to
108
[`read(io, nb)`](@ref). This allows zero-copy conversion of I/O data to strings.
109
In other cases, `Vector{UInt8}` data may be copied, but `v` is truncated anyway
110
to guarantee consistent behavior.
111
"""
112
String(v::AbstractVector{UInt8}) = unsafe_takestring(copyto!(StringMemory(length(v)), v))
13,050,275✔
113

114
function String(v::Vector{UInt8})
4,064✔
115
    len = length(v)
21,102,748✔
116
    len == 0 && return ""
21,102,752✔
117
    ref = v.ref
20,950,754✔
118
    if ref.ptr_or_offset == ref.mem.ptr
20,951,050✔
119
        str = ccall(:jl_genericmemory_to_string, Ref{String}, (Any, Int), ref.mem, len)
20,951,030✔
120
    else
121
        str = ccall(:jl_pchar_to_string, Ref{String}, (Ptr{UInt8}, Int), ref, len)
21✔
122
    end
123
    # optimized empty!(v); sizehint!(v, 0) calls
124
    setfield!(v, :size, (0,))
20,951,051✔
125
    setfield!(v, :ref, memoryref(Memory{UInt8}()))
20,950,754✔
126
    return str
20,951,050✔
127
end
128

129
"""
130
    unsafe_takestring(m::Memory{UInt8})::String
131

132
Create a `String` from `m`, changing the interpretation of the contents of `m`.
133
This is done without copying, if possible. Thus, any access to `m` after
134
calling this function, either to read or to write, is undefined behavior.
135
"""
136
function unsafe_takestring(m::Memory{UInt8})
×
137
    isempty(m) ? "" : ccall(:jl_genericmemory_to_string, Ref{String}, (Any, Int), m, length(m))
13,052,198✔
138
end
139

140
"""
141
    takestring!(x)::AbstractString
142

143
Create a string from the content of `x`, emptying `x`.
144

145
# Examples
146
```jldoctest
147
julia> v = [0x61, 0x62, 0x63];
148

149
julia> s = takestring!(v)
150
"abc"
151

152
julia> isempty(v)
153
true
154
```
155

156
!!! compat "Julia 1.13"
157
    This function requires at least Julia 1.13.
158
"""
159
takestring!(v::Vector{UInt8}) = String(v)
×
160

161
"""
162
    unsafe_string(p::Ptr{UInt8}, [length::Integer])
163
    unsafe_string(p::Cstring)
164

165
Copy a string from the address of a C-style (NUL-terminated) string encoded as UTF-8.
166
(The pointer can be safely freed afterwards.) If `length` is specified
167
(the length of the data in bytes), the string does not have to be NUL-terminated.
168

169
This function is labeled "unsafe" because it will crash if `p` is not
170
a valid memory address to data of the requested length.
171
"""
172
function unsafe_string(p::Union{Ptr{UInt8},Ptr{Int8}}, len::Integer)
472✔
173
    p == C_NULL && throw(ArgumentError("cannot convert NULL to string"))
5,681,077✔
174
    ccall(:jl_pchar_to_string, Ref{String}, (Ptr{UInt8}, Int), p, len)
5,685,414✔
175
end
176
function unsafe_string(p::Union{Ptr{UInt8},Ptr{Int8}})
2,563✔
177
    p == C_NULL && throw(ArgumentError("cannot convert NULL to string"))
7,820,886✔
178
    ccall(:jl_cstr_to_string, Ref{String}, (Ptr{UInt8},), p)
7,820,839✔
179
end
180

181
# This is `@assume_effects :total !:consistent @ccall jl_alloc_string(n::Csize_t)::Ref{String}`,
182
# but the macro is not available at this time in bootstrap, so we write it manually.
183
const _string_n_override = 0x04ee
184
@eval _string_n(n::Integer) = $(Expr(:foreigncall, QuoteNode(:jl_alloc_string), Ref{String},
189,667,688✔
185
    :(Core.svec(Csize_t)), 1, QuoteNode((:ccall, _string_n_override, false)), :(convert(Csize_t, n))))
186

187
"""
188
    String(s::AbstractString)
189

190
Create a new `String` from an existing `AbstractString`.
191
"""
192
String(s::AbstractString) = print_to_string(s)
1,108✔
193
@assume_effects :total String(s::Symbol) = unsafe_string(unsafe_convert(Ptr{UInt8}, s))
7,003,663✔
194

195
unsafe_wrap(::Type{Memory{UInt8}}, s::String) = ccall(:jl_string_to_genericmemory, Ref{Memory{UInt8}}, (Any,), s)
27,870,985✔
196
unsafe_wrap(::Type{Vector{UInt8}}, s::String) = wrap(Array, unsafe_wrap(Memory{UInt8}, s))
109,699✔
197

198
Vector{UInt8}(s::CodeUnits{UInt8,String}) = copyto!(Vector{UInt8}(undef, length(s)), s)
63,128✔
199
Vector{UInt8}(s::String) = Vector{UInt8}(codeunits(s))
63,088✔
200
Array{UInt8}(s::String)  = Vector{UInt8}(codeunits(s))
×
201

202
String(s::CodeUnits{UInt8,String}) = s.s
2✔
203

204
## low-level functions ##
205

206
pointer(s::String) = unsafe_convert(Ptr{UInt8}, s)
2,177,930,482✔
207
pointer(s::String, i::Integer) = pointer(s) + Int(i)::Int - 1
1,310,251,402✔
208

209
ncodeunits(s::String) = Core.sizeof(s)
1,617,367,208✔
210
codeunit(s::String) = UInt8
6,938,399✔
211

212
codeunit(s::String, i::Integer) = codeunit(s, Int(i)::Int)
4✔
213
@assume_effects :foldable @inline function codeunit(s::String, i::Int)
5,521✔
214
    @boundscheck checkbounds(s, i)
1,049,450,739✔
215
    b = GC.@preserve s unsafe_load(pointer(s, i))
1,050,234,324✔
216
    return b
1,042,905,887✔
217
end
218

219
## comparison ##
220

221
@assume_effects :total _memcmp(a::String, b::String) = @invoke _memcmp(a::Union{Ptr{UInt8},AbstractString},b::Union{Ptr{UInt8},AbstractString})
156,973✔
222

223
_memcmp(a::Union{Ptr{UInt8},AbstractString}, b::Union{Ptr{UInt8},AbstractString}) = _memcmp(a, b, min(sizeof(a), sizeof(b)))
1,519,066✔
224
function _memcmp(a::Union{Ptr{UInt8},AbstractString}, b::Union{Ptr{UInt8},AbstractString}, len::Int)
×
225
    GC.@preserve a b begin
1,720,277✔
226
        pa = unsafe_convert(Ptr{UInt8}, a)
1,720,267✔
227
        pb = unsafe_convert(Ptr{UInt8}, b)
1,720,277✔
228
        memcmp(pa, pb, len % Csize_t) % Int
1,720,277✔
229
    end
230
end
231

232
function cmp(a::String, b::String)
233
    al, bl = sizeof(a), sizeof(b)
156,973✔
234
    c = _memcmp(a, b)
156,973✔
235
    return c < 0 ? -1 : c > 0 ? +1 : cmp(al,bl)
292,057✔
236
end
237

238
==(a::String, b::String) = a===b
36,025,395✔
239

240
typemin(::Type{String}) = ""
×
241
typemin(::String) = typemin(String)
×
242

243
## thisind, nextind ##
244

245
@propagate_inbounds thisind(s::String, i::Int) = _thisind_str(s, i)
267,853,513✔
246

247
# nothrow: i == ncodeunits(s) always satisfies the bounds check inside _thisind_str
248
# (it short-circuits when i == 0, otherwise 1 ≤ i ≤ n).
249
@assume_effects :nothrow lastindex(s::String) = thisind(s, ncodeunits(s)::Int)
51,894,195✔
250

251
# s should be String, StringView, or SubString{String}
252
@inline function _thisind_str(s, i::Int)
2,354✔
253
    i == 0 && return 0
134,772,929✔
254
    n = ncodeunits(s)
134,643,120✔
255
    i == n + 1 && return i
134,570,736✔
256
    @boundscheck between(i, 1, n) || throw(BoundsError(s, i))
134,420,035✔
257
    @inbounds b = codeunit(s, i)
134,224,060✔
258
    (b & 0xc0 == 0x80) & (i-1 > 0) || return i
241,965,888✔
259
    (@noinline function _thisind_continued(s, i) # mark the rest of the function as a slow-path
24,598,322✔
260
        local b
×
261
        @inbounds b = codeunit(s, i-1)
138,762✔
262
        between(b, 0b11000000, 0b11110111) && return i-1
138,762✔
263
        (b & 0xc0 == 0x80) & (i-2 > 0) || return i
93,054✔
264
        @inbounds b = codeunit(s, i-2)
91,976✔
265
        between(b, 0b11100000, 0b11110111) && return i-2
91,976✔
266
        (b & 0xc0 == 0x80) & (i-3 > 0) || return i
28✔
267
        @inbounds b = codeunit(s, i-3)
4✔
268
        between(b, 0b11110000, 0b11110111) && return i-3
4✔
269
        return i
×
270
    end)(s, i)
271
end
272

273
@propagate_inbounds nextind(s::String, i::Int) = _nextind_str(s, i)
95,278,401✔
274

275
# s should be String or SubString{String}
276
@inline function _nextind_str(s, i::Int)
160✔
277
    i == 0 && return 1
135,740,060✔
278
    n = ncodeunits(s)
136,196,625✔
279
    @boundscheck between(i, 1, n) || throw(BoundsError(s, i))
136,039,801✔
280
    @inbounds l = codeunit(s, i)
135,755,365✔
281
    between(l, 0x80, 0xf7) || return i+1
269,926,542✔
282
    (@noinline function _nextind_continued(s, i, n, l) # mark the rest of the function as a slow-path
265,420✔
283
        if l < 0xc0
47,717✔
284
            # handle invalid codeunit index by scanning back to the start of this index
285
            # (which may be the same as this index)
286
            i′ = @inbounds thisind(s, i)
638✔
287
            i′ >= i && return i+1
319✔
288
            i = i′
4✔
289
            @inbounds l = codeunit(s, i)
4✔
290
            (l < 0x80) | (0xf8 ≤ l) && return i+1
4✔
291
            @assert l >= 0xc0 "invalid codeunit"
4✔
292
        end
293
        # first continuation byte
294
        (i += 1) > n && return i
47,402✔
295
        @inbounds b = codeunit(s, i)
47,402✔
296
        b & 0xc0 ≠ 0x80 && return i
47,402✔
297
        ((i += 1) > n) | (l < 0xe0) && return i
46,768✔
298
        # second continuation byte
299
        @inbounds b = codeunit(s, i)
46,694✔
300
        b & 0xc0 ≠ 0x80 && return i
46,694✔
301
        ((i += 1) > n) | (l < 0xf0) && return i
46,694✔
302
        # third continuation byte
303
        @inbounds b = codeunit(s, i)
2✔
304
        return ifelse(b & 0xc0 ≠ 0x80, i, i+1)
2✔
305
    end)(s, i, n, l)
306
end
307

308
## checking UTF-8 & ASCII validity ##
309
#=
310
    The UTF-8 Validation is performed by a shift based DFA.
311
    ┌───────────────────────────────────────────────────────────────────┐
312
    │    UTF-8 DFA State Diagram    ┌──────────────2──────────────┐     │
313
    │                               ├────────3────────┐           │     │
314
    │                 ┌──────────┐  │     ┌─┐        ┌▼┐          │     │
315
    │      ASCII      │  UTF-8   │  ├─5──►│9├───1────► │          │     │
316
    │                 │          │  │     ├─┤        │ │         ┌▼┐    │
317
    │                 │  ┌─0─┐   │  ├─6──►│8├─1,7,9──►4├──1,7,9──► │    │
318
    │      ┌─0─┐      │  │   │   │  │     ├─┤        │ │         │ │    │
319
    │      │   │      │ ┌▼───┴┐  │  ├─11─►│7├──7,9───► │ ┌───────►3├─┐  │
320
    │     ┌▼───┴┐     │ │     │  ▼  │     └─┘        └─┘ │       │ │ │  │
321
    │     │  0  ├─────┘ │  1  ├─► ──┤                    │  ┌────► │ │  │
322
    │     └─────┘       │     │     │     ┌─┐            │  │    └─┘ │  │
323
    │                   └──▲──┘     ├─10─►│5├─────7──────┘  │        │  │
324
    │                      │        │     ├─┤               │        │  │
325
    │                      │        └─4──►│6├─────1,9───────┘        │  │
326
    │          INVALID     │              └─┘                        │  │
327
    │           ┌─*─┐      └──────────────────1,7,9──────────────────┘  │
328
    │          ┌▼───┴┐                                                  │
329
    │          │  2  ◄─── All undefined transitions result in state 2   │
330
    │          └─────┘                                                  │
331
    └───────────────────────────────────────────────────────────────────┘
332

333
        Validation States
334
            0 -> _UTF8_DFA_ASCII is the start state and will only stay in this state if the string is only ASCII characters
335
                        If the DFA ends in this state the string is ASCII only
336
            1 -> _UTF8_DFA_ACCEPT is the valid complete character state of the DFA once it has encountered a UTF-8 Unicode character
337
            2 -> _UTF8_DFA_INVALID is only reached by invalid bytes and once in this state it will not change
338
                    as seen by all 1s in that column of table below
339
            3 -> One valid continuation byte needed to return to state 0
340
        4,5,6 -> Two valid continuation bytes needed to return to state 0
341
        7,8,9 -> Three valid continuation bytes needed to return to state 0
342

343
                        Current State
344
                    0̲  1̲  2̲  3̲  4̲  5̲  6̲  7̲  8̲  9̲
345
                0 | 0  1  2  2  2  2  2  2  2  2
346
                1 | 2  2  2  1  3  2  3  2  4  4
347
                2 | 3  3  2  2  2  2  2  2  2  2
348
                3 | 4  4  2  2  2  2  2  2  2  2
349
                4 | 6  6  2  2  2  2  2  2  2  2
350
    Character   5 | 9  9  2  2  2  2  2  2  2  2     <- Next State
351
    Class       6 | 8  8  2  2  2  2  2  2  2  2
352
                7 | 2  2  2  1  3  3  2  4  4  2
353
                8 | 2  2  2  2  2  2  2  2  2  2
354
                9 | 2  2  2  1  3  2  3  4  4  2
355
               10 | 5  5  2  2  2  2  2  2  2  2
356
               11 | 7  7  2  2  2  2  2  2  2  2
357

358
           Shifts | 0  4 10 14 18 24  8 20 12 26
359

360
    The shifts that represent each state were derived using the SMT solver Z3, to ensure when encoded into
361
    the rows the correct shift was a result.
362

363
    Each character class row is encoding 10 states with shifts as defined above. By shifting the bits of a row by
364
    the current state then masking the result with 0x11110 give the shift for the new state
365

366

367
=#
368

369
#State type used by UTF-8 DFA
370
const _UTF8DFAState = UInt32
371
# Fill the table with 256 UInt64 representing the DFA transitions for all bytes
372
const _UTF8_DFA_TABLE = let # let block rather than function doesn't pollute base
373
    num_classes=12
374
    num_states=10
375

376
    # These shifts were derived using a SMT solver
377
    state_shifts = [0, 4, 10, 14, 18, 24, 8, 20, 12, 26]
378

379
    character_classes = [   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
380
                            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
381
                            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
382
                            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
383
                            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
384
                            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
385
                            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
386
                            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
387
                            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
388
                            9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
389
                            7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
390
                            7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
391
                            8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
392
                            2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
393
                            10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3,
394
                            11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 ]
395

396
    # These are the rows discussed in comments above
397
    state_arrays = [ 0  1  2  2  2  2  2  2  2  2;
398
                     2  2  2  1  3  2  3  2  4  4;
399
                     3  3  2  2  2  2  2  2  2  2;
400
                     4  4  2  2  2  2  2  2  2  2;
401
                     6  6  2  2  2  2  2  2  2  2;
402
                     9  9  2  2  2  2  2  2  2  2;
403
                     8  8  2  2  2  2  2  2  2  2;
404
                     2  2  2  1  3  3  2  4  4  2;
405
                     2  2  2  2  2  2  2  2  2  2;
406
                     2  2  2  1  3  2  3  4  4  2;
407
                     5  5  2  2  2  2  2  2  2  2;
408
                     7  7  2  2  2  2  2  2  2  2]
409

410
    #This converts the state_arrays into the shift encoded _UTF8DFAState
411
    class_row = zeros(_UTF8DFAState, num_classes)
412

413
    for i = 1:num_classes
414
        row = _UTF8DFAState(0)
415
        for j in 1:num_states
416
            #Calculate the shift required for the next state
417
            to_shift = UInt8((state_shifts[state_arrays[i,j]+1]) )
418
            #Shift the next state into the position of the current state
419
            row = row | (_UTF8DFAState(to_shift) << state_shifts[j])
420
        end
421
        class_row[i]=row
422
    end
423

424
    map(c->class_row[c+1],character_classes)
×
425
end
426

427

428
const _UTF8_DFA_ASCII = _UTF8DFAState(0) #This state represents the start and end of any valid string
429
const _UTF8_DFA_ACCEPT = _UTF8DFAState(4) #This state represents the start and end of any valid string
430
const _UTF8_DFA_INVALID = _UTF8DFAState(10) # If the state machine is ever in this state just stop
431

432
# The dfa step is broken out so that it may be used in other functions. The mask was calculated to work with state shifts above
433
@inline _utf_dfa_step(state::_UTF8DFAState, byte::UInt8) = @inbounds (_UTF8_DFA_TABLE[byte+1] >> state) & _UTF8DFAState(0x0000001E)
110,832✔
434

435
@inline function _isvalid_utf8_dfa(state::_UTF8DFAState, bytes::AbstractVector{UInt8}, first::Int = firstindex(bytes), last::Int = lastindex(bytes))
10,496✔
436
    for i = first:last
51,888✔
437
       @inbounds state = _utf_dfa_step(state, bytes[i])
110,832✔
438
    end
169,776✔
439
    return (state)
51,888✔
440
end
441

442
@inline function  _find_nonascii_chunk(chunk_size,cu::AbstractVector{CU}, first,last) where {CU}
443
    n=first
20✔
444
    while n <= last - chunk_size
80✔
445
        _isascii(cu,n,n+chunk_size-1) || return n
60✔
446
        n += chunk_size
60✔
447
    end
60✔
448
    n= last-chunk_size+1
20✔
449
    _isascii(cu,n,last) || return n
20✔
450
    return nothing
20✔
451
end
452

453
##
454

455
# Classifications of string
456
    # 0: neither valid ASCII nor UTF-8
457
    # 1: valid ASCII
458
    # 2: valid UTF-8
459
 byte_string_classify(s::AbstractString) = byte_string_classify(codeunits(s))
48✔
460

461

462
function byte_string_classify(bytes::AbstractVector{UInt8})
48✔
463
    chunk_size = 1024
41,909✔
464
    chunk_threshold =  chunk_size + (chunk_size ÷ 2)
41,909✔
465
    n = length(bytes)
41,909✔
466
    if n > chunk_threshold
41,909✔
467
        start = _find_nonascii_chunk(chunk_size,bytes,1,n)
20✔
468
        isnothing(start) && return 1
20✔
469
    else
470
        _isascii(bytes,1,n) && return 1
41,889✔
471
        start = 1
40,624✔
472
    end
473
    return _byte_string_classify_nonascii(bytes,start,n)
40,624✔
474
end
475

476
function _byte_string_classify_nonascii(bytes::AbstractVector{UInt8}, first::Int, last::Int)
40,624✔
477
    chunk_size = 256
40,624✔
478

479
    start = first
40,624✔
480
    stop = min(last,first + chunk_size - 1)
40,624✔
481
    state = _UTF8_DFA_ACCEPT
40,624✔
482
    while start <= last
50,334✔
483
        # try to process ascii chunks
484
        while state == _UTF8_DFA_ACCEPT
40,624✔
485
            _isascii(bytes,start,stop) || break
40,624✔
486
            (start = start + chunk_size) <= last || break
×
487
            stop = min(last,stop + chunk_size)
×
488
        end
×
489
        # Process non ascii chunk
490
        state = _isvalid_utf8_dfa(state,bytes,start,stop)
99,568✔
491
        state == _UTF8_DFA_INVALID && return 0
40,624✔
492

493
        start = start + chunk_size
9,710✔
494
        stop = min(last,stop + chunk_size)
9,710✔
495
    end
9,710✔
496
    return ifelse(state == _UTF8_DFA_ACCEPT,2,0)
9,710✔
497
end
498

499
isvalid(::Type{String}, bytes::AbstractVector{UInt8}) = (@inline byte_string_classify(bytes)) ≠ 0
42,149✔
500
isvalid(::Type{String}, s::AbstractString) =  (@inline byte_string_classify(s)) ≠ 0
48✔
501

502
@inline isvalid(s::AbstractString) = @inline isvalid(String, codeunits(s))
1,631✔
503

504
is_valid_continuation(c) = c & 0xc0 == 0x80
838✔
505

506
## required core functionality ##
507

508
@inline function iterate(s::Union{String, StringView}, i::Int=firstindex(s))
2,338✔
509
    (i % UInt) - 1 < ncodeunits(s) || return nothing
563,997,902✔
510
    b = @inbounds codeunit(s, i)
497,696,332✔
511
    u = UInt32(b) << 24
497,698,688✔
512
    between(b, 0x80, 0xf7) || return reinterpret(Char, u), i+1
994,703,042✔
513
    return @noinline iterate_continued(s, i, u)
680,530✔
514
end
515

516
# duck-type s so that external UTF-8 string packages like StringViews can hook in
517
function iterate_continued(s, i::Int, u::UInt32)
294,750✔
518
    @label begin
294,750✔
519
        u < 0xc0000000 && (i += 1; break)
294,750✔
520
        n = ncodeunits(s)
294,427✔
521
        # first continuation byte
522
        (i += 1) > n && break
294,427✔
523
        @inbounds b = codeunit(s, i)
294,425✔
524
        b & 0xc0 == 0x80 || break
294,425✔
525
        u |= UInt32(b) << 16
293,791✔
526
        # second continuation byte
527
        ((i += 1) > n) | (u < 0xe0000000) && break
293,791✔
528
        @inbounds b = codeunit(s, i)
190,160✔
529
        b & 0xc0 == 0x80 || break
190,160✔
530
        u |= UInt32(b) << 8
190,160✔
531
        # third continuation byte
532
        ((i += 1) > n) | (u < 0xf0000000) && break
190,160✔
533
        @inbounds b = codeunit(s, i)
881✔
534
        b & 0xc0 == 0x80 || break
881✔
535
        u |= UInt32(b); i += 1
881✔
536
    end
537
    return reinterpret(Char, u), i
294,750✔
538
end
539

540
@propagate_inbounds function getindex(s::Union{String, StringView}, i::Int)
136✔
541
    b = codeunit(s, i)
132,660,279✔
542
    u = UInt32(b) << 24
132,695,520✔
543
    between(b, 0x80, 0xf7) || return reinterpret(Char, u)
263,585,368✔
544
    return getindex_continued(s, i, u)
186,235✔
545
end
546

547
# duck-type s so that external UTF-8 string packages like StringViews can hook in
548
function getindex_continued(s, i::Int, u::UInt32)
1,572✔
549
    @label begin
1,582✔
550
        if u < 0xc0000000
1,582✔
551
            # called from `getindex` which checks bounds
552
            @inbounds isvalid(s, i) && break
16✔
553
            string_index_err(s, i)
2✔
554
        end
555
        n = ncodeunits(s)
1,574✔
556

557
        (i += 1) > n && break
1,574✔
558
        @inbounds b = codeunit(s, i) # cont byte 1
1,574✔
559
        b & 0xc0 == 0x80 || break
1,574✔
560
        u |= UInt32(b) << 16
1,574✔
561

562
        ((i += 1) > n) | (u < 0xe0000000) && break
1,574✔
563
        @inbounds b = codeunit(s, i) # cont byte 2
1,518✔
564
        b & 0xc0 == 0x80 || break
1,518✔
565
        u |= UInt32(b) << 8
1,518✔
566

567
        ((i += 1) > n) | (u < 0xf0000000) && break
1,518✔
568
        @inbounds b = codeunit(s, i) # cont byte 3
×
569
        b & 0xc0 == 0x80 || break
×
570
        u |= UInt32(b)
×
571
    end
572
    return reinterpret(Char, u)
1,580✔
573
end
574

575
function getindex(s::Union{String, StringView}, r::AbstractUnitRange{<:Integer})
8✔
576
    span = (Int(first(r))::Int):(Int(last(r)))::Int
8✔
577
    return s[span]
8✔
578
end
579

580
@inline function getindex(s::String, r::UnitRange{Int})
×
581
    isempty(r) && return ""
2,690,744✔
582
    i, j = first(r), last(r)
1,550,459✔
583
    @boundscheck begin
2,627,354✔
584
        checkbounds(s, r)
2,627,229✔
585
        @inbounds isvalid(s, i) || string_index_err(s, i)
2,627,094✔
586
        @inbounds isvalid(s, j) || string_index_err(s, j)
2,626,641✔
587
    end
588
    # Safety: The boundscheck checked r is inbounds in s,
589
    # and since we also checked r is not empty, j must be inbounds in s
590
    j = @inbounds nextind(s, j) - 1
5,252,558✔
591
    n = (j - i + 1) % UInt
2,626,875✔
592
    ss = _string_n(n)
2,626,882✔
593
    GC.@preserve s ss unsafe_copyto!(pointer(ss), pointer(s, i), n)
2,627,283✔
594
    return ss
2,627,460✔
595
end
596

597
# nothrow because we know the start and end indices are valid
598
@assume_effects :nothrow function length(s::String)
100,614✔
599
    return length_continued(s, 1, ncodeunits(s), ncodeunits(s))
100,622✔
600
end
601

602
function length(s::StringView)
36✔
603
    return length_continued(s, 1, ncodeunits(s), ncodeunits(s))
36✔
604
end
605

606
# effects needed because @inbounds
607
@assume_effects :consistent :effect_free @inline function length(s::String, i::Int, j::Int)
608
    _length(s, i, j)
169,224✔
609
end
610

611
@inline function length(s::StringView, i::Int, j::Int)
6✔
612
    _length(s, i, j)
14✔
613
end
614

615
@inline function _length(s::Union{String, StringView}, i::Int, j::Int)
616
    @boundscheck begin
113,538✔
617
        0 < i ≤ ncodeunits(s)+1 || throw(BoundsError(s, i))
113,538✔
618
        0 ≤ j < ncodeunits(s)+1 || throw(BoundsError(s, j))
113,538✔
619
    end
620
    j < i && return 0
113,538✔
621
    @inbounds i, k = thisind(s, i), i
113,868✔
622
    c = j - i + (i == k)
56,934✔
623
    @inbounds length_continued(s, i, j, c)
56,934✔
624
end
625

626
@assume_effects :terminates_globally @propagate_inbounds function length_continued(s::String, i::Int, n::Int, c::Int)
627
    _length_continued(s, i, n, c)
157,558✔
628
end
629

630
@propagate_inbounds function length_continued(s::StringView, i::Int, n::Int, c::Int)
631
    _length_continued(s, i, n, c)
48✔
632
end
633

634

635
@propagate_inbounds function _length_continued(s::Union{String, StringView}, i::Int, n::Int, c::Int)
636
    i < n || return c
159,035✔
637
    b = codeunit(s, i)
156,157✔
638
    while true
905,104✔
639
        while true
3,404,674✔
640
            (i += 1) ≤ n || return c
18,550,069✔
641
            0xc0 ≤ b ≤ 0xf7 && break
18,254,167✔
642
            b = codeunit(s, i)
17,517,304✔
643
        end
17,497,511✔
644
        l = b
28✔
645
        b = codeunit(s, i) # cont byte 1
750,256✔
646
        c -= (x = b & 0xc0 == 0x80)
750,256✔
647
        x & (l ≥ 0xe0) || continue
750,256✔
648

649
        (i += 1) ≤ n || return c
60,472✔
650
        b = codeunit(s, i) # cont byte 2
57,876✔
651
        c -= (x = b & 0xc0 == 0x80)
57,876✔
652
        x & (l ≥ 0xf0) || continue
115,750✔
653

654
        (i += 1) ≤ n || return c
2✔
655
        b = codeunit(s, i) # cont byte 3
2✔
656
        c -= (b & 0xc0 == 0x80)
2✔
657
    end
748,958✔
658
end
659

660
## overload methods for efficiency ##
661

662
isvalid(s::String, i::Int) = checkbounds(Bool, s, i) && thisind(s, i) == i
216,950,742✔
663

664
# `isascii(::AbstractVector)` reduces to `@inbounds codeunit(::String, ::Int)`, total.
665
isascii(s::String) = @assume_effects :nothrow :foldable isascii(codeunits(s))
6,881,017✔
666

667
# don't assume effects for general integers since we cannot know their implementation
668
@assume_effects :foldable repeat(c::Char, r::BitInteger) = @invoke repeat(c::Char, r::Integer)
8,109,189✔
669

670
"""
671
    repeat(c::AbstractChar, r::Integer)::String
672

673
Repeat a character `r` times. This can equivalently be accomplished by calling
674
[`c^r`](@ref :^(::Union{AbstractString, AbstractChar}, ::Integer)).
675

676
# Examples
677
```jldoctest
678
julia> repeat('A', 3)
679
"AAA"
680
```
681
"""
682
function repeat(c::AbstractChar, r::Integer)
8,102,388✔
683
    r < 0 && throw(ArgumentError("can't repeat a character $r times"))
8,103,764✔
684
    r = UInt(r)::UInt
8,103,764✔
685
    c = Char(c)::Char
8,103,764✔
686
    r == 0 && return ""
8,103,764✔
687
    u = bswap(reinterpret(UInt32, c))
8,103,679✔
688
    n = 4 - (leading_zeros(u | 0xff) >> 3)
8,103,679✔
689
    r > typemax(UInt) ÷ UInt(n) && throw(OutOfMemoryError())
8,103,679✔
690
    s = _string_n(n*r)
8,103,673✔
691
    p = pointer(s)
8,103,671✔
692
    GC.@preserve s if n == 1
8,103,671✔
693
        memset(p, u % UInt8, r)
8,103,659✔
694
    elseif n == 2
12✔
695
        p16 = reinterpret(Ptr{UInt16}, p)
4✔
696
        for i = 1:r
4✔
697
            unsafe_store!(p16, u % UInt16, i)
8✔
698
        end
8✔
699
    elseif n == 3
8✔
700
        b1 = (u >> 0) % UInt8
4✔
701
        b2 = (u >> 8) % UInt8
4✔
702
        b3 = (u >> 16) % UInt8
4✔
703
        for i = 0:r-1
4✔
704
            unsafe_store!(p, b1, 3i + 1)
8✔
705
            unsafe_store!(p, b2, 3i + 2)
8✔
706
            unsafe_store!(p, b3, 3i + 3)
8✔
707
        end
8✔
708
    elseif n == 4
4✔
709
        p32 = reinterpret(Ptr{UInt32}, p)
4✔
710
        for i = 1:r
4✔
711
            unsafe_store!(p32, u, i)
8✔
712
        end
8,103,679✔
713
    end
714
    return s
8,103,671✔
715
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc