File Coverage

blib/lib/Parse/StackTrace.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Parse::StackTrace;
2 1     1   2238 use 5.006;
  1         4  
  1         32  
3 1     1   468 use Moose;
  0            
  0            
4             use Parse::StackTrace::Exceptions;
5             use Exception::Class;
6             use List::Util qw(max min);
7             use Scalar::Util qw(blessed);
8              
9             our $VERSION = '0.08';
10              
11             has 'threads' => (is => 'ro', isa => 'ArrayRef[Parse::StackTrace::Thread]',
12             required => 1);
13             has 'binary' => (is => 'ro', isa => 'Str|Undef');
14             has 'text_lines' => (is => 'ro', isa => 'ArrayRef[Str]', required => 1);
15              
16             # Defaults for StackTrace types that don't define these.
17             use constant BIN_REGEX => '';
18             use constant IGNORE_LINES => {};
19              
20             our $WHITESPACE_ONLY = qr/^\s*$/;
21              
22             #####################
23             # Parsing Functions #
24             #####################
25              
26             sub parse {
27             my $class = shift;
28             # If you call parse() directly on this class, then we use the "type"
29             # parameter to determine what type of StackTrace we're parsing.
30             if ($class eq 'Parse::StackTrace') {
31             my %params = @_;
32             my $types = $params{'types'};
33             die "You must specify trace types" if !$types || !scalar @$types;
34             my $trace;
35             foreach my $type (@$types) {
36             my $parser = $class->_class("Type::$type");
37             $trace = $parser->parse(@_);
38             return $trace if $trace;
39             }
40            
41             return undef;
42             }
43            
44             # For subclasses
45             return $class->_do_parse(@_);
46             }
47              
48             sub _do_parse {
49             my ($class, %params) = @_;
50             my $text = $params{text};
51             my $debug = $params{debug};
52            
53             die "You must specify a value for the 'text' argument" if !defined $text;
54             if ($text !~ $class->HAS_TRACE) {
55             return undef;
56             }
57            
58             my $binary;
59             if ($class->BIN_REGEX and $text =~ $class->BIN_REGEX) {
60             $binary = $1;
61             }
62              
63             my ($threads, $trace_lines) = $class->_parse_text($text, $debug);
64             my $trace = $class->new(threads => $threads, binary => $binary,
65             text_lines => $trace_lines);
66             return $trace;
67             }
68              
69             sub _parse_text {
70             my ($class, $text, $debug) = @_;
71            
72             my @lines = split(/\r?\n/, $text);
73             my @threads;
74             my $default_thread = $class->thread_class->new();
75             my $current_thread = $default_thread;
76             my $current_end_line = 0;
77             print STDERR "Current Thread: Default\n" if $debug;
78             while (scalar @lines) {
79             my $lines_start_size = scalar @lines;
80            
81             my $frame_lines = $class->_get_next_trace_block(\@lines);
82            
83             my $lines_read = $lines_start_size - scalar(@lines);
84             my $current_start_line = $current_end_line + 1;
85             $current_end_line += $lines_read;
86            
87             if ($debug) {
88             my $joined_lines = join("\n", @$frame_lines);
89             print STDERR "Trace Line(s) $current_start_line-$current_end_line:",
90             " [$joined_lines]\n";
91             }
92            
93             next if (scalar(@$frame_lines) == 1
94             and ($frame_lines->[0] =~ $WHITESPACE_ONLY
95             or $class->_ignore_line($frame_lines->[0])));
96              
97             $current_thread = $class->_handle_block(
98             start_line_number => $current_start_line,
99             end_line_number => $current_end_line,
100             frame_lines => $frame_lines,
101             thread => $current_thread,
102             threads => \@threads,
103             debug => $debug,
104             lines => \@lines,
105             );
106             }
107            
108             # Don't include any threads that don't have frames.
109             @threads = grep { scalar @{ $_->frames } } @threads;
110            
111             @threads = ($default_thread) if not @threads;
112              
113             my @thread_starts = $threads[0]->starting_line;
114             # Sometimes default_thread isn't in the final @threads, but if we parsed
115             # frames into it, then it should be considered part of the trace text.
116             if ($default_thread->has_starting_line) {
117             push(@thread_starts, $default_thread->starting_line);
118             }
119             my $trace_start = min(@thread_starts) - 1;
120             my $trace_end = $threads[-1]->ending_line - 1;
121             my @trace_lines = (split(/\r?\n/, $text))[$trace_start..$trace_end];
122              
123             return (\@threads, \@trace_lines);
124             }
125              
126             sub _handle_block {
127             my ($class, %params) = @_;
128             my ($frame_lines, $current_thread, $threads, $start, $end, $lines, $debug) =
129             @params{qw(frame_lines thread threads start_line_number end_line_number
130             lines debug)};
131              
132             if (my ($number, $description) = $class->_line_starts_thread($frame_lines->[0])) {
133             $current_thread = $class->thread_class->new(
134             number => $number, description => $description,
135             starting_line => $start);
136             push(@$threads, $current_thread);
137             print STDERR "Current Thread: " . $current_thread->number . "\n"
138             if $debug;
139             }
140             elsif ($frame_lines->[0] =~ $class->HAS_TRACE) {
141             # For the default thread, we have to set its starting line as soon
142             # as we parse a frame in it.
143             if (!$current_thread->has_starting_line) {
144             $current_thread->starting_line($start);
145             }
146            
147             my $thread_end = $end;
148             # If the next line is ignored, it's still part of the thread
149             # in terms of its textual content, so we want to make the thread
150             # end on that line instead.
151             if ($lines->[0] and $class->_ignore_line($lines->[0])) {
152             $thread_end++;
153             }
154             $current_thread->ending_line($thread_end);
155              
156             my $frame;
157             eval {
158             $frame = $class->frame_class->parse(lines => $frame_lines,
159             debug => $debug);
160             };
161             my $e;
162             if ($e = Exception::Class->caught('Parse::StackTrace::Exception::NotAFrame')) {
163             warn $e;
164             }
165             elsif ($e = Exception::Class->caught) {
166             (blessed $e and $e->isa('Exception::Class')) ? $e->rethrow : die $e;
167             }
168             else {
169             $current_thread->add_frame($frame);
170             }
171             }
172            
173             return $current_thread;
174             }
175              
176             sub _get_next_trace_block {
177             my ($class, $lines) = @_;
178             my @frame_lines = (shift @$lines);
179             if ($frame_lines[0] =~ $class->HAS_TRACE) {
180             while (@$lines) {
181             my $next_line = $class->_get_next_frame_line($lines);
182             last if not $next_line;
183             push(@frame_lines, $next_line);
184             }
185             }
186            
187             return \@frame_lines;
188             }
189              
190             # Returns the next line only if it's a *fragment* of a stack frame.
191             # If it's the start of a new frame, a new thread, or a line that we
192             # ignore, then we return nothing.
193             sub _get_next_frame_line {
194             my ($class, $lines) = @_;
195             my $line = $lines->[0];
196             if ($class->_next_line_ends_frame($line)) {
197             return undef
198             }
199             return shift @$lines
200             }
201              
202             sub _next_line_ends_frame {
203             my ($class, $line) = @_;
204             if ($line =~ $WHITESPACE_ONLY or $class->_ignore_line($line)
205             or $line =~ $class->HAS_TRACE
206             or $class->_line_starts_thread($line))
207             {
208             return 1;
209             }
210             return 0;
211             }
212              
213             # By default we don't support threads.
214             sub _line_starts_thread { return (); }
215              
216             sub _ignore_line {
217             my ($class, $line) = @_;
218             return grep($_ eq $line, $class->IGNORE_LINES) ? 1 : 0;
219             }
220              
221             #####################
222             # Complex Accessors #
223             #####################
224              
225             sub text {
226             my $self = shift;
227             return join("\n", @{ $self->text_lines });
228             }
229              
230             sub thread_number {
231             my ($self, $number) = @_;
232             my ($thread) = grep { defined $_->number and $_->number == $number }
233             @{ $self->threads };
234             return $thread;
235             }
236              
237             sub thread_with_crash {
238             my $self = shift;
239             foreach my $thread (@{ $self->threads }) {
240             return $thread if defined $thread->frame_with_crash;
241             }
242             return undef;
243             }
244              
245             ####################
246             # Subclass Helpers #
247             ####################
248              
249             sub thread_class { return shift->_class('Thread') }
250             sub frame_class { return shift->_class('Frame') }
251              
252             sub _class {
253             my ($invocant, $what) = @_;
254             my $class = ref $invocant || $invocant;
255             my $module = $class . '::' . $what;
256             my $file = $module;
257             $file =~ s{::}{/}g;
258             eval { require "$file.pm" }
259             || die("Error requiring $module: $@");
260             return $module;
261             }
262              
263             __PACKAGE__->meta->make_immutable;
264              
265             1;
266              
267             __END__
268              
269             =head1 NAME
270              
271             Parse::StackTrace - Parse the text representation of a stack trace into an
272             object.
273              
274             =head1 SYNOPSIS
275              
276             my $trace = Parse::StackTrace->parse(types => ['GDB', 'Python'],
277             text => $text,
278             debug => 1);
279             my $thread = $trace->thread_number(1);
280             my $frame = $thread->frame_number(0);
281              
282             =head1 DESCRIPTION
283              
284             This module parses stack traces thrown by different types of languages
285             and converts them into an object that you can use to get information
286             about the trace.
287              
288             If the text you're parsing could contain different types of
289             stack traces, then you should call L</parse> in this module to parse
290             your text. (So, you'd be calling C<< Parse::StackTrace->parse >>.)
291              
292             Alternately, if you know you just want to parse one type of trace (say,
293             just GDB traces) you can call C<parse()> on the Type class you want.
294             For example, if you just want to parse GDB traces, you could call
295             C<< Parse::StackTrace::Type::GDB->parse >>. The only difference between
296             the Type-specific C<parse> methods and L</parse> in this module is
297             that the Type-specific C<parse> methods don't take a C<types> argument.
298              
299             =head1 PARTS OF A STACK TRACE
300              
301             Stack traces have two main components: L<Threads|Parse::StackTrace::Thread>
302             and L<Frames|Parse::StackTrace::Frame>. A running program can have multiple
303             threads, and then each thread has a "stack" of functions that were called.
304             Each function is a single "frame". A frame also can contain other
305             information, like what arguments were passed to the function, or what
306             code file the frame's function is in.
307              
308             You access Threads by calling methods on the returned StackTrace object
309             you get from L</parse>.
310              
311             You access Frames by calling methods on Threads.
312              
313             =head1 CLASS METHODS
314              
315             =head2 C<parse>
316              
317             =over
318              
319             =item B<Description>
320              
321             Takes a block of text, and if there are any valid stack traces in that
322             block of text, it will find the first one and return it.
323              
324             =item B<Parameters>
325              
326             This method takes the following named parameters:
327              
328             =over
329              
330             =item C<text>
331              
332             A string. The block of text that you want to find a stack trace in.
333              
334             =item C<types>
335              
336             An arrayref containing strings. These are the types of traces that you
337             want to find in the block. Traces are searched for in the order specified,
338             so if there is a trace of the first type in the list, inside the text, then
339             the second type in the list will be ignored, and so on.
340              
341             Currently valid types are: C<GDB>, C<Python>
342              
343             Types are case-sensitive.
344              
345             =item C<debug>
346              
347             Set to 1 if you want the method to output detailed information about
348             its parsing.
349              
350             =back
351              
352             =item B<Returns>
353              
354             An object that is a subclass of Parse::StackTrace, or C<undef> if no
355             traces of any of the specified types were found in the list.
356              
357             =back
358              
359              
360             =head1 INSTANCE ATTRIBUTES
361              
362             These are methods of Parse::StackTrace objects that return data.
363             You should always call them as methods (never like
364             C<< $object->{attribute} >>).
365              
366             =head2 C<binary>
367              
368             Some stack traces contain information on what binary threw the stack trace.
369             If the parsed trace had that information, this will be a string specifying
370             the binary that threw the stack trace. If this information was not in
371             the stack trace, then this will be C<undef>.
372              
373             =head2 C<threads>
374              
375             An arrayref of L<Parse::StackTrace::Thread> objects, representing the
376             threads in the trace. Sometimes traces have only one thread, in which
377             case that will simply be C<< threads->[0] >>.
378              
379             The threads will be in the array in the order that they were listed
380             in the trace.
381              
382             There is always at least one thread in every trace.
383              
384             =head2 C<text>
385              
386             Returns the text of just the trace, with line endings converted to
387             just CR. (That is, just C<\n>, never C<\r\n>.)
388              
389             =head2 C<text_lines>
390              
391             An arrayref containining the lines of just the trace, without line endings.
392              
393             =head1 INSTANCE METHODS
394              
395             These are additional methods to get information about the stacktrace.
396              
397             =head2 C<thread_number>
398              
399             Takes a single integer argument. Returns the thread with that number, from
400             the L</threads> array. Thread numbering starts at 1, not at 0 (because this
401             is how GDB does it, and GDB was our first implementation).
402              
403             Note that if you want a particular-numbered thread, you should use
404             this method, not L</threads>, because it's possible somebody could have
405             a stack trace with the threads out of order, in which case C<< threads->[0] >>
406             would not be "Thread 1".
407              
408             =head2 C<thread_with_crash>
409              
410             Returns the first thread where C<Parse::StackTrace::Thread/frame_with_crash>
411             is defined, or C<undef> if no threads contain a C<frame_with_crash>.
412              
413             =head1 SEE ALSO
414              
415             The various parts of a stack trace:
416              
417             =over
418              
419             =item L<Parse::StackTrace::Thread>
420              
421             =item L<Parse::StackTrace::Frame>
422              
423             =back
424              
425             The different types of stack traces we can parse (which have special methods
426             for the specific sort of data available in those traces that aren't available
427             in all traces):
428              
429             =over
430              
431             =item L<Parse::StackTrace::Type::GDB>
432              
433             =item L<Parse::StackTrace::Type::Python>
434              
435             =back
436              
437             =head1 TODO
438              
439             Eventually we should be able to parse out multiple stack traces from
440             one block of text. (This will be the list-context return of L</parse>.)
441              
442             =head1 BUGS
443              
444             There are no known issues with this module. It has received extensive
445             testing, and it should be considered stable.
446              
447             Any bugs found should be reported to the author by email.
448              
449             =head1 AUTHOR
450              
451             Max Kanat-Alexander <mkanat@cpan.org>
452              
453             =head1 COPYRIGHT AND LICENSE
454              
455             Copyright (C) 2009 Canonical Ltd.
456              
457             This library (the entirety of Parse-StackTrace) is free software;
458             you can redistribute it and/or modify it under the same terms as Perl
459             itself.