From be2dfffe86b0541377b146de0c9809e88625f424 Mon Sep 17 00:00:00 2001 From: Gem Newman Date: Tue, 12 Jan 2016 16:47:25 -0600 Subject: [PATCH] Refactored, doctests, show_stacktrace is now show. Added doctests to the manual. Removed show_stacktrace in favour of show(x::StackFrame). Removed format_stacktrace and format_stackframe. Additional test cases (which currently fail due to difficult to track down line number issues) --- base/docs/helpdb.jl | 1 - base/docs/helpdb/StackTraces.jl | 104 ------------- base/exports.jl | 5 +- base/profile.jl | 5 +- base/stacktraces.jl | 95 ++++++----- doc/manual/stacktraces.rst | 268 ++++++++++++++++++-------------- doc/stdlib/stacktraces.rst | 52 ++----- test/stacktraces.jl | 163 ++++++------------- 8 files changed, 275 insertions(+), 418 deletions(-) delete mode 100644 base/docs/helpdb/StackTraces.jl diff --git a/base/docs/helpdb.jl b/base/docs/helpdb.jl index 857bf2b749305..117105cd94162 100644 --- a/base/docs/helpdb.jl +++ b/base/docs/helpdb.jl @@ -7,6 +7,5 @@ include("helpdb/Libdl.jl") include("helpdb/Libc.jl") include("helpdb/Collections.jl") include("helpdb/Profile.jl") -include("helpdb/StackTraces.jl") include("helpdb/Base.jl") include("helpdb/Dates.jl") diff --git a/base/docs/helpdb/StackTraces.jl b/base/docs/helpdb/StackTraces.jl deleted file mode 100644 index 6e3d6dff68b9b..0000000000000 --- a/base/docs/helpdb/StackTraces.jl +++ /dev/null @@ -1,104 +0,0 @@ -# This file is a part of Julia. License is MIT: http://julialang.org/license - - -# Base - -""" - stacktrace([trace::Vector{Ptr{Void}},] [c_funcs::Bool=false]) -> StackTraces.StackTrace - -Returns a stack trace in the form of a vector of `StackFrame`s. (By default stacktrace -doesn't return C functions, but this can be enabled.) When called without specifying a -trace, `stacktrace` first calls `backtrace`. -""" -stacktrace - -""" - catch_stacktrace([c_funcs::Bool=false]) -> StackTraces.StackTrace - -Returns the stack trace for the most recent error thrown, rather than the current execution -context. -""" -catch_stacktrace - -""" - show_stacktrace([io::IO,] [stack::StackTrace]; full_path::Bool=false) - -An analogue of `Base.show_backtrace` that prints the stacktrace to the specified IO stream. -Only the base name of the `file` and `inlined_file` fields are displayed unless the optional -`full_path` keyword argument is set to `true`. -""" -show_stacktrace - -""" - format_stackframe(frame::StackFrame; full_path::Bool=false) -> str - -Returns a string representation of a `StackFrame` for display purposes. Only the base name -of the `file` and `inlined_file` fields are displayed unless the optional `full_path` -keyword argument is set to `true`. -""" -format_stackframe - -""" - format_stacktrace(stack::StackTrace, separator::AbstractString, [start::AbstractString="", finish::AbstractString=""]; full_path::Bool=false) -> str - -Returns a string representation of a `StackTrace` for display purposes. Only the base name -of the `file` and `inlined_file` fields are displayed unless the optional `full_path` -keyword argument is set to `true`. -""" -format_stacktrace - -# Base.StackTraces - -""" - StackFrame - -Stack information representing execution context, with the following fields: - -`func::Symbol` - the name of the function containing the execution context - -`file::Symbol` - the path to the file containing the execution context - -`line::Int` - the line number in the file containing the execution context - -`inlined_file::Symbol` - the path to the file containing the context for inlined code - -`inlined_line::Int` - the line number in the file containing the context for inlined code - -`from_c::Bool` - true if the code is from C - -`pointer::Int64` - representation of the pointer to the execution context as returned by `backtrace` -""" -StackTraces.StackFrame - -""" - StackTrace - -An alias for `Vector{StackFrame}` provided for convenience; returned by calls to -`stacktrace` and `catch_stacktrace`. -""" -StackTraces.StackTrace - -""" - lookup(pointer::Union{Ptr{Void}, UInt}) -> StackTraces.StackFrame - -Given a pointer to an execution context (usually generated by a call to `backtrace`), looks -up stack frame context information. -""" -StackTraces.lookup - -""" - remove_frames!(stack::StackTrace, name::Symbol) - -Takes a `StackTrace` (a vector of `StackFrames`) and a function name (a `Symbol`) and -removes the `StackFrame` specified by the function name from the `StackTrace` (also removing -all frames above the specified function). Primarily used to remove `StackTraces` functions -from the `StackTrace` prior to returning it. -""" -StackTraces.remove_frames! diff --git a/base/exports.jl b/base/exports.jl index 6bc95d89aec48..5e5ade077e6a6 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -1050,11 +1050,10 @@ export systemerror, # stack traces + StackTrace, + StackFrame, stacktrace, catch_stacktrace, - format_stacktrace, - format_stackframe, - show_stacktrace, # types convert, diff --git a/base/profile.jl b/base/profile.jl index da678ce4cc265..beaff2b037a3c 100644 --- a/base/profile.jl +++ b/base/profile.jl @@ -2,7 +2,7 @@ module Profile -import Base.StackTraces: StackFrame, UNKNOWN, lookup +import Base.StackTraces: StackFrame, lookup export @profile @@ -23,6 +23,7 @@ end #### #### User-level functions #### + function init(; n::Union{Void,Integer} = nothing, delay::Union{Void,Float64} = nothing) n_cur = ccall(:jl_profile_maxlen_data, Csize_t, ()) delay_cur = ccall(:jl_profile_delay_nsec, UInt64, ())/10^9 @@ -127,6 +128,8 @@ end #@windows_only const btskip = 0 const btskip = 0 +const UNKNOWN = StackFrame(symbol("???"), symbol("???"), 0, symbol(""), -1, true, 0) + ## Print as a flat list # Counts the number of times each line appears, at any nesting level function count_flat{T<:Unsigned}(data::Vector{T}) diff --git a/base/stacktraces.jl b/base/stacktraces.jl index 8917d88c9e654..26dcbef10b098 100644 --- a/base/stacktraces.jl +++ b/base/stacktraces.jl @@ -3,25 +3,40 @@ module StackTraces -import Base: hash, == +import Base: hash, ==, show -export stacktrace, catch_stacktrace, format_stacktrace, format_stackframe, show_stacktrace +export StackTrace, StackFrame, stacktrace, catch_stacktrace +""" + StackFrame + +Stack information representing execution context. +""" immutable StackFrame + "the name of the function containing the execution context" func::Symbol + "the path to the file containing the execution context" file::Symbol + "the line number in the file containing the execution context" line::Int + "the path to the file containing the context for inlined code" inlined_file::Symbol + "the line number in the file containing the context for inlined code" inlined_line::Int + "true if the code is from C" from_c::Bool + "representation of the pointer to the execution context as returned by `backtrace`" pointer::Int64 # Large enough to be read losslessly on 32- and 64-bit machines. end -typealias StackTrace Vector{StackFrame} +""" + StackTrace +An alias for `Vector{StackFrame}` provided for convenience; returned by calls to +`stacktrace` and `catch_stacktrace`. +""" +typealias StackTrace Vector{StackFrame} -# Placeholder for unknown execution context. -const UNKNOWN = StackFrame(:?, :?, -1, :?, -1, true, 0) #= If the StackFrame has function and line information, we consider two of them the same if @@ -40,15 +55,27 @@ function hash(frame::StackFrame, h::UInt) end +""" + lookup(pointer::Union{Ptr{Void}, UInt}) -> StackFrame + +Given a pointer to an execution context (usually generated by a call to `backtrace`), looks +up stack frame context information. +""" function lookup(pointer::Ptr{Void}) - frame_info = ccall(:jl_lookup_code_address, Any, (Ptr{Void}, Cint), pointer, 0) - return (length(frame_info) == 7) ? StackFrame(frame_info...) : UNKNOWN + return StackFrame(ccall(:jl_lookup_code_address, Any, (Ptr{Void}, Cint), pointer, 0)...) end lookup(pointer::UInt) = lookup(convert(Ptr{Void}, pointer)) +""" + stacktrace([trace::Vector{Ptr{Void}},] [c_funcs::Bool=false]) -> StackTrace + +Returns a stack trace in the form of a vector of `StackFrame`s. (By default stacktrace +doesn't return C functions, but this can be enabled.) When called without specifying a +trace, `stacktrace` first calls `backtrace`. +""" function stacktrace(trace::Vector{Ptr{Void}}, c_funcs::Bool=false) - stack = map(lookup, trace) + stack = map(lookup, trace)::StackTrace # Remove frames that come from C calls. if !c_funcs @@ -61,19 +88,33 @@ end stacktrace(c_funcs::Bool=false) = stacktrace(backtrace(), c_funcs) +""" + catch_stacktrace([c_funcs::Bool=false]) -> StackTrace + +Returns the stack trace for the most recent error thrown, rather than the current execution +context. +""" catch_stacktrace(c_funcs::Bool=false) = stacktrace(catch_backtrace(), c_funcs) +""" + remove_frames!(stack::StackTrace, name::Symbol) + +Takes a `StackTrace` (a vector of `StackFrames`) and a function name (a `Symbol`) and +removes the `StackFrame` specified by the function name from the `StackTrace` (also removing +all frames above the specified function). Primarily used to remove `StackTraces` functions +from the `StackTrace` prior to returning it. +""" function remove_frames!(stack::StackTrace, name::Symbol) splice!(stack, 1:findlast(frame -> frame.func == name, stack)) return stack end function remove_frames!(stack::StackTrace, names::Vector{Symbol}) - splice!(stack, 1:findlast(frame -> in(frame.func, names), stack)) + splice!(stack, 1:findlast(frame -> frame.func in names, stack)) return stack end -function format_stackframe(frame::StackFrame; full_path::Bool=false) +function show(io::IO, frame::StackFrame; full_path::Bool=false) file_info = "$(full_path ? frame.file : basename(string(frame.file))):$(frame.line)" if frame.inlined_file != Symbol("") @@ -86,39 +127,7 @@ function format_stackframe(frame::StackFrame; full_path::Bool=false) inline_info = "" end - return string(inline_info, frame.func != "" ? frame.func : "?", " at ", file_info) -end - -function format_stacktrace( - stack::StackTrace, separator::AbstractString, start::AbstractString="", - finish::AbstractString=""; full_path::Bool=false -) - if isempty(stack) - return "" - end - - string( - start, - join(map(f -> format_stackframe(f, full_path=full_path), stack), separator), - finish - ) -end - -function show_stacktrace(io::IO, stack::StackTrace; full_path::Bool=false) - println( - io, "StackTrace with $(length(stack)) StackFrames$(isempty(stack) ? "" : ":")", - format_stacktrace(stack, "\n ", "\n "; full_path=full_path) - ) -end - -show_stacktrace(; full_path::Bool=false) = show_stacktrace(STDOUT; full_path=full_path) - -function show_stacktrace(io::IO; full_path::Bool=false) - show_stacktrace(io, remove_frames!(stacktrace(), :show_stacktrace); full_path=full_path) -end - -function show_stacktrace(stack::StackTrace; full_path::Bool=false) - show_stacktrace(STDOUT, stack; full_path=full_path) + print(io, string(inline_info, frame.func != "" ? frame.func : "?", " at ", file_info)) end diff --git a/doc/manual/stacktraces.rst b/doc/manual/stacktraces.rst index 376571feeb45f..fcefaf386a5f1 100644 --- a/doc/manual/stacktraces.rst +++ b/doc/manual/stacktraces.rst @@ -2,54 +2,39 @@ .. currentmodule:: Base +************ Stack Traces -============ +************ -The :mod:`StackTraces` module provides simple stack traces that are both human readable and easy to use programmatically. +The :mod:`StackTraces` module provides simple stack traces that are both human readable and +easy to use programmatically. Viewing a stack trace --------------------- The primary function used to obtain a stack trace is :func:`stacktrace`:: - julia> using StackTraces - julia> stacktrace() - 2-element Array{StackTraces.StackFrame,1}: - StackTraces.StackFrame(:eval_user_input,symbol("REPL.jl"),62,symbol(""),-1,false,13041465684) - StackTraces.StackFrame(:anonymous,symbol("REPL.jl"),92,symbol("task.jl"),63,false,1304140086) + 3-element Array{StackFrame,1}: + eval at boot.jl:265 + [inlined code from REPL.jl:3] eval_user_input at REPL.jl:62 + [inlined code from REPL.jl:92] anonymous at task.jl:63 -Calling :func:`stacktrace` returns a vector of :obj:`StackFrame` s. For ease of use, the alias :obj:`StackTrace` can be used in place of ``Vector{StackFrame}``. +Calling :func:`stacktrace` returns a vector of :obj:`StackFrame` s. For ease of use, the +alias :obj:`StackTrace` can be used in place of ``Vector{StackFrame}``. -:: +.. doctest:: julia> example() = stacktrace() example (generic function with 1 method) julia> example() - 3-element Array{StackTraces.StackFrame,1}: - StackTraces.StackFrame(:example,:none,1,symbol(""),-1,false,13041535346) - StackTraces.StackFrame(:eval_user_input,symbol("REPL.jl"),62,symbol(""),-1,false,13041465684) - StackTraces.StackFrame(:anonymous,symbol("REPL.jl"),92,symbol("task.jl"),63,false,13041400866) - -If you'd like the output to be a little more human-readable, replace calls to :func:`stacktrace` (which returns a vector of :obj:`StackFrame` s) with :func:`show_stacktrace` (which prints the stacktrace to an IO stream). - -:: - - julia> example() = show_stacktrace() - example (generic function with 1 method) + ...-element Array{StackFrame,1}: + example at none:1 + eval at boot.jl:265 + ... - julia> example() - StackTrace with 3 StackFrames: - example at none:1 - eval_user_input at REPL.jl:62 - [inlined code from REPL.jl:92] anonymous at task.jl:63 - -Note that when calling :func:`stacktrace` from the REPL you'll always have those last two frames in the stack from ``REPL.jl`` (including the anonymous function from ``task.jl``). - -:: - - julia> @noinline child() = show_stacktrace() + julia> @noinline child() = stacktrace() child (generic function with 1 method) julia> @noinline parent() = child() @@ -59,29 +44,48 @@ Note that when calling :func:`stacktrace` from the REPL you'll always have those grandparent (generic function with 1 method) julia> grandparent() - StackTrace with 5 StackFrames: - child at none:1 - parent at none:1 - grandparent at none:1 - eval_user_input at REPL.jl:62 - [inlined code from REPL.jl:92] anonymous at task.jl:63 + ...-element Array{StackFrame,1}: + child at none:1 + parent at none:1 + grandparent at none:1 + eval at boot.jl:265 + ... + +Note that when calling :func:`stacktrace` you'll typically see a frame with +``eval at boot.jl``. When calling :func:`stacktrace` from the REPL you'll also have a few +extra frames in the stack from ``REPL.jl``, usually looking something like this:: + + julia> example() = show_stacktrace() + example (generic function with 1 method) + + julia> example() + 4-element Array{StackFrame,1}: + example at none:1 + eval at boot.jl:265 + [inlined code from REPL.jl:3] eval_user_input at REPL.jl:62 + [inlined code from REPL.jl:92] anonymous at task.jl:63 Extracting useful information ----------------------------- -Each :obj:`StackFrame` contains the function name, file name, line number, file and line information for inlined functions, a flag indicating whether it is a C function (by default C functions do not appear in the stack trace), and an integer representation of the pointer returned by :func:`backtrace`:: +Each :obj:`StackFrame` contains the function name, file name, line number, file and line +information for inlined functions, a flag indicating whether it is a C function (by default +C functions do not appear in the stack trace), and an integer representation of the pointer +returned by :func:`backtrace`: + +.. doctest:: julia> top_frame = stacktrace()[1] - StackTraces.StackFrame(:eval_user_input,symbol("REPL.jl"),62,symbol(""),-1,false, 13203085684) + eval at boot.jl:265 julia> top_frame.func - :eval_user_input + :eval julia> top_frame.file - symbol("REPL.jl") + symbol("./boot.jl") julia> top_frame.line - 62 + 265 julia> top_frame.inlined_file symbol("") @@ -91,52 +95,66 @@ Each :obj:`StackFrame` contains the function name, file name, line number, file julia> top_frame.from_c false - +:: julia> top_frame.pointer 13203085684 -This makes stack trace information available programmatically without having to capture and parse the output from something like ``Base.show_backtrace(io, backtrace())``. +This makes stack trace information available programmatically for logging, error handling, +and more. Error handling -------------- -While having easy access to information about the current state of the callstack can be helpful in many places, the most obvious application is in error handling and debugging. +While having easy access to information about the current state of the callstack can be +helpful in many places, the most obvious application is in error handling and debugging. -:: +.. doctest:: - julia> example() = try - error("Oh no!") + julia> bad_function() = undeclared_variable + 1 + + julia> @noinline example() = try + bad_function() catch - show_stacktrace() + stacktrace() end example (generic function with 1 method) julia> example() - StackTrace with 3 StackFrames: - example at none:4 - eval_user_input at REPL.jl:62 - [inlined code from REPL.jl:92] anonymous at task.jl:63 - -You may notice that in the example above the first stack frame points points at line 4, where :func:`stacktrace` is called, rather than line 2, where the error occurred. While in this example it's trivial to track down the actual source of the error, things can get misleading pretty quickly if the stack trace doesn't even point to the right function. - -This can be remedied by calling :func:`catch_stacktrace` instead of :func:`stacktrace`. Instead of returning callstack information for the current context, :func:`catch_stacktrace` returns stack information for the context of the most recent error:: - - julia> example() = try - error("Oh no!") + ...-element Array{StackFrame,1}: + example at none:2 + eval at boot.jl:265 + ... + +You may notice that in the example above the first stack frame points points at line 4, +where :func:`stacktrace` is called, rather than line 2, where `bad_function` is called, and +``bad_function``'s frame is missing entirely. This is understandable, given that +:func:`stacktrace` is called from the context of the `catch`. While in this example it's +fairly easy to find the actual source of the error, in complex cases tracking down the +source of the error becomes nontrivial. + +This can be remedied by calling :func:`catch_stacktrace` instead of :func:`stacktrace`. +Instead of returning callstack information for the current context, :func:`catch_stacktrace` +returns stack information for the context of the most recent exception: + +.. doctest:: + + julia> @noinline example() = try + bad_function() catch - show_stacktrace(catch_stacktrace()) + catch_stacktrace() end example (generic function with 1 method) julia> example() - StackTrace with 3 StackFrames: - example at none:2 - eval_user_input at REPL.jl:62 - [inlined code from REPL.jl:92] anonymous at task.jl:63 + ...-element Array{StackFrame,1}: + bad_function at none:1 + example at none:2 + eval at boot.jl:265 + ... -Notice that the stack trace now indicates the appropriate line number. +Notice that the stack trace now indicates the appropriate line number and the missing frame. -:: +.. doctest:: julia> @noinline child() = error("Whoops!") child (generic function with 1 method) @@ -144,70 +162,86 @@ Notice that the stack trace now indicates the appropriate line number. julia> @noinline parent() = child() parent (generic function with 1 method) - julia> function grandparent() + julia> @noinline function grandparent() try parent() catch err println("ERROR: ", err.msg) - show_stacktrace(catch_stacktrace()) + catch_stacktrace() end end grandparent (generic function with 1 method) julia> grandparent() ERROR: Whoops! - StackTrace with 5 StackFrames: - child at none:1 - parent at none:1 - grandparent at none:3 - eval_user_input at REPL.jl:62 - [inlined code from REPL.jl:92] anonymous at task.jl:63 - -Comparison with ``backtrace`` ------------------------------ + ...-element Array{StackFrame,1}: + child at none:1 + parent at none:1 + grandparent at none:3 + eval at boot.jl:265 + ... + +Comparison with :func:`backtrace` +--------------------------------- -A call to :func:`backtrace` returns a vector of ``Ptr{Void}``, which may then be passed into :func:`stacktrace` for translation:: +A call to :func:`backtrace` returns a vector of ``Ptr{Void}``, which may then be passed into +:func:`stacktrace` for translation:: - julia> stack = backtrace() + julia> trace = backtrace() 15-element Array{Ptr{Void},1}: - Ptr{Void} @0x000000010e9562ed - Ptr{Void} @0x0000000312f95f20 - Ptr{Void} @0x0000000312f95ea0 - Ptr{Void} @0x000000010e8e5776 - Ptr{Void} @0x000000010e950c04 - Ptr{Void} @0x000000010e94f2a8 - Ptr{Void} @0x000000010e94f137 - Ptr{Void} @0x000000010e95070d - Ptr{Void} @0x000000010e95053f - Ptr{Void} @0x000000010e963348 - Ptr{Void} @0x000000010e8edd67 - Ptr{Void} @0x0000000312f71974 - Ptr{Void} @0x0000000312f715c7 - Ptr{Void} @0x0000000312f65c22 - Ptr{Void} @0x000000010e95708f - - julia> stacktrace(stack) - 3-element Array{StackTraces.StackFrame,1}: - StackTraces.StackFrame(:backtrace,symbol("error.jl"),26,symbol(""),-1,false,13203234592) - StackTraces.StackFrame(:eval_user_input,symbol("REPL.jl"),62,symbol(""),-1,false,13203085684) - StackTraces.StackFrame(:anonymous,symbol("REPL.jl"),92,symbol("task.jl"),63,false,13203037218) + Ptr{Void} @0x000000010527895e + Ptr{Void} @0x0000000309bf6220 + Ptr{Void} @0x0000000309bf61a0 + Ptr{Void} @0x00000001052733b4 + Ptr{Void} @0x0000000105271a0e + Ptr{Void} @0x000000010527189d + Ptr{Void} @0x0000000105272e6d + Ptr{Void} @0x0000000105272cef + Ptr{Void} @0x0000000105285b88 + Ptr{Void} @0x000000010526b50e + Ptr{Void} @0x000000010663cc28 + Ptr{Void} @0x0000000309bbc20f + Ptr{Void} @0x0000000309bbbde7 + Ptr{Void} @0x0000000309bb0262 + Ptr{Void} @0x000000010527980e + + julia> stacktrace(trace) + 4-element Array{StackFrame,1}: + backtrace at error.jl:26 + eval at boot.jl:265 + [inlined code from REPL.jl:3] eval_user_input at REPL.jl:62 + [inlined code from REPL.jl:92] anonymous at task.jl:63 Notice that the vector returned by :func:`backtrace` had 15 pointers, while the vector returned by :func:`stacktrace` only has 3. This is because, by default, :func:`stacktrace` removes any lower-level C functions from the stack. If you want to include stack frames from C calls, you can do it like this:: julia> stacktrace(stack, true) - 15-element Array{StackTraces.StackFrame,1}: - StackTraces.StackFrame(:rec_backtrace,symbol("/private/tmp/julia20151107-44794-o1d6wy/src/task.c"),644,symbol("/private/tmp/julia20151107-44794-o1d6wy/src/task.c"),703,true,4539638509) - StackTraces.StackFrame(:backtrace,symbol("error.jl"),26,symbol(""),-1,false,13203234592) - StackTraces.StackFrame(:jlcall_backtrace_21483,symbol(""),-1,symbol(""),-1,true,13203234464) - StackTraces.StackFrame(:jl_apply,symbol("/private/tmp/julia20151107-44794-o1d6wy/src/gf.c"),1691,symbol("/private/tmp/julia20151107-44794-o1d6wy/src/gf.c"),1708,true,4539176822) - StackTraces.StackFrame(:jl_apply,symbol("/private/tmp/julia20151107-44794-o1d6wy/src/interpreter.c"),55,symbol("/private/tmp/julia20151107-44794-o1d6wy/src/interpreter.c"),65,true,4539616260 - StackTraces.StackFrame(:eval,symbol("/private/tmp/julia20151107-44794-o1d6wy/src/interpreter.c"),213,symbol(""),-1,true,4539609768) - StackTraces.StackFrame(:eval,symbol("/private/tmp/julia20151107-44794-o1d6wy/src/interpreter.c"),219,symbol(""),-1,true,4539609399) - StackTraces.StackFrame(:eval_body,symbol("/private/tmp/julia20151107-44794-o1d6wy/src/interpreter.c"),592,symbol(""),-1,true,4539614989) - StackTraces.StackFrame(:jl_toplevel_eval_body,symbol("/private/tmp/julia20151107-44794-o1d6wy/src/interpreter.c"),527,symbol(""),-1,true,4539614527) - StackTraces.StackFrame(:jl_toplevel_eval_flex,symbol("/private/tmp/julia20151107-44794-o1d6wy/src/toplevel.c"),521,symbol(""),-1,true,4539691848) - StackTraces.StackFrame(:jl_toplevel_eval_in,symbol("/private/tmp/julia20151107-44794-o1d6wy/src/builtins.c"),579,symbol(""),-1,true,4539211111) - StackTraces.StackFrame(:eval_user_input,symbol("REPL.jl"),62,symbol(""),-1,false,13203085684) - StackTraces.StackFrame(:jlcall_eval_user_input_21232,symbol(""),-1,symbol(""),-1,true,13203084743) - StackTraces.StackFrame(:anonymous,symbol("REPL.jl"),92,symbol("task.jl"),63,false,13203037218) - StackTraces.StackFrame(:jl_apply,symbol("/private/tmp/julia20151107-44794-o1d6wy/src/task.c"),241,symbol("/private/tmp/julia20151107-44794-o1d6wy/src/task.c"),240,true,4539641999) + 15-element Array{StackFrame,1}: + [inlined code from task.c:651] rec_backtrace at task.c:711 + backtrace at error.jl:26 + jlcall_backtrace_23146 at :-1 + [inlined code from interpreter.c:55] jl_apply at interpreter.c:65 + eval at interpreter.c:214 + eval at interpreter.c:220 + eval_body at interpreter.c:601 + jl_toplevel_eval_body at interpreter.c:534 + jl_toplevel_eval_flex at toplevel.c:525 + jl_toplevel_eval_in_warn at builtins.c:590 + eval at boot.jl:265 + [inlined code from REPL.jl:3] eval_user_input at REPL.jl:62 + jlcall_eval_user_input_22658 at :-1 + [inlined code from REPL.jl:92] anonymous at task.jl:63 + [inlined code from julia.h:1352] jl_apply at task.c:246 + +Individual pointers returned by :func:`backtrace` can be translated into :obj:`StackFrame` s +by passing them into :func:`StackTraces.lookup`: + +.. doctest:: + + julia> pointer = backtrace()[1] + Ptr{Void} @0x... + + julia> frame = StackTraces.lookup(pointer) + [inlined code from task.c:651] rec_backtrace at task.c:711 + + julia> println("The top frame is from $(frame.func)!") + The top frame is from rec_backtrace! diff --git a/doc/stdlib/stacktraces.rst b/doc/stdlib/stacktraces.rst index 271ec08a0f8f8..3a48958abae7f 100644 --- a/doc/stdlib/stacktraces.rst +++ b/doc/stdlib/stacktraces.rst @@ -8,40 +8,6 @@ .. currentmodule:: Base -.. function:: stacktrace([trace::Vector{Ptr{Void}},] [c_funcs::Bool=false]) -> StackTraces.StackTrace - - .. Docstring generated from Julia source - - Returns a stack trace in the form of a vector of ``StackFrame``\ s. (By default stacktrace doesn't return C functions, but this can be enabled.) When called without specifying a trace, ``stacktrace`` first calls ``backtrace``\ . - -.. function:: catch_stacktrace([c_funcs::Bool=false]) -> StackTraces.StackTrace - - .. Docstring generated from Julia source - - Returns the stack trace for the most recent error thrown, rather than the current execution context. - -.. function:: show_stacktrace([io::IO,] [stack::StackTrace]; full_path::Bool=false) - - .. Docstring generated from Julia source - - An analogue of ``Base.show_backtrace`` that prints the stacktrace to the specified IO stream. Only the base name of the ``file`` and ``inlined_file`` fields are displayed unless the optional ``full_path`` keyword argument is set to ``true``\ . - -.. function:: format_stackframe(frame::StackFrame; full_path::Bool=false) -> str - - .. Docstring generated from Julia source - - Returns a string representation of a ``StackFrame`` for display purposes. Only the base name of the ``file`` and ``inlined_file`` fields are displayed unless the optional ``full_path`` keyword argument is set to ``true``\ . - -.. function:: format_stacktrace(stack::StackTrace, separator::AbstractString, [start::AbstractString="", finish::AbstractString=""]; full_path::Bool=false) -> str - - .. Docstring generated from Julia source - - Returns a string representation of a ``StackTrace`` for display purposes. Only the base name of the ``file`` and ``inlined_file`` fields are displayed unless the optional ``full_path`` keyword argument is set to ``true``\ . - -.. currentmodule:: Base.StackTraces - -The following methods and types in :mod:`Base.StackTraces` are not exported and need to be called e.g. as ``StackTraces.lookup(ptr)``. - .. data:: StackFrame Stack information representing execution context, with the following fields: @@ -72,7 +38,23 @@ The following methods and types in :mod:`Base.StackTraces` are not exported and An alias for ``Vector{StackFrame}`` provided for convenience; returned by calls to ``stacktrace`` and ``catch_stacktrace``. -.. function:: lookup(pointer::Union{Ptr{Void}, UInt}) -> StackTraces.StackFrame +.. function:: stacktrace([trace::Vector{Ptr{Void}},] [c_funcs::Bool=false]) -> StackTrace + + .. Docstring generated from Julia source + + Returns a stack trace in the form of a vector of ``StackFrame``\ s. (By default stacktrace doesn't return C functions, but this can be enabled.) When called without specifying a trace, ``stacktrace`` first calls ``backtrace``\ . + +.. function:: catch_stacktrace([c_funcs::Bool=false]) -> StackTrace + + .. Docstring generated from Julia source + + Returns the stack trace for the most recent error thrown, rather than the current execution context. + +.. currentmodule:: Base.StackTraces + +The following methods and types in :mod:`Base.StackTraces` are not exported and need to be called e.g. as ``StackTraces.lookup(ptr)``. + +.. function:: lookup(pointer::Union{Ptr{Void}, UInt}) -> StackFrame .. Docstring generated from Julia source diff --git a/test/stacktraces.jl b/test/stacktraces.jl index b0d369f78fa0f..dd2e1c8fed2f2 100644 --- a/test/stacktraces.jl +++ b/test/stacktraces.jl @@ -2,135 +2,70 @@ # Tests for /base/stacktraces.jl -using Base.StackTraces -using Base.StackTraces: StackFrame, StackTrace, remove_frames! - let @noinline child() = stacktrace() @noinline parent() = child() @noinline grandparent() = parent() - line_numbers = [@__LINE__] .- [3, 2, 1] - - @testset "basic" begin - stack = grandparent() - @test [:child, :parent, :grandparent] == [f.func for f in stack[1:3]] - for (line, frame) in zip(line_numbers, stack[1:3]) - @test in( - [Symbol(@__FILE__), line], - ([frame.file, frame.line], [frame.inlined_file, frame.inlined_line]) - ) - end - @test [false, false, false] == [f.from_c for f in stack[1:3]] - end - - @testset "from_c" begin - default, with_c, without_c = stacktrace(), stacktrace(true), stacktrace(false) - @test default == without_c - @test length(with_c) > length(without_c) - @test !isempty(filter(frame -> frame.from_c, with_c)) - @test isempty(filter(frame -> frame.from_c, without_c)) - end - - @testset "remove_frames!" begin - stack = remove_frames!(grandparent(), :parent) - @test stack[1] == StackFrame( - :grandparent, @__FILE__, line_numbers[3], Symbol(""), -1, false, 0 - ) - - stack = remove_frames!(grandparent(), [:child, :something_nonexistent]) - @test stack[1:2] == [ - StackFrame(:parent, @__FILE__, line_numbers[2], Symbol(""), -1, false, 0), - StackFrame(:grandparent, @__FILE__, line_numbers[3], Symbol(""), -1, false, 0) - ] + line_numbers = @__LINE__ - [3, 2, 1] + + # Basic tests. + stack = grandparent() + @test [:child, :parent, :grandparent] == [f.func for f in stack[1:3]] + for (line, frame) in zip(line_numbers, stack[1:3]) + @test [Symbol(@__FILE__), line] in + ([frame.file, frame.line], [frame.inlined_file, frame.inlined_line]) end + @test [false, false, false] == [f.from_c for f in stack[1:3]] + + # Test from_c + default, with_c, without_c = stacktrace(), stacktrace(true), stacktrace(false) + @test default == without_c + @test length(with_c) > length(without_c) + @test !isempty(filter(frame -> frame.from_c, with_c)) + @test isempty(filter(frame -> frame.from_c, without_c)) + + # Test remove_frames! + stack = StackTraces.remove_frames!(grandparent(), :parent) + @test stack[1] == StackFrame( + :grandparent, @__FILE__, line_numbers[3], Symbol(""), -1, false, 0 + ) + + stack = StackTraces.remove_frames!(grandparent(), [:child, :something_nonexistent]) + @test stack[1:2] == [ + StackFrame(:parent, @__FILE__, line_numbers[2], Symbol(""), -1, false, 0), + StackFrame(:grandparent, @__FILE__, line_numbers[3], Symbol(""), -1, false, 0) + ] end let - @noinline bad_function() = nonexistent_var - @noinline function good_function() + # No errors should mean nothing in catch_backtrace + @test catch_backtrace() == StackFrame[] + + @noinline bad_function() = nonexistent_var + 1 + @noinline function try_stacktrace() + try + bad_function() + catch + return stacktrace() + end + end + @noinline function try_catch() try bad_function() catch return catch_stacktrace() end end - e_line_numbers = [@__LINE__] .- [8, 5] + line_numbers = @__LINE__ .- [5, 10, 15] - @testset "try...catch" begin - stack = good_function() - @test stack[1:2] == [ - StackFrame(:bad_function, @__FILE__, e_line_numbers[1], Symbol(""), -1, false, 0), - StackFrame(:good_function, @__FILE__, e_line_numbers[2], Symbol(""), -1, false, 0) - ] - end -end + # Test try...catch with stacktrace + @test try_stacktrace()[1] == StackFrame( + :try_stacktrace, @__FILE__, line_numbers[2], Symbol(""), -1, false, 0 + ) -let - format_stack = [ - StackFrame(:frame1, "path/file.1", 10, "path/file.inline", 0, false, 0), - StackFrame(:frame2, "path/file.2", 20, Symbol(""), -1, false, 0) + # Test try...catch with catch_stacktrace + @test try_catch()[1:2] == [ + StackFrame(:bad_function, @__FILE__, line_numbers[1], Symbol(""), -1, false, 0), + StackFrame(:try_catch, @__FILE__, line_numbers[3], Symbol(""), -1, false, 0) ] - - @testset "formatting" begin - @testset "frame" begin - @test format_stackframe(format_stack[1]) == - "[inlined code from file.1:10] frame1 at file.inline:0" - @test format_stackframe(format_stack[1]; full_path=true) == - "[inlined code from path/file.1:10] frame1 at path/file.inline:0" - - @test format_stackframe(format_stack[2]) == "frame2 at file.2:20" - @test format_stackframe(format_stack[2]; full_path=true) == - "frame2 at path/file.2:20" - end - - @testset "stack" begin - @test format_stacktrace(format_stack, ", ") == - "[inlined code from file.1:10] frame1 at file.inline:0, frame2 at file.2:20" - @test format_stacktrace(format_stack, ", "; full_path=true) == - string( - "[inlined code from path/file.1:10] ", - "frame1 at path/file.inline:0, frame2 at path/file.2:20" - ) - - @test format_stacktrace(format_stack, ", ", "Stack: ") == - string( - "Stack: [inlined code from file.1:10] ", - "frame1 at file.inline:0, frame2 at file.2:20" - ) - @test format_stacktrace(format_stack, ", ", "Stack: "; full_path=true) == - string( - "Stack: [inlined code from path/file.1:10] ", - "frame1 at path/file.inline:0, frame2 at path/file.2:20" - ) - - @test format_stacktrace(format_stack, ", ", "{", "}") == - string( - "{[inlined code from file.1:10] ", - "frame1 at file.inline:0, frame2 at file.2:20}" - ) - @test format_stacktrace(format_stack, ", ", "{", "}", full_path=true) == - string( - "{[inlined code from path/file.1:10] ", - "frame1 at path/file.inline:0, frame2 at path/file.2:20}" - ) - end - - @testset "empty" begin - @test format_stacktrace(StackTrace(), ", ") == "" - @test format_stacktrace(StackTrace(), ", ", "Stack: ") == "" - @test format_stacktrace(StackTrace(), ", ", "{", "}") == "" - end - end - - @testset "output" begin - io = IOBuffer() - show_stacktrace(io, format_stack) - @test takebuf_string(io) == - """ - StackTrace with 2 StackFrames: - [inlined code from file.1:10] frame1 at file.inline:0 - frame2 at file.2:20 - """ - show_stacktrace() # Improves code coverage. - end end