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

JuliaLang / julia / #37596

pending completion
#37596

push

local

web-flow
🤖 [master] Bump the Pkg stdlib from 2c04d5a98 to b044bf6a2 (#50851)

Co-authored-by: Dilum Aluthge <dilum@aluthge.com>

71913 of 84418 relevant lines covered (85.19%)

32144286.87 hits per line

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

80.81
/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"
31,552✔
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)
776✔
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
45,915✔
88
end
89

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

100
get_inlinetable(::Any) = nothing
×
101
function get_inlinetable(mi::MethodInstance)
7,787✔
102
    isdefined(mi, :def) && mi.def isa Method && isdefined(mi, :cache) && isdefined(mi.cache, :inferred) &&
13,105✔
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
2,469✔
105
    return filter!(x -> x.inlined_at > 0, linetable)
1,340,689✔
106
end
107

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

115
function lookup_inline_frame_info(func::Symbol, file::Symbol, linenum::Int, inlinetable::Vector{Core.LineInfoNode})
1,824✔
116
    #REPL frames and some base files lack this prefix while others have it; should fix?
117
    filestripped = Symbol(lstrip(string(file), ('.', '\\', '/')))
1,824✔
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)
3,648✔
129
        Base.IRShow.method_name(line) === func && line.file ∈ (file, filestripped) && line.line == linenum || continue
2,458,954✔
130
        if line.method isa MethodInstance
7,904✔
131
            linfo = line.method
×
132
            break
×
133
        elseif line.method isa Method || line.method isa Symbol
15,808✔
134
            linfo = line.method isa Method ? line.method : line.module
15,808✔
135
            # backtrack to find the matching MethodInstance, if possible
136
            for j in (i - 1):-1:1
15,615✔
137
                nextline = inlinetable[j]
11,598✔
138
                nextline.inlined_at == line.inlined_at && Base.IRShow.method_name(line) === Base.IRShow.method_name(nextline) && line.file === nextline.file || break
19,232✔
139
                if nextline.method isa MethodInstance
3,964✔
140
                    linfo = nextline.method
×
141
                    break
×
142
                end
143
            end
3,964✔
144
        end
145
    end
2,458,954✔
146
    return linfo
1,824✔
147
end
148

149
function lookup_inline_frame_info(func::Symbol, file::Symbol, miroots::Vector{Any})
6,927✔
150
    # REPL frames and some base files lack this prefix while others have it; should fix?
151
    filestripped = Symbol(lstrip(string(file), ('.', '\\', '/')))
6,927✔
152
    matches = filter(miroots) do x
6,927✔
153
        x.def isa Method || return false
65,461✔
154
        m = x.def::Method
65,461✔
155
        return m.name == func && m.file ∈ (file, filestripped)
65,461✔
156
    end
157
    if length(matches) > 1
6,927✔
158
        # ambiguous, check if method is same and return that instead
159
        all_matched = true
×
160
        for m in matches
77✔
161
            all_matched = m.def.line == matches[1].def.line &&
178✔
162
                m.def.module == matches[1].def.module
163
            all_matched || break
178✔
164
        end
191✔
165
        if all_matched
77✔
166
            return matches[1].def
45✔
167
        end
168
        # all else fails, return module if they match, or give up
169
        all_matched = true
×
170
        for m in matches
32✔
171
            all_matched = m.def.module == matches[1].def.module
84✔
172
            all_matched || break
84✔
173
        end
116✔
174
        return all_matched ? matches[1].def.module : nothing
32✔
175
    elseif length(matches) == 1
6,850✔
176
        return matches[1]
61✔
177
    end
178
    return nothing
6,789✔
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})
18,046✔
189
    infos = ccall(:jl_lookup_code_address, Any, (Ptr{Cvoid}, Cint), pointer, false)::Core.SimpleVector
18,046✔
190
    pointer = convert(UInt64, pointer)
18,046✔
191
    isempty(infos) && return [StackFrame(empty_sym, empty_sym, -1, nothing, true, false, pointer)] # this is equal to UNKNOWN
18,046✔
192
    parent_linfo = infos[end][4]
18,046✔
193
    inlinetable = get_inlinetable(parent_linfo)
28,305✔
194
    miroots = inlinetable === nothing ? get_method_instance_roots(parent_linfo) : nothing # fallback if linetable missing
18,046✔
195
    res = Vector{StackFrame}(undef, length(infos))
18,046✔
196
    for i in reverse(1:length(infos))
36,092✔
197
        info = infos[i]::Core.SimpleVector
30,220✔
198
        @assert(length(info) == 6)
30,220✔
199
        func = info[1]::Symbol
30,220✔
200
        file = info[2]::Symbol
30,220✔
201
        linenum = info[3]::Int
30,220✔
202
        linfo = info[4]
30,220✔
203
        if i < length(infos)
30,220✔
204
            if inlinetable !== nothing
12,174✔
205
                linfo = lookup_inline_frame_info(func, file, linenum, inlinetable)
1,824✔
206
            elseif miroots !== nothing
10,350✔
207
                linfo = lookup_inline_frame_info(func, file, miroots)
6,927✔
208
            end
209
            linfo = linfo === nothing ? parentmodule(res[i + 1]) : linfo # e.g. `macro expansion`
15,175✔
210
        end
211
        res[i] = StackFrame(func, file, linenum, linfo, info[5]::Bool, info[6]::Bool, pointer)
30,220✔
212
    end
42,394✔
213
    return res
18,046✔
214
end
215

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

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

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

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

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

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

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

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

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

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

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

309
function show_spec_linfo(io::IO, frame::StackFrame)
2,975✔
310
    linfo = frame.linfo
2,975✔
311
    if linfo === nothing
2,975✔
312
        if frame.func === empty_sym
293✔
313
            print(io, "ip:0x", string(frame.pointer, base=16))
×
314
        elseif frame.func === top_level_scope_sym
293✔
315
            print(io, "top-level scope")
18✔
316
        else
317
            Base.print_within_stacktrace(io, Base.demangle_function_name(string(frame.func)), bold=true)
275✔
318
        end
319
    elseif linfo isa CodeInfo
2,682✔
320
        print(io, "top-level scope")
106✔
321
    elseif linfo isa Module
2,576✔
322
        Base.print_within_stacktrace(io, Base.demangle_function_name(string(frame.func)), bold=true)
1,320✔
323
    elseif linfo isa MethodInstance
1,256✔
324
        def = linfo.def
1,255✔
325
        if def isa Module
1,255✔
326
            Base.show_mi(io, linfo, #=from_stackframe=#true)
×
327
        else
328
            show_spec_sig(io, def, linfo.specTypes)
1,255✔
329
        end
330
    else
331
        m = linfo::Method
1✔
332
        show_spec_sig(io, m, m.sig)
1✔
333
    end
334
end
335

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

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

382
function Base.parentmodule(frame::StackFrame)
2,681✔
383
    linfo = frame.linfo
15,139✔
384
    if linfo isa MethodInstance
15,139✔
385
        def = linfo.def
5,570✔
386
        if def isa Module
5,570✔
387
            return def
×
388
        else
389
            return (def::Method).module
5,570✔
390
        end
391
    elseif linfo isa Method
9,569✔
392
        return linfo.module
6✔
393
    elseif linfo isa Module
9,563✔
394
        return linfo
5,226✔
395
    else
396
        # The module is not always available (common reasons include
397
        # frames arising from the interpreter)
398
        nothing
4,337✔
399
    end
400
end
401

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

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

411
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