File Coverage

src/xs/backtrace.cc
Criterion Covered Total %
statement 174 176 98.8
branch 190 324 58.6
condition n/a
subroutine n/a
pod n/a
total 364 500 72.8


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