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

JuliaLang / julia / #37997

29 Jan 2025 02:08AM UTC coverage: 17.283% (-68.7%) from 85.981%
#37997

push

local

web-flow
bpart: Start enforcing min_world for global variable definitions (#57150)

This is the analog of #57102 for global variables. Unlike for consants,
there is no automatic global backdate mechanism. The reasoning for this
is that global variables can be declared at any time, unlike constants
which can only be decalared once their value is available. As a result
code patterns using `Core.eval` to declare globals are rarer and likely
incorrect.

1 of 22 new or added lines in 3 files covered. (4.55%)

31430 existing lines in 188 files now uncovered.

7903 of 45728 relevant lines covered (17.28%)

98663.7 hits per line

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

33.54
/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, CodeInstance
11
using Base.IRShow: normalize_method_name, append_scopes!, LineInfoNode
12

13
export StackTrace, StackFrame, stacktrace
14

15
"""
16
    StackFrame
17

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

20
- `func::Symbol`
21

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

24
- `linfo::Union{Method, Core.MethodInstance, Core.CodeInstance, Core.CodeInfo, Nothing}`
25

26
  The Method, MethodInstance, CodeInstance, or CodeInfo containing the execution context (if it could be found), \
27
     or nothing (for example, if the inlining was a result of macro expansion).
28

29
- `file::Symbol`
30

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

33
- `line::Int`
34

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

37
- `from_c::Bool`
38

39
  True if the code is from C.
40

41
- `inlined::Bool`
42

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

45
- `pointer::UInt64`
46

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

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

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

71
"""
72
    StackTrace
73

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

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

82

83
#=
84
If the StackFrame has function and line information, we consider two of them the same if
85
they share the same function/line information.
86
=#
UNCOV
87
function ==(a::StackFrame, b::StackFrame)
×
UNCOV
88
    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
×
89
end
90

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

101
"""
102
    lookup(pointer::Ptr{Cvoid}) -> Vector{StackFrame}
103

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

125
const top_level_scope_sym = Symbol("top-level scope")
126

UNCOV
127
function lookup(ip::Base.InterpreterIP)
×
UNCOV
128
    code = ip.code
×
UNCOV
129
    if code === nothing
×
130
        # interpreted top-level expression with no CodeInfo
UNCOV
131
        return [StackFrame(top_level_scope_sym, empty_sym, 0, nothing, false, false, 0)]
×
132
    end
133
    # prepare approximate code info
UNCOV
134
    if code isa MethodInstance && (meth = code.def; meth isa Method)
×
135
        func = meth.name
×
136
        file = meth.file
×
137
        line = meth.line
×
138
        codeinfo = meth.source
×
139
    else
UNCOV
140
        func = top_level_scope_sym
×
UNCOV
141
        file = empty_sym
×
UNCOV
142
        line = Int32(0)
×
UNCOV
143
        if code isa Core.CodeInstance
×
144
            codeinfo = code.inferred::CodeInfo
×
145
            def = code.def
×
146
            if isa(def, Core.ABIOverride)
×
147
                def = def.def
×
148
            end
149
            if isa(def, MethodInstance) && isa(def.def, Method)
×
150
                meth = def.def
×
151
                func = meth.name
×
152
                file = meth.file
×
153
                line = meth.line
×
154
            end
155
        else
UNCOV
156
            codeinfo = code::CodeInfo
×
157
        end
158
    end
UNCOV
159
    def = (code isa CodeInfo ? StackTraces : code) # Module just used as a token for top-level code
×
UNCOV
160
    pc::Int = max(ip.stmt + 1, 0) # n.b. ip.stmt is 0-indexed
×
UNCOV
161
    scopes = LineInfoNode[]
×
UNCOV
162
    append_scopes!(scopes, pc, codeinfo.debuginfo, def)
×
UNCOV
163
    if isempty(scopes)
×
164
        return [StackFrame(func, file, line, code, false, false, 0)]
×
165
    end
UNCOV
166
    inlined = false
×
UNCOV
167
    scopes = map(scopes) do lno
×
UNCOV
168
        if inlined
×
UNCOV
169
            def = lno.method
×
UNCOV
170
            def isa Union{Method,Core.CodeInstance,MethodInstance} || (def = nothing)
×
171
        else
UNCOV
172
            def = codeinfo
×
173
        end
UNCOV
174
        sf = StackFrame(normalize_method_name(lno.method), lno.file, lno.line, def, false, inlined, 0)
×
UNCOV
175
        inlined = true
×
UNCOV
176
        return sf
×
177
    end
UNCOV
178
    return scopes
×
179
end
180

181
"""
182
    stacktrace([trace::Vector{Ptr{Cvoid}},] [c_funcs::Bool=false]) -> StackTrace
183

184
Return a stack trace in the form of a vector of `StackFrame`s. (By default stacktrace
185
doesn't return C functions, but this can be enabled.) When called without specifying a
186
trace, `stacktrace` first calls `backtrace`.
187
"""
188
Base.@constprop :none function stacktrace(trace::Vector{<:Union{Base.InterpreterIP,Ptr{Cvoid}}}, c_funcs::Bool=false)
189
    stack = StackTrace()
9✔
190
    for ip in trace
191
        for frame in lookup(ip)
192
            # Skip frames that come from C calls.
193
            if c_funcs || !frame.from_c
194
                push!(stack, frame)
195
            end
196
        end
197
    end
198
    return stack
199
end
200

UNCOV
201
Base.@constprop :none function stacktrace(c_funcs::Bool=false)
×
UNCOV
202
    stack = stacktrace(backtrace(), c_funcs)
×
203
    # Remove frame for this function (and any functions called by this function).
UNCOV
204
    remove_frames!(stack, :stacktrace)
×
205
    # also remove all of the non-Julia functions that led up to this point (if that list is non-empty)
UNCOV
206
    c_funcs && deleteat!(stack, 1:(something(findfirst(frame -> !frame.from_c, stack), 1) - 1))
×
UNCOV
207
    return stack
×
208
end
209

210
"""
211
    remove_frames!(stack::StackTrace, name::Symbol)
212

213
Takes a `StackTrace` (a vector of `StackFrames`) and a function name (a `Symbol`) and
214
removes the `StackFrame` specified by the function name from the `StackTrace` (also removing
215
all frames above the specified function). Primarily used to remove `StackTraces` functions
216
from the `StackTrace` prior to returning it.
217
"""
UNCOV
218
function remove_frames!(stack::StackTrace, name::Symbol)
×
UNCOV
219
    deleteat!(stack, 1:something(findlast(frame -> frame.func == name, stack), 0))
×
UNCOV
220
    return stack
×
221
end
222

UNCOV
223
function remove_frames!(stack::StackTrace, names::Vector{Symbol})
×
UNCOV
224
    deleteat!(stack, 1:something(findlast(frame -> frame.func in names, stack), 0))
×
UNCOV
225
    return stack
×
226
end
227

228
"""
229
    remove_frames!(stack::StackTrace, m::Module)
230

231
Return the `StackTrace` with all `StackFrame`s from the provided `Module` removed.
232
"""
UNCOV
233
function remove_frames!(stack::StackTrace, m::Module)
×
UNCOV
234
    filter!(f -> !from(f, m), stack)
×
235
    return stack
×
236
end
237

UNCOV
238
is_top_level_frame(f::StackFrame) = f.linfo isa CodeInfo || (f.linfo === nothing && f.func === top_level_scope_sym)
×
239

240
function frame_method_or_module(lkup::StackFrame)
241
    code = lkup.linfo
80✔
242
    code isa Method && return code
80✔
243
    code isa Module && return code
×
244
    mi = frame_mi(lkup)
80✔
245
    mi isa MethodInstance || return nothing
80✔
246
    return mi.def
80✔
247
end
248

249
function frame_mi(lkup::StackFrame)
250
    code = lkup.linfo
120✔
251
    code isa Core.CodeInstance && (code = code.def)
120✔
252
    code isa Core.ABIOverride && (code = code.def)
120✔
253
    code isa MethodInstance || return nothing
120✔
254
    return code
120✔
255
end
256

257
function show_spec_linfo(io::IO, frame::StackFrame)
61✔
258
    linfo = frame.linfo
61✔
259
    if linfo === nothing
61✔
260
        if frame.func === empty_sym
13✔
261
            print(io, "ip:0x", string(frame.pointer, base=16))
×
262
        elseif frame.func === top_level_scope_sym
13✔
263
            print(io, "top-level scope")
1✔
264
        else
265
            Base.print_within_stacktrace(io, Base.demangle_function_name(string(frame.func)), bold=true)
12✔
266
        end
267
    elseif linfo isa CodeInfo
48✔
268
        print(io, "top-level scope")
8✔
269
    elseif linfo isa Module
40✔
270
        Base.print_within_stacktrace(io, Base.demangle_function_name(string(frame.func)), bold=true)
×
271
    else
272
        if linfo isa Union{MethodInstance, CodeInstance}
40✔
273
            def = frame_method_or_module(frame)
80✔
274
            if def isa Module
40✔
UNCOV
275
                Base.show_mi(io, linfo, #=from_stackframe=#true)
×
276
            else
277
                show_spec_sig(io, def, frame_mi(frame).specTypes)
40✔
278
            end
279
        else
280
            m = linfo::Method
×
281
            show_spec_sig(io, m, m.sig)
×
282
        end
283
    end
284
end
285

286
function show_spec_sig(io::IO, m::Method, @nospecialize(sig::Type))
40✔
287
    if get(io, :limit, :false)::Bool
82✔
288
        if !haskey(io, :displaysize)
147✔
289
            io = IOContext(io, :displaysize => displaysize(io))
38✔
290
        end
291
    end
292
    argnames = Base.method_argnames(m)
80✔
293
    argnames = replace(argnames, :var"#unused#" => :var"")
40✔
294
    if m.nkw > 0
40✔
295
        # rearrange call kw_impl(kw_args..., func, pos_args...) to func(pos_args...; kw_args)
296
        kwarg_types = Any[ fieldtype(sig, i) for i = 2:(1+m.nkw) ]
11✔
297
        uw = Base.unwrap_unionall(sig)::DataType
3✔
298
        pos_sig = Base.rewrap_unionall(Tuple{uw.parameters[(m.nkw+2):end]...}, sig)
3✔
299
        kwnames = argnames[2:(m.nkw+1)]
6✔
300
        for i = 1:length(kwnames)
3✔
301
            str = string(kwnames[i])::String
11✔
302
            if endswith(str, "...")
11✔
303
                kwnames[i] = Symbol(str[1:end-3])
×
304
            end
305
        end
19✔
306
        Base.show_tuple_as_call(io, m.name, pos_sig;
3✔
307
                                demangle=true,
308
                                kwargs=zip(kwnames, kwarg_types),
309
                                argnames=argnames[m.nkw+2:end])
310
    else
311
        Base.show_tuple_as_call(io, m.name, sig; demangle=true, argnames)
37✔
312
    end
313
end
314

UNCOV
315
function show(io::IO, frame::StackFrame)
×
UNCOV
316
    show_spec_linfo(io, frame)
×
UNCOV
317
    if frame.file !== empty_sym
×
UNCOV
318
        file_info = basename(string(frame.file))
×
UNCOV
319
        print(io, " at ")
×
UNCOV
320
        print(io, file_info, ":")
×
UNCOV
321
        if frame.line >= 0
×
UNCOV
322
            print(io, frame.line)
×
323
        else
324
            print(io, "?")
×
325
        end
326
    end
UNCOV
327
    if frame.inlined
×
UNCOV
328
        print(io, " [inlined]")
×
329
    end
330
end
331

332
function Base.parentmodule(frame::StackFrame)
333
    linfo = frame.linfo
122✔
334
    if linfo isa CodeInstance
122✔
335
        linfo = linfo.def
12✔
336
        if isa(linfo, Core.ABIOverride)
12✔
337
            linfo = linfo.def
×
338
        end
339
    end
340
    if linfo isa MethodInstance
122✔
341
        def = linfo.def
80✔
342
        if def isa Module
80✔
343
            return def
×
344
        else
345
            return (def::Method).module
80✔
346
        end
347
    elseif linfo isa Method
42✔
348
        return linfo.module
×
349
    elseif linfo isa Module
42✔
350
        return linfo
×
351
    else
352
        # The module is not always available (common reasons include
353
        # frames arising from the interpreter)
354
        nothing
×
355
    end
356
end
357

358
"""
359
    from(frame::StackFrame, filter_mod::Module) -> Bool
360

361
Return whether the `frame` is from the provided `Module`
362
"""
UNCOV
363
function from(frame::StackFrame, m::Module)
×
UNCOV
364
    return parentmodule(frame) === m
×
365
end
366

367
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