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

JuliaLang / julia / #37605

30 Aug 2023 07:57AM UTC coverage: 86.2% (-0.3%) from 86.533%
#37605

push

local

web-flow
Speed up `logdet` for diagonal and triangular matrices (#50950)

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

73227 of 84950 relevant lines covered (86.2%)

12640960.64 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"
34,029✔
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)
842✔
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
50,946✔
88
end
89

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

100
get_inlinetable(::Any) = nothing
×
101
function get_inlinetable(mi::MethodInstance)
7,360✔
102
    isdefined(mi, :def) && mi.def isa Method && isdefined(mi, :cache) && isdefined(mi.cache, :inferred) &&
12,804✔
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,916✔
105
    return filter!(x -> x.inlined_at > 0, linetable)
820,262✔
106
end
107

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

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

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

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

218
function lookup(ip::Union{Base.InterpreterIP,Core.Compiler.InterpreterIP})
93✔
219
    code = ip.code
93✔
220
    if code === nothing
93✔
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
184✔
225
    # prepare approximate code info
226
    if code isa MethodInstance && (meth = code.def; meth isa Method)
92✔
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
92✔
236
    if i > length(codeinfo.codelocs) || codeinfo.codelocs[i] == 0
184✔
237
        return [StackFrame(func, file, line, code, false, false, 0)]
×
238
    end
239
    lineinfo = codeinfo.linetable[codeinfo.codelocs[i]]::Core.LineInfoNode
92✔
240
    scopes = StackFrame[]
92✔
241
    while true
115✔
242
        inlined = lineinfo.inlined_at != 0
115✔
243
        push!(scopes, StackFrame(Base.IRShow.method_name(lineinfo)::Symbol, lineinfo.file, lineinfo.line, inlined ? nothing : code, false, inlined, 0))
207✔
244
        inlined || break
115✔
245
        lineinfo = codeinfo.linetable[lineinfo.inlined_at]::Core.LineInfoNode
23✔
246
    end
23✔
247
    return scopes
92✔
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)
40✔
258
    stack = StackTrace()
60✔
259
    for ip in trace
32✔
260
        for frame in lookup(ip)
911✔
261
            # Skip frames that come from C calls.
262
            if c_funcs || !frame.from_c
2,519✔
263
                push!(stack, frame)
496✔
264
            end
265
        end
2,236✔
266
    end
941✔
267
    return stack
31✔
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)
9✔
308

309
function show_spec_linfo(io::IO, frame::StackFrame)
2,607✔
310
    linfo = frame.linfo
2,607✔
311
    if linfo === nothing
2,607✔
312
        if frame.func === empty_sym
122✔
313
            print(io, "ip:0x", string(frame.pointer, base=16))
×
314
        elseif frame.func === top_level_scope_sym
122✔
315
            print(io, "top-level scope")
2✔
316
        else
317
            Base.print_within_stacktrace(io, Base.demangle_function_name(string(frame.func)), bold=true)
120✔
318
        end
319
    elseif linfo isa CodeInfo
2,485✔
320
        print(io, "top-level scope")
67✔
321
    elseif linfo isa Module
2,418✔
322
        Base.print_within_stacktrace(io, Base.demangle_function_name(string(frame.func)), bold=true)
1,299✔
323
    elseif linfo isa MethodInstance
1,119✔
324
        def = linfo.def
1,118✔
325
        if def isa Module
1,118✔
326
            Base.show_mi(io, linfo, #=from_stackframe=#true)
×
327
        else
328
            show_spec_sig(io, def, linfo.specTypes)
1,118✔
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,119✔
337
    if get(io, :limit, :false)::Bool
1,024✔
338
        if !haskey(io, :displaysize)
379✔
339
            io = IOContext(io, :displaysize => displaysize(io))
65✔
340
        end
341
    end
342
    argnames = Base.method_argnames(m)
2,238✔
343
    argnames = replace(argnames, :var"#unused#" => :var"")
1,119✔
344
    if m.nkw > 0
1,119✔
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) ]
253✔
347
        uw = Base.unwrap_unionall(sig)::DataType
238✔
348
        pos_sig = Base.rewrap_unionall(Tuple{uw.parameters[(m.nkw+2):end]...}, sig)
238✔
349
        kwnames = argnames[2:(m.nkw+1)]
238✔
350
        for i = 1:length(kwnames)
476✔
351
            str = string(kwnames[i])::String
253✔
352
            if endswith(str, "...")
253✔
353
                kwnames[i] = Symbol(str[1:end-3])
×
354
            end
355
        end
268✔
356
        Base.show_tuple_as_call(io, m.name, pos_sig;
238✔
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)
881✔
362
    end
363
end
364

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

382
function Base.parentmodule(frame::StackFrame)
1,739✔
383
    linfo = frame.linfo
14,401✔
384
    if linfo isa MethodInstance
14,401✔
385
        def = linfo.def
5,637✔
386
        if def isa Module
5,637✔
387
            return def
×
388
        else
389
            return (def::Method).module
5,637✔
390
        end
391
    elseif linfo isa Method
8,764✔
392
        return linfo.module
9✔
393
    elseif linfo isa Module
8,755✔
394
        return linfo
5,000✔
395
    else
396
        # The module is not always available (common reasons include
397
        # frames arising from the interpreter)
398
        nothing
3,755✔
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