diff --git a/base/client.jl b/base/client.jl index a9377b5b920a9..e07d9f73630cf 100644 --- a/base/client.jl +++ b/base/client.jl @@ -79,8 +79,10 @@ function repl_cmd(cmd, out) nothing end +# deprecated function--preserved for DocTests.jl function ip_matches_func(ip, func::Symbol) - for fr in StackTraces.lookup(ip) + ip isa InterpreterIP || (ip -= 1) + for fr in StackTraces.lookupat(ip) if fr === StackTraces.UNKNOWN || fr.from_c return false end @@ -90,28 +92,28 @@ function ip_matches_func(ip, func::Symbol) end function scrub_repl_backtrace(bt) - if bt !== nothing + if bt !== nothing && !(bt isa Vector{Any}) # ignore our sentinel value types + bt = stacktrace(bt) # remove REPL-related frames from interactive printing - eval_ind = findlast(addr->ip_matches_func(addr, :eval), bt) - if eval_ind !== nothing - return bt[1:eval_ind-1] - end + eval_ind = findlast(frame -> !frame.from_c && frame.func == :eval, bt) + eval_ind === nothing || deleteat!(bt, eval_ind:length(bt)) end return bt end function display_error(io::IO, er, bt) printstyled(io, "ERROR: "; bold=true, color=Base.error_color()) - showerror(IOContext(io, :limit => true), er, scrub_repl_backtrace(bt)) + bt = scrub_repl_backtrace(bt) + showerror(IOContext(io, :limit => true), er, bt, backtrace = bt!==nothing) println(io) end function display_error(io::IO, stack::Vector) printstyled(io, "ERROR: "; bold=true, color=Base.error_color()) - show_exception_stack(IOContext(io, :limit => true), Any[ (x[1], scrub_repl_backtrace(x[2])) for x in stack ]) + bt = Any[ (x[1], scrub_repl_backtrace(x[2])) for x in stack ] + show_exception_stack(IOContext(io, :limit => true), bt) end display_error(stack::Vector) = display_error(stderr, stack) -display_error(er, bt) = display_error(stderr, er, bt) -display_error(er) = display_error(er, []) +display_error(er, bt=nothing) = display_error(stderr, er, bt) function eval_user_input(errio, @nospecialize(ast), show_value::Bool) errcount = 0 diff --git a/base/deprecated.jl b/base/deprecated.jl index 1f185ff83df12..a6dca85091e55 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -101,33 +101,29 @@ firstcaller(bt::Vector, funcsym::Symbol) = firstcaller(bt, (funcsym,)) function firstcaller(bt::Vector, funcsyms) # Identify the calling line found = false - lkup = StackTraces.UNKNOWN - found_frame = Ptr{Cvoid}(0) - for frame in bt - lkups = StackTraces.lookup(frame) - for outer lkup in lkups + for ip in bt + ip isa Base.InterpreterIP || (ip -= 1) # convert from return stack to call stack (for inlining info) + lkups = StackTraces.lookupat(ip) + for lkup in lkups if lkup == StackTraces.UNKNOWN || lkup.from_c continue end if found - found_frame = frame - @goto found + return ip, lkup end found = lkup.func in funcsyms # look for constructor type name if !found && lkup.linfo isa Core.MethodInstance li = lkup.linfo ft = ccall(:jl_first_argument_datatype, Any, (Any,), li.def.sig) - if isa(ft,DataType) && ft.name === Type.body.name + if isa(ft, DataType) && ft.name === Type.body.name ft = unwrap_unionall(ft.parameters[1]) - found = (isa(ft,DataType) && ft.name.name in funcsyms) + found = (isa(ft, DataType) && ft.name.name in funcsyms) end end end end - return found_frame, StackTraces.UNKNOWN - @label found - return found_frame, lkup + return C_NULL, StackTraces.UNKNOWN end deprecate(m::Module, s::Symbol, flag=1) = ccall(:jl_deprecate_binding, Cvoid, (Any, Any, Cint), m, s, flag) diff --git a/base/errorshow.jl b/base/errorshow.jl index c9062eb0c5cb7..61ec32faee776 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -522,7 +522,8 @@ function show_reduced_backtrace(io::IO, t::Vector, with_prefix::Bool) i = frame_counter j = p while i < length(t) && t[i] == t[j] - i+=1 ; j+=1 + i += 1 + j += 1 end if j >= frame_counter-1 #= At least one cycle repeated =# @@ -590,6 +591,7 @@ function show_backtrace(io::IO, t::Vector) end function show_backtrace(io::IO, t::Vector{Any}) + # t is a pre-processed backtrace (ref #12856) if length(t) < BIG_STACKTRACE_SIZE try invokelatest(update_stackframes_callback[], t) catch end for entry in t @@ -605,21 +607,31 @@ function process_backtrace(t::Vector, limit::Int=typemax(Int); skipC = true) last_frame = StackTraces.UNKNOWN count = 0 ret = Any[] - for i = eachindex(t) - lkups = StackTraces.lookup(t[i]) + for i in eachindex(t) + lkups = t[i] + if lkups isa StackFrame + lkups = [lkups] + elseif lkups isa Base.InterpreterIP + lkups = StackTraces.lookupat(lkups) + else + lkups = StackTraces.lookupat(lkups - 1) + end for lkup in lkups if lkup === StackTraces.UNKNOWN continue end - if lkup.from_c && skipC; continue; end - if i == 1 && lkup.func == :error; continue; end + if lkup.from_c && skipC + continue + end count += 1 - if count > limit; break; end + if count > limit + break + end if lkup.file != last_frame.file || lkup.line != last_frame.line || lkup.func != last_frame.func || lkup.linfo !== lkup.linfo if n > 0 - push!(ret, (last_frame,n)) + push!(ret, (last_frame, n)) end n = 1 last_frame = lkup @@ -627,9 +639,10 @@ function process_backtrace(t::Vector, limit::Int=typemax(Int); skipC = true) n += 1 end end + count > limit && break end if n > 0 - push!(ret, (last_frame,n)) + push!(ret, (last_frame, n)) end return ret end diff --git a/base/stacktraces.jl b/base/stacktraces.jl index 0fca8a8d4fbcf..cd0c0d45462f8 100644 --- a/base/stacktraces.jl +++ b/base/stacktraces.jl @@ -7,8 +7,6 @@ module StackTraces import Base: hash, ==, show -using Base.Printf: @printf -using Base: something export StackTrace, StackFrame, stacktrace @@ -98,31 +96,28 @@ end """ - lookup(pointer::Union{Ptr{Cvoid}, UInt}) -> Vector{StackFrame} + lookupat(pointer::Union{Ptr{Cvoid}, UInt}) -> Vector{StackFrame} Given a pointer to an execution context (usually generated by a call to `backtrace`), looks up stack frame context information. Returns an array of frame information for all functions inlined at that point, innermost function first. """ -function lookup(pointer::Ptr{Cvoid}) - infos = ccall(:jl_lookup_code_address, Any, (Ptr{Cvoid}, Cint), pointer - 1, false) - isempty(infos) && return [StackFrame(empty_sym, empty_sym, -1, nothing, true, false, convert(UInt64, pointer))] +function lookupat(pointer::Ptr{Cvoid}) + infos = ccall(:jl_lookup_code_address, Any, (Ptr{Cvoid}, Cint), pointer, false) + pointer = convert(UInt64, pointer) + isempty(infos) && return [StackFrame(empty_sym, empty_sym, -1, nothing, true, false, pointer)] # this is equal to UNKNOWN res = Vector{StackFrame}(undef, length(infos)) for i in 1:length(infos) info = infos[i] - @assert(length(info) == 7) - res[i] = StackFrame(info[1], info[2], info[3], info[4], info[5], info[6], info[7]) + @assert(length(info) == 6) + res[i] = StackFrame(info[1], info[2], info[3], info[4], info[5], info[6], pointer) end return res end -lookup(pointer::UInt) = lookup(convert(Ptr{Cvoid}, pointer)) - const top_level_scope_sym = Symbol("top-level scope") -using Base.Meta -is_loc_meta(expr, kind) = isexpr(expr, :meta) && length(expr.args) >= 1 && expr.args[1] === kind -function lookup(ip::Base.InterpreterIP) +function lookupat(ip::Base.InterpreterIP) if ip.code isa Core.MethodInstance && ip.code.def isa Method codeinfo = ip.code.uninferred func = ip.code.def.name @@ -154,10 +149,6 @@ function lookup(ip::Base.InterpreterIP) return scopes end -# allow lookup on already-looked-up data for easier handling of pre-processed frames -lookup(s::StackFrame) = StackFrame[s] -lookup(s::Tuple{StackFrame,Int}) = StackFrame[s[1]] - """ backtrace() @@ -195,26 +186,28 @@ doesn't return C functions, but this can be enabled.) When called without specif trace, `stacktrace` first calls `backtrace`. """ function stacktrace(trace::Vector{<:Union{Base.InterpreterIP,Ptr{Cvoid}}}, c_funcs::Bool=false) - stack = vcat(StackTrace(), map(lookup, trace)...)::StackTrace - - # Remove frames that come from C calls. - if !c_funcs - filter!(frame -> !frame.from_c, stack) + stack = StackTrace() + for ip in trace + ip isa Base.InterpreterIP || (ip -= 1) # convert from return stack to call stack (for inlining info) + for frame in lookupat(ip) + # Skip frames that come from C calls. + if c_funcs || !frame.from_c + push!(stack, frame) + end + end end + return stack +end +function stacktrace(c_funcs::Bool=false) + stack = stacktrace(backtrace(), c_funcs) # Remove frame for this function (and any functions called by this function). remove_frames!(stack, :stacktrace) - - # is there a better way? the func symbol has a number suffix which changes. - # it's possible that no test is needed and we could just popfirst! all the time. - # this line was added to PR #16213 because otherwise stacktrace() != stacktrace(false). - # not sure why. possibly b/c of re-ordering of base/sysimg.jl - !isempty(stack) && startswith(string(stack[1].func),"jlcall_stacktrace") && popfirst!(stack) - stack + # also remove all of the non-Julia functions that led up to this point (if that list is non-empty) + c_funcs && deleteat!(stack, 1:(something(findfirst(frame -> !frame.from_c, stack), 1) - 1)) + return stack end -stacktrace(c_funcs::Bool=false) = stacktrace(backtrace(), c_funcs) - """ remove_frames!(stack::StackTrace, name::Symbol) @@ -224,12 +217,12 @@ all frames above the specified function). Primarily used to remove `StackTraces` from the `StackTrace` prior to returning it. """ function remove_frames!(stack::StackTrace, name::Symbol) - splice!(stack, 1:something(findlast(frame -> frame.func == name, stack), 0)) + deleteat!(stack, 1:something(findlast(frame -> frame.func == name, stack), 0)) return stack end function remove_frames!(stack::StackTrace, names::Vector{Symbol}) - splice!(stack, 1:something(findlast(frame -> frame.func in names, stack), 0)) + deleteat!(stack, 1:something(findlast(frame -> frame.func in names, stack), 0)) return stack end @@ -248,7 +241,7 @@ is_top_level_frame(f::StackFrame) = f.linfo isa Core.CodeInfo || (f.linfo === no function show_spec_linfo(io::IO, frame::StackFrame) if frame.linfo === nothing if frame.func === empty_sym - @printf(io, "ip:%#x", frame.pointer) + print(io, "ip:0x", string(frame.pointer, base=16)) elseif frame.func === top_level_scope_sym print(io, "top-level scope") else diff --git a/doc/src/base/stacktraces.md b/doc/src/base/stacktraces.md index 3286ee204dc74..ce3931e9b0512 100644 --- a/doc/src/base/stacktraces.md +++ b/doc/src/base/stacktraces.md @@ -7,9 +7,9 @@ Base.StackTraces.stacktrace ``` The following methods and types in `Base.StackTraces` are not exported and need to be called e.g. -as `StackTraces.lookup(ptr)`. +as `StackTraces.lookupat(ptr)`. ```@docs -Base.StackTraces.lookup +Base.StackTraces.lookupat Base.StackTraces.remove_frames! ``` diff --git a/doc/src/manual/stacktraces.md b/doc/src/manual/stacktraces.md index 8deff082140f1..bfbe1ceb58c50 100644 --- a/doc/src/manual/stacktraces.md +++ b/doc/src/manual/stacktraces.md @@ -300,16 +300,15 @@ julia> stacktrace(trace, true) ``` Individual pointers returned by [`backtrace`](@ref) can be translated into [`StackTraces.StackFrame`](@ref) -s by passing them into [`StackTraces.lookup`](@ref): +s by passing them into [`StackTraces.lookupat`](@ref): ```julia-repl julia> pointer = backtrace()[1]; -julia> frame = StackTraces.lookup(pointer) +julia> frame = StackTraces.lookupat(pointer - 1) 1-element Array{Base.StackTraces.StackFrame,1}: jl_apply_generic at gf.c:2167 julia> println("The top frame is from $(frame[1].func)!") The top frame is from jl_apply_generic! ``` - diff --git a/src/julia_internal.h b/src/julia_internal.h index b29030920b265..8a48fed9b3f59 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -637,9 +637,9 @@ typedef int bt_cursor_t; // Special marker in backtrace data for encoding interpreter frames #define JL_BT_INTERP_FRAME (((uintptr_t)0)-1) size_t rec_backtrace(uintptr_t *data, size_t maxsize) JL_NOTSAFEPOINT; -size_t rec_backtrace_ctx(uintptr_t *data, size_t maxsize, bt_context_t *ctx) JL_NOTSAFEPOINT; +size_t rec_backtrace_ctx(uintptr_t *data, size_t maxsize, bt_context_t *ctx, int add_interp_frames) JL_NOTSAFEPOINT; #ifdef LIBOSXUNWIND -size_t rec_backtrace_ctx_dwarf(uintptr_t *data, size_t maxsize, bt_context_t *ctx); +size_t rec_backtrace_ctx_dwarf(uintptr_t *data, size_t maxsize, bt_context_t *ctx, int add_interp_frames) JL_NOTSAFEPOINT; #endif JL_DLLEXPORT void jl_get_backtrace(jl_array_t **bt, jl_array_t **bt2); void jl_critical_error(int sig, bt_context_t *context, uintptr_t *bt_data, size_t *bt_size); diff --git a/src/signal-handling.c b/src/signal-handling.c index ebaa18b12db36..73ca73e22d708 100644 --- a/src/signal-handling.c +++ b/src/signal-handling.c @@ -231,7 +231,7 @@ void jl_critical_error(int sig, bt_context_t *context, uintptr_t *bt_data, size_ jl_safe_printf("\nsignal (%d): %s\n", sig, strsignal(sig)); jl_safe_printf("in expression starting at %s:%d\n", jl_filename, jl_lineno); if (context) - *bt_size = n = rec_backtrace_ctx(bt_data, JL_MAX_BT_SIZE, context); + *bt_size = n = rec_backtrace_ctx(bt_data, JL_MAX_BT_SIZE, context, 0); for (i = 0; i < n; i++) jl_gdblookup(bt_data[i] - 1); gc_debug_print_status(); diff --git a/src/signals-mach.c b/src/signals-mach.c index 03df4123280b5..09caa0a874ec4 100644 --- a/src/signals-mach.c +++ b/src/signals-mach.c @@ -142,13 +142,13 @@ static void jl_throw_in_thread(int tid, mach_port_t thread, jl_value_t *exceptio if (!ptls2->safe_restore) { assert(exception); ptls2->bt_size = rec_backtrace_ctx(ptls2->bt_data, JL_MAX_BT_SIZE, - (bt_context_t*)&state); + (bt_context_t*)&state, 1); ptls2->sig_exception = exception; } jl_call_in_state(ptls2, &state, &jl_sig_throw); ret = thread_set_state(thread, x86_THREAD_STATE64, (thread_state_t)&state, count); - HANDLE_MACH_ERROR("thread_set_state",ret); + HANDLE_MACH_ERROR("thread_set_state", ret); } //exc_server uses dlsym to find symbol @@ -343,7 +343,7 @@ static void jl_exit_thread0(int exitstate) jl_call_in_state(ptls2, &state, (void (*)(void))exit_func); ret = thread_set_state(thread, x86_THREAD_STATE64, (thread_state_t)&state, count); - HANDLE_MACH_ERROR("thread_set_state",ret); + HANDLE_MACH_ERROR("thread_set_state", ret); ret = thread_resume(thread); HANDLE_MACH_ERROR("thread_resume", ret); @@ -427,52 +427,50 @@ void *mach_profile_listener(void *arg) unw_context_t *uc; jl_thread_suspend_and_get_state(i, &uc); - + if (running) { #ifdef LIBOSXUNWIND - /* - * Unfortunately compact unwind info is incorrectly generated for quite a number of - * libraries by quite a large number of compilers. We can fall back to DWARF unwind info - * in some cases, but in quite a number of cases (especially libraries not compiled in debug - * mode, only the compact unwind info may be available). Even more unfortunately, there is no - * way to detect such bogus compact unwind info (other than noticing the resulting segfault). - * What we do here is ugly, but necessary until the compact unwind info situation improves. - * We try to use the compact unwind info and if that results in a segfault, we retry with DWARF info. - * Note that in a small number of cases this may result in bogus stack traces, but at least the topmost - * entry will always be correct, and the number of cases in which this is an issue is rather small. - * Other than that, this implementation is not incorrect as the other thread is paused while we are profiling - * and during stack unwinding we only ever read memory, but never write it. - */ - - forceDwarf = 0; - unw_getcontext(&profiler_uc); // will resume from this point if the next lines segfault at any point - - if (forceDwarf == 0) { - // Save the backtrace - bt_size_cur += rec_backtrace_ctx((uintptr_t*)bt_data_prof + bt_size_cur, bt_size_max - bt_size_cur - 1, uc); - } - else if (forceDwarf == 1) { - bt_size_cur += rec_backtrace_ctx_dwarf((uintptr_t*)bt_data_prof + bt_size_cur, bt_size_max - bt_size_cur - 1, uc); - } - else if (forceDwarf == -1) { - jl_safe_printf("WARNING: profiler attempt to access an invalid memory location\n"); - } - - forceDwarf = -2; + /* + * Unfortunately compact unwind info is incorrectly generated for quite a number of + * libraries by quite a large number of compilers. We can fall back to DWARF unwind info + * in some cases, but in quite a number of cases (especially libraries not compiled in debug + * mode, only the compact unwind info may be available). Even more unfortunately, there is no + * way to detect such bogus compact unwind info (other than noticing the resulting segfault). + * What we do here is ugly, but necessary until the compact unwind info situation improves. + * We try to use the compact unwind info and if that results in a segfault, we retry with DWARF info. + * Note that in a small number of cases this may result in bogus stack traces, but at least the topmost + * entry will always be correct, and the number of cases in which this is an issue is rather small. + * Other than that, this implementation is not incorrect as the other thread is paused while we are profiling + * and during stack unwinding we only ever read memory, but never write it. + */ + + forceDwarf = 0; + unw_getcontext(&profiler_uc); // will resume from this point if the next lines segfault at any point + + if (forceDwarf == 0) { + // Save the backtrace + bt_size_cur += rec_backtrace_ctx((uintptr_t*)bt_data_prof + bt_size_cur, bt_size_max - bt_size_cur - 1, uc, 0); + } + else if (forceDwarf == 1) { + bt_size_cur += rec_backtrace_ctx_dwarf((uintptr_t*)bt_data_prof + bt_size_cur, bt_size_max - bt_size_cur - 1, uc, 0); + } + else if (forceDwarf == -1) { + jl_safe_printf("WARNING: profiler attempt to access an invalid memory location\n"); + } + + forceDwarf = -2; #else - bt_size_cur += rec_backtrace_ctx((uintptr_t*)bt_data_prof + bt_size_cur, bt_size_max - bt_size_cur - 1, uc); + bt_size_cur += rec_backtrace_ctx((uintptr_t*)bt_data_prof + bt_size_cur, bt_size_max - bt_size_cur - 1, uc, 0); #endif - // Mark the end of this block with 0 - bt_data_prof[bt_size_cur++] = 0; - - // We're done! Resume the thread. - jl_thread_resume(i, 0); + // Mark the end of this block with 0 + bt_data_prof[bt_size_cur++] = 0; - if (running) { // Reset the alarm kern_return_t ret = clock_alarm(clk, TIME_RELATIVE, timerprof, profile_port); HANDLE_MACH_ERROR("clock_alarm", ret) } + // We're done! Resume the thread. + jl_thread_resume(i, 0); } } } diff --git a/src/signals-unix.c b/src/signals-unix.c index 82434558fe912..fa234c337714c 100644 --- a/src/signals-unix.c +++ b/src/signals-unix.c @@ -176,7 +176,7 @@ static void jl_throw_in_ctx(jl_ptls_t ptls, jl_value_t *e, int sig, void *sigctx { if (!ptls->safe_restore) ptls->bt_size = rec_backtrace_ctx(ptls->bt_data, JL_MAX_BT_SIZE, - jl_to_bt_context(sigctx)); + jl_to_bt_context(sigctx), 1); ptls->sig_exception = e; jl_call_in_ctx(ptls, &jl_sig_throw, sig, sigctx); } @@ -668,7 +668,7 @@ static void *signal_listener(void *arg) if (critical) { bt_size += rec_backtrace_ctx(bt_data + bt_size, JL_MAX_BT_SIZE / jl_n_threads - 1, - signal_context); + signal_context, 0); bt_data[bt_size++] = 0; } @@ -687,7 +687,7 @@ static void *signal_listener(void *arg) } else { // Get backtrace data bt_size_cur += rec_backtrace_ctx((uintptr_t*)bt_data_prof + bt_size_cur, - bt_size_max - bt_size_cur - 1, signal_context); + bt_size_max - bt_size_cur - 1, signal_context, 0); } ptls->safe_restore = old_buf; diff --git a/src/signals-win.c b/src/signals-win.c index 58b179677eb26..49ea19780c0b5 100644 --- a/src/signals-win.c +++ b/src/signals-win.c @@ -104,7 +104,7 @@ static void JL_NORETURN start_backtrace_fiber(void) { jl_ptls_t ptls = jl_get_ptls_states(); // collect the backtrace - ptls->bt_size = rec_backtrace_ctx(ptls->bt_data, JL_MAX_BT_SIZE, error_ctx); + ptls->bt_size = rec_backtrace_ctx(ptls->bt_data, JL_MAX_BT_SIZE, error_ctx, 1); // switch back to the execution fiber jl_setcontext(&error_return_fiber); abort(); @@ -130,7 +130,7 @@ void jl_throw_in_ctx(jl_value_t *excpt, PCONTEXT ctxThread) assert(excpt != NULL); ptls->bt_size = 0; if (excpt != jl_stackovf_exception) { - ptls->bt_size = rec_backtrace_ctx(ptls->bt_data, JL_MAX_BT_SIZE, ctxThread); + ptls->bt_size = rec_backtrace_ctx(ptls->bt_data, JL_MAX_BT_SIZE, ctxThread, 1); } else if (have_backtrace_fiber) { error_ctx = ctxThread; @@ -321,35 +321,37 @@ static DWORD WINAPI profile_bt( LPVOID lparam ) // Note: illegal to use jl_* functions from this thread TIMECAPS tc; - if (MMSYSERR_NOERROR!=timeGetDevCaps(&tc, sizeof(tc))) { - fputs("failed to get timer resolution",stderr); + if (MMSYSERR_NOERROR != timeGetDevCaps(&tc, sizeof(tc))) { + fputs("failed to get timer resolution", stderr); hBtThread = 0; return 0; } while (1) { - if (running && bt_size_cur < bt_size_max) { + if (bt_size_cur < bt_size_max) { DWORD timeout = nsecprof/GIGA; - timeout = min(max(timeout,tc.wPeriodMin*2),tc.wPeriodMax/2); + timeout = min(max(timeout, tc.wPeriodMin*2), tc.wPeriodMax/2); Sleep(timeout); if ((DWORD)-1 == SuspendThread(hMainThread)) { - fputs("failed to suspend main thread. aborting profiling.",stderr); + fputs("failed to suspend main thread. aborting profiling.", stderr); break; } - CONTEXT ctxThread; - memset(&ctxThread, 0, sizeof(CONTEXT)); - ctxThread.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; - if (!GetThreadContext(hMainThread, &ctxThread)) { - fputs("failed to get context from main thread. aborting profiling.",stderr); - break; + if (running) { + CONTEXT ctxThread; + memset(&ctxThread, 0, sizeof(CONTEXT)); + ctxThread.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; + if (!GetThreadContext(hMainThread, &ctxThread)) { + fputs("failed to get context from main thread. aborting profiling.", stderr); + break; + } + // Get backtrace data + bt_size_cur += rec_backtrace_ctx((uintptr_t*)bt_data_prof + bt_size_cur, + bt_size_max - bt_size_cur - 1, &ctxThread, 0); + // Mark the end of this block with 0 + bt_data_prof[bt_size_cur] = 0; + bt_size_cur++; } - // Get backtrace data - bt_size_cur += rec_backtrace_ctx((uintptr_t*)bt_data_prof + bt_size_cur, - bt_size_max - bt_size_cur - 1, &ctxThread); - // Mark the end of this block with 0 - bt_data_prof[bt_size_cur] = 0; - bt_size_cur++; if ((DWORD)-1 == ResumeThread(hMainThread)) { - fputs("failed to resume main thread! aborting.",stderr); + fputs("failed to resume main thread! aborting.", stderr); gc_debug_critical_error(); abort(); } @@ -369,11 +371,11 @@ JL_DLLEXPORT int jl_profile_start_timer(void) hBtThread = CreateThread( NULL, // default security attributes 0, // use default stack size - profile_bt, // thread function name + profile_bt, // thread function name 0, // argument to thread function 0, // use default creation flags 0); // returns the thread identifier - (void)SetThreadPriority(hBtThread,THREAD_PRIORITY_ABOVE_NORMAL); + (void)SetThreadPriority(hBtThread, THREAD_PRIORITY_ABOVE_NORMAL); } else { if ((DWORD)-1 == ResumeThread(hBtThread)) { diff --git a/src/stackwalk.c b/src/stackwalk.c index 7db66e2150275..7dad4e0436a6c 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -77,13 +77,13 @@ size_t jl_unw_stepn(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, size_t ma } size_t rec_backtrace_ctx(uintptr_t *data, size_t maxsize, - bt_context_t *context) + bt_context_t *context, int add_interp_frames) { size_t n = 0; bt_cursor_t cursor; if (!jl_unw_init(&cursor, context)) return 0; - n = jl_unw_stepn(&cursor, data, NULL, maxsize, 1); + n = jl_unw_stepn(&cursor, data, NULL, maxsize, add_interp_frames); return n > maxsize ? maxsize : n; } @@ -92,7 +92,7 @@ size_t rec_backtrace(uintptr_t *data, size_t maxsize) bt_context_t context; memset(&context, 0, sizeof(context)); jl_unw_get(&context); - return rec_backtrace_ctx(data, maxsize, &context); + return rec_backtrace_ctx(data, maxsize, &context, 1); } static jl_value_t *array_ptr_void_type JL_ALWAYS_LEAFTYPE = NULL; @@ -427,13 +427,13 @@ int jl_unw_init_dwarf(bt_cursor_t *cursor, bt_context_t *uc) return unw_init_local_dwarf(cursor, uc) != 0; } size_t rec_backtrace_ctx_dwarf(uintptr_t *data, size_t maxsize, - bt_context_t *context) + bt_context_t *context, int add_interp_frames) { size_t n; bt_cursor_t cursor; if (!jl_unw_init_dwarf(&cursor, context)) return 0; - n = jl_unw_stepn(&cursor, data, NULL, maxsize, 1); + n = jl_unw_stepn(&cursor, data, NULL, maxsize, add_interp_frames); return n > maxsize ? maxsize : n; } #endif @@ -462,7 +462,7 @@ JL_DLLEXPORT jl_value_t *jl_lookup_code_address(void *ip, int skipC) JL_GC_PUSH1(&rs); for (int i = 0; i < n; i++) { jl_frame_t frame = frames[i]; - jl_value_t *r = (jl_value_t*)jl_alloc_svec(7); + jl_value_t *r = (jl_value_t*)jl_alloc_svec(6); jl_svecset(rs, i, r); if (frame.func_name) jl_svecset(r, 0, jl_symbol(frame.func_name)); @@ -478,7 +478,6 @@ JL_DLLEXPORT jl_value_t *jl_lookup_code_address(void *ip, int skipC) jl_svecset(r, 3, frame.linfo != NULL ? (jl_value_t*)frame.linfo : jl_nothing); jl_svecset(r, 4, jl_box_bool(frame.fromC)); jl_svecset(r, 5, jl_box_bool(frame.inlined)); - jl_svecset(r, 6, jl_box_voidpointer(ip)); } free(frames); JL_GC_POP(); diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index df645b8e97c5c..124be6f5a7ef0 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -5,7 +5,11 @@ Profiling support, main entry point is the [`@profile`](@ref) macro. """ module Profile -import Base.StackTraces: lookup, UNKNOWN, show_spec_linfo, StackFrame +import Base.StackTraces: lookupat, UNKNOWN, show_spec_linfo, StackFrame + +# deprecated functions: use `getdict` instead +lookup(ip::UInt) = lookupat(convert(Ptr{Cvoid}, ip) - 1) +lookup(ip::Ptr{Cvoid}) = lookupat(ip - 1) export @profile @@ -85,14 +89,16 @@ struct ProfileFormat sortedby::Symbol combine::Bool C::Bool + recur::Symbol function ProfileFormat(; C = false, combine = true, maxdepth::Int = typemax(Int), mincount::Int = 0, noisefloor = 0, - sortedby::Symbol = :filefuncline) - return new(maxdepth, mincount, noisefloor, sortedby, combine, C) + sortedby::Symbol = :filefuncline, + recur::Symbol = :off) + return new(maxdepth, mincount, noisefloor, sortedby, combine, C, recur) end end @@ -115,37 +121,50 @@ The keyword arguments can be any combination of: - `maxdepth` -- Limits the depth higher than `maxdepth` in the `:tree` format. - `sortedby` -- Controls the order in `:flat` format. `:filefuncline` (default) sorts by the source - line, whereas `:count` sorts in order of number of collected samples. + line, `:count` sorts in order of number of collected samples, and `:overhead` sorts by the number of samples + incurred by each function by itself. - `noisefloor` -- Limits frames that exceed the heuristic noise floor of the sample (only applies to format `:tree`). A suggested value to try for this is 2.0 (the default is 0). This parameter hides samples for which `n <= noisefloor * √N`, where `n` is the number of samples on this line, and `N` is the number of samples for the callee. - `mincount` -- Limits the printout to only those lines with at least `mincount` occurrences. + + - `recur` -- Controls the recursion handling in `:tree` format. `:off` (default) prints the tree as normal. `:flat` instead + compresses any recursion (by ip), showing the approximate effect of converting any self-recursion into an iterator. + `:flatc` does the same but also includes collapsing of C frames (may do odd things around `jl_apply`). """ -function print(io::IO, data::Vector{<:Unsigned} = fetch(), lidict::Union{LineInfoDict, LineInfoFlatDict} = getdict(data); +function print(io::IO, + data::Vector{<:Unsigned} = fetch(), + lidict::Union{LineInfoDict, LineInfoFlatDict} = getdict(data) + ; format = :tree, C = false, combine = true, maxdepth::Int = typemax(Int), mincount::Int = 0, noisefloor = 0, - sortedby::Symbol = :filefuncline) - print(io, data, lidict, ProfileFormat(C = C, + sortedby::Symbol = :filefuncline, + recur::Symbol = :off) + print(io, data, lidict, ProfileFormat( + C = C, combine = combine, maxdepth = maxdepth, mincount = mincount, noisefloor = noisefloor, - sortedby = sortedby), + sortedby = sortedby, + recur = recur), format) end function print(io::IO, data::Vector{<:Unsigned}, lidict::Union{LineInfoDict, LineInfoFlatDict}, fmt::ProfileFormat, format::Symbol) cols::Int = Base.displaysize(io)[2] data = convert(Vector{UInt64}, data) - if format == :tree + fmt.recur ∈ (:off, :flat, :flatc) || throw(ArgumentError("recur value not recognized")) + if format === :tree tree(io, data, lidict, cols, fmt) - elseif format == :flat + elseif format === :flat + fmt.recur === :off || throw(ArgumentError("format flat only implements recur=:off")) flat(io, data, lidict, cols, fmt) else throw(ArgumentError("output format $(repr(format)) not recognized")) @@ -174,12 +193,15 @@ allows you to save profiling results for future analysis. """ function retrieve() data = fetch() - return (copy(data), getdict(data)) + return (data, getdict(data)) end function getdict(data::Vector{UInt}) - uip = unique(data) - return LineInfoDict(UInt64(ip)=>lookup(ip) for ip in uip) + dict = LineInfoDict() + for ip in data + get!(() -> lookupat(convert(Ptr{Cvoid}, ip)), dict, UInt64(ip)) + end + return dict end """ @@ -299,104 +321,113 @@ error_codes = Dict( """ fetch() -> data -Returns a reference to the internal buffer of backtraces. Note that subsequent operations, -like [`clear`](@ref), can affect `data` unless you first make a copy. Note that the +Returns a copy of the buffer of profile backtraces. Note that the values in `data` have meaning only on this machine in the current session, because it depends on the exact memory addresses used in JIT-compiling. This function is primarily for internal use; [`retrieve`](@ref) may be a better choice for most users. """ function fetch() - len = len_data() maxlen = maxlen_data() + len = len_data() if (len == maxlen) @warn """The profile data buffer is full; profiling probably terminated before your program finished. To profile for longer runs, call `Profile.init()` with a larger buffer and/or larger delay.""" end - return unsafe_wrap(Array, get_data_pointer(), (len,)) + data = Vector{UInt}(undef, len) + GC.@preserve data unsafe_copyto!(pointer(data), get_data_pointer(), len) + # post-process the data to convert from a return-stack to a call-stack + first = true + for i = 1:length(data) + if data[i] == 0 + first = true + elseif first + first = false + else + data[i] -= 1 + end + end + return data end ## Print as a flat list -# Counts the number of times each line appears, at any nesting level -function count_flat(data::Vector{UInt64}) - linecount = Dict{UInt64, Int}() +# Counts the number of times each line appears, at any nesting level and at the topmost level +# Merging multiple equivalent entries and recursive calls +function parse_flat(::Type{T}, data::Vector{UInt64}, lidict::Union{LineInfoDict, LineInfoFlatDict}, C::Bool) where {T} + lilist = StackFrame[] + n = Int[] + m = Int[] + lilist_idx = Dict{T, Int}() + recursive = Set{T}() + first = true + totalshots = 0 for ip in data - if ip != 0 - linecount[ip] = get(linecount, ip, 0) + 1 + if ip == 0 + totalshots += 1 + empty!(recursive) + first = true + continue end - end - iplist = Vector{UInt64}() - n = Vector{Int}() - for (k, v) in linecount - push!(iplist, k) - push!(n, v) - end - return (iplist, n) -end - -function parse_flat(iplist::Vector{UInt64}, n::Vector{Int}, lidict::Union{LineInfoDict, LineInfoFlatDict}, C::Bool) - # Convert instruction pointers to names & line numbers - lilist = StackFrame[] - nlist = Int[] - for (ip, count) in zip(iplist, n) frames = lidict[ip] nframes = (frames isa Vector ? length(frames) : 1) - # add all the inlining frames - for i = nframes:-1:1 + for i = 1:nframes frame = (frames isa Vector ? frames[i] : frames) - # Keep only the interpretable ones - # The ones with no line number might appear multiple times in a single - # backtrace, giving the wrong impression about the total number of backtraces. - # Delete them too. - if frame != UNKNOWN && frame.line != 0 && (C || !frame.from_c) + !C && frame.from_c && continue + key = (T === UInt64 ? ip : frame) + idx = get!(lilist_idx, key, length(lilist) + 1) + if idx > length(lilist) + push!(recursive, key) push!(lilist, frame) - push!(nlist, count) + push!(n, 1) + push!(m, 0) + elseif !(key in recursive) + push!(recursive, key) + n[idx] += 1 + end + if first + m[idx] += 1 + first = false end end end - return (lilist, nlist) + @assert length(lilist) == length(n) == length(m) == length(lilist_idx) + return (lilist, n, m, totalshots) end function flat(io::IO, data::Vector{UInt64}, lidict::Union{LineInfoDict, LineInfoFlatDict}, cols::Int, fmt::ProfileFormat) - iplist, n = count_flat(data) - lilist, n = parse_flat(iplist, n, lidict, fmt.C) - if isempty(n) + lilist, n, m, totalshots = parse_flat(fmt.combine ? StackFrame : UInt64, data, lidict, fmt.C) + if isempty(lilist) warning_empty() return end - print_flat(io, lilist, n, cols, fmt) + if false # optional: drop the "non-interpretable" ones + keep = map(frame -> frame != UNKNOWN && frame.line != 0, lilist) + lilist = lilist[keep] + n = n[keep] + m = m[keep] + end + print_flat(io, lilist, n, m, cols, fmt) + Base.println(io, "Total snapshots: ", totalshots) nothing end -function print_flat(io::IO, lilist::Vector{StackFrame}, n::Vector{Int}, +function print_flat(io::IO, lilist::Vector{StackFrame}, + n::Vector{Int}, m::Vector{Int}, cols::Int, fmt::ProfileFormat) - p = liperm(lilist) - lilist = lilist[p] - n = n[p] - if fmt.combine - # now that lilist is sorted by li, - # combine adjacent entries that are equivlent - j = 1 - for i = 2:length(lilist) - if lilist[i] == lilist[j] - n[j] += n[i] - n[i] = 0 - else - j = i - end - end - keep = n .> 0 - n = n[keep] - lilist = lilist[keep] - end if fmt.sortedby == :count p = sortperm(n) - n = n[p] - lilist = lilist[p] + elseif fmt.sortedby == :overhead + p = sortperm(m) + else + p = liperm(lilist) end + lilist = lilist[p] + n = n[p] + m = m[p] wcounts = max(6, ndigits(maximum(n))) - maxline = 0 + wself = max(9, ndigits(maximum(m))) + maxline = 1 maxfile = 6 maxfunc = 10 for li in lilist @@ -405,28 +436,40 @@ function print_flat(io::IO, lilist::Vector{StackFrame}, n::Vector{Int}, maxfunc = max(maxfunc, length(string(li.func))) end wline = max(5, ndigits(maxline)) - ntext = cols - wcounts - wline - 3 - maxfunc += 25 + ntext = max(20, cols - wcounts - wself - wline - 3) + maxfunc += 25 # for type signatures if maxfile + maxfunc <= ntext wfile = maxfile wfunc = maxfunc else - wfile = floor(Integer, 2*ntext/5) - wfunc = floor(Integer, 3*ntext/5) + wfile = 2*ntext÷5 + wfunc = 3*ntext÷5 end - println(io, lpad("Count", wcounts, " "), " ", rpad("File", wfile, " "), " ", - lpad("Line", wline, " "), " ", rpad("Function", wfunc, " ")) + println(io, lpad("Count", wcounts, " "), " ", lpad("Overhead", wself, " "), " ", + rpad("File", wfile, " "), " ", lpad("Line", wline, " "), " ", rpad("Function", wfunc, " ")) for i = 1:length(n) n[i] < fmt.mincount && continue li = lilist[i] Base.print(io, lpad(string(n[i]), wcounts, " "), " ") - Base.print(io, rpad(rtruncto(string(li.file), wfile), wfile, " "), " ") - Base.print(io, lpad(string(li.line), wline, " "), " ") - fname = string(li.func) - if !li.from_c && li.linfo !== nothing - fname = sprint(show_spec_linfo, li) + Base.print(io, lpad(string(m[i]), wself, " "), " ") + if li == UNKNOWN + if !fmt.combine && li.pointer != 0 + Base.print(io, "@0x", string(li.pointer, base=16)) + else + Base.print(io, "[any unknown stackframes]") + end + else + file = string(li.file) + isempty(file) && (file = "[unknown file]") + Base.print(io, rpad(rtruncto(file, wfile), wfile, " "), " ") + Base.print(io, lpad(li.line > 0 ? string(li.line) : "?", wline, " "), " ") + fname = string(li.func) + if !li.from_c && li.linfo !== nothing + fname = sprint(show_spec_linfo, li) + end + isempty(fname) && (fname = "[unknown function]") + Base.print(io, rpad(ltruncto(fname, wfunc), wfunc, " ")) end - Base.print(io, rpad(ltruncto(fname, wfunc), wfunc, " ")) println(io) end nothing @@ -435,13 +478,22 @@ end ## A tree representation tree_format_linewidth(x::StackFrame) = ndigits(x.line) + 6 -function tree_format(lilist::Vector{StackFrame}, counts::Vector{Int}, level::Int, cols::Int) +const indent_s = " ╎ "^10 +const indent_z = collect(eachindex(indent_s)) +function indent(depth::Int) + depth < 1 && return "" + depth <= length(indent_z) && return indent_s[1:indent_z[depth]] + div, rem = divrem(depth, length(indent_z)) + return (indent_s^div) * SubString(indent_s, 1, indent_z[rem]) +end + +function tree_format(lilist::Vector{StackFrame}, counts::Vector{Int}, level::Int, cols::Int, showpointer::Bool) nindent = min(cols>>1, level) ndigcounts = ndigits(maximum(counts)) ndigline = maximum([tree_format_linewidth(x) for x in lilist]) - ntext = cols - nindent - ndigcounts - ndigline - 5 - widthfile = floor(Integer, 0.4ntext) - widthfunc = floor(Integer, 0.6ntext) + ntext = max(20, cols - nindent - ndigcounts - ndigline - 5) + widthfile = 2*ntext÷5 + widthfunc = 3*ntext÷5 strs = Vector{String}(undef, length(lilist)) showextra = false if level > nindent @@ -452,7 +504,7 @@ function tree_format(lilist::Vector{StackFrame}, counts::Vector{Int}, level::Int for i = 1:length(lilist) li = lilist[i] if li != UNKNOWN - base = " "^nindent + base = nindent == 0 ? "" : indent(nindent - 1) * " " if showextra base = string(base, "+", nextra, " ") end @@ -464,9 +516,17 @@ function tree_format(lilist::Vector{StackFrame}, counts::Vector{Int}, level::Int string(li.pointer, base = 16, pad = 2*sizeof(Ptr{Cvoid})), ")") else - fname = string(li.func) if !li.from_c && li.linfo !== nothing fname = sprint(show_spec_linfo, li) + else + fname = string(li.func) + end + if showpointer + fname = string( + "0x", + string(li.pointer, base = 16, pad = 2*sizeof(Ptr{Cvoid})), + " ", + fname) end strs[i] = string(base, rpad(string(counts[i]), ndigcounts, " "), @@ -490,26 +550,63 @@ mutable struct StackFrameTree{T} # where T <: Union{UInt64, StackFrame} frame::StackFrame count::Int down::Dict{T, StackFrameTree{T}} - # construction helpers: + # construction workers: + recur::Bool builder_key::Vector{UInt64} builder_value::Vector{StackFrameTree{T}} up::StackFrameTree{T} - StackFrameTree{T}() where {T} = new(UNKNOWN, 0, Dict{T, StackFrameTree{T}}(), UInt64[], StackFrameTree{T}[]) + StackFrameTree{T}() where {T} = new(UNKNOWN, 0, Dict{T, StackFrameTree{T}}(), false, UInt64[], StackFrameTree{T}[]) end # turn a list of backtraces into a tree (implicitly separated by NULL markers) -function tree!(root::StackFrameTree{T}, all::Vector{UInt64}, lidict::Union{LineInfoFlatDict, LineInfoDict}, C::Bool) where {T} +function tree!(root::StackFrameTree{T}, all::Vector{UInt64}, lidict::Union{LineInfoFlatDict, LineInfoDict}, C::Bool, recur::Symbol) where {T} parent = root - for i in length(all):-1:1 + tops = Vector{StackFrameTree{T}}() + build = Vector{StackFrameTree{T}}() + startframe = length(all) + for i in startframe:-1:1 ip = all[i] if ip == 0 # sentinel value indicates the start of a new backtrace + empty!(build) + if recur !== :off + # We mark all visited nodes to so we'll only count those branches + # once for each backtrace. Reset that now for the next backtrace. + push!(tops, parent) + for top in tops + while top.recur + top.recur = false + top = top.up + end + end + empty!(tops) + end parent = root parent.count += 1 + startframe = i else + pushfirst!(build, parent) + if recur === :flat || recur == :flatc + # Rewind the `parent` tree back, if this exact ip was already present *higher* in the current tree + found = false + for j in 1:(startframe - i) + if ip == all[i + j] + if recur === :flat # if not flattening C frames, check that now + frames = lidict[ip] + frame = (frames isa Vector ? frames[1] : frames) + frame.from_c && break + end + push!(tops, parent) + parent = build[j] + found = true + break + end + end + found && continue + end builder_key = parent.builder_key builder_value = parent.builder_value - fastkey = searchsortedfirst(parent.builder_key, ip) + fastkey = searchsortedfirst(builder_key, ip) if fastkey < length(builder_key) && builder_key[fastkey] === ip # jump forward to the end of the inlining chain # avoiding an extra (slow) lookup of `ip` in `lidict` @@ -517,9 +614,12 @@ function tree!(root::StackFrameTree{T}, all::Vector{UInt64}, lidict::Union{LineI # note that we may even have this === parent (if we're ignoring this frame ip) this = builder_value[fastkey] let this = this - while this !== parent - this.count += 1 - this = this.up + if recur === :off || !this.recur + while this !== parent && !this.recur + this.count += 1 + this.recur = true + this = this.up + end end end parent = this @@ -533,12 +633,13 @@ function tree!(root::StackFrameTree{T}, all::Vector{UInt64}, lidict::Union{LineI frame = (frames isa Vector ? frames[i] : frames) !C && frame.from_c && continue key = (T === UInt64 ? ip : frame) - this = get!(parent.down, key) do - return StackFrameTree{T}() + this = get!(StackFrameTree{T}, parent.down, key) + if recur === :off || !this.recur + this.frame = frame + this.up = parent + this.count += 1 + this.recur = true end - this.frame = frame - this.up = parent - this.count += 1 parent = this end # record where the end of this chain is for this ip @@ -547,9 +648,10 @@ function tree!(root::StackFrameTree{T}, all::Vector{UInt64}, lidict::Union{LineI end end function cleanup!(node::StackFrameTree) - stack = StackFrameTree[node] + stack = [node] while !isempty(stack) node = pop!(stack) + node.recur = false empty!(node.builder_key) empty!(node.builder_value) append!(stack, values(node.down)) @@ -562,7 +664,7 @@ end # Print the stack frame tree starting at a particular root. Uses a worklist to # avoid stack overflows. -function tree(io::IO, bt::StackFrameTree, cols::Int, fmt::ProfileFormat) +function print_tree(io::IO, bt::StackFrameTree{T}, cols::Int, fmt::ProfileFormat) where T worklist = [(bt, 0, 0, "")] while !isempty(worklist) (bt, level, noisefloor, str) = popfirst!(worklist) @@ -574,7 +676,7 @@ function tree(io::IO, bt::StackFrameTree, cols::Int, fmt::ProfileFormat) lilist = collect(frame.frame for frame in nexts) counts = collect(frame.count for frame in nexts) # Generate the string for each line - strs = tree_format(lilist, counts, level, cols) + strs = tree_format(lilist, counts, level, cols, T === UInt64) # Recurse to the next level for i in reverse(liperm(lilist)) down = nexts[i] @@ -591,15 +693,16 @@ end function tree(io::IO, data::Vector{UInt64}, lidict::Union{LineInfoFlatDict, LineInfoDict}, cols::Int, fmt::ProfileFormat) if fmt.combine - root = tree!(StackFrameTree{StackFrame}(), data, lidict, fmt.C) + root = tree!(StackFrameTree{StackFrame}(), data, lidict, fmt.C, fmt.recur) else - root = tree!(StackFrameTree{UInt64}(), data, lidict, fmt.C) + root = tree!(StackFrameTree{UInt64}(), data, lidict, fmt.C, fmt.recur) end if isempty(root.down) warning_empty() return end - tree(io, root, cols, fmt) + print_tree(io, root, cols, fmt) + Base.println(io, "Total snapshots: ", root.count) nothing end diff --git a/stdlib/Profile/test/runtests.jl b/stdlib/Profile/test/runtests.jl index 319079e2940b1..24fa5ea086861 100644 --- a/stdlib/Profile/test/runtests.jl +++ b/stdlib/Profile/test/runtests.jl @@ -52,8 +52,16 @@ let iobuf = IOBuffer() truncate(iobuf, 0) Profile.print(iobuf, format=:flat, sortedby=:count) @test !isempty(String(take!(iobuf))) - Profile.clear() - @test isempty(Profile.fetch()) + Profile.print(iobuf, format=:tree, recur=:flat) + str = String(take!(iobuf)) + @test !isempty(str) + truncate(iobuf, 0) +end + +Profile.clear() +@test isempty(Profile.fetch()) + +let @test Profile.callers("\\") !== nothing @test Profile.callers(\) !== nothing # linerange with no filename provided should fail diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index 6bf697cda7dc1..5da586b4586aa 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -775,12 +775,12 @@ let exename = Base.julia_cmd() @test read(p, String) == "1\n" end # let exename -# issue #19864: +# issue #19864 mutable struct Error19864 <: Exception; end function test19864() @eval Base.showerror(io::IO, e::Error19864) = print(io, "correct19864") buf = IOBuffer() - fake_response = (Any[(Error19864(),[])],true) + fake_response = (Any[(Error19864(), Ptr{Cvoid}[])], true) REPL.print_response(buf, fake_response, false, false, nothing) return String(take!(buf)) end @@ -791,6 +791,7 @@ let io = IOBuffer() Base.display_error(io, try [][trues(6000)] + @assert false catch e e end, []) diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 98c0e8c983e8d..ce8097cbe5c64 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -45,7 +45,8 @@ const DISPLAY_FAILED = ( # Backtrace utility functions function ip_has_file_and_func(ip, file, funcs) - return any(fr -> (string(fr.file) == file && fr.func in funcs), StackTraces.lookup(ip)) + ip isa Base.InterpreterIP || (ip -= 1) # convert from return stack to call stack (for inlining info) + return any(fr -> (string(fr.file) == file && fr.func in funcs), StackTraces.lookupat(ip)) end function scrub_backtrace(bt) diff --git a/test/backtrace.jl b/test/backtrace.jl index 5fab345183fd4..41dd7f4b04a36 100644 --- a/test/backtrace.jl +++ b/test/backtrace.jl @@ -1,14 +1,19 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +lookup(ip) = StackTraces.lookupat(ip - 1) +lookup(ip::Base.InterpreterIP) = StackTraces.lookupat(ip) # TODO: Base.InterpreterIP should not need a special-case + # 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 = Base.StackTraces.lookup(bt[i]) - lkup[end].func == functionname && (return lkup) + lkup = lookup(bt[i]) + lkup[end].func == functionname && return lkup end + return StackTraces.StackFrame[] end # same-file inline @@ -91,14 +96,14 @@ let end module BackTraceTesting - using Test +import ..lookup @inline bt2() = backtrace() @inline bt1() = bt2() bt() = bt1() -lkup = map(StackTraces.lookup, bt()) +lkup = map(lookup, bt()) hasbt = hasbt2 = false for sfs in lkup for sf in sfs @@ -117,7 +122,7 @@ function btmacro() ret = @timed backtrace() ret[1] end -lkup = map(StackTraces.lookup, btmacro()) +lkup = map(lookup, btmacro()) hasme = hasbtmacro = false for sfs in lkup for sf in sfs @@ -142,7 +147,7 @@ bt = eval(quote catch_backtrace() end end) -lkup = map(StackTraces.lookup, bt) +lkup = map(lookup, bt) hastoplevel = false for sfs in lkup for sf in sfs @@ -170,7 +175,7 @@ let bt, found = false @testset begin bt = backtrace() end - for frame in map(StackTraces.lookup, bt) + for frame in map(lookup, bt) if frame[1].line == @__LINE__() - 3 && frame[1].file == Symbol(@__FILE__) found = true; break end @@ -182,7 +187,7 @@ end let bt, found = false @info "" bt = backtrace() - for frame in map(StackTraces.lookup, bt) + for frame in map(lookup, bt) if frame[1].line == @__LINE__() - 2 && frame[1].file == Symbol(@__FILE__) found = true; break end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index d82d4d96b7065..555cf5e8af6a2 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -894,7 +894,7 @@ function break_21369() i = 1 local fr while true - fr = Base.StackTraces.lookup(bt[i])[end] + fr = Base.StackTraces.lookupat(bt[i] - 1)[end] if !fr.from_c && fr.func !== :error break end diff --git a/test/meta.jl b/test/meta.jl index eafa1dd04b162..9f67a98252f02 100644 --- a/test/meta.jl +++ b/test/meta.jl @@ -32,7 +32,7 @@ h_noinlined() = g_noinlined() function foundfunc(bt, funcname) for b in bt - for lkup in StackTraces.lookup(b) + for lkup in StackTraces.lookupat(b - 1) if lkup.func == funcname return true end diff --git a/test/precompile.jl b/test/precompile.jl index 7ee30ee2006c8..b355e7914ee3b 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -374,12 +374,12 @@ try end """) @test_warn "ERROR: LoadError: break me\nStacktrace:\n [1] error" try - Base.require(Main, :FooBar2) - error("\"LoadError: break me\" test failed") - catch exc - isa(exc, ErrorException) || rethrow() - occursin("ERROR: LoadError: break me", exc.msg) && rethrow() - end + Base.require(Main, :FooBar2) + error("the \"break me\" test failed") + catch exc + isa(exc, ErrorException) || rethrow() + occursin("ERROR: LoadError: break me", exc.msg) && rethrow() + end # Test transitive dependency for #21266 FooBarT_file = joinpath(dir, "FooBarT.jl") diff --git a/test/stacktraces.jl b/test/stacktraces.jl index e2c6a49503a1a..836d300e7e1c3 100644 --- a/test/stacktraces.jl +++ b/test/stacktraces.jl @@ -48,7 +48,7 @@ let (default, with_c, without_c) = (stacktrace(), stacktrace(true), stacktrace(f @test isempty(filter(frame -> frame.from_c, without_c)) end -@test StackTraces.lookup(C_NULL) == [StackTraces.UNKNOWN] +@test StackTraces.lookupat(C_NULL) == [StackTraces.UNKNOWN] == StackTraces.lookupat(C_NULL + 1) == StackTraces.lookupat(C_NULL - 1) let ct = current_task() # After a task switch, there should be nothing in catch_backtrace @@ -118,7 +118,7 @@ let li = typeof(fieldtype).name.mt.cache.func::Core.MethodInstance, end let ctestptr = cglobal((:ctest, "libccalltest")), - ctest = StackTraces.lookup(ctestptr + 1) + ctest = StackTraces.lookupat(ctestptr) @test length(ctest) == 1 @test ctest[1].func === :ctest