# 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 == 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