mirror of
https://github.com/JuliaLang/julia.git
synced 2026-05-28 03:10:33 +08:00
383 lines
9.6 KiB
Julia
383 lines
9.6 KiB
Julia
# This file is a part of Julia. License is MIT: https://julialang.org/license
|
|
|
|
import Base.StackTraces: lookup
|
|
|
|
# Test location information for inlined code (ref issues #1334 #12544)
|
|
module test_inline_bt
|
|
using Test
|
|
import ..lookup
|
|
|
|
function get_bt_frames(functionname, bt)
|
|
for i = 1:length(bt)
|
|
lkup = lookup(bt[i])
|
|
lkup[end].func == functionname && return lkup
|
|
end
|
|
return StackTraces.StackFrame[]
|
|
end
|
|
|
|
# same-file inline
|
|
eval(Expr(:function, Expr(:call, :test_inline_1),
|
|
Expr(:block, Expr(:line, 99, Symbol("backtrace.jl")),
|
|
Expr(:block, Expr(:line, 42),
|
|
Expr(:meta, :push_loc, Symbol("backtrace.jl"), :inlfunc),
|
|
Expr(:line, 37),
|
|
Expr(:call, :throw, "foo"),
|
|
Expr(:meta, :pop_loc),
|
|
Expr(:line, 99)))))
|
|
|
|
@test functionloc(test_inline_1) == ("backtrace.jl", 99)
|
|
try
|
|
test_inline_1()
|
|
error("unexpected")
|
|
catch err
|
|
lkup = get_bt_frames(:test_inline_1, catch_backtrace())
|
|
@test length(lkup) == 2
|
|
@test endswith(string(lkup[2].file), "backtrace.jl")
|
|
@test lkup[2].line == 42
|
|
# TODO: we don't support surface AST locations with inlined function names
|
|
@test_broken lkup[1].func === :inlfunc
|
|
@test endswith(string(lkup[1].file), "backtrace.jl")
|
|
@test lkup[1].line == 37
|
|
end
|
|
|
|
# different-file inline
|
|
const absfilepath = Sys.iswindows() ? "C:\\foo\\bar\\baz.jl" : "/foo/bar/baz.jl"
|
|
eval(Expr(:function, Expr(:call, :test_inline_2),
|
|
Expr(:block, Expr(:line, 81, Symbol("backtrace.jl")),
|
|
Expr(:block, Expr(:meta, :push_loc, Symbol(absfilepath)),
|
|
Expr(:line, 111),
|
|
Expr(:call, :throw, "foo"),
|
|
Expr(:meta, :pop_loc),
|
|
Expr(:line, 99)))))
|
|
|
|
@test functionloc(test_inline_2) == ("backtrace.jl", 81)
|
|
try
|
|
test_inline_2()
|
|
error("unexpected")
|
|
catch err
|
|
lkup = get_bt_frames(:test_inline_2, catch_backtrace())
|
|
@test length(lkup) == 2
|
|
@test endswith(string(lkup[2].file), "backtrace.jl")
|
|
@test lkup[2].line == 81
|
|
@test string(lkup[1].file) == absfilepath
|
|
@test lkup[1].line == 111
|
|
end
|
|
|
|
end # module
|
|
|
|
#issue 12977: line numbers for kwarg methods.
|
|
linenum = @__LINE__; f12977(; args...) = ()
|
|
loc = functionloc(f12977)
|
|
@test endswith(loc[1], "backtrace.jl")
|
|
@test loc[2] == linenum
|
|
|
|
@noinline function test_throw_commoning(x)
|
|
if x==1; throw(AssertionError()); end
|
|
if x==2; throw(AssertionError()); end
|
|
end
|
|
|
|
let
|
|
local b1, b2
|
|
try
|
|
test_throw_commoning(1)
|
|
catch
|
|
b1 = stacktrace(catch_backtrace())
|
|
end
|
|
try
|
|
test_throw_commoning(2)
|
|
catch
|
|
b2 = stacktrace(catch_backtrace())
|
|
end
|
|
i1 = findfirst(frame -> frame.func === :test_throw_commoning, b1)
|
|
i2 = findfirst(frame -> frame.func === :test_throw_commoning, b2)
|
|
@test i1 > 0 && i2 > 0
|
|
@test b1[i1].line != b2[i2].line
|
|
end
|
|
|
|
module BackTraceTesting
|
|
using Test
|
|
import ..lookup
|
|
|
|
@inline bt2() = backtrace()
|
|
@inline bt1() = bt2()
|
|
bt() = bt1()
|
|
|
|
lkup = map(lookup, bt())
|
|
hasbt = hasbt2 = false
|
|
for sfs in lkup
|
|
for sf in sfs
|
|
if sf.func === :bt
|
|
global hasbt = true
|
|
end
|
|
if sf.func === :bt2
|
|
global hasbt2 = true
|
|
end
|
|
end
|
|
end
|
|
@test hasbt
|
|
@test hasbt2
|
|
|
|
function btmacro()
|
|
ret = @timed backtrace()
|
|
ret[1]
|
|
end
|
|
lkup = map(lookup, btmacro())
|
|
hasme = hasbtmacro = false
|
|
for sfs in lkup
|
|
for sf in sfs
|
|
if sf.func === Symbol("macro expansion")
|
|
global hasme = true
|
|
end
|
|
if sf.func === :btmacro
|
|
global hasbtmacro = true
|
|
end
|
|
end
|
|
end
|
|
@test hasme
|
|
@test hasbtmacro
|
|
|
|
end
|
|
|
|
# Interpreter backtraces
|
|
bt = eval(quote
|
|
try
|
|
error()
|
|
catch
|
|
catch_backtrace()
|
|
end
|
|
end)
|
|
lkup = map(lookup, bt)
|
|
hastoplevel = false
|
|
for sfs in lkup
|
|
for sf in sfs
|
|
if sf.linfo isa Core.CodeInfo
|
|
global hastoplevel = true
|
|
end
|
|
end
|
|
end
|
|
@test hastoplevel
|
|
|
|
# issue #23971
|
|
let
|
|
for i = 1:1
|
|
global bt23971 = backtrace()
|
|
end
|
|
end
|
|
let st = stacktrace(bt23971)
|
|
@test StackTraces.is_top_level_frame(st[1])
|
|
@test string(st[1].file) == @__FILE__
|
|
@test !occursin("missing", string(st[2].file))
|
|
end
|
|
|
|
# issue #27959
|
|
let bt, found = false
|
|
@testset begin
|
|
bt = backtrace()
|
|
end
|
|
for frame in map(lookup, bt)
|
|
if frame[1].line == @__LINE__() - 3 && frame[1].file === Symbol(@__FILE__)
|
|
found = true; break
|
|
end
|
|
end
|
|
@test found
|
|
end
|
|
|
|
# issue 28618
|
|
let bt, found = false
|
|
@debug ""
|
|
bt = backtrace()
|
|
for frame in map(lookup, bt)
|
|
if frame[1].line == @__LINE__() - 2 && frame[1].file === Symbol(@__FILE__)
|
|
found = true; break
|
|
end
|
|
end
|
|
@test found
|
|
end
|
|
|
|
# Syntax error locations appear in backtraces
|
|
let trace = try
|
|
eval(Expr(:error, 1))
|
|
catch
|
|
stacktrace(catch_backtrace())
|
|
end
|
|
@test trace[1].func === Symbol("top-level scope")
|
|
end
|
|
let trace = try
|
|
eval(Expr(:toplevel, LineNumberNode(3, :a_filename), Expr(:error, 1)))
|
|
catch
|
|
stacktrace(catch_backtrace())
|
|
end
|
|
@test trace[1].func === Symbol("top-level scope")
|
|
@test trace[1].file === :a_filename
|
|
@test trace[1].line == 3
|
|
end
|
|
let trace = try
|
|
include_string(@__MODULE__,
|
|
"""
|
|
|
|
)
|
|
|
|
""", "a_filename")
|
|
catch
|
|
stacktrace(catch_backtrace())
|
|
end
|
|
@test trace[1].func === Symbol("top-level scope")
|
|
@test trace[1].file === :a_filename
|
|
@test trace[1].line == 2
|
|
end
|
|
let trace = try
|
|
include_string(@__MODULE__,
|
|
"""
|
|
|
|
incomplete_syntax(
|
|
|
|
""", "a_filename")
|
|
catch
|
|
stacktrace(catch_backtrace())
|
|
end
|
|
@test trace[1].func === Symbol("top-level scope")
|
|
@test trace[1].file === :a_filename
|
|
@test trace[1].line in (2, 3)
|
|
end
|
|
|
|
# issue #45171
|
|
linenum = @__LINE__; function f45171(;kwarg = true)
|
|
1
|
|
error()
|
|
end
|
|
let trace = try
|
|
f45171()
|
|
catch
|
|
stacktrace(catch_backtrace())
|
|
end
|
|
@test trace[3].line == linenum
|
|
end
|
|
|
|
# issue #29695 (see also test for #28442)
|
|
let code = """
|
|
f29695(c) = g29695(c)
|
|
g29695(c) = c >= 1000 ? (return backtrace()) : f29695(c + 1)
|
|
bt = f29695(1)
|
|
meth_names = [ip.code.def.name for ip in bt
|
|
if ip isa Base.InterpreterIP && ip.code isa Core.MethodInstance]
|
|
num_fs = sum(meth_names .== :f29695)
|
|
num_gs = sum(meth_names .== :g29695)
|
|
if num_fs != 1000 || num_gs != 1000
|
|
Base.show_backtrace(stderr, bt)
|
|
error("Expected 1000 frames each, got \$num_fs, \$num_fs")
|
|
end
|
|
exit()
|
|
"""
|
|
|
|
@test success(pipeline(`$(Base.julia_cmd()) --startup-file=no --compile=min -e $code`; stderr))
|
|
end
|
|
|
|
# Test that modules make it into InterpreterIP for top-level code
|
|
let code = """
|
|
module A
|
|
foo() = error("Expected")
|
|
try
|
|
foo()
|
|
catch
|
|
global bt = catch_backtrace()
|
|
end
|
|
end
|
|
|
|
foreach(println, A.bt)
|
|
"""
|
|
|
|
bt_str = read(`$(Base.julia_cmd()) --startup-file=no --compile=min -e $code`, String)
|
|
@test occursin(r"InterpreterIP in MethodInstance for .*A\.foo", bt_str)
|
|
@test occursin("InterpreterIP in top-level CodeInfo for Main.A", bt_str)
|
|
end
|
|
|
|
"""
|
|
_reformat_sp(bt_data...) -> sp::Vector{Ptr{Cvoid}}
|
|
|
|
Convert the output `bt_data` of `jl_backtrace_from_here` with `returnsp` flag set to a
|
|
vector of valid stack pointers `sp`; i.e., `sp` is a subset of `bt_data[3]`.
|
|
|
|
See also `Base._reformat_bt`.
|
|
"""
|
|
function _reformat_sp(
|
|
bt_raw::Array{Ptr{Cvoid},1},
|
|
bt2::Array{Any,1},
|
|
sp_raw::Array{Ptr{Cvoid},1},
|
|
)
|
|
bt = Base._reformat_bt(bt_raw, bt2)
|
|
sp = empty!(similar(sp_raw))
|
|
i = j = 0
|
|
while true
|
|
# Advance `i` such that `bt[i] isa Ptr{Cvoid}` (native pointer).
|
|
local ip
|
|
while true
|
|
if i == lastindex(bt)
|
|
return sp
|
|
end
|
|
i += 1
|
|
x = bt[i]
|
|
if x isa Ptr{Cvoid}
|
|
ip = x
|
|
break
|
|
end
|
|
end
|
|
# Advance `j` such that `bt_raw[j] == bt[i]` to find a valid stack pointer.
|
|
while true
|
|
if j == lastindex(bt_raw)
|
|
return sp
|
|
end
|
|
j += 1
|
|
if bt_raw[j] == ip
|
|
push!(sp, sp_raw[j])
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
"""
|
|
withframeaddress(f)
|
|
|
|
Call function `f` with an address `ptr::Ptr{Cvoid}` of an independent frame
|
|
immediately outer to `f`.
|
|
"""
|
|
withframeaddress
|
|
@eval @noinline function withframeaddress(f)
|
|
sp = Core.Intrinsics.llvmcall(
|
|
($"""
|
|
declare i8* @llvm.frameaddress(i32)
|
|
define private i$(Sys.WORD_SIZE) @frameaddr() {
|
|
%1 = call i8* @llvm.frameaddress(i32 0)
|
|
%2 = ptrtoint i8* %1 to i$(Sys.WORD_SIZE)
|
|
ret i$(Sys.WORD_SIZE) %2
|
|
}""", "frameaddr"),
|
|
UInt,
|
|
Tuple{},
|
|
)
|
|
@noinline f(Ptr{Cvoid}(sp))
|
|
end
|
|
|
|
function sandwiched_backtrace()
|
|
local ptr1, ptr2, bt
|
|
withframeaddress() do p1
|
|
ptr1 = p1
|
|
bt = ccall(:jl_backtrace_from_here, Ref{Base.SimpleVector}, (Cint, Cint), true, 0)
|
|
withframeaddress() do p2
|
|
ptr2 = p2
|
|
end
|
|
end
|
|
return ptr1, ptr2, bt
|
|
end
|
|
|
|
@testset "stack pointers" begin
|
|
ptr1, ptr2, bt_data = sandwiched_backtrace()
|
|
sp = _reformat_sp(bt_data...)
|
|
@test ptr2 < sp[2]
|
|
@test sp[1] < ptr1
|
|
@test all(diff(Int128.(UInt.(sp))) .> 0)
|
|
end
|
|
|
|
@testset "`lookup` return type inference" begin
|
|
@test Vector{StackTraces.StackFrame} === Base.infer_return_type(lookup)
|
|
end
|