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

JuliaLang / julia / #37550

pending completion
#37550

push

local

web-flow
Extend comparison lifting to `Core.ifelse` (#49882)

This change extends our existing transformation for:
   φ(a,b) === Const(c)   =>   φ(a === c, b === c)

to perform the analogous transformation for `Core.ifelse`:
   Core.ifelse(cond, a, b) === Const(c)
  =>
   Core.ifelse(cond, a === c, b === c)

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

72705 of 83986 relevant lines covered (86.57%)

20561515.97 hits per line

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

82.56
/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"
221,901✔
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
     or Module (for macro expansions)"
57
    linfo::Union{MethodInstance, Method, Module, CodeInfo, Nothing}
58
    "true if the code is from C"
59
    from_c::Bool
60
    "true if the code is from an inlined frame"
61
    inlined::Bool
62
    "representation of the pointer to the execution context as returned by `backtrace`"
63
    pointer::UInt64  # Large enough to be read losslessly on 32- and 64-bit machines.
64
end
65

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

69
"""
70
    StackTrace
71

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

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

80

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

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

99
get_inlinetable(::Any) = nothing
×
100
function get_inlinetable(mi::MethodInstance)
183,557✔
101
    isdefined(mi, :def) && mi.def isa Method && isdefined(mi, :cache) && isdefined(mi.cache, :inferred) &&
355,419✔
102
        mi.cache.inferred !== nothing || return nothing
103
    linetable = ccall(:jl_uncompress_ir, Any, (Any, Any, Any), mi.def, mi.cache, mi.cache.inferred).linetable
11,695✔
104
    return filter!(x -> x.inlined_at > 0, linetable)
141,208✔
105
end
106

107
get_method_instance_roots(::Any) = nothing
×
108
function get_method_instance_roots(mi::Union{Method, MethodInstance})
171,862✔
109
    m = mi isa MethodInstance ? mi.def : mi
171,862✔
110
    m isa Method && isdefined(m, :roots) || return nothing
171,862✔
111
    return filter(x -> x isa MethodInstance, m.roots)
1,821,930✔
112
end
113

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

148
function lookup_inline_frame_info(func::Symbol, file::Symbol, miroots::Vector{Any})
9,036✔
149
    # REPL frames and some base files lack this prefix while others have it; should fix?
150
    filestripped = Symbol(lstrip(string(file), ('.', '\\', '/')))
9,036✔
151
    matches = filter(miroots) do x
9,036✔
152
        x.def isa Method || return false
950,772✔
153
        m = x.def::Method
950,772✔
154
        return m.name == func && m.file ∈ (file, filestripped)
950,772✔
155
    end
156
    if length(matches) > 1
9,036✔
157
        # ambiguous, check if method is same and return that instead
158
        all_matched = true
×
159
        for m in matches
5,511✔
160
            all_matched = m.def.line == matches[1].def.line &&
15,547✔
161
                m.def.module == matches[1].def.module
162
            all_matched || break
15,547✔
163
        end
11,764✔
164
        if all_matched
5,511✔
165
            return matches[1].def
864✔
166
        end
167
        # all else fails, return module if they match, or give up
168
        all_matched = true
×
169
        for m in matches
4,647✔
170
            all_matched = m.def.module == matches[1].def.module
31,277✔
171
            all_matched || break
31,277✔
172
        end
35,924✔
173
        return all_matched ? matches[1].def.module : nothing
4,647✔
174
    elseif length(matches) == 1
3,525✔
175
        return matches[1]
2,752✔
176
    end
177
    return nothing
773✔
178
end
179

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

183
Given a pointer to an execution context (usually generated by a call to `backtrace`), looks
184
up stack frame context information. Returns an array of frame information for all functions
185
inlined at that point, innermost function first.
186
"""
187
Base.@constprop :none function lookup(pointer::Ptr{Cvoid})
202,054✔
188
    infos = ccall(:jl_lookup_code_address, Any, (Ptr{Cvoid}, Cint), pointer, false)::Core.SimpleVector
202,054✔
189
    pointer = convert(UInt64, pointer)
202,054✔
190
    isempty(infos) && return [StackFrame(empty_sym, empty_sym, -1, nothing, true, false, pointer)] # this is equal to UNKNOWN
202,054✔
191
    parent_linfo = infos[end][4]
202,054✔
192
    inlinetable = get_inlinetable(parent_linfo)
220,551✔
193
    miroots = inlinetable === nothing ? get_method_instance_roots(parent_linfo) : nothing # fallback if linetable missing
202,054✔
194
    res = Vector{StackFrame}(undef, length(infos))
202,054✔
195
    for i in reverse(1:length(infos))
404,108✔
196
        info = infos[i]::Core.SimpleVector
220,230✔
197
        @assert(length(info) == 6)
220,230✔
198
        func = info[1]::Symbol
220,230✔
199
        file = info[2]::Symbol
220,230✔
200
        linenum = info[3]::Int
220,230✔
201
        linfo = info[4]
220,230✔
202
        if i < length(infos)
220,230✔
203
            if inlinetable !== nothing
18,176✔
204
                linfo = lookup_inline_frame_info(func, file, linenum, inlinetable)
447✔
205
            elseif miroots !== nothing
17,729✔
206
                linfo = lookup_inline_frame_info(func, file, miroots)
9,036✔
207
            end
208
            linfo = linfo === nothing ? parentmodule(res[i + 1]) : linfo # e.g. `macro expansion`
18,437✔
209
        end
210
        res[i] = StackFrame(func, file, linenum, linfo, info[5]::Bool, info[6]::Bool, pointer)
220,230✔
211
    end
238,406✔
212
    return res
202,054✔
213
end
214

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

217
function lookup(ip::Union{Base.InterpreterIP,Core.Compiler.InterpreterIP})
226✔
218
    code = ip.code
226✔
219
    if code === nothing
226✔
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
450✔
224
    # prepare approximate code info
225
    if code isa MethodInstance && (meth = code.def; meth isa Method)
225✔
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
225✔
235
    if i > length(codeinfo.codelocs) || codeinfo.codelocs[i] == 0
450✔
236
        return [StackFrame(func, file, line, code, false, false, 0)]
×
237
    end
238
    lineinfo = codeinfo.linetable[codeinfo.codelocs[i]]::Core.LineInfoNode
225✔
239
    scopes = StackFrame[]
225✔
240
    while true
596✔
241
        inlined = lineinfo.inlined_at != 0
596✔
242
        push!(scopes, StackFrame(Base.IRShow.method_name(lineinfo)::Symbol, lineinfo.file, lineinfo.line, inlined ? nothing : code, false, inlined, 0))
821✔
243
        inlined || break
596✔
244
        lineinfo = codeinfo.linetable[lineinfo.inlined_at]::Core.LineInfoNode
371✔
245
    end
371✔
246
    return scopes
225✔
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)
67✔
257
    stack = StackTrace()
