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

JuliaLang / julia / #37658

20 Oct 2023 08:24PM UTC coverage: 87.459% (-0.5%) from 87.929%
#37658

push

local

web-flow
fix unicode indexing in parse(Complex, string) (#51758)

This fixes a string-indexing bug introduced in #24713 (Julia 0.7).
Sometimes, this would cause `parse(Complex{T}, string)` to throw a
`StringIndexError` rather than an `ArgumentError`, e.g. for
`parse(ComplexF64, "3 β+ 4im")` or `parse(ComplexF64, "3 + 4αm")`. (As
far as I can tell, it can never cause parsing to fail for valid
strings.)

The source of the error is that if `i` is the index of an ASCII
character in a string `s`, then `s[i+1]` is valid (even if the next
character is non-ASCII) but `s[i-1]` is invalid if the previous
character is non-ASCII.

5 of 5 new or added lines in 1 file covered. (100.0%)

73572 of 84122 relevant lines covered (87.46%)

11577017.06 hits per line

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

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

3
"""
4
Tools for collecting and manipulating stack traces. Mainly used for building errors.
5
"""
6
module StackTraces
7

8

9
import Base: hash, ==, show
10
import Core: CodeInfo, MethodInstance
11

12
export StackTrace, StackFrame, stacktrace
13

14
"""
15
    StackFrame
16

17
Stack information representing execution context, with the following fields:
18

19
- `func::Symbol`
20

21
  The name of the function containing the execution context.
22

23
- `linfo::Union{Core.MethodInstance, Method, Module, Core.CodeInfo, Nothing}`
24

25
  The MethodInstance or CodeInfo containing the execution context (if it could be found), \
26
     or Module (for macro expansions)"
27

28
- `file::Symbol`
29

30
  The path to the file containing the execution context.
31

32
- `line::Int`
33

34
  The line number in the file containing the execution context.
35

36
- `from_c::Bool`
37

38
  True if the code is from C.
39

40
- `inlined::Bool`
41

42
  True if the code is from an inlined frame.
43

44
- `pointer::UInt64`
45

46
  Representation of the pointer to the execution context as returned by `backtrace`.
47

48
"""
49
struct StackFrame # this type should be kept platform-agnostic so that profiles can be dumped on one machine and read on another
50
    "the name of the function containing the execution context"
34,616✔
51
    func::Symbol
52
    "the path to the file containing the execution context"
53
    file::Symbol
54
    "the line number in the file containing the execution context"
55
    line::Int
56
    "the MethodInstance or CodeInfo containing the execution context (if it could be found), \
57
     or Module (for macro expansions)"
58
    linfo::Union{MethodInstance, Method, Module, CodeInfo, Nothing}
59
    "true if the code is from C"
60
    from_c::Bool
61
    "true if the code is from an inlined frame"
62
    inlined::Bool
63
    "representation of the pointer to the execution context as returned by `backtrace`"
64
    pointer::UInt64  # Large enough to be read losslessly on 32- and 64-bit machines.
65
end
66

67
StackFrame(func, file, line) = StackFrame(Symbol(func), Symbol(file), line,
×
68
                                          nothing, false, false, 0)
69

70
"""
71
    StackTrace
72

73
An alias for `Vector{StackFrame}` provided for convenience; returned by calls to
74
`stacktrace`.
75
"""
76
const StackTrace = Vector{StackFrame}
77

78
const empty_sym = Symbol("")
79
const UNKNOWN = StackFrame(empty_sym, empty_sym, -1, nothing, true, false, 0) # === lookup(C_NULL)
80

81

82
#=
83
If the StackFrame has function and line information, we consider two of them the same if
84
they share the same function/line information.
85
=#
86
function ==(a::StackFrame, b::StackFrame)
804✔
87
    return a.line == b.line && a.from_c == b.from_c && a.func == b.func && a.file == b.file && a.inlined == b.inlined # excluding linfo and pointer
42,908✔
88
end
89

90
function hash(frame::StackFrame, h::UInt)
×
91
    h += 0xf4fbda67fe20ce88 % UInt
×
92
    h = hash(frame.line, h)
693,639✔
93
    h = hash(frame.file, h)
693,639✔
94
    h = hash(frame.func, h)
693,639✔
95
    h = hash(frame.from_c, h)
693,639✔
96
    h = hash(frame.inlined, h)
693,639✔
97
    return h
×
98
end
99

100
get_inlinetable(::Any) = nothing
13,394✔
101
function get_inlinetable(mi::MethodInstance)
7,369✔
102
    isdefined(mi, :def) && mi.def isa Method && isdefined(mi, :cache) && isdefined(mi.cache, :inferred) &&
13,245✔
103
        mi.cache.inferred !== nothing || return nothing
104
    linetable = ccall(:jl_uncompress_ir, Any, (Any, Any, Any), mi.def, mi.cache, mi.cache.inferred).linetable
1,493✔
105
    return filter!(x -> x.inlined_at > 0, linetable)
79,188✔
106
end
107

108
get_method_instance_roots(::Any) = nothing
13,394✔
109
function get_method_instance_roots(mi::Union{Method, MethodInstance})
5,903✔
110
    m = mi isa MethodInstance ? mi.def : mi
5,903✔
111
    m isa Method && isdefined(m, :roots) || return nothing
5,903✔
112
    return filter(x -> x isa MethodInstance, m.roots)
401,412✔
113
end
114

115
function lookup_inline_frame_info(func::Symbol, file::Symbol, linenum::Int, inlinetable::Vector{Core.LineInfoNode})
349✔
116
    #REPL frames and some base files lack this prefix while others have it; should fix?
117
    filestripped = Symbol(lstrip(string(file), ('.', '\\', '/')))
349✔
118
    linfo = nothing
×
119
    #=
120
    Some matching entries contain the MethodInstance directly.
121
    Other matching entries contain only a Method or Symbol (function name); such entries
122
    are located after the entry with the MethodInstance, so backtracking is required.
123
    If backtracking fails, the Method or Module is stored for return, but we continue
124
    the search in case a MethodInstance is found later.
125
    TODO: If a backtrack has failed, do we need to backtrack again later if another Method
126
    or Symbol match is found? Or can a limit on the subsequent backtracks be placed?
127
    =#
128
    for (i, line) in enumerate(inlinetable)
698✔
129
        Base.IRShow.method_name(line) === func && line.file ∈ (file, filestripped) && line.line == linenum || continue
75,884✔
130
        if line.method isa MethodInstance
602✔
131
            linfo = line.method
×
132
            break
×
133
        elseif line.method isa Method || line.method isa Symbol
1,204✔
134
            linfo = line.method isa Method ? line.method : line.module
1,204✔
135
            # backtrack to find the matching MethodInstance, if possible
136
            for j in (i - 1):-1:1
1,038✔
137
                nextline = inlinetable[j]
774✔
138
                nextline.inlined_at == line.inlined_at && Base.IRShow.method_name(line) === Base.IRShow.method_name(nextline) && line.file === nextline.file || break
1,187✔
139
                if nextline.method isa MethodInstance
361✔
140
                    linfo = nextline.method
×
141
                    break
×
142
                end
143
            end
361✔
144
        end
145
    end
75,884✔
146
    return linfo
349✔
147
end
148

149
function lookup_inline_frame_info(func::Symbol, file::Symbol, miroots::Vector{Any})
8,026✔
150
    # REPL frames and some base files lack this prefix while others have it; should fix?
151
    filestripped = Symbol(lstrip(string(file), ('.', '\\', '/')))
8,026✔
152
    matches = filter(miroots) do x
8,026✔
153
        x.def isa Method || return false
80,497✔
154
        m = x.def::Method
80,497✔
155
        return m.name == func && m.file ∈ (file, filestripped)
80,497✔
156
    end
157
    if length(matches) > 1
8,026✔
158
        # ambiguous, check if method is same and return that instead
159
        all_matched = true
×
160
        for m in matches
93✔
161
            all_matched = m.def.line == matches[1].def.line &&
200✔
162
                m.def.module == matches[1].def.module
163
            all_matched || break
200✔
164
        end
201✔
165
        if all_matched
93✔
166
            return matches[1].def
47✔
167
        end
168
        # all else fails, return module if they match, or give up
169
        all_matched = true
×
170
        for m in matches
46✔
171
            all_matched = m.def.module == matches[1].def.module
112✔
172
            all_matched || break
112✔
173
        end
158✔
174
        return all_matched ? matches[1].def.module : nothing
46✔
175
    elseif length(matches) == 1
7,933✔
176
        return matches[1]
160✔
177
    end
178
    return nothing
7,773✔
179
end
180

181
"""
182
    lookup(pointer::Ptr{Cvoid}) -> Vector{StackFrame}
183

184
Given a pointer to an execution context (usually generated by a call to `backtrace`), looks
185
up stack frame context information. Returns an array of frame information for all functions
186
inlined at that point, innermost function first.
187
"""
188
Base.@constprop :none function lookup(pointer::Ptr{Cvoid})
20,848✔
189
    infos = ccall(:jl_lookup_code_address, Any, (Ptr{Cvoid}, Cint), pointer, false)::Core.SimpleVector
20,848✔
190
    pointer = convert(UInt64, pointer)
20,848✔
191
    isempty(infos) && return [StackFrame(empty_sym, empty_sym, -1, nothing, true, false, pointer)] # this is equal to UNKNOWN
20,848✔
192
    parent_linfo = infos[end][4]
20,848✔
193
    inlinetable = get_inlinetable(parent_linfo)
34,327✔
194
    miroots = inlinetable === nothing ? get_method_instance_roots(parent_linfo) : nothing # fallback if linetable missing
20,848✔
195
    res = Vector{StackFrame}(undef, length(infos))
20,848✔
196
    for i in reverse(1:length(infos))
41,696✔
197
        info = infos[i]::Core.SimpleVector
32,850✔
198
        @assert(length(info) == 6)
32,850✔
199
        func = info[1]::Symbol
32,850✔
200
        file = info[2]::Symbol
32,850✔
201
        linenum = info[3]::Int
32,850✔
202
        linfo = info[4]
32,850✔
203
        if i < length(infos)
32,850✔
204
            if inlinetable !== nothing
12,002✔
205
                linfo = lookup_inline_frame_info(func, file, linenum, inlinetable)
349✔
206
            elseif miroots !== nothing
11,653✔
207
                linfo = lookup_inline_frame_info(func, file, miroots)
8,026✔
208
            end
209
        end
210
        res[i] = StackFrame(func, file, linenum, linfo, info[5]::Bool, info[6]::Bool, pointer)
32,850✔
211
    end
44,852✔
212
    return res
20,848✔
213
end
214

215
const top_level_scope_sym = Symbol("top-level scope")
216

217
function lookup(ip::Union{Base.InterpreterIP,Core.Compiler.InterpreterIP})
221✔
218
    code = ip.code
221✔
219
    if code === nothing
221✔
220
        # interpreted top-level expression with no CodeInfo
221
        return [StackFrame(top_level_scope_sym, empty_sym, 0, nothing, false, false, 0)]
1✔
222
    end
223
    codeinfo = (code isa MethodInstance ? code.uninferred : code)::CodeInfo
440✔
224
    # prepare approximate code info
225
    if code isa MethodInstance && (meth = code.def; meth isa Method)
220✔
226
        func = meth.name
×
227
        file = meth.file
×
228
        line = meth.line
×
229
    else
230
        func = top_level_scope_sym
×
231
        file = empty_sym
×
232
        line = Int32(0)
×
233
    end
234
    i = max(ip.stmt+1, 1)  # ip.stmt is 0-indexed
220✔
235
    if i > length(codeinfo.codelocs) || codeinfo.codelocs[i] == 0
440✔
236
        return [StackFrame(func, file, line, code, false, false, 0)]
×
237
    end
238
    lineinfo = codeinfo.linetable[codeinfo.codelocs[i]]::Core.LineInfoNode
220✔
239
    scopes = StackFrame[]
220✔
240
    while true
635✔
241
        inlined = lineinfo.inlined_at != 0
635✔
242
        push!(scopes, StackFrame(Base.IRShow.method_name(lineinfo)::Symbol, lineinfo.file, lineinfo.line, inlined ? nothing : code, false, inlined, 0))
855✔
243
        inlined || break
635✔
244
        lineinfo = codeinfo.linetable[lineinfo.inlined_at]::Core.LineInfoNode
415✔
245
    end
415✔
246
    return scopes
220✔
247
end
248

249
"""
250
    stacktrace([trace::Vector{Ptr{Cvoid}},] [c_funcs::Bool=false]) -> StackTrace
251

252
Return a stack trace in the form of a vector of `StackFrame`s. (By default stacktrace
253
doesn't return C functions, but this can be enabled.) When called without specifying a
254
trace, `stacktrace` first calls `backtrace`.
255
"""
256
Base.@constprop :none function stacktrace(trace::Vector{<:Union{Base.InterpreterIP,Core.Compiler.InterpreterIP,Ptr{Cvoid}}}, c_funcs::Bool=false)
92✔
257
    stack = StackTrace()
101✔
258
    for ip in trace
48✔
259
        for frame in lookup(ip)
1,316✔
260
            # Skip frames that come from C calls.
261
            if c_funcs || !frame.from_c
3,607✔
262
                push!(stack, frame)
658✔
263
            end
264
        end
3,185✔
265
    end
1,362✔
266
    return stack
47✔
267
end
268

269
Base.@constprop :none function stacktrace(c_funcs::Bool=false)
×
270
    stack = stacktrace(backtrace(), c_funcs)
×
271
    # Remove frame for this function (and any functions called by this function).
272
    remove_frames!(stack, :stacktrace)
×
273
    # also remove all of the non-Julia functions that led up to this point (if that list is non-empty)
274
    c_funcs && deleteat!(stack, 1:(something(findfirst(frame -> !frame.from_c, stack), 1) - 1))
×
275
    return stack
×
276
end
277

278
"""
279
    remove_frames!(stack::StackTrace, name::Symbol)
280

281
Takes a `StackTrace` (a vector of `StackFrames`) and a function name (a `Symbol`) and
282
removes the `StackFrame` specified by the function name from the `StackTrace` (also removing
283
all frames above the specified function). Primarily used to remove `StackTraces` functions
284
from the `StackTrace` prior to returning it.
285
"""
286
function remove_frames!(stack::StackTrace, name::Symbol)
×
287
    deleteat!(stack, 1:something(findlast(frame -> frame.func == name, stack), 0))
×
288
    return stack
×
289
end
290

291
function remove_frames!(stack::StackTrace, names::Vector{Symbol})
×
292
    deleteat!(stack, 1:something(findlast(frame -> frame.func in names, stack), 0))
×
293
    return stack
×
294
end
295

296
"""
297
    remove_frames!(stack::StackTrace, m::Module)
298

299
Return the `StackTrace` with all `StackFrame`s from the provided `Module` removed.
300
"""
301
function remove_frames!(stack::StackTrace, m::Module)
×
302
    filter!(f -> !from(f, m), stack)
×
303
    return stack
×
304
end
305

306
is_top_level_frame(f::StackFrame) = f.linfo isa CodeInfo || (f.linfo === nothing && f.func === top_level_scope_sym)
9✔
307

308
function show_spec_linfo(io::IO, frame::StackFrame)
2,130✔
309
    linfo = frame.linfo
2,130✔
310
    if linfo === nothing
2,130✔
311
        if frame.func === empty_sym
654✔
312
            print(io, "ip:0x", string(frame.pointer, base=16))
×
313
        elseif frame.func === top_level_scope_sym
654✔
314
            print(io, "top-level scope")
20✔
315
        else
316
            Base.print_within_stacktrace(io, Base.demangle_function_name(string(frame.func)), bold=true)
634✔
317
        end
318
    elseif linfo isa CodeInfo
1,476✔
319
        print(io, "top-level scope")
103✔
320
    elseif linfo isa Module
1,373✔
321
        Base.print_within_stacktrace(io, Base.demangle_function_name(string(frame.func)), bold=true)
119✔
322
    elseif linfo isa MethodInstance
1,254✔
323
        def = linfo.def
1,253✔
324
        if def isa Module
1,253✔
325
            Base.show_mi(io, linfo, #=from_stackframe=#true)
×
326
        else
327
            show_spec_sig(io, def, linfo.specTypes)
1,253✔
328
        end
329
    else
330
        m = linfo::Method
1✔
331
        show_spec_sig(io, m, m.sig)
1✔
332
    end
333
end
334

335
function show_spec_sig(io::IO, m::Method, @nospecialize(sig::Type))
1,254✔
336
    if get(io, :limit, :false)::Bool
1,309✔
337
        if !haskey(io, :displaysize)
446✔
338
            io = IOContext(io, :displaysize => displaysize(io))
92✔
339
        end
340
    end
341
    argnames = Base.method_argnames(m)
1,254✔
342
    argnames = replace(argnames, :var"#unused#" => :var"")
1,254✔
343
    if m.nkw > 0
1,254✔
344
        # rearrange call kw_impl(kw_args..., func, pos_args...) to func(pos_args...; kw_args)
345
        kwarg_types = Any[ fieldtype(sig, i) for i = 2:(1+m.nkw) ]
305✔
346
        uw = Base.unwrap_unionall(sig)::DataType
280✔
347
        pos_sig = Base.rewrap_unionall(Tuple{uw.parameters[(m.nkw+2):end]...}, sig)
280✔
348
        kwnames = argnames[2:(m.nkw+1)]
560✔
349
        for i = 1:length(kwnames)
560✔
350
            str = string(kwnames[i])::String
305✔
351
            if endswith(str, "...")
305✔
352
                kwnames[i] = Symbol(str[1:end-3])
×
353
            end
354
        end
330✔
355
        Base.show_tuple_as_call(io, m.name, pos_sig;
280✔
356
                                demangle=true,
357
                                kwargs=zip(kwnames, kwarg_types),
358
                                argnames=argnames[m.nkw+2:end])
359
    else
360
        Base.show_tuple_as_call(io, m.name, sig; demangle=true, argnames)
974✔
361
    end
362
end
363

364
function show(io::IO, frame::StackFrame)
196✔
365
    show_spec_linfo(io, frame)
196✔
366
    if frame.file !== empty_sym
196✔
367
        file_info = basename(string(frame.file))
189✔
368
        print(io, " at ")
189✔
369
        print(io, file_info, ":")
189✔
370
        if frame.line >= 0
189✔
371
            print(io, frame.line)
189✔
372
        else
373
            print(io, "?")
×
374
        end
375
    end
376
    if frame.inlined
196✔
377
        print(io, " [inlined]")
56✔
378
    end
379
end
380

381
function Base.parentmodule(frame::StackFrame)
2,596✔
382
    linfo = frame.linfo
4,775✔
383
    if linfo isa MethodInstance
4,775✔
384
        def = linfo.def
2,496✔
385
        if def isa Module
2,496✔
386
            return def
×
387
        else
388
            return (def::Method).module
2,496✔
389
        end
390
    elseif linfo isa Method
2,279✔
391
        return linfo.module
3✔
392
    elseif linfo isa Module
2,276✔
393
        return linfo
205✔
394
    else
395
        # The module is not always available (common reasons include
396
        # frames arising from the interpreter)
397
        nothing
2,071✔
398
    end
399
end
400

401
"""
402
    from(frame::StackFrame, filter_mod::Module) -> Bool
403

404
Return whether the `frame` is from the provided `Module`
405
"""
406
function from(frame::StackFrame, m::Module)
×
407
    return parentmodule(frame) === m
×
408
end
409

410
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc