From 3ef0fb91b397fd1486e0ec1ec83a9b9efba64154 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Mon, 9 Sep 2024 23:02:17 -0400 Subject: [PATCH 01/18] First try --- base/errorshow.jl | 234 ++++++++++++++++++++++++++++------------------ base/task.jl | 2 +- 2 files changed, 145 insertions(+), 91 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index a3bf464439d44..6f062c614d6c8 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -634,24 +634,9 @@ const update_stackframes_callback = Ref{Function}(identity) const STACKTRACE_MODULECOLORS = Iterators.Stateful(Iterators.cycle([:magenta, :cyan, :green, :yellow])) const STACKTRACE_FIXEDCOLORS = IdDict(Base => :light_black, Core => :light_black) -function show_full_backtrace(io::IO, trace::Vector; print_linebreaks::Bool) - num_frames = length(trace) - ndigits_max = ndigits(num_frames) - - println(io, "\nStacktrace:") - - for (i, (frame, n)) in enumerate(trace) - print_stackframe(io, i, frame, n, ndigits_max, STACKTRACE_FIXEDCOLORS, STACKTRACE_MODULECOLORS) - if i < num_frames - println(io) - print_linebreaks && println(io) - end - end -end - const BIG_STACKTRACE_SIZE = 50 # Arbitrary constant chosen here -function show_reduced_backtrace(io::IO, t::Vector) +function _backtrace_find_and_remove_cycles(t) recorded_positions = IdDict{UInt, Vector{Int}}() #= For each frame of hash h, recorded_positions[h] is the list of indices i such that hash(t[i-1]) == h, ie the list of positions in which the @@ -694,23 +679,25 @@ function show_reduced_backtrace(io::IO, t::Vector) push!(displayed_stackframes, (last_frame, n)) end end + return displayed_stackframes, repeated_cycle +end - try invokelatest(update_stackframes_callback[], displayed_stackframes) catch end - +function show_processed_backtrace(io::IO, trace::Vector, repeated_cycle::Vector{NTuple{3, Int}}, visible_frame_indexes; print_linebreaks::Bool) println(io, "\nStacktrace:") - ndigits_max = ndigits(length(t)) + num_frames = length(trace) + ndigits_max = ndigits(num_frames) push!(repeated_cycle, (0,0,0)) # repeated_cycle is never empty frame_counter = 1 - for i in eachindex(displayed_stackframes) - (frame, n) = displayed_stackframes[i] + for i in eachindex(trace) + (frame, n) = trace[i] print_stackframe(io, frame_counter, frame, n, ndigits_max, STACKTRACE_FIXEDCOLORS, STACKTRACE_MODULECOLORS) - if i < length(displayed_stackframes) + if i < length(trace) println(io) - stacktrace_linebreaks() && println(io) + print_linebreaks && println(io) end while repeated_cycle[1][1] == i # never empty because of the initial (0,0,0) @@ -719,10 +706,10 @@ function show_reduced_backtrace(io::IO, t::Vector) popfirst!(repeated_cycle) printstyled(io, "--- the above ", cycle_length, " lines are repeated ", - repetitions, " more time", repetitions>1 ? "s" : "", " ---", color = :light_black) - if i < length(displayed_stackframes) + repetitions, " more time", repetitions > 1 ? "s" : "", " ---", color = :light_black) + if i < length(trace) println(io) - stacktrace_linebreaks() && println(io) + print_linebreaks && println(io) end frame_counter += cycle_length * repetitions end @@ -730,7 +717,6 @@ function show_reduced_backtrace(io::IO, t::Vector) end end - # Print a stack frame where the module color is determined by looking up the parent module in # `modulecolordict`. If the module does not have a color, yet, a new one can be drawn # from `modulecolorcycler`. @@ -809,16 +795,63 @@ function print_module_path_file(io, modul, file, line; modulecolor = :light_blac printstyled(io, basename(file), ":", line; color = :light_black, underline = true) end + +#= + + +In Test, scrub_exc_stack > scrub_backtrace removes Test-related internal frames + +In client.jl, scrub_repl_backtrace removes REPL-related internal frames + +In show_backtrace: + - In process_backtrace: + - perform lookups if necessary + - decide whether to omit C frames + - omit kwcall frames + - stop processing if trace is larger than some value (e.g. typemax(Int) by default) + - record unique frames, recording a count for repeated frames + - filter out frames in `include` stack + - filter out some frames that have the same location + - don't print a lone top-level frame without location info + - if backtrace is "too big": In show_reduced_backtrace: + - Finds cycles in the trace and prints differently + - otherwise: + - allows one single function to be registered to update line info of the trace (for Revise; run inside show_reduced_backtrace as well) + - prints each line, except special display for repeated lines + - + + +Stacktrace processing pipeline: +1. Raw traces extracted with `backtrace` or `catch_backtrace` as vector of instruction pointers. +2. IP traces converted to frames with `stacktrace`, which may or may not include C frames. +3. Originator trims frames related to itself (e.g. REPL removes REPL-specific frames) + - CapturedException only keeps a limit of 100 frames by processing before display +4. Julia implementation detail frames are hidden and rewritten (e.g. kwcall) +5. Repeated frames are removed and summarized with a count +6. `include` stack frames are filtered out +7. Some frames that have the same location info are merged +8. During display, if a trace is too long it may be further abridged + - cycles found and summarized + +=# + function show_backtrace(io::IO, t::Vector) if haskey(io, :last_shown_line_infos) empty!(io[:last_shown_line_infos]) end - # t is a pre-processed backtrace (ref #12856) + # Process backtrace if it has not yet been. A processed backtrace is a Vector{Any} + # with elements of type Tuple{StackFrame, Int}. (ref #12856) if t isa Vector{Any} && (length(t) == 0 || t[1] isa Tuple{StackFrame,Int}) filtered = t else - filtered = process_backtrace(t) + # t is a raw trace requiring lookup + if t isa Vector{<:Union{Base.InterpreterIP,Core.Compiler.InterpreterIP,Ptr{Cvoid}}} + frametrace = stacktrace(t) + else + frametrace = t + end + filtered = process_backtrace(tracecounts) end isempty(filtered) && return @@ -831,20 +864,62 @@ function show_backtrace(io::IO, t::Vector) end if length(filtered) > BIG_STACKTRACE_SIZE - show_reduced_backtrace(IOContext(io, :backtrace => true), filtered) - return + filtered, repeated_cycle = _backtrace_find_and_remove_cycles(filtered) else - try invokelatest(update_stackframes_callback[], filtered) catch end - # process_backtrace returns a Vector{Tuple{Frame, Int}} - show_full_backtrace(io, filtered; print_linebreaks = stacktrace_linebreaks()) + repeated_cycle = NTuple{3, Int}[] end + + visible_frame_indexes = _backtrace_display_filter[](filtered) + + try invokelatest(update_stackframes_callback[], filtered) catch end + + show_processed_backtrace(IOContext(io, :backtrace => true), filtered, repeated_cycle, visible_frame_indexes; print_linebreaks = stacktrace_linebreaks()) nothing end +function _backtrace_collapse_and_count_repeated_frames(frames::Vector{StackFrame}) + n = 0 + last_frame = StackTraces.UNKNOWN + tracecount = Tuple{StackFrame,Int}[] + for frame in frames + if frame.file != last_frame.file || frame.line != last_frame.line || frame.func != last_frame.func || frame.linfo !== last_frame.linfo + if n > 0 + push!(tracecount, (last_frame, n)) + end + n = 1 + last_frame = frame + else + n += 1 + end + end + if n > 0 + push!(tracecount, (last_frame, n)) + end + return tracecount +end + +function _backtrace_remove_kwcall_frames!(trace) + todelete = findall(t) do frame, _ + code = frame.linfo + if code isa MethodInstance + def = code.def + if def isa Method && def.name !== :kwcall && def.sig <: Tuple{typeof(Core.kwcall),NamedTuple,Any,Vararg} + # hide kwcall() methods, which are probably internal keyword sorter methods + # (we print the internal method instead, after demangling + # the argument list, since it has the right line number info) + return true + end + else + frame.func === :kwcall && return true + end + return false + end + deleteat!(t, todelete) +end # For improved user experience, filter out frames for include() implementation # - see #33065. See also #35371 for extended discussion of internal frames. -function _simplify_include_frames(trace) +function _backtrace_simplify_include_frames!(trace) kept_frames = trues(length(trace)) first_ignored = nothing for i in length(trace):-1:1 @@ -873,11 +948,11 @@ function _simplify_include_frames(trace) if first_ignored !== nothing kept_frames[1:first_ignored] .= false end - return trace[kept_frames] + keepat!(trace, kept_frames) end # Collapse frames that have the same location (in some cases) -function _collapse_repeated_frames(trace) +function _backtrace_collapse_repeated_locations!(trace) kept_frames = trues(length(trace)) last_frame = nothing for i in eachindex(trace) @@ -938,67 +1013,46 @@ function _collapse_repeated_frames(trace) end last_frame = frame end - return trace[kept_frames] + keepat!(trace, kept_frames) end +const _backtrace_editors! = [ + _backtrace_remove_kwcall_frames!, + _backtrace_simplify_include_frames!, + _backtrace_collapse_repeated_locations! +] -function process_backtrace(t::Vector, limit::Int=typemax(Int); skipC = true) - n = 0 - last_frame = StackTraces.UNKNOWN - count = 0 - ret = Any[] - for i in eachindex(t) - lkups = t[i] - if lkups isa StackFrame - lkups = [lkups] - else - lkups = StackTraces.lookup(lkups) - end - for lkup in lkups - if lkup === StackTraces.UNKNOWN - continue - end +""" + register_backtrace_editor(btfilter) - if (lkup.from_c && skipC) - continue - end - code = lkup.linfo - if code isa MethodInstance - def = code.def - if def isa Method && def.name !== :kwcall && def.sig <: Tuple{typeof(Core.kwcall),NamedTuple,Any,Vararg} - # hide kwcall() methods, which are probably internal keyword sorter methods - # (we print the internal method instead, after demangling - # the argument list, since it has the right line number info) - continue - end - elseif !lkup.from_c - lkup.func === :kwcall && continue - end - count += 1 - if count > limit - break - end +Provide a function that accepts a Vector{Any} with elements of type Tuple{Frame, Int}, and edits it in-place. +""" +function register_backtrace_editor(btfilter!) + push!(_backtrace_editors!, btfilter!) + nothing +end - if lkup.file != last_frame.file || lkup.line != last_frame.line || lkup.func != last_frame.func || lkup.linfo !== last_frame.linfo - if n > 0 - push!(ret, (last_frame, n)) - end - n = 1 - last_frame = lkup - else - n += 1 - end - end - count > limit && break - end - if n > 0 - push!(ret, (last_frame, n)) +function process_backtrace(t::Vector{StackFrame}) + tracecounts = _backtrace_collapse_and_count_repeated_frames(t) + process_backtrace(tracecounts) +end + +function process_backtrace(t::Vector{Any}) + for trace_editor! ∈ _backtrace_editors! + trace_editor!(t) end - trace = _simplify_include_frames(ret) - trace = _collapse_repeated_frames(trace) - return trace + return t end +""" + _backtrace_display_filter[] + +Provide a function that accepts a Vector{Any} with elements of type Tuple{Frame, Int} and returns +a Vector{Int} of indexes of frames to show. +""" +const _backtrace_display_filter = Ref{Function}(x -> eachindex(x)) + + function show_exception_stack(io::IO, stack) # Display exception stack with the top of the stack first. This ordering # means that the user doesn't have to scroll up in the REPL to discover the diff --git a/base/task.jl b/base/task.jl index 6cb1ff785eeee..11a6e9be80a0f 100644 --- a/base/task.jl +++ b/base/task.jl @@ -14,7 +14,7 @@ struct CapturedException <: Exception # Typically the result of a catch_backtrace() # Process bt_raw so that it can be safely serialized - bt_lines = process_backtrace(bt_raw, 100) # Limiting this to 100 lines. + bt_lines = process_backtrace(stacktrace(bt_raw))[1:min(100, end)] # Limiting this to 100 lines. CapturedException(ex, bt_lines) end From dd50e8f417e204908756ecb29bf1271ec6b01d73 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Tue, 10 Sep 2024 20:49:45 -0400 Subject: [PATCH 02/18] Fixes --- base/errorshow.jl | 51 +++++++++++++++++++---------------------------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index 6f062c614d6c8..bb7cdf3a5f857 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -851,26 +851,33 @@ function show_backtrace(io::IO, t::Vector) else frametrace = t end - filtered = process_backtrace(tracecounts) + filtered = process_backtrace(frametrace) end isempty(filtered) && return + # don't show a single top-level frame with no location info if length(filtered) == 1 && StackTraces.is_top_level_frame(filtered[1][1]) f = filtered[1][1]::StackFrame - if f.line == 0 && f.file === :var"" - # don't show a single top-level frame with no location info + if f.line == 0 && f.file === :var"" return end end + # Find repeated cycles if trace is too long if length(filtered) > BIG_STACKTRACE_SIZE filtered, repeated_cycle = _backtrace_find_and_remove_cycles(filtered) else repeated_cycle = NTuple{3, Int}[] end - visible_frame_indexes = _backtrace_display_filter[](filtered) + # Allow external code to determine frames to show and hide + visible_frame_indexes = try + invokelatest(_backtrace_display_filter[], filtered) + catch + collect(eachindex(filtered)) + end + # Allow external code to edit information in the frames (e.g. line numbers with Revise) try invokelatest(update_stackframes_callback[], filtered) catch end show_processed_backtrace(IOContext(io, :backtrace => true), filtered, repeated_cycle, visible_frame_indexes; print_linebreaks = stacktrace_linebreaks()) @@ -880,7 +887,7 @@ end function _backtrace_collapse_and_count_repeated_frames(frames::Vector{StackFrame}) n = 0 last_frame = StackTraces.UNKNOWN - tracecount = Tuple{StackFrame,Int}[] + tracecount = Any[] for frame in frames if frame.file != last_frame.file || frame.line != last_frame.line || frame.func != last_frame.func || frame.linfo !== last_frame.linfo if n > 0 @@ -899,7 +906,7 @@ function _backtrace_collapse_and_count_repeated_frames(frames::Vector{StackFrame end function _backtrace_remove_kwcall_frames!(trace) - todelete = findall(t) do frame, _ + todelete = findall(trace) do (frame, _) code = frame.linfo if code isa MethodInstance def = code.def @@ -914,7 +921,7 @@ function _backtrace_remove_kwcall_frames!(trace) end return false end - deleteat!(t, todelete) + deleteat!(trace, todelete) end # For improved user experience, filter out frames for include() implementation @@ -1016,32 +1023,16 @@ function _backtrace_collapse_repeated_locations!(trace) keepat!(trace, kept_frames) end -const _backtrace_editors! = [ - _backtrace_remove_kwcall_frames!, - _backtrace_simplify_include_frames!, - _backtrace_collapse_repeated_locations! -] - -""" - register_backtrace_editor(btfilter) - -Provide a function that accepts a Vector{Any} with elements of type Tuple{Frame, Int}, and edits it in-place. -""" -function register_backtrace_editor(btfilter!) - push!(_backtrace_editors!, btfilter!) - nothing -end - function process_backtrace(t::Vector{StackFrame}) - tracecounts = _backtrace_collapse_and_count_repeated_frames(t) - process_backtrace(tracecounts) + tracecount = _backtrace_collapse_and_count_repeated_frames(t) + process_backtrace(tracecount) end -function process_backtrace(t::Vector{Any}) - for trace_editor! ∈ _backtrace_editors! - trace_editor!(t) - end - return t +function process_backtrace(tracecount::Vector{Any}) + _backtrace_remove_kwcall_frames!(tracecount) + _backtrace_simplify_include_frames!(tracecount) + _backtrace_collapse_repeated_locations!(tracecount) + return tracecount end """ From 1100409bede6f7e50850c12a8ef818181a6535f9 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Sun, 22 Sep 2024 11:05:15 -0400 Subject: [PATCH 03/18] Cycles and repeated frames consolidated --- base/errorshow.jl | 128 +++++++++++++++++++++++++++++++--------------- 1 file changed, 88 insertions(+), 40 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index bb7cdf3a5f857..e32f290e9e97a 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -642,24 +642,28 @@ function _backtrace_find_and_remove_cycles(t) such that hash(t[i-1]) == h, ie the list of positions in which the frame appears just before. =# + max_nested_cycles = 1 displayed_stackframes = [] - repeated_cycle = Tuple{Int,Int,Int}[] - # First: line to introuce the "cycle repetition" message + repeated_cycles = Tuple{Int,Int,Int}[] + # First: line to introuce the cycle bracket on # Second: length of the cycle # Third: number of repetitions frame_counter = 1 + accumulated_repetitions = 0 while frame_counter < length(t) (last_frame, n) = t[frame_counter] + accumulated_repetitions += n - 1 frame_counter += 1 # Indicating the next frame current_hash = hash(last_frame) positions = get(recorded_positions, current_hash, Int[]) - recorded_positions[current_hash] = push!(positions, frame_counter) + recorded_positions[current_hash] = push!(positions, frame_counter + accumulated_repetitions) repetitions = 0 + nnested_cycles = 1 for index_p in length(positions)-1:-1:1 # More recent is more likely p = positions[index_p] - cycle_length = frame_counter - p + cycle_length = frame_counter + accumulated_repetitions - p i = frame_counter j = p while i < length(t) && t[i] == t[j] @@ -668,51 +672,84 @@ function _backtrace_find_and_remove_cycles(t) end if j >= frame_counter-1 #= At least one cycle repeated =# - repetitions = div(i - frame_counter + 1, cycle_length) - push!(repeated_cycle, (length(displayed_stackframes), cycle_length, repetitions)) + repetitions = div(i - frame_counter + accumulated_repetitions + 1, cycle_length) + push!(repeated_cycles, (p - 1, cycle_length, repetitions)) frame_counter += cycle_length * repetitions - 1 + nnested_cycles += 1 break end end + sort!(repeated_cycles, by = x -> (x[1], -x[2])) + max_nested_cycles = max(max_nested_cycles, nnested_cycles) if repetitions==0 push!(displayed_stackframes, (last_frame, n)) end end - return displayed_stackframes, repeated_cycle + push!(displayed_stackframes, t[end]) + return displayed_stackframes, repeated_cycles, max_nested_cycles end -function show_processed_backtrace(io::IO, trace::Vector, repeated_cycle::Vector{NTuple{3, Int}}, visible_frame_indexes; print_linebreaks::Bool) +function _backtrace_print_repetition_closings!(io::IO, i, current_cycles, frame_counter, max_nested_cycles, nactive_cycles, ndigits_max, nframes, print_linebreaks) + while !isempty(current_cycles) + start_line = current_cycles[end][1] + cycle_length = current_cycles[end][2] + end_line = start_line + cycle_length - 1 + repetitions = current_cycles[end][3] + + frame_counter != end_line && break + + nactive_cycles -= 1 + line_length = max_nested_cycles + (max_nested_cycles - nactive_cycles) + ndigits_max + printstyled(io, " ", "│" ^ nactive_cycles, "└", "─" ^ (line_length); color = :light_black) + printstyled(io, " repeated $repetitions more time", repetitions > 1 ? "s" : ""; color = :light_black, italic = true) + + if i < nframes + println(io) + print_linebreaks && println(io) + end + + pop!(current_cycles) + frame_counter += cycle_length * repetitions + end + return frame_counter, nactive_cycles +end + +function show_processed_backtrace(io::IO, trace::Vector, num_frames::Int, repeated_cycles::Vector{NTuple{3, Int}}, max_nested_cycles::Int, visible_frame_indexes; print_linebreaks::Bool) println(io, "\nStacktrace:") - num_frames = length(trace) ndigits_max = ndigits(num_frames) - push!(repeated_cycle, (0,0,0)) # repeated_cycle is never empty + if any(x -> last(x) > 1, trace) + max_nested_cycles += 1 + end + + push!(repeated_cycles, (0,0,0)) # repeated_cycles is never empty frame_counter = 1 + current_cycles = NTuple{3, Int}[] + for i in eachindex(trace) (frame, n) = trace[i] - print_stackframe(io, frame_counter, frame, n, ndigits_max, STACKTRACE_FIXEDCOLORS, STACKTRACE_MODULECOLORS) + ncycle_starts = 0 + while repeated_cycles[1][1] == frame_counter + push!(current_cycles, popfirst!(repeated_cycles)) + ncycle_starts += 1 + end + if n > 1 + push!(current_cycles, (frame_counter, 1, n - 1)) + ncycle_starts += 1 + end + nactive_cycles = length(current_cycles) + + print_stackframe(io, frame_counter, frame, ndigits_max, max_nested_cycles, nactive_cycles, ncycle_starts, STACKTRACE_FIXEDCOLORS, STACKTRACE_MODULECOLORS) if i < length(trace) println(io) print_linebreaks && println(io) end - while repeated_cycle[1][1] == i # never empty because of the initial (0,0,0) - cycle_length = repeated_cycle[1][2] - repetitions = repeated_cycle[1][3] - popfirst!(repeated_cycle) - printstyled(io, - "--- the above ", cycle_length, " lines are repeated ", - repetitions, " more time", repetitions > 1 ? "s" : "", " ---", color = :light_black) - if i < length(trace) - println(io) - print_linebreaks && println(io) - end - frame_counter += cycle_length * repetitions - end + frame_counter, nactive_cycles = _backtrace_print_repetition_closings!(io, i, current_cycles, frame_counter, max_nested_cycles, nactive_cycles, ndigits_max, length(trace), print_linebreaks) frame_counter += 1 end end @@ -720,7 +757,7 @@ end # Print a stack frame where the module color is determined by looking up the parent module in # `modulecolordict`. If the module does not have a color, yet, a new one can be drawn # from `modulecolorcycler`. -function print_stackframe(io, i, frame::StackFrame, n::Int, ndigits_max, modulecolordict, modulecolorcycler) +function print_stackframe(io, i, frame::StackFrame, ndigits_max::Int, max_nested_cycles::Int, nactive_cycles::Int, ncycle_starts::Int, modulecolordict, modulecolorcycler) m = Base.parentmodule(frame) modulecolor = if m !== nothing m = parentmodule_before_main(m) @@ -728,7 +765,7 @@ function print_stackframe(io, i, frame::StackFrame, n::Int, ndigits_max, modulec else :default end - print_stackframe(io, i, frame, n, ndigits_max, modulecolor) + print_stackframe(io, i, frame, ndigits_max, max_nested_cycles, nactive_cycles, ncycle_starts, modulecolor) end # Gets the topmost parent module that isn't Main @@ -743,7 +780,7 @@ end parentmodule_before_main(x) = parentmodule_before_main(parentmodule(x)) # Print a stack frame where the module color is set manually with `modulecolor`. -function print_stackframe(io, i, frame::StackFrame, n::Int, ndigits_max, modulecolor) +function print_stackframe(io, i, frame::StackFrame, ndigits_max::Int, max_nested_cycles::Int, nactive_cycles::Int, ncycle_starts::Int, modulecolor) file, line = string(frame.file), frame.line # Used by the REPL to make it possible to open @@ -755,18 +792,26 @@ function print_stackframe(io, i, frame::StackFrame, n::Int, ndigits_max, modulec inlined = getfield(frame, :inlined) modul = parentmodule(frame) - digit_align_width = ndigits_max + 2 + digit_align_width = ndigits_max + 2 + max_nested_cycles + + # repeated section bracket line 1 + print(io, " ") + printstyled(io, "│" ^ (nactive_cycles - ncycle_starts); color = :light_black) + printstyled(io, "┌" ^ ncycle_starts; color = :light_black) # frame number - print(io, " ", lpad("[" * string(i) * "]", digit_align_width)) + print(io, lpad("[" * string(i) * "]", digit_align_width - nactive_cycles - 1)) print(io, " ") + # func name and arguments StackTraces.show_spec_linfo(IOContext(io, :backtrace=>true), frame) - if n > 1 - printstyled(io, " (repeats $n times)"; color=Base.warn_color(), bold=true) - end println(io) + # repeated section bracket line 2 + print(io, " ") + printstyled(io, "│" ^ (nactive_cycles); color = :light_black) + print(io, " " ^ (max_nested_cycles - nactive_cycles)) + # @ Module path / file : line print_module_path_file(io, modul, file, line; modulecolor, digit_align_width) @@ -855,8 +900,10 @@ function show_backtrace(io::IO, t::Vector) end isempty(filtered) && return + nframes = sum(last(x) for x ∈ filtered) + # don't show a single top-level frame with no location info - if length(filtered) == 1 && StackTraces.is_top_level_frame(filtered[1][1]) + if nframes == 1 && StackTraces.is_top_level_frame(filtered[1][1]) f = filtered[1][1]::StackFrame if f.line == 0 && f.file === :var"" return @@ -865,22 +912,23 @@ function show_backtrace(io::IO, t::Vector) # Find repeated cycles if trace is too long if length(filtered) > BIG_STACKTRACE_SIZE - filtered, repeated_cycle = _backtrace_find_and_remove_cycles(filtered) + filtered, repeated_cycles, max_nested_cycles = _backtrace_find_and_remove_cycles(filtered) else - repeated_cycle = NTuple{3, Int}[] + repeated_cycles = NTuple{3, Int}[] + max_nested_cycles = 0 end - # Allow external code to determine frames to show and hide + # Allow external code to edit information in the frames (e.g. line numbers with Revise) + try invokelatest(update_stackframes_callback[], filtered) catch end + + # Allow external code to determine frames to hide (e.g. AbbreviatedStackTraces) visible_frame_indexes = try invokelatest(_backtrace_display_filter[], filtered) catch collect(eachindex(filtered)) end - # Allow external code to edit information in the frames (e.g. line numbers with Revise) - try invokelatest(update_stackframes_callback[], filtered) catch end - - show_processed_backtrace(IOContext(io, :backtrace => true), filtered, repeated_cycle, visible_frame_indexes; print_linebreaks = stacktrace_linebreaks()) + show_processed_backtrace(IOContext(io, :backtrace => true), filtered, nframes, repeated_cycles, max_nested_cycles, visible_frame_indexes; print_linebreaks = stacktrace_linebreaks()) nothing end From 5b8e2db1a38f00cd1bddecaf582733bf559c8097 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Sun, 22 Sep 2024 11:46:43 -0400 Subject: [PATCH 04/18] Fix spacing --- base/errorshow.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index e32f290e9e97a..fd312f7cc2724 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -792,7 +792,7 @@ function print_stackframe(io, i, frame::StackFrame, ndigits_max::Int, max_nested inlined = getfield(frame, :inlined) modul = parentmodule(frame) - digit_align_width = ndigits_max + 2 + max_nested_cycles + digit_align_width = ndigits_max + 2 + max_nested_cycles - nactive_cycles # repeated section bracket line 1 print(io, " ") @@ -800,7 +800,7 @@ function print_stackframe(io, i, frame::StackFrame, ndigits_max::Int, max_nested printstyled(io, "┌" ^ ncycle_starts; color = :light_black) # frame number - print(io, lpad("[" * string(i) * "]", digit_align_width - nactive_cycles - 1)) + print(io, lpad("[" * string(i) * "]", digit_align_width)) print(io, " ") # func name and arguments @@ -810,10 +810,9 @@ function print_stackframe(io, i, frame::StackFrame, ndigits_max::Int, max_nested # repeated section bracket line 2 print(io, " ") printstyled(io, "│" ^ (nactive_cycles); color = :light_black) - print(io, " " ^ (max_nested_cycles - nactive_cycles)) # @ Module path / file : line - print_module_path_file(io, modul, file, line; modulecolor, digit_align_width) + print_module_path_file(io, modul, file, line; modulecolor, digit_align_width = digit_align_width - 1) # inlined printstyled(io, inlined ? " [inlined]" : "", color = :light_black) From d63c83c8074412298d3fa715a195ae0b27661c3f Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Sun, 22 Sep 2024 11:47:04 -0400 Subject: [PATCH 05/18] Refine comment and callback name --- base/errorshow.jl | 47 +++++++++++++---------------------------------- 1 file changed, 13 insertions(+), 34 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index fd312f7cc2724..6555f90954f1b 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -839,43 +839,23 @@ function print_module_path_file(io, modul, file, line; modulecolor = :light_blac printstyled(io, basename(file), ":", line; color = :light_black, underline = true) end - #= - -In Test, scrub_exc_stack > scrub_backtrace removes Test-related internal frames - -In client.jl, scrub_repl_backtrace removes REPL-related internal frames - -In show_backtrace: - - In process_backtrace: - - perform lookups if necessary - - decide whether to omit C frames - - omit kwcall frames - - stop processing if trace is larger than some value (e.g. typemax(Int) by default) - - record unique frames, recording a count for repeated frames - - filter out frames in `include` stack - - filter out some frames that have the same location - - don't print a lone top-level frame without location info - - if backtrace is "too big": In show_reduced_backtrace: - - Finds cycles in the trace and prints differently - - otherwise: - - allows one single function to be registered to update line info of the trace (for Revise; run inside show_reduced_backtrace as well) - - prints each line, except special display for repeated lines - - - - Stacktrace processing pipeline: 1. Raw traces extracted with `backtrace` or `catch_backtrace` as vector of instruction pointers. 2. IP traces converted to frames with `stacktrace`, which may or may not include C frames. 3. Originator trims frames related to itself (e.g. REPL removes REPL-specific frames) - CapturedException only keeps a limit of 100 frames by processing before display -4. Julia implementation detail frames are hidden and rewritten (e.g. kwcall) -5. Repeated frames are removed and summarized with a count -6. `include` stack frames are filtered out -7. Some frames that have the same location info are merged -8. During display, if a trace is too long it may be further abridged - - cycles found and summarized +4. `process_backtrace` filters a trace for frames and summarizes repeated single frames: + - `kwcall` frames removed + - `include`-related stack frames removed + - Some frames that have the same location info are merged + - Repeated frames are removed and summarized with a count + - Output is an Any[] containing (StackFrame, count) tuple elements and this form is exposed to e.g. Revise +5. If a trace is too long, cycles are identified and summarized +6. `update_stackframes_callback[]` provides e.g. Revise an opportunity to edit line info +7. `stackframes_visibility_callback[]` provides e.g. AbbreviatedStackTraces an opportunity + to determine which frames should be displayed. =# @@ -908,7 +888,6 @@ function show_backtrace(io::IO, t::Vector) return end end - # Find repeated cycles if trace is too long if length(filtered) > BIG_STACKTRACE_SIZE filtered, repeated_cycles, max_nested_cycles = _backtrace_find_and_remove_cycles(filtered) @@ -922,7 +901,7 @@ function show_backtrace(io::IO, t::Vector) # Allow external code to determine frames to hide (e.g. AbbreviatedStackTraces) visible_frame_indexes = try - invokelatest(_backtrace_display_filter[], filtered) + invokelatest(stackframes_visibility_callback[], filtered) catch collect(eachindex(filtered)) end @@ -1083,12 +1062,12 @@ function process_backtrace(tracecount::Vector{Any}) end """ - _backtrace_display_filter[] + stackframes_visibility_callback[] Provide a function that accepts a Vector{Any} with elements of type Tuple{Frame, Int} and returns a Vector{Int} of indexes of frames to show. """ -const _backtrace_display_filter = Ref{Function}(x -> eachindex(x)) +const stackframes_visibility_callback = Ref{Function}(x -> eachindex(x)) function show_exception_stack(io::IO, stack) From 194fe101512e4346ebbedc8c2d8c58b61d19727c Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Sun, 22 Sep 2024 12:04:41 -0400 Subject: [PATCH 06/18] Strip AbbreviatedStackTraces bits --- base/errorshow.jl | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index 6555f90954f1b..2e64000d57090 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -854,8 +854,6 @@ Stacktrace processing pipeline: - Output is an Any[] containing (StackFrame, count) tuple elements and this form is exposed to e.g. Revise 5. If a trace is too long, cycles are identified and summarized 6. `update_stackframes_callback[]` provides e.g. Revise an opportunity to edit line info -7. `stackframes_visibility_callback[]` provides e.g. AbbreviatedStackTraces an opportunity - to determine which frames should be displayed. =# @@ -899,13 +897,6 @@ function show_backtrace(io::IO, t::Vector) # Allow external code to edit information in the frames (e.g. line numbers with Revise) try invokelatest(update_stackframes_callback[], filtered) catch end - # Allow external code to determine frames to hide (e.g. AbbreviatedStackTraces) - visible_frame_indexes = try - invokelatest(stackframes_visibility_callback[], filtered) - catch - collect(eachindex(filtered)) - end - show_processed_backtrace(IOContext(io, :backtrace => true), filtered, nframes, repeated_cycles, max_nested_cycles, visible_frame_indexes; print_linebreaks = stacktrace_linebreaks()) nothing end @@ -1061,15 +1052,6 @@ function process_backtrace(tracecount::Vector{Any}) return tracecount end -""" - stackframes_visibility_callback[] - -Provide a function that accepts a Vector{Any} with elements of type Tuple{Frame, Int} and returns -a Vector{Int} of indexes of frames to show. -""" -const stackframes_visibility_callback = Ref{Function}(x -> eachindex(x)) - - function show_exception_stack(io::IO, stack) # Display exception stack with the top of the stack first. This ordering # means that the user doesn't have to scroll up in the REPL to discover the From 0c10ff1f693a2ed0c110a8f166b325c15d14bfd9 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Sun, 22 Sep 2024 12:05:26 -0400 Subject: [PATCH 07/18] format --- base/errorshow.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/base/errorshow.jl b/base/errorshow.jl index 2e64000d57090..2f7695ae2ee7a 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -886,6 +886,7 @@ function show_backtrace(io::IO, t::Vector) return end end + # Find repeated cycles if trace is too long if length(filtered) > BIG_STACKTRACE_SIZE filtered, repeated_cycles, max_nested_cycles = _backtrace_find_and_remove_cycles(filtered) From b19d1cf1246a55981e455e9eebfe50c2259bf7dc Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Sun, 22 Sep 2024 12:23:01 -0400 Subject: [PATCH 08/18] Update tests --- test/errorshow.jl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/errorshow.jl b/test/errorshow.jl index 80352ddeaa9cf..1742301c665db 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -775,11 +775,13 @@ backtrace() Base.show_backtrace(io, bt) output = split(String(take!(io)), '\n') length(output) >= 8 || println(output) # for better errors when this fails - @test lstrip(output[3])[1:3] == "[1]" + @test lstrip(output[3])[1] == "┌" + @test lstrip(lstrip(output[3])[4:end])[1:3] == "[1]" @test occursin("g28442", output[3]) - @test lstrip(output[5])[1:3] == "[2]" + @test lstrip(output[5])[1] == "│" + @test lstrip(lstrip(output[5])[4:end])[1:3] == "[2]" @test occursin("f28442", output[5]) - @test occursin("the above 2 lines are repeated 5000 more times", output[7]) + @test occursin("repeated 5000 more times", output[7]) @test lstrip(output[8])[1:7] == "[10003]" end @@ -943,7 +945,7 @@ if (Sys.isapple() || Sys.islinux()) && Sys.ARCH === :x86_64 catch_backtrace() end bt_str = sprint(Base.show_backtrace, bt) - @test occursin(r"repeats \d+ times", bt_str) + @test occursin(r"repeated \d+ more times", bt_str) end let bt = try @@ -952,7 +954,7 @@ if (Sys.isapple() || Sys.islinux()) && Sys.ARCH === :x86_64 catch_backtrace() end bt_str = sprint(Base.show_backtrace, bt) - @test occursin(r"the above 2 lines are repeated \d+ more times", bt_str) + @test occursin(r"repeated \d+ more times", bt_str) end end end From 81ac0e7b361ffd1d03054f6a8737ebbee37b366f Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Sun, 22 Sep 2024 12:35:55 -0400 Subject: [PATCH 09/18] Fix linebreak organization --- base/errorshow.jl | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index 2f7695ae2ee7a..0aa7558204c6e 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -699,15 +699,12 @@ function _backtrace_print_repetition_closings!(io::IO, i, current_cycles, frame_ frame_counter != end_line && break + println(io) + nactive_cycles -= 1 line_length = max_nested_cycles + (max_nested_cycles - nactive_cycles) + ndigits_max printstyled(io, " ", "│" ^ nactive_cycles, "└", "─" ^ (line_length); color = :light_black) printstyled(io, " repeated $repetitions more time", repetitions > 1 ? "s" : ""; color = :light_black, italic = true) - - if i < nframes - println(io) - print_linebreaks && println(io) - end pop!(current_cycles) frame_counter += cycle_length * repetitions @@ -744,13 +741,13 @@ function show_processed_backtrace(io::IO, trace::Vector, num_frames::Int, repeat print_stackframe(io, frame_counter, frame, ndigits_max, max_nested_cycles, nactive_cycles, ncycle_starts, STACKTRACE_FIXEDCOLORS, STACKTRACE_MODULECOLORS) + frame_counter, nactive_cycles = _backtrace_print_repetition_closings!(io, i, current_cycles, frame_counter, max_nested_cycles, nactive_cycles, ndigits_max, length(trace), print_linebreaks) + frame_counter += 1 + if i < length(trace) println(io) print_linebreaks && println(io) end - - frame_counter, nactive_cycles = _backtrace_print_repetition_closings!(io, i, current_cycles, frame_counter, max_nested_cycles, nactive_cycles, ndigits_max, length(trace), print_linebreaks) - frame_counter += 1 end end @@ -886,7 +883,7 @@ function show_backtrace(io::IO, t::Vector) return end end - + # Find repeated cycles if trace is too long if length(filtered) > BIG_STACKTRACE_SIZE filtered, repeated_cycles, max_nested_cycles = _backtrace_find_and_remove_cycles(filtered) From fba7909dc761305621c366f2a9cc90e586267ca1 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Sun, 22 Sep 2024 12:52:50 -0400 Subject: [PATCH 10/18] Remove arg --- base/errorshow.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index 0aa7558204c6e..ebc43f459c23c 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -712,7 +712,7 @@ function _backtrace_print_repetition_closings!(io::IO, i, current_cycles, frame_ return frame_counter, nactive_cycles end -function show_processed_backtrace(io::IO, trace::Vector, num_frames::Int, repeated_cycles::Vector{NTuple{3, Int}}, max_nested_cycles::Int, visible_frame_indexes; print_linebreaks::Bool) +function show_processed_backtrace(io::IO, trace::Vector, num_frames::Int, repeated_cycles::Vector{NTuple{3, Int}}, max_nested_cycles::Int; print_linebreaks::Bool) println(io, "\nStacktrace:") ndigits_max = ndigits(num_frames) @@ -895,7 +895,7 @@ function show_backtrace(io::IO, t::Vector) # Allow external code to edit information in the frames (e.g. line numbers with Revise) try invokelatest(update_stackframes_callback[], filtered) catch end - show_processed_backtrace(IOContext(io, :backtrace => true), filtered, nframes, repeated_cycles, max_nested_cycles, visible_frame_indexes; print_linebreaks = stacktrace_linebreaks()) + show_processed_backtrace(IOContext(io, :backtrace => true), filtered, nframes, repeated_cycles, max_nested_cycles; print_linebreaks = stacktrace_linebreaks()) nothing end From c57c15a5ae2aba2ee3160ac58c887eb6a3736e41 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Sun, 22 Sep 2024 13:14:54 -0400 Subject: [PATCH 11/18] Fix closing line printing --- base/errorshow.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index ebc43f459c23c..c885397cab6aa 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -690,7 +690,7 @@ function _backtrace_find_and_remove_cycles(t) return displayed_stackframes, repeated_cycles, max_nested_cycles end -function _backtrace_print_repetition_closings!(io::IO, i, current_cycles, frame_counter, max_nested_cycles, nactive_cycles, ndigits_max, nframes, print_linebreaks) +function _backtrace_print_repetition_closings!(io::IO, i, current_cycles, frame_counter, max_nested_cycles, nactive_cycles, ndigits_max) while !isempty(current_cycles) start_line = current_cycles[end][1] cycle_length = current_cycles[end][2] @@ -701,8 +701,8 @@ function _backtrace_print_repetition_closings!(io::IO, i, current_cycles, frame_ println(io) + line_length = (max_nested_cycles - nactive_cycles) + ndigits_max + 2 nactive_cycles -= 1 - line_length = max_nested_cycles + (max_nested_cycles - nactive_cycles) + ndigits_max printstyled(io, " ", "│" ^ nactive_cycles, "└", "─" ^ (line_length); color = :light_black) printstyled(io, " repeated $repetitions more time", repetitions > 1 ? "s" : ""; color = :light_black, italic = true) @@ -741,7 +741,7 @@ function show_processed_backtrace(io::IO, trace::Vector, num_frames::Int, repeat print_stackframe(io, frame_counter, frame, ndigits_max, max_nested_cycles, nactive_cycles, ncycle_starts, STACKTRACE_FIXEDCOLORS, STACKTRACE_MODULECOLORS) - frame_counter, nactive_cycles = _backtrace_print_repetition_closings!(io, i, current_cycles, frame_counter, max_nested_cycles, nactive_cycles, ndigits_max, length(trace), print_linebreaks) + frame_counter, nactive_cycles = _backtrace_print_repetition_closings!(io, i, current_cycles, frame_counter, max_nested_cycles, nactive_cycles, ndigits_max) frame_counter += 1 if i < length(trace) From f5a6a0e0a464196c38ea96a2d102a6c3ce7d2d8b Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Sun, 22 Sep 2024 13:18:44 -0400 Subject: [PATCH 12/18] whitespace --- base/errorshow.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index c885397cab6aa..bff49a53f69fe 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -879,7 +879,7 @@ function show_backtrace(io::IO, t::Vector) # don't show a single top-level frame with no location info if nframes == 1 && StackTraces.is_top_level_frame(filtered[1][1]) f = filtered[1][1]::StackFrame - if f.line == 0 && f.file === :var"" + if f.line == 0 && f.file === :var"" return end end From a08ab65c5c30de6ef0d48231f9b496d32c0552d3 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Sun, 22 Sep 2024 13:20:38 -0400 Subject: [PATCH 13/18] whitespace --- base/errorshow.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index bff49a53f69fe..0396e046b56a5 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -724,7 +724,7 @@ function show_processed_backtrace(io::IO, trace::Vector, num_frames::Int, repeat push!(repeated_cycles, (0,0,0)) # repeated_cycles is never empty frame_counter = 1 current_cycles = NTuple{3, Int}[] - + for i in eachindex(trace) (frame, n) = trace[i] @@ -743,7 +743,7 @@ function show_processed_backtrace(io::IO, trace::Vector, num_frames::Int, repeat frame_counter, nactive_cycles = _backtrace_print_repetition_closings!(io, i, current_cycles, frame_counter, max_nested_cycles, nactive_cycles, ndigits_max) frame_counter += 1 - + if i < length(trace) println(io) print_linebreaks && println(io) From aac357852fe7290148548af92b10fe0d0a9beb79 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Mon, 23 Sep 2024 13:27:29 -0400 Subject: [PATCH 14/18] adjust tests --- stdlib/REPL/test/repl.jl | 4 ++-- test/errorshow.jl | 4 ++-- test/stacktraces.jl | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index f4d594b2a02e1..ec2671bd7f7b6 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -244,8 +244,8 @@ fake_repl(options = REPL.Options(confirm_exit=false,hascolor=true)) do stdin_wri @test occursin("shell> ", s) # check for the echo of the prompt @test occursin("'", s) # check for the echo of the input s = readuntil(stdout_read, "\n\n") - @test(startswith(s, "\e[0mERROR: unterminated single quote\nStacktrace:\n [1] ") || - startswith(s, "\e[0m\e[1m\e[91mERROR: \e[39m\e[22m\e[91munterminated single quote\e[39m\nStacktrace:\n [1] "), + @test(startswith(s, "\e[0mERROR: unterminated single quote\nStacktrace:\n [1] ") || + startswith(s, "\e[0m\e[1m\e[91mERROR: \e[39m\e[22m\e[91munterminated single quote\e[39m\nStacktrace:\n [1] "), skip = Sys.iswindows() && Sys.WORD_SIZE == 32) write(stdin_write, "\b") wait(t) diff --git a/test/errorshow.jl b/test/errorshow.jl index 1742301c665db..32117985e8b67 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -775,10 +775,10 @@ backtrace() Base.show_backtrace(io, bt) output = split(String(take!(io)), '\n') length(output) >= 8 || println(output) # for better errors when this fails - @test lstrip(output[3])[1] == "┌" + @test lstrip(output[3])[1] == '┌' @test lstrip(lstrip(output[3])[4:end])[1:3] == "[1]" @test occursin("g28442", output[3]) - @test lstrip(output[5])[1] == "│" + @test lstrip(output[5])[1] == '│' @test lstrip(lstrip(output[5])[4:end])[1:3] == "[2]" @test occursin("f28442", output[5]) @test occursin("repeated 5000 more times", output[7]) diff --git a/test/stacktraces.jl b/test/stacktraces.jl index bc86479dbab4b..f6b2d9193e9c5 100644 --- a/test/stacktraces.jl +++ b/test/stacktraces.jl @@ -250,7 +250,7 @@ struct F49231{a,b,c,d,e,f,g} end stacktrace(catch_backtrace()) end str = sprint(Base.show_backtrace, st, context = (:limit=>true, :stacktrace_types_limited => Ref(false), :color=>true, :displaysize=>(50,105))) - @test contains(str, "[5] \e[0m\e[1mcollect_to!\e[22m\e[0m\e[1m(\e[22m\e[90mdest\e[39m::\e[0mVector\e[90m{…}\e[39m, \e[90mitr\e[39m::\e[0mBase.Generator\e[90m{…}\e[39m, \e[90moffs\e[39m::\e[0m$Int, \e[90mst\e[39m::\e[0mTuple\e[90m{…}\e[39m\e[0m\e[1m)\e[22m\n\e[90m") + @test contains(str, "[5] \e[0m\e[1mcollect_to!\e[22m\e[0m\e[1m(\e[22m\e[90mdest\e[39m::\e[0mVector\e[90m{…}\e[39m, \e[90mitr\e[39m::\e[0mBase.Generator\e[90m{…}\e[39m, \e[90moffs\e[39m::\e[0m$Int, \e[90mst\e[39m::\e[0mTuple\e[90m{…}\e[39m\e[0m\e[1m)\e[22m\n \e[90m") st = try F49231{Vector,Val{'}'},Vector{Vector{Vector{Vector}}},Tuple{Int,Int,Int,Int,Int,Int,Int},Int,Int,Int}()(1,2,3) From 4fcfb972a962cb1ad6fa661faee0b0b8bf792f76 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Thu, 12 Dec 2024 14:53:11 -0500 Subject: [PATCH 15/18] Remove reference to Core.Compiler --- base/errorshow.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index 3bcfa4a9d3d40..54aa9f07fa555 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -865,7 +865,7 @@ function show_backtrace(io::IO, t::Vector) filtered = t else # t is a raw trace requiring lookup - if t isa Vector{<:Union{Base.InterpreterIP,Core.Compiler.InterpreterIP,Ptr{Cvoid}}} + if t isa Vector{<:Union{Base.InterpreterIP,Ptr{Cvoid}}} frametrace = stacktrace(t) else frametrace = t From 8776c5253afb0e816095e036acbecb8030f0ef3c Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Thu, 12 Dec 2024 16:16:30 -0500 Subject: [PATCH 16/18] Fix test --- test/stacktraces.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/stacktraces.jl b/test/stacktraces.jl index cd99dcb75a482..512dd4121fe6b 100644 --- a/test/stacktraces.jl +++ b/test/stacktraces.jl @@ -255,7 +255,7 @@ struct F49231{a,b,c,d,e,f,g} end stacktrace(catch_backtrace()) end str = sprint(Base.show_backtrace, st, context = (:limit=>true, :stacktrace_types_limited => Ref(false), :color=>true, :displaysize=>(50,132))) - @test contains(str, "[2] \e[0m\e[1m(::$F49231{Vector, Val{…}, Vector{…}, NTuple{…}, $Int, $Int, $Int})\e[22m\e[0m\e[1m(\e[22m\e[90ma\e[39m::\e[0m$Int, \e[90mb\e[39m::\e[0m$Int, \e[90mc\e[39m::\e[0m$Int\e[0m\e[1m)\e[22m\n\e[90m") + @test contains(str, "[2] \e[0m\e[1m(::$F49231{Vector, Val{…}, Vector{…}, NTuple{…}, $Int, $Int, $Int})\e[22m\e[0m\e[1m(\e[22m\e[90ma\e[39m::\e[0m$Int, \e[90mb\e[39m::\e[0m$Int, \e[90mc\e[39m::\e[0m$Int\e[0m\e[1m)\e[22m\n \e[90m") end @testset "Base.StackTraces docstrings" begin From 210b0f37f02fba11d4d004dca72160fd95215444 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Tue, 17 Dec 2024 09:32:41 -0500 Subject: [PATCH 17/18] Adjust presentation to have a tick for each included frame --- base/errorshow.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index 54aa9f07fa555..3ceb677c74a02 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -700,10 +700,9 @@ function _backtrace_print_repetition_closings!(io::IO, i, current_cycles, frame_ frame_counter != end_line && break println(io) - line_length = (max_nested_cycles - nactive_cycles) + ndigits_max + 2 nactive_cycles -= 1 - printstyled(io, " ", "│" ^ nactive_cycles, "└", "─" ^ (line_length); color = :light_black) + printstyled(io, " ", "│" ^ nactive_cycles, "╰", "─" ^ (line_length); color = :light_black) printstyled(io, " repeated $repetitions more time", repetitions > 1 ? "s" : ""; color = :light_black, italic = true) pop!(current_cycles) @@ -793,7 +792,7 @@ function print_stackframe(io, i, frame::StackFrame, ndigits_max::Int, max_nested # repeated section bracket line 1 print(io, " ") - printstyled(io, "│" ^ (nactive_cycles - ncycle_starts); color = :light_black) + printstyled(io, "├" ^ (nactive_cycles - ncycle_starts); color = :light_black) printstyled(io, "┌" ^ ncycle_starts; color = :light_black) # frame number @@ -806,7 +805,7 @@ function print_stackframe(io, i, frame::StackFrame, ndigits_max::Int, max_nested # repeated section bracket line 2 print(io, " ") - printstyled(io, "│" ^ (nactive_cycles); color = :light_black) + printstyled(io, "│" ^ nactive_cycles; color = :light_black) # @ Module path / file : line print_module_path_file(io, modul, file, line; modulecolor, digit_align_width = digit_align_width - 1) From 9ea9df8d987080adb9796bee1d59461ded951a8c Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Tue, 17 Dec 2024 14:57:00 -0500 Subject: [PATCH 18/18] Fix test --- test/errorshow.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/errorshow.jl b/test/errorshow.jl index 0c1019e8f678e..b55d70b22ed5f 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -790,7 +790,7 @@ backtrace() @test lstrip(output[3])[1] == '┌' @test lstrip(lstrip(output[3])[4:end])[1:3] == "[1]" @test occursin("g28442", output[3]) - @test lstrip(output[5])[1] == '│' + @test lstrip(output[5])[1] == '├' @test lstrip(lstrip(output[5])[4:end])[1:3] == "[2]" @test occursin("f28442", output[5]) is_windows_32_bit = Sys.iswindows() && (Sys.WORD_SIZE == 32)