102✔
258
    for ip in trace
53✔
259
        for frame in lookup(ip)
1,517✔
260
            # Skip frames that come from C calls.
261
            if c_funcs || !frame.from_c
4,273✔
262
                push!(stack, frame)
750✔
263
            end
264
        end
3,719✔
265
    end
1,568✔
266
    return stack
52✔
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)
13✔
307

308
function show_spec_linfo(io::IO, frame::StackFrame)
3,597✔
309
    linfo = frame.linfo
3,597✔
310
    if linfo === nothing
3,597✔
311
        if frame.func === empty_sym
299✔
312
            print(io, "ip:0x", string(frame.pointer, base=16))
×
313
        elseif frame.func === top_level_scope_sym
299✔
314
            print(io, "top-level scope")
17✔
315
        else
316
            Base.print_within_stacktrace(io, Base.demangle_function_name(string(frame.func)), bold=true)
282✔
317
        end
318
    elseif linfo isa CodeInfo
3,298✔
319
        print(io, "top-level scope")
114✔
320
    elseif linfo isa Module
3,184✔
321
        Base.print_within_stacktrace(io, Base.demangle_function_name(string(frame.func)), bold=true)
801✔
322
    else
323
        def, sig = if linfo isa MethodInstance
2,383✔
324
             linfo.def, linfo.specTypes
2,251✔
325
        else
326
            linfo, linfo.sig
2,515✔
327
        end
328
        if def isa Method
2,383✔
329
            if get(io, :limit, :false)::Bool
2,548✔
330
                if !haskey(io, :displaysize)
561✔
331
                    io = IOContext(io, :displaysize => displaysize(io))
95✔
332
                end
333
            end
334
            argnames = Base.method_argnames(def)
4,766✔
335
            argnames = replace(argnames, :var"#unused#" => :var"")
2,383✔
336
            if def.nkw > 0
2,383✔
337
                # rearrange call kw_impl(kw_args..., func, pos_args...) to func(pos_args...)
338
                kwarg_types = Any[ fieldtype(sig, i) for i = 2:(1+def.nkw) ]
360✔
339
                uw = Base.unwrap_unionall(sig)::DataType
336✔
340
                pos_sig = Base.rewrap_unionall(Tuple{uw.parameters[(def.nkw+2):end]...}, sig)
340✔
341
                kwnames = argnames[2:(def.nkw+1)]
336✔
342
                for i = 1:length(kwnames)
672✔
343
                    str = string(kwnames[i])::String
360✔
344
                    if endswith(str, "...")
360✔
345
                        kwnames[i] = Symbol(str[1:end-3])
×
346
                    end
347
                end
384✔
348
                Base.show_tuple_as_call(io, def.name, pos_sig;
336✔
349
                                        demangle=true,
350
                                        kwargs=zip(kwnames, kwarg_types),
351
                                        argnames=argnames[def.nkw+2:end])
352
            else
353
                Base.show_tuple_as_call(io, def.name, sig; demangle=true, argnames)
2,047✔
354
            end
355
        else
356
            Base.show_mi(io, linfo, true)
×
357
        end
358
    end
359
end
360

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

378
function Base.parentmodule(frame::StackFrame)
2,739✔
379
    linfo = frame.linfo
104,717✔
380
    if linfo isa MethodInstance
104,717✔
381
        def = linfo.def
93,731✔
382
        if def isa Module
93,731✔
383
            return def
×
384
        else
385
            return (def::Method).module
93,731✔
386
        end
387
    elseif linfo isa Method
10,986✔
388
        return linfo.module
90✔
389
    elseif linfo isa Module
10,896✔
390
        return linfo
1,245✔
391
    else
392
        # The module is not always available (common reasons include
393
        # frames arising from the interpreter)
394
        nothing
9,651✔
395
    end
396
end
397

398
"""
399
    from(frame::StackFrame, filter_mod::Module) -> Bool
400

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

407
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