-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
raise.cr
241 lines (203 loc) · 7.21 KB
/
raise.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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
require "c/stdio"
require "c/stdlib"
require "exception/call_stack"
Exception::CallStack.skip(__FILE__)
private struct LEBReader
def initialize(@data : UInt8*)
end
def data : UInt8*
@data
end
def read_uint8 : UInt8
value = @data.value
@data += 1
value
end
def read_uint32 : UInt32
value = @data.as(UInt32*).value
@data += 4
value
end
def read_uleb128 : UInt64
result = 0_u64
shift = 0
while true
byte = read_uint8
result |= ((0x7f_u64 & byte) << shift)
break if (byte & 0x80_u8) == 0
shift += 7
end
result
end
end
private def traverse_eh_table(leb, start, ip, actions)
# Ref: https://chromium.googlesource.com/native_client/pnacl-libcxxabi/+/master/src/cxa_personality.cpp
throw_offset = ip - 1 - start
# puts "Personality - actions : #{actions}, start: #{start}, ip: #{ip}, throw_offset: #{throw_offset}"
lp_start_encoding = leb.read_uint8 # @LPStart encoding
if lp_start_encoding != 0xff_u8
Crystal::System.print_error "Unexpected encoding for LPStart: #{lp_start_encoding}\n"
LibC.exit 1
end
if leb.read_uint8 != 0xff_u8 # @TType encoding
leb.read_uleb128 # @TType base offset
end
cs_encoding = leb.read_uint8 # CS Encoding (1: uleb128, 3: uint32)
if cs_encoding != 1 && cs_encoding != 3
Crystal::System.print_error "Unexpected CS encoding: #{cs_encoding}\n"
LibC.exit 1
end
cs_table_length = leb.read_uleb128 # CS table length
cs_table_end = leb.data + cs_table_length
while leb.data < cs_table_end
cs_offset = cs_encoding == 3 ? leb.read_uint32 : leb.read_uleb128
cs_length = cs_encoding == 3 ? leb.read_uint32 : leb.read_uleb128
cs_addr = cs_encoding == 3 ? leb.read_uint32 : leb.read_uleb128
action = leb.read_uleb128
# puts "cs_offset: #{cs_offset}, cs_length: #{cs_length}, cs_addr: #{cs_addr}, action: #{action}"
if cs_addr != 0
if cs_offset <= throw_offset && throw_offset <= cs_offset + cs_length
if actions.includes? LibUnwind::Action::SEARCH_PHASE
# puts "found"
return LibUnwind::ReasonCode::HANDLER_FOUND
end
if actions.includes? LibUnwind::Action::HANDLER_FRAME
unwind_ip = start + cs_addr
yield unwind_ip
# puts "install"
return LibUnwind::ReasonCode::INSTALL_CONTEXT
end
end
end
end
return nil
end
{% if flag?(:win32) %}
require "exception/lib_unwind"
lib LibC
fun _CxxThrowException(ex : Void*, throw_info : Void*) : NoReturn
end
@[Primitive(:throw_info)]
def throw_info : Void*
end
# Raises the *exception*.
#
# This will set the exception's callstack if it hasn't been already.
# Re-raising a previously caught exception won't replace the callstack.
def raise(exception : Exception) : NoReturn
{% if flag?(:debug_raise) %}
STDERR.puts
STDERR.puts "Attempting to raise: "
exception.inspect_with_backtrace(STDERR)
{% end %}
LibC._CxxThrowException(pointerof(exception).as(Void*), throw_info)
end
# :nodoc:
@[Raises]
fun __crystal_raise(unwind_ex : LibUnwind::Exception*) : NoReturn
LibC.printf("EXITING: __crystal_raise called")
LibC.exit(1)
end
{% elsif flag?(:arm) %}
# On ARM EHABI the personality routine is responsible for actually
# unwinding a single stack frame before returning (ARM EHABI Sec. 6.1).
private macro __crystal_continue_unwind
if LibUnwind.__gnu_unwind_frame(ucb, context) != LibUnwind::ReasonCode::NO_REASON
return LibUnwind::ReasonCode::FAILURE
end
#puts "continue"
return LibUnwind::ReasonCode::CONTINUE_UNWIND
end
# :nodoc:
fun __crystal_personality(state : LibUnwind::State, ucb : LibUnwind::ControlBlock*, context : LibUnwind::Context) : LibUnwind::ReasonCode
# puts "\n__crystal_personality(#{state}, #{ucb}, #{context})"
case LibUnwind::State.new(state.value & LibUnwind::State::ACTION_MASK.value)
when LibUnwind::State::VIRTUAL_UNWIND_FRAME
if state.force_unwind?
__crystal_continue_unwind
else
actions = LibUnwind::Action::SEARCH_PHASE
end
when LibUnwind::State::UNWIND_FRAME_STARTING
actions = LibUnwind::Action::HANDLER_FRAME
when LibUnwind::State::UNWIND_FRAME_RESUME
__crystal_continue_unwind
else
exit(-1)
end
if state.force_unwind?
actions |= LibUnwind::Action::FORCE_UNWIND
end
start = __crystal_get_region_start(ucb)
lsd = __crystal_get_language_specific_data(ucb)
ip = __crystal_unwind_get_ip(context)
leb = LEBReader.new(lsd)
reason = traverse_eh_table(leb, start, ip, actions) do |unwind_ip|
__crystal_unwind_set_gr(context, LibUnwind::EH_REGISTER_0, ucb.address.to_u32)
__crystal_unwind_set_gr(context, LibUnwind::EH_REGISTER_1, ucb.value.exception_type_id.to_u32)
__crystal_unwind_set_ip(context, unwind_ip)
end
return reason if reason
__crystal_continue_unwind
end
{% else %}
# :nodoc:
fun __crystal_personality(version : Int32, actions : LibUnwind::Action, exception_class : UInt64, exception_object : LibUnwind::Exception*, context : Void*) : LibUnwind::ReasonCode
start = LibUnwind.get_region_start(context)
ip = LibUnwind.get_ip(context)
lsd = LibUnwind.get_language_specific_data(context)
leb = LEBReader.new(lsd)
reason = traverse_eh_table(leb, start, ip, actions) do |unwind_ip|
LibUnwind.set_gr(context, LibUnwind::EH_REGISTER_0, exception_object.address)
LibUnwind.set_gr(context, LibUnwind::EH_REGISTER_1, exception_object.value.exception_type_id)
LibUnwind.set_ip(context, unwind_ip)
end
return reason if reason
return LibUnwind::ReasonCode::CONTINUE_UNWIND
end
{% end %}
{% unless flag?(:win32) %}
# :nodoc:
@[Raises]
fun __crystal_raise(unwind_ex : LibUnwind::Exception*) : NoReturn
ret = LibUnwind.raise_exception(unwind_ex)
Crystal::System.print_error "Failed to raise an exception: %s\n", ret.to_s
Exception::CallStack.print_backtrace
Crystal::System.print_exception("\nTried to raise:", unwind_ex.value.exception_object.as(Exception))
LibC.exit(ret)
end
# :nodoc:
fun __crystal_get_exception(unwind_ex : LibUnwind::Exception*) : UInt64
unwind_ex.value.exception_object.address
end
# Raises the *exception*.
#
# This will set the exception's callstack if it hasn't been already.
# Re-raising a previously caught exception won't replace the callstack.
def raise(exception : Exception) : NoReturn
{% if flag?(:debug_raise) %}
STDERR.puts
STDERR.puts "Attempting to raise: "
exception.inspect_with_backtrace(STDERR)
{% end %}
exception.callstack ||= Exception::CallStack.new
unwind_ex = Pointer(LibUnwind::Exception).malloc
unwind_ex.value.exception_class = LibC::SizeT.zero
unwind_ex.value.exception_cleanup = LibC::SizeT.zero
unwind_ex.value.exception_object = exception.as(Void*)
unwind_ex.value.exception_type_id = exception.crystal_type_id
__crystal_raise(unwind_ex)
end
{% end %}
# Raises an Exception with the *message*.
def raise(message : String) : NoReturn
raise Exception.new(message)
end
# :nodoc:
fun __crystal_raise_string(message : UInt8*)
raise String.new(message)
end
# :nodoc:
fun __crystal_raise_overflow : NoReturn
raise OverflowError.new
end