Skip to content

Commit

Permalink
fix StackTraces and DRY some of the code
Browse files Browse the repository at this point in the history
  • Loading branch information
vtjnash committed Jan 30, 2016
1 parent fc5bbc0 commit 0d7d141
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 114 deletions.
7 changes: 3 additions & 4 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,12 @@ function firstcaller(bt::Array{Ptr{Void},1}, funcsym::Symbol)
# Identify the calling line
i = 1
while i <= length(bt)
lkup = ccall(:jl_lookup_code_address, Any, (Ptr{Void},Cint), bt[i], true)
lkup = StackTraces.lookup(bt[i])
i += 1
if lkup === ()
if lkup === StackTraces.UNKNOWN
continue
end
fname, file, line, inlinedat_file, inlinedat_line, linfo, fromC = lkup
if fname == funcsym
if lkup.func == funcsym
break
end
end
Expand Down
15 changes: 7 additions & 8 deletions base/profile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

module Profile

import Base: show_spec_linfo
import Base.StackTraces: lookup, UNKNOWN
import Base.StackTraces: lookup, UNKNOWN, show_spec_linfo

export @profile

Expand Down Expand Up @@ -308,11 +307,11 @@ function print_flat(io::IO, lilist::Vector{StackFrame}, n::Vector{Int}, combine:
for i = 1:length(n)
li = lilist[i]
Base.print(io, lpad(string(n[i]), wcounts, " "), " ")
Base.print(io, rpad(rtruncto(li.file, wfile), wfile, " "), " ")
Base.print(io, rpad(rtruncto(string(li.file), wfile), wfile, " "), " ")
Base.print(io, lpad(string(li.line), wline, " "), " ")
fname = li.func
if !li.fromC && !isnull(li.outer_linfo)
fname = sprint(show_spec_linfo, Symbol(li.func), get(li.outer_linfo))
fname = string(li.func)
if !li.from_c && !isnull(li.outer_linfo)
fname = sprint(show_spec_linfo, li)
end
Base.print(io, rpad(ltruncto(fname, wfunc), wfunc, " "))
println(io)
Expand Down Expand Up @@ -373,8 +372,8 @@ function tree_format(lilist::Vector{StackFrame}, counts::Vector{Int}, level::Int
")")
else
fname = string(li.func)
if !li.fromC && !isnull(li.outer_linfo)
fname = sprint(show_spec_linfo, Symbol(li.func), get(li.outer_linfo))
if !li.from_c && !isnull(li.outer_linfo)
fname = sprint(show_spec_linfo, li)
end
strs[i] = string(base,
rpad(string(counts[i]), ndigcounts, " "),
Expand Down
94 changes: 24 additions & 70 deletions base/replutil.jl
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,13 @@ showerror(io::IO, ex::InitError) = showerror(io, ex, [])
function showerror(io::IO, ex::DomainError, bt; backtrace=true)
print(io, "DomainError:")
for b in bt
code = ccall(:jl_lookup_code_address, Any, (Ptr{Void}, Cint), b-1, true)
if length(code) == 8 && !code[7] # code[7] == fromC
if code[1] in (:log, :log2, :log10, :sqrt) # TODO add :besselj, :besseli, :bessely, :besselk
print(io,"\n$(code[1]) will only return a complex result if called with a complex argument. Try $(code[1])(complex(x)).")
elseif (code[1] == :^ && code[2] == symbol("intfuncs.jl")) || code[1] == :power_by_squaring #3024
code = StackTraces.lookup(b)
if code !== lookup && code.from_c
if code.func in (:log, :log2, :log10, :sqrt) # TODO add :besselj, :besseli, :bessely, :besselk
print(io,"\n$(code.func) will only return a complex result if called with a complex argument. Try $(code[1])(complex(x)).")
elseif (code.func == :^ && code.file == symbol("intfuncs.jl")) || code.func == :power_by_squaring #3024
print(io, "\nCannot raise an integer x to a negative power -n. \nMake x a float by adding a zero decimal (e.g. 2.0^-n instead of 2^-n), or write 1/x^n, float(x)^-n, or (x//1)^-n.")
elseif code[1] == :^ && (code[2] == symbol("promotion.jl") || code[2] == symbol("math.jl"))
elseif code.func == :^ && (code.file == symbol("promotion.jl") || code.file == symbol("math.jl"))
print(io, "\nExponentiation yielding a complex result requires a complex argument.\nReplace x^y with (x+0im)^y, Complex(x)^y, or similar.")
end
break
Expand Down Expand Up @@ -387,52 +387,10 @@ function show_method_candidates(io::IO, ex::MethodError)
end
end

function show_spec_linfo(io::IO, fname::Symbol, linfo::Union{LambdaStaticData, Void})
if linfo === nothing
print(io, fname)
else
print(io, linfo.name)
Base.show_delim_array(io, linfo.(#=specTypes=#8).parameters, "(", ", ", ")", false)
end
end

const empty_symbol = symbol("")
show_trace_entry(io, fname, file, line, inlinedat_file, inlinedat_line, n) =
show_trace_entry(io, fname, file, line, inlinedat_file, inlinedat_line, nothing, n)
function show_trace_entry(io, fname, file, line, inlinedat_file, inlinedat_line, outer_linfo, n)
function show_trace_entry(io, frame, n)
print(io, "\n")
# if we have inlining information, we print the `file`:`line` first,
# then show the inlining info, because the inlining location
# corresponds to `fname`.
if inlinedat_file !== empty_symbol
# align the location text
print(io, " [inlined code] from ")
else
print(io, " in ")
show_spec_linfo(io, fname, outer_linfo)
print(io, " at ")
end

print(io, file)

if line >= 1
try
print(io, ":", line)
catch
print(io, '?') #for when dec is not yet defined
end
end

if n > 1
print(io, " (repeats ", n, " times)")
end

if inlinedat_file !== empty_symbol
print(io, "\n in ")
show_spec_linfo(io, fname, outer_linfo)
print(io, " at ")
print(io, inlinedat_file, ":", inlinedat_line)
end
show(io, frame, full_path=true)
n > 1 && print(io, " (repeats ", n, " times)")
end

function show_backtrace(io::IO, t, set=1:typemax(Int))
Expand All @@ -448,8 +406,8 @@ function show_backtrace(io::IO, t, set=1:typemax(Int))
end

function show_backtrace(io::IO, top_function::Symbol, t, set)
process_entry(lastname, lastfile, lastline, last_inlinedat_file, last_inlinedat_line, outer_linfo, n) =
show_trace_entry(io, lastname, lastfile, lastline, last_inlinedat_file, last_inlinedat_line, outer_linfo, n)
process_entry(last_frame, n) =
show_trace_entry(io, last_frame, n)
process_backtrace(process_entry, top_function, t, set)
end

Expand All @@ -461,36 +419,32 @@ end

# process the backtrace, up to (but not including) top_function
function process_backtrace(process_func::Function, top_function::Symbol, t, set; skipC = true)
n = 1
lastfile = ""; lastline = -11; lastname = symbol("#");
last_inlinedat_file = ""; last_inlinedat_line = -1; last_outer_linfo = nothing
local fname, file, line
n = 0
last_frame = StackTraces.UNKNOWN
count = 0
for i = 1:length(t)
lkup = ccall(:jl_lookup_code_address, Any, (Ptr{Void}, Cint), t[i]-1, skipC)
if lkup === nothing
lkup = StackTraces.lookup(t[i])
if lkup === StackTraces.UNKNOWN
continue
end
fname, file, line, inlinedat_file, inlinedat_line, outer_linfo, fromC = lkup

if fromC && skipC; continue; end
if i == 1 && fname == :error; continue; end
if fname == top_function; break; end
if lkup.from_c && skipC; continue; end
if i == 1 && lkup.func == :error; continue; end
if lkup.func == top_function; break; end
count += 1
if !in(count, set); continue; end

if file != lastfile || line != lastline || fname != lastname
if lastline != -11
process_func(lastname, lastfile, lastline, last_inlinedat_file, last_inlinedat_line, last_outer_linfo, n)
if lkup.file != last_frame.file || lkup.line != last_frame.line || lkup.func != last_frame.func
if n > 0
process_func(last_frame, n)
end
n = 1
lastfile = file; lastline = line; lastname = fname;
last_inlinedat_file = inlinedat_file; last_inlinedat_line = inlinedat_line; last_outer_linfo = outer_linfo
last_frame = lkup
else
n += 1
end
end
if n > 1 || lastline != -11
process_func(lastname, lastfile, lastline, last_inlinedat_file, last_inlinedat_line, last_outer_linfo, n)
if n > 0
process_func(last_frame, n)
end
end
48 changes: 30 additions & 18 deletions base/stacktraces.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ immutable StackFrame
pointer::Int64 # Large enough to be read losslessly on 32- and 64-bit machines.
end

StackFrame(func, file, line) = StackFrame(func, file, line, symbol(""), -1, false, 0)
StackFrame(func, file, line) = StackFrame(func, Nullable{LambdaStaticData}(), file, line, empty_sym, -1, false, 0)

"""
StackTrace
Expand All @@ -41,9 +41,9 @@ An alias for `Vector{StackFrame}` provided for convenience; returned by calls to
"""
typealias StackTrace Vector{StackFrame}

const empty_symbol = symbol("")
const unk_symbol = symbol("???")
const UNKNOWN = StackFrame(unk_symbol, Nullable{LambdaStaticData}(), unk_symbol, 0, empty_symbol, -1, true, 0)
const empty_sym = symbol("")
const unk_sym = symbol("???")
const UNKNOWN = StackFrame(unk_sym, Nullable{LambdaStaticData}(), unk_sym, -1, empty_sym, -1, true, 0) # === lookup(C_NULL)


#=
Expand All @@ -70,10 +70,10 @@ Given a pointer to an execution context (usually generated by a call to `backtra
up stack frame context information.
"""
function lookup(pointer::Ptr{Void})
info = ccall(:jl_lookup_code_address, Any, (Ptr{Void}, Cint), ip, false)
info = ccall(:jl_lookup_code_address, Any, (Ptr{Void}, Cint), pointer - 1, false)
if length(info) == 8
is_inlined = (info[4] !== empty_sym)
return LineInfo(string(info[1]),
return StackFrame(string(info[1]),
info[6] === nothing ? Nullable{LambdaStaticData}() : Nullable{LambdaStaticData}(info[6]),
is_inlined ? info[4] : info[2],
Int(is_inlined ? info[5] : info[3]),
Expand Down Expand Up @@ -134,21 +134,33 @@ function remove_frames!(stack::StackTrace, names::Vector{Symbol})
return stack
end

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("")
inline_info = string("[inlined code from ", file_info, "] ")
file_info = string(
full_path ? frame.inlined_file : basename(string(frame.inlined_file)),
":", frame.inlined_line
)
function show_spec_linfo(io::IO, frame::StackFrame)
if isnull(frame.outer_linfo)
print(io, frame.func !== empty_sym ? frame.func : "?")
else
inline_info = ""
linfo = get(frame.outer_linfo)
print(io, linfo.name)
if isdefined(linfo, 8)
Base.show_delim_array(io, linfo.(#=specTypes=#8).parameters, "(", ", ", ")", false)
end
end

print(io, string(inline_info, frame.func != "" ? frame.func : "?", " at ", file_info))
end

function show(io::IO, frame::StackFrame; full_path::Bool=false)
if frame.inlined_file !== empty_sym
# if we have inlining information, print it first
inline_info = full_path ? string(frame.inlined_file) : basename(string(frame.inlined_file))
print(io, " [inlined code] from ", inline_info)
if frame.inlined_line > 0
print(io, ":", frame.inlined_line)
end
println(io)
end

print(io, " in ")
show_spec_linfo(io, frame)
file_info = full_path ? string(frame.file) : basename(string(frame.file))
print(io, " at ", file_info, ":", frame.line)
end

end
3 changes: 1 addition & 2 deletions base/task.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ type CapturedException

# Process bt_raw so that it can be safely serialized
bt_lines = Any[]
process_func(name, file, line, inlined_file, inlined_line, outer_linfo, n) =
push!(bt_lines, (name, file, line, inlined_file, inlined_line, nothing, n))
process_func(args...) = push!(bt_lines, args)
process_backtrace(process_func, :(:), bt_raw, 1:100) # Limiting this to 100 lines.

new(ex, bt_lines)
Expand Down
32 changes: 20 additions & 12 deletions test/stacktraces.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@ const APPVEYOR_WIN32 = (
OS_NAME == :Windows && WORD_SIZE == 32 && get(ENV, "APPVEYOR", "False") == "True"
)

if !APPVEYOR_WIN32
let
@noinline child() = stacktrace()
@noinline parent() = child()
@noinline grandparent() = parent()
line_numbers = @__LINE__ - [3, 2, 1]
let
@noinline child() = stacktrace()
@noinline parent() = child()
@noinline grandparent() = parent()
line_numbers = @__LINE__ - [3, 2, 1]
stack = grandparent()

if APPVEYOR_WIN32
display(backtrace())
display(stack)
else
# Basic tests.
stack = grandparent()
@assert length(stack) >= 3 "Compiler has unexpectedly inlined functions"
@test [:child, :parent, :grandparent] == [f.func for f in stack[1:3]]
for (line, frame) in zip(line_numbers, stack[1:3])
Expand Down Expand Up @@ -56,6 +59,7 @@ let
try
bad_function()
catch
i_need_a_line_number_julia_bug = true # julia lowering doesn't emit a proper line number for catch
return stacktrace()
end
end
Expand All @@ -66,14 +70,18 @@ let
return catch_stacktrace()
end
end
line_numbers = @__LINE__ .- [15, 10, 5]
line_numbers = @__LINE__ .- [16, 10, 5]

# Test try...catch with stacktrace
@test try_stacktrace()[1] == StackFrame(:try_stacktrace, @__FILE__, line_numbers[2])

# Test try...catch with catch_stacktrace
@test try_catch()[1:2] == [
StackFrame(:bad_function, @__FILE__, line_numbers[1]),
StackFrame(:try_catch, @__FILE__, line_numbers[3])
]
bt = try_catch()
@test bt[1] == StackFrame(:bad_function, @__FILE__, line_numbers[1])
if APPVEYOR_WIN32
display(backtrace())
display(bt)
else
@test bt[2] == StackFrame(:try_catch, @__FILE__, line_numbers[3])
end
end

0 comments on commit 0d7d141

Please sign in to comment.