-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
call_stack.cr
105 lines (87 loc) · 2.83 KB
/
call_stack.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
{% if flag?(:win32) %}
require "./call_stack/stackwalk"
{% elsif flag?(:interpreted) %}
require "./call_stack/interpreter"
{% elsif flag?(:wasm32) %}
require "./call_stack/null"
{% else %}
require "./call_stack/libunwind"
{% end %}
# Returns the current execution stack as an array containing strings
# usually in the form file:line:column or file:line:column in 'method'.
def caller : Array(String)
Exception::CallStack.new.printable_backtrace
end
# :nodoc:
struct Exception::CallStack
# Compute current directory at the beginning so filenames
# are always shown relative to the *starting* working directory.
private CURRENT_DIR = Process::INITIAL_PWD.try { |dir| Path[dir] }
@@skip = [] of String
def self.skip(filename) : Nil
@@skip << filename
end
skip(__FILE__)
@callstack : Array(Void*)
@backtrace : Array(String)?
def initialize
@callstack = CallStack.unwind
end
def printable_backtrace : Array(String)
@backtrace ||= decode_backtrace
end
private def decode_backtrace
{% if flag?(:wasm32) %}
[] of String
{% else %}
show_full_info = ENV["CRYSTAL_CALLSTACK_FULL_INFO"]? == "1"
@callstack.compact_map do |ip|
pc = CallStack.decode_address(ip)
file, line_number, column_number = CallStack.decode_line_number(pc)
if file && file != "??"
next if @@skip.includes?(file)
# Turn to relative to the current dir, if possible
if current_dir = CURRENT_DIR
if rel = Path[file].relative_to?(current_dir)
rel = rel.to_s
file = rel unless rel.starts_with?("..")
end
end
file_line_column = file
unless line_number == 0
file_line_column = "#{file_line_column}:#{line_number}"
file_line_column = "#{file_line_column}:#{column_number}" unless column_number == 0
end
end
if name = CallStack.decode_function_name(pc)
function = name
elsif frame = CallStack.decode_frame(ip)
_, function, file = frame
# Crystal methods (their mangled name) start with `*`, so
# we remove that to have less clutter in the output.
function = function.lchop('*')
else
function = "??"
end
if file_line_column
if show_full_info && (frame = CallStack.decode_frame(ip))
_, sname, _ = frame
line = "#{file_line_column} in '#{sname}'"
else
line = "#{file_line_column} in '#{function}'"
end
else
if file == "??" && function == "??"
line = "???"
else
line = "#{file} in '#{function}'"
end
end
if show_full_info
line = "#{line} at 0x#{ip.address.to_s(16)}"
end
line
end
{% end %}
end
end