line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#include "backtrace.h" |
2
|
|
|
|
|
|
|
#include |
3
|
|
|
|
|
|
|
#include |
4
|
|
|
|
|
|
|
#include |
5
|
|
|
|
|
|
|
#include "PerlTrace.h" |
6
|
|
|
|
|
|
|
#include |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
using namespace panda; |
9
|
|
|
|
|
|
|
using panda::Backtrace; |
10
|
|
|
|
|
|
|
using xs::my_perl; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
#ifndef CvHASGV |
13
|
|
|
|
|
|
|
// for perls < 5.22 |
14
|
|
|
|
|
|
|
#define CvHASGV(cv) cBOOL(SvANY(cv)->xcv_gv_u.xcv_gv) |
15
|
|
|
|
|
|
|
#endif |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
namespace xs { |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
using PerlTraceSP = iptr; |
21
|
|
|
|
|
|
|
|
22
|
106
|
|
|
|
|
|
static string stringize_arg(SV* it) { |
23
|
106
|
|
|
|
|
|
string value; |
24
|
106
|
50
|
|
|
|
|
if (SvIS_FREED(it)) { value = "n/a"; } // already freed |
|
|
0
|
|
|
|
|
|
25
|
|
|
|
|
|
|
else { |
26
|
212
|
|
|
|
|
|
Sv sv(it); |
27
|
106
|
|
|
|
|
|
bool escape = false; |
28
|
106
|
100
|
|
|
|
|
if (!sv.defined()) { value += "undef"; } |
|
|
50
|
|
|
|
|
|
29
|
104
|
100
|
|
|
|
|
else if (sv.is_simple()) { |
30
|
100
|
50
|
|
|
|
|
Simple simple(sv); |
31
|
50
|
50
|
|
|
|
|
value = simple.as_string(); |
|
|
50
|
|
|
|
|
|
32
|
50
|
50
|
|
|
|
|
escape = !simple.is_like_number(); |
33
|
|
|
|
|
|
|
} |
34
|
|
|
|
|
|
|
else { |
35
|
|
|
|
|
|
|
char buff[32]; |
36
|
54
|
50
|
|
|
|
|
auto res = to_chars(buff, buff+32, uint64_t(it), 16); |
37
|
54
|
50
|
|
|
|
|
if (!res.ec) { |
38
|
54
|
|
|
|
|
|
escape = true; |
39
|
54
|
|
|
|
|
|
auto size = res.ptr - buff; |
40
|
108
|
50
|
|
|
|
|
string addr = string("(0x") + string(buff, static_cast(size)) + ")"; |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
41
|
|
|
|
|
|
|
|
42
|
108
|
|
|
|
|
|
string type = "UNKNOWN"; |
43
|
54
|
50
|
|
|
|
|
if (sv.is_io_ref()) { type = "IO"; } |
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
44
|
54
|
50
|
|
|
|
|
else if(sv.is_sub_ref()) { type = "CODE"; } |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
45
|
9
|
50
|
|
|
|
|
else if(sv.is_array_ref()) { type = "ARRAY"; } |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
46
|
7
|
50
|
|
|
|
|
else if(sv.is_hash_ref()) { type = "HASH"; } |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
47
|
2
|
50
|
|
|
|
|
else if(sv.is_stash()) { type = "STASH"; } |
|
|
0
|
|
|
|
|
|
48
|
2
|
50
|
|
|
|
|
else if(sv.is_ref()) { type = "SCALAR"; } |
|
|
50
|
|
|
|
|
|
49
|
|
|
|
|
|
|
|
50
|
54
|
100
|
|
|
|
|
if(sv.is_object_ref()) { |
51
|
3
|
50
|
|
|
|
|
addr = string("=") + type + addr; |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
52
|
6
|
50
|
|
|
|
|
Object obj(sv); |
53
|
3
|
50
|
|
|
|
|
type = obj.stash().effective_name(); |
|
|
50
|
|
|
|
|
|
54
|
|
|
|
|
|
|
} |
55
|
54
|
50
|
|
|
|
|
value = type + addr; |
|
|
50
|
|
|
|
|
|
56
|
|
|
|
|
|
|
} |
57
|
54
|
0
|
|
|
|
|
else { value = "*ERROR*"; } |
58
|
|
|
|
|
|
|
} |
59
|
106
|
100
|
|
|
|
|
value = (escape ? "'" : "") + value + (escape ? "'" : ""); |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
60
|
|
|
|
|
|
|
} |
61
|
106
|
|
|
|
|
|
return value; |
62
|
|
|
|
|
|
|
} |
63
|
|
|
|
|
|
|
|
64
|
139
|
|
|
|
|
|
static std::vector get_args(const PERL_CONTEXT* cx) { |
65
|
139
|
|
|
|
|
|
std::vector r; |
66
|
139
|
100
|
|
|
|
|
if (CxTYPE(cx) == CXt_SUB && CxHASARGS(cx)) { |
|
|
50
|
|
|
|
|
|
67
|
|
|
|
|
|
|
/* slot 0 of the pad contains the original @_ */ |
68
|
91
|
|
|
|
|
|
AV * const ary = MUTABLE_AV(AvARRAY(MUTABLE_AV(PadlistARRAY(CvPADLIST(cx->blk_sub.cv))[cx->blk_sub.olddepth+1]))[0]); |
69
|
91
|
50
|
|
|
|
|
auto args_count = av_top_index(ary); |
70
|
|
|
|
|
|
|
//auto off = AvARRAY(ary) - AvALLOC(ary); |
71
|
91
|
|
|
|
|
|
auto off = 0; |
72
|
91
|
|
|
|
|
|
auto arr = AvARRAY(ary); |
73
|
91
|
|
|
|
|
|
auto last = args_count + off; |
74
|
197
|
100
|
|
|
|
|
for(decltype(off) i = off; i <= last; ++i) { |
75
|
106
|
|
|
|
|
|
auto it = arr[i]; |
76
|
106
|
50
|
|
|
|
|
r.emplace_back(stringize_arg(it)); |
|
|
50
|
|
|
|
|
|
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
} |
79
|
139
|
|
|
|
|
|
return r; |
80
|
|
|
|
|
|
|
} |
81
|
|
|
|
|
|
|
|
82
|
25
|
|
|
|
|
|
static PerlTraceSP get_trace() noexcept { |
83
|
|
|
|
|
|
|
dTHX; |
84
|
50
|
|
|
|
|
|
std::vector frames; |
85
|
25
|
|
|
|
|
|
I32 level = 0; |
86
|
25
|
|
|
|
|
|
const PERL_CONTEXT *dbcx = nullptr; |
87
|
25
|
|
|
|
|
|
const PERL_CONTEXT* cx = caller_cx(level, &dbcx); |
88
|
164
|
100
|
|
|
|
|
while (cx) { |
89
|
139
|
50
|
|
|
|
|
auto pv_raw = CopSTASHPV(cx->blk_oldcop); |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
90
|
139
|
50
|
|
|
|
|
auto file = CopFILE(cx->blk_oldcop); |
91
|
139
|
|
|
|
|
|
auto line = CopLINE(cx->blk_oldcop); |
92
|
|
|
|
|
|
|
|
93
|
278
|
|
|
|
|
|
xs::Sub sub; |
94
|
278
|
|
|
|
|
|
string name; |
95
|
278
|
|
|
|
|
|
string library; |
96
|
139
|
100
|
|
|
|
|
if ((CxTYPE(cx) == CXt_SUB || CxTYPE(cx) == CXt_FORMAT)) { |
|
|
50
|
|
|
|
|
|
97
|
91
|
50
|
|
|
|
|
if (CvHASGV(dbcx->blk_sub.cv)) { |
98
|
182
|
|
|
|
|
|
xs::Sub sub(dbcx->blk_sub.cv); |
99
|
91
|
|
|
|
|
|
name = sub.name(); |
100
|
|
|
|
|
|
|
// just sub.stash().name() can't be called, as it omits |
101
|
|
|
|
|
|
|
// the effects of Sub::Name |
102
|
91
|
|
|
|
|
|
library = sub.glob().effective_stash().name(); |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
} else { |
105
|
0
|
|
|
|
|
|
name = "(unknown)"; |
106
|
91
|
|
|
|
|
|
} |
107
|
|
|
|
|
|
|
} else { |
108
|
48
|
|
|
|
|
|
name = "(eval)"; |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
|
111
|
139
|
100
|
|
|
|
|
if (!library && pv_raw) { library = pv_raw; }; |
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
112
|
|
|
|
|
|
|
|
113
|
278
|
|
|
|
|
|
auto args = get_args(cx); |
114
|
|
|
|
|
|
|
|
115
|
278
|
|
|
|
|
|
StackframeSP frame(new Stackframe()); |
116
|
139
|
|
|
|
|
|
frame->library = library; |
117
|
139
|
|
|
|
|
|
frame->file = file; |
118
|
139
|
|
|
|
|
|
frame->line_no = line; |
119
|
139
|
|
|
|
|
|
frame->name = name; |
120
|
139
|
|
|
|
|
|
frame->args = std::move(args); |
121
|
139
|
|
|
|
|
|
frames.emplace_back(std::move(frame)); |
122
|
|
|
|
|
|
|
|
123
|
139
|
|
|
|
|
|
++level; |
124
|
139
|
|
|
|
|
|
cx = caller_cx(level, &dbcx); |
125
|
|
|
|
|
|
|
} |
126
|
25
|
|
|
|
|
|
return new PerlTrace(std::move(frames)); |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
Sv::payload_marker_t backtrace_c_marker{}; |
132
|
|
|
|
|
|
|
Sv::payload_marker_t backtrace_perl_marker{}; |
133
|
|
|
|
|
|
|
|
134
|
17
|
|
|
|
|
|
int payload_backtrace_c_free(pTHX_ SV*, MAGIC* mg) { |
135
|
17
|
50
|
|
|
|
|
if (mg->mg_virtual == &backtrace_c_marker) { |
136
|
17
|
|
|
|
|
|
auto* payload = static_cast((void*)mg->mg_ptr); |
137
|
17
|
50
|
|
|
|
|
delete payload; |
138
|
|
|
|
|
|
|
} |
139
|
17
|
|
|
|
|
|
return 0; |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
|
142
|
13
|
|
|
|
|
|
string _get_backtrace_string(Ref except, bool include_c_trace) { |
143
|
13
|
|
|
|
|
|
string result; |
144
|
26
|
|
|
|
|
|
auto it = except.value(); |
145
|
13
|
100
|
|
|
|
|
if (include_c_trace) { |
146
|
24
|
|
|
|
|
|
string c_trace; |
147
|
12
|
100
|
|
|
|
|
if (it.payload_exists(&backtrace_c_marker)) { |
148
|
11
|
|
|
|
|
|
auto payload = it.payload(&backtrace_c_marker); |
149
|
11
|
|
|
|
|
|
auto bt = static_cast(payload.ptr); |
150
|
22
|
|
|
|
|
|
auto bt_info = bt->get_backtrace_info(); |
151
|
11
|
50
|
|
|
|
|
if (bt_info) { |
152
|
11
|
50
|
|
|
|
|
c_trace += "C backtrace:\n"; |
153
|
11
|
50
|
|
|
|
|
c_trace += bt_info->to_string(); |
154
|
|
|
|
|
|
|
} |
155
|
|
|
|
|
|
|
} |
156
|
12
|
100
|
|
|
|
|
if (!c_trace) { result = "\n"; } |
|
|
50
|
|
|
|
|
|
157
|
11
|
50
|
|
|
|
|
else { result = c_trace; } |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
13
|
100
|
|
|
|
|
if (it.payload_exists(&backtrace_perl_marker)) { |
161
|
12
|
50
|
|
|
|
|
result += "Perl backtrace:\n"; |
162
|
12
|
|
|
|
|
|
auto payload = it.payload(&backtrace_perl_marker); |
163
|
12
|
50
|
|
|
|
|
auto bt = xs::in(payload.obj); |
164
|
83
|
100
|
|
|
|
|
for (const auto& frame : bt->get_frames() ) { |
165
|
71
|
50
|
|
|
|
|
result += frame->library + "::" + frame->name + " at " + frame->file + ":" + string::from_number(frame->line_no, 10) + "\n"; |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
else { |
169
|
1
|
50
|
|
|
|
|
result += ""; |
170
|
|
|
|
|
|
|
} |
171
|
26
|
|
|
|
|
|
return result; |
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
|
174
|
12
|
50
|
|
|
|
|
string get_backtrace_string(Ref except) { return _get_backtrace_string(except, true); } |
175
|
1
|
50
|
|
|
|
|
string get_backtrace_string_pp(Ref except) { return _get_backtrace_string(except, false); } |
176
|
|
|
|
|
|
|
|
177
|
2
|
|
|
|
|
|
panda::iptr get_backtrace(Ref except) { |
178
|
2
|
|
|
|
|
|
panda::iptr r; |
179
|
4
|
|
|
|
|
|
auto it = except.value(); |
180
|
2
|
50
|
|
|
|
|
if (it.payload_exists(&backtrace_perl_marker)) { |
181
|
2
|
50
|
|
|
|
|
r = new DualTrace(); |
|
|
50
|
|
|
|
|
|
182
|
2
|
|
|
|
|
|
auto payload = it.payload(&backtrace_perl_marker); |
183
|
2
|
50
|
|
|
|
|
auto bt = xs::in(payload.obj); |
184
|
2
|
|
|
|
|
|
r->perl_trace = bt; |
185
|
|
|
|
|
|
|
} |
186
|
2
|
50
|
|
|
|
|
if (r && it.payload_exists(&backtrace_c_marker)) { |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
187
|
2
|
|
|
|
|
|
auto payload = it.payload(&backtrace_c_marker); |
188
|
2
|
|
|
|
|
|
auto bt = static_cast(payload.ptr); |
189
|
2
|
|
|
|
|
|
r->c_trace = bt->get_backtrace_info(); |
190
|
|
|
|
|
|
|
} |
191
|
4
|
|
|
|
|
|
return r; |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
|
194
|
1
|
|
|
|
|
|
panda::iptr create_backtrace() { |
195
|
1
|
50
|
|
|
|
|
panda::iptr r(new DualTrace()); |
|
|
50
|
|
|
|
|
|
196
|
2
|
|
|
|
|
|
Backtrace bt; |
197
|
1
|
|
|
|
|
|
r->c_trace = bt.get_backtrace_info(); |
198
|
1
|
|
|
|
|
|
r->perl_trace = get_trace(); |
199
|
2
|
|
|
|
|
|
return r; |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
26
|
|
|
|
|
|
Ref _is_safe_to_wrap(Sv& ex, bool add_frame_info) { |
203
|
26
|
|
|
|
|
|
Ref ref; |
204
|
26
|
100
|
|
|
|
|
if (!ex.is_ref()) { |
205
|
|
|
|
|
|
|
/* try to mimic perl string error, i.e. "my-error at t/06-c-exceptions.t line 10." |
206
|
|
|
|
|
|
|
* we need that as when an exception is thrown from C-code, we wrap it into object |
207
|
|
|
|
|
|
|
* and frame info isn't addeded by Perl. |
208
|
|
|
|
|
|
|
* |
209
|
|
|
|
|
|
|
* When an exception is thrown from Perl, Perl already added frame info. |
210
|
|
|
|
|
|
|
*/ |
211
|
13
|
100
|
|
|
|
|
if (add_frame_info && ex.is_simple()) { |
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
212
|
|
|
|
|
|
|
|
213
|
8
|
50
|
|
|
|
|
auto str = Simple(ex).as_string(); |
|
|
50
|
|
|
|
|
|
214
|
4
|
50
|
|
|
|
|
bool ends_with_newline = str.size() && str[str.size() - 1] == '\n'; |
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
215
|
4
|
100
|
|
|
|
|
if (!ends_with_newline) { |
216
|
3
|
50
|
|
|
|
|
auto messed = Perl_mess_sv(aTHX_ ex, false); |
217
|
3
|
50
|
|
|
|
|
ref = Stash("Exception::Backtrace").call("new", Simple(messed)); |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
218
|
|
|
|
|
|
|
} |
219
|
|
|
|
|
|
|
} |
220
|
13
|
100
|
|
|
|
|
if (!ref) { |
221
|
13
|
50
|
|
|
|
|
ref = Stash("Exception::Backtrace").call("new", ex); |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
else { |
225
|
26
|
50
|
|
|
|
|
Ref tmp_ref(ex); |
226
|
26
|
|
|
|
|
|
auto it = tmp_ref.value(); |
227
|
13
|
50
|
|
|
|
|
if (!(it.is_scalar() && it.readonly())) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
228
|
12
|
50
|
|
|
|
|
ref = tmp_ref; |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
} |
231
|
26
|
|
|
|
|
|
return ref; |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
}; |
234
|
|
|
|
|
|
|
|
235
|
19
|
|
|
|
|
|
static void attach_backtraces(Ref except, const PerlTraceSP& perl_trace) { |
236
|
38
|
|
|
|
|
|
auto it = except.value(); |
237
|
19
|
100
|
|
|
|
|
if (!it.payload_exists(&backtrace_c_marker)) { |
238
|
13
|
50
|
|
|
|
|
auto bt = new Backtrace(); |
239
|
13
|
50
|
|
|
|
|
it.payload_attach(bt, &backtrace_c_marker); |
240
|
|
|
|
|
|
|
} |
241
|
19
|
100
|
|
|
|
|
if (!it.payload_exists(&backtrace_perl_marker)) { |
242
|
13
|
50
|
|
|
|
|
it.payload_attach(xs::out(perl_trace.get()), &backtrace_perl_marker); |
|
|
50
|
|
|
|
|
|
243
|
|
|
|
|
|
|
} |
244
|
19
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
|
246
|
21
|
|
|
|
|
|
Sv safe_wrap_exception(Sv ex) { |
247
|
42
|
50
|
|
|
|
|
auto ref = _is_safe_to_wrap(ex, false); |
248
|
21
|
100
|
|
|
|
|
if (ref) { |
249
|
40
|
|
|
|
|
|
auto perl_traces = get_trace(); |
250
|
20
|
|
|
|
|
|
auto& frames = perl_traces->get_frames(); |
251
|
238
|
50
|
|
|
|
|
bool in_destroy = std::any_of(frames.begin(), frames.end(), [](auto& frame) { return frame->name == "DESTROY"; } ); |
252
|
20
|
100
|
|
|
|
|
if (in_destroy) { |
253
|
|
|
|
|
|
|
// we don't want to corrupt Perl's warning with Exception::Backtrace handler, instead let it warns |
254
|
|
|
|
|
|
|
// to the origin of the exception |
255
|
1
|
50
|
|
|
|
|
return Simple::undef; |
256
|
|
|
|
|
|
|
} |
257
|
19
|
50
|
|
|
|
|
attach_backtraces(ref, perl_traces); |
|
|
50
|
|
|
|
|
|
258
|
19
|
50
|
|
|
|
|
return Sv(ref); |
259
|
|
|
|
|
|
|
} |
260
|
1
|
50
|
|
|
|
|
return Simple::undef; |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
|
264
|
6
|
|
|
|
|
|
void install_exception_processor() { |
265
|
17
|
50
|
|
|
|
|
add_exception_processor([](Sv& ex) -> Sv { |
266
|
14
|
50
|
|
|
|
|
auto ref = _is_safe_to_wrap(ex, true); |
|
|
100
|
|
|
|
|
|
267
|
5
|
50
|
|
|
|
|
if (ref) { |
268
|
10
|
|
|
|
|
|
auto it = ref.value(); |
269
|
5
|
100
|
|
|
|
|
if (!it.payload_exists(&backtrace_c_marker)) { |
270
|
8
|
|
|
|
|
|
try { throw; } |
271
|
4
|
50
|
|
|
|
|
catch (const panda::Backtrace& err) { |
272
|
|
|
|
|
|
|
// reuse existing c trace |
273
|
2
|
50
|
|
|
|
|
it.payload_attach(new Backtrace(err), &backtrace_c_marker); |
|
|
50
|
|
|
|
|
|
274
|
|
|
|
|
|
|
} |
275
|
4
|
50
|
|
|
|
|
catch (...) { |
276
|
|
|
|
|
|
|
// add new c trace |
277
|
2
|
50
|
|
|
|
|
it.payload_attach(new Backtrace(), &backtrace_c_marker); |
|
|
50
|
|
|
|
|
|
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
} |
280
|
5
|
100
|
|
|
|
|
if (!it.payload_exists(&backtrace_perl_marker)) { |
281
|
8
|
|
|
|
|
|
auto bt = get_trace(); |
282
|
4
|
50
|
|
|
|
|
it.payload_attach(xs::out(bt), &backtrace_perl_marker); |
|
|
50
|
|
|
|
|
|
283
|
|
|
|
|
|
|
} |
284
|
5
|
50
|
|
|
|
|
return Sv(ref); |
285
|
|
|
|
|
|
|
} |
286
|
0
|
0
|
|
|
|
|
return ex; |
287
|
6
|
50
|
|
|
|
|
}); |
288
|
6
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
|
290
|
24
|
50
|
|
|
|
|
} |
|
|
50
|
|
|
|
|
|