Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

track the exact lambda associated with a fptr #14815

Merged
merged 3 commits into from
Jan 31, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
fix StackTraces and DRY some of the code
  • Loading branch information
vtjnash committed Jan 31, 2016
commit 8a0a7d69cd4891757844be52d39a0d39d0b92669
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be StackTraces.lookup? the error message from sqrt(-1) is currently broken by this, evidently needs a test

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
50 changes: 31 additions & 19 deletions base/stacktraces.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Stack information representing execution context.
immutable StackFrame
"the name of the function containing the execution context"
func::Symbol
"the LambdaStaticData for the the execution context"
"the LambdaStaticData containing the execution context (if it could be found)"
outer_linfo::Nullable{LambdaStaticData}
"the path to the file containing the execution context"
file::Symbol
Expand All @@ -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
3 changes: 3 additions & 0 deletions doc/stdlib/stacktraces.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
``func::Symbol``
the name of the function containing the execution context

``outer_linfo::Nullable{LambdaStaticData}``
the LambdaStaticData containing the execution context (if it could be found)

``file::Symbol``
the path to the file containing the execution context

Expand Down
54 changes: 24 additions & 30 deletions test/stacktraces.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,31 @@

# Tests for /base/stacktraces.jl

# Some tests don't currently work for Appveyor 32-bit Windows
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()

# 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])
@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]]
# Basic tests.
@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])
@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 remove_frames!
stack = StackTraces.remove_frames!(grandparent(), :parent)
@test stack[1] == StackFrame(:grandparent, @__FILE__, line_numbers[3])
# Test remove_frames!
stack = StackTraces.remove_frames!(grandparent(), :parent)
@test stack[1] == StackFrame(:grandparent, @__FILE__, line_numbers[3])

stack = StackTraces.remove_frames!(grandparent(), [:child, :something_nonexistent])
@test stack[1:2] == [
StackFrame(:parent, @__FILE__, line_numbers[2]),
StackFrame(:grandparent, @__FILE__, line_numbers[3])
]
end
stack = StackTraces.remove_frames!(grandparent(), [:child, :something_nonexistent])
@test stack[1:2] == [
StackFrame(:parent, @__FILE__, line_numbers[2]),
StackFrame(:grandparent, @__FILE__, line_numbers[3])
]
end

let
Expand All @@ -56,6 +49,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,7 +60,7 @@ 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])
Expand Down