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

JuliaLang / julia / #37484

pending completion
#37484

push

local

web-flow
minor follow up on #48996 (#49129)

- removed unnecessary `Union`-signature
- use one-liner definition when applicable
- add type assertion to make it more robust against invalidation

5 of 5 new or added lines in 2 files covered. (100.0%)

70963 of 82484 relevant lines covered (86.03%)

33340438.71 hits per line

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

76.47
/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, CodeInfo, Nothing}`
24

25
  The MethodInstance containing the execution context (if it could be found).
26

27
- `file::Symbol`
28

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

31
- `line::Int`
32

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

35
- `from_c::Bool`
36

37
  True if the code is from C.
38

39
- `inlined::Bool`
40

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

43
- `pointer::UInt64`
44

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

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

65
StackFrame(func, file, line) = StackFrame(Symbol(func), Symbol(file), line,
×
66
                                          nothing, false, false, 0)
67

68
"""
69
    StackTrace
70

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

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

79

80
#=
81
If the StackFrame has function and line information, we consider two of them the same if
82
they share the same function/line information.
83
=#
84
function ==(a::StackFrame, b::StackFrame)
846✔
85
    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
48,113✔
86
end
87

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

98

99
"""
100
    lookup(pointer::Ptr{Cvoid}) -> Vector{StackFrame}
101

102
Given a pointer to an execution context (usually generated by a call to `backtrace`), looks
103
up stack frame context information. Returns an array of frame information for all functions
104
inlined at that point, innermost function first.
105
"""
106
Base.@constprop :none function lookup(pointer::Ptr{Cvoid})
19,125✔
107
    infos = ccall(:jl_lookup_code_address, Any, (Ptr{Cvoid}, Cint), pointer, false)::Core.SimpleVector
19,125✔
108
    pointer = convert(UInt64, pointer)
19,125✔
109
    isempty(infos) && return [StackFrame(empty_sym, empty_sym, -1, nothing, true, false, pointer)] # this is equal to UNKNOWN
19,125✔
110
    res = Vector{StackFrame}(undef, length(infos))
19,125✔
111
    for i in 1:length(infos)
38,250✔
112
        info = infos[i]::Core.SimpleVector
31,038✔
113
        @assert(length(info) == 6)
31,038✔
114
        res[i] = StackFrame(info[1]::Symbol, info[2]::Symbol, info[3]::Int, info[4], info[5]::Bool, info[6]::Bool, pointer)
31,038✔
115
    end
42,951✔
116
    return res
19,125✔
117
end
118

119
const top_level_scope_sym = Symbol("top-level scope")
120

121
function lookup(ip::Union{Base.InterpreterIP,Core.Compiler.InterpreterIP})
94✔
122
    code = ip.code
94✔
123
    if code === nothing
94✔
124
        # interpreted top-level expression with no CodeInfo
125
        return [StackFrame(top_level_scope_sym, empty_sym, 0, nothing, false, false, 0)]
1✔
126
    end
127
    codeinfo = (code isa MethodInstance ? code.uninferred : code)::CodeInfo
186✔
128
    # prepare approximate code info
129
    if code isa MethodInstance && (meth = code.def; meth isa Method)
93✔
130
        func = meth.name
×
131
        file = meth.file
×
132
        line = meth.line
×
133
    else
134
        func = top_level_scope_sym
×
135
        file = empty_sym
×
136
        line = Int32(0)
×
137
    end
138
    i = max(ip.stmt+1, 1)  # ip.stmt is 0-indexed
93✔
139
    if i > length(codeinfo.codelocs) || codeinfo.codelocs[i] == 0
186✔
140
        return [StackFrame(func, file, line, code, false, false, 0)]
×
141
    end
142
    lineinfo = codeinfo.linetable[codeinfo.codelocs[i]]::Core.LineInfoNode
93✔
143
    scopes = StackFrame[]
93✔
144
    while true
240✔
145
        inlined = lineinfo.inlined_at != 0
240✔
146
        push!(scopes, StackFrame(Base.IRShow.method_name(lineinfo)::Symbol, lineinfo.file, lineinfo.line, inlined ? nothing : code, false, inlined, 0))
333✔
147
        inlined || break
240✔
148
        lineinfo = codeinfo.linetable[lineinfo.inlined_at]::Core.LineInfoNode
147✔
149
    end
147✔
150
    return scopes
93✔
151
end
152

153
"""
154
    stacktrace([trace::Vector{Ptr{Cvoid}},] [c_funcs::Bool=false]) -> StackTrace
155

156
Return a stack trace in the form of a vector of `StackFrame`s. (By default stacktrace
157
doesn't return C functions, but this can be enabled.) When called without specifying a
158
trace, `stacktrace` first calls `backtrace`.
159
"""
160
Base.@constprop :none function stacktrace(trace::Vector{<:Union{Base.InterpreterIP,Core.Compiler.InterpreterIP,Ptr{Cvoid}}}, c_funcs::Bool=false)
48✔
161
    stack = StackTrace()
80✔
162
    for ip in trace
42✔
163
        for frame in lookup(ip)
1,130✔
164
            # Skip frames that come from C calls.
165
            if c_funcs || !frame.from_c
3,051✔
166
                push!(stack, frame)
557✔
167
            end
168
        end
2,722✔
169
    end
1,170✔
170
    return stack
41✔
171
end
172

173
Base.@constprop :none function stacktrace(c_funcs::Bool=false)
×
174
    stack = stacktrace(backtrace(), c_funcs)
×
175
    # Remove frame for this function (and any functions called by this function).
176
    remove_frames!(stack, :stacktrace)
×
177
    # also remove all of the non-Julia functions that led up to this point (if that list is non-empty)
178
    c_funcs && deleteat!(stack, 1:(something(findfirst(frame -> !frame.from_c, stack), 1) - 1))
×
179
    return stack
×
180
end
181

182
"""
183
    remove_frames!(stack::StackTrace, name::Symbol)
184

185
Takes a `StackTrace` (a vector of `StackFrames`) and a function name (a `Symbol`) and
186
removes the `StackFrame` specified by the function name from the `StackTrace` (also removing
187
all frames above the specified function). Primarily used to remove `StackTraces` functions
188
from the `StackTrace` prior to returning it.
189
"""
190
function remove_frames!(stack::StackTrace, name::Symbol)
×
191
    deleteat!(stack, 1:something(findlast(frame -> frame.func == name, stack), 0))
×
192
    return stack
×
193
end
194

195
function remove_frames!(stack::StackTrace, names::Vector{Symbol})
×
196
    deleteat!(stack, 1:something(findlast(frame -> frame.func in names, stack), 0))
×
197
    return stack
×
198
end
199

200
"""
201
    remove_frames!(stack::StackTrace, m::Module)
202

203
Return the `StackTrace` with all `StackFrame`s from the provided `Module` removed.
204
"""
205
function remove_frames!(stack::StackTrace, m::Module)
×
206
    filter!(f -> !from(f, m), stack)
×
207
    return stack
×
208
end
209

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

212
function show_spec_linfo(io::IO, frame::StackFrame)
1,350✔
213
    linfo = frame.linfo
1,350✔
214
    if linfo === nothing
1,350✔
215
        if frame.func === empty_sym
482✔
216
            print(io, "ip:0x", string(frame.pointer, base=16))
×
217
        elseif frame.func === top_level_scope_sym
482✔
218
            print(io, "top-level scope")
17✔
219
        else
220
            Base.print_within_stacktrace(io, Base.demangle_function_name(string(frame.func)), bold=true)
465✔
221
        end
222
    elseif linfo isa MethodInstance
868✔
223
        def = linfo.def
821✔
224
        if isa(def, Method)
821✔
225
            sig = linfo.specTypes
821✔
226
            argnames = Base.method_argnames(def)
1,642✔
227
            argnames = replace(argnames, :var"#unused#" => :var"")
821✔
228
            if def.nkw > 0
821✔
229
                # rearrange call kw_impl(kw_args..., func, pos_args...) to func(pos_args...)
230
                kwarg_types = Any[ fieldtype(sig, i) for i = 2:(1+def.nkw) ]
191✔
231
                uw = Base.unwrap_unionall(sig)::DataType
171✔
232
                pos_sig = Base.rewrap_unionall(Tuple{uw.parameters[(def.nkw+2):end]...}, sig)
171✔
233
                kwnames = argnames[2:(def.nkw+1)]
171✔
234
                for i = 1:length(kwnames)
342✔
235
                    str = string(kwnames[i])::String
191✔
236
                    if endswith(str, "...")
191✔
237
                        kwnames[i] = Symbol(str[1:end-3])
×
238
                    end
239
                end
211✔
240
                Base.show_tuple_as_call(io, def.name, pos_sig;
171✔
241
                                        demangle=true,
242
                                        kwargs=zip(kwnames, kwarg_types),
243
                                        argnames=argnames[def.nkw+2:end])
244
            else
245
                Base.show_tuple_as_call(io, def.name, sig; demangle=true, argnames)
650✔
246
            end
247
        else
248
            Base.show_mi(io, linfo, true)
×
249
        end
250
    elseif linfo isa CodeInfo
39✔
251
        print(io, "top-level scope")
47✔
252
    end
253
end
254

255
function show(io::IO, frame::StackFrame)
169✔
256
    show_spec_linfo(io, frame)
169✔
257
    if frame.file !== empty_sym
169✔
258
        file_info = basename(string(frame.file))
162✔
259
        print(io, " at ")
162✔
260
        print(io, file_info, ":")
162✔
261
        if frame.line >= 0
162✔
262
            print(io, frame.line)
162✔
263
        else
264
            print(io, "?")
×
265
        end
266
    end
267
    if frame.inlined
169✔
268
        print(io, " [inlined]")
86✔
269
    end
270
end
271

272
function Base.parentmodule(frame::StackFrame)
1,174✔
273
    linfo = frame.linfo
2,909✔
274
    if linfo isa MethodInstance
2,909✔
275
        def = linfo.def
1,479✔
276
        return def isa Module ? def : parentmodule(def::Method)
2,958✔
277
    else
278
        # The module is not always available (common reasons include inlined
279
        # frames and frames arising from the interpreter)
280
        nothing
1,430✔
281
    end
282
end
283

284
"""
285
    from(frame::StackFrame, filter_mod::Module) -> Bool
286

287
Return whether the `frame` is from the provided `Module`
288
"""
289
function from(frame::StackFrame, m::Module)
×
290
    return parentmodule(frame) === m
×
291
end
292

293
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