File Coverage

blib/lib/Carp/Parse.pm
Criterion Covered Total %
statement 48 49 97.9
branch 11 14 78.5
condition 12 17 70.5
subroutine 6 6 100.0
pod 1 1 100.0
total 78 87 89.6


line stmt bran cond sub pod time code
1             package Carp::Parse;
2              
3 4     4   24523 use 5.010;
  4         13  
  4         142  
4              
5 4     4   20 use warnings;
  4         7  
  4         95  
6 4     4   31 use strict;
  4         10  
  4         125  
7              
8 4     4   20 use Carp;
  4         7  
  4         265  
9 4     4   2207 use Carp::Parse::CallerInformation;
  4         51  
  4         2337  
10              
11              
12             =head1 NAME
13              
14             Carp::Parse - Parse a Carp stack trace into an array of caller information with parsed arguments.
15              
16              
17             =head1 DESCRIPTION
18              
19             Carp produces a stacktrace that includes caller arguments; this module parses
20             each line of the stack trace to extract its arguments, which allows rewriting
21             the stack trace (for example, to redact sensitive information).
22              
23              
24             =head1 VERSION
25              
26             Version 1.0.7
27              
28             =cut
29              
30             our $VERSION = '1.0.7';
31              
32             our $MAX_ARGUMENTS_PER_CALL = 1000;
33              
34              
35             =head1 SYNOPSIS
36              
37             # Retrieve a Carp stack trace with longmess(). This is tedious, but you will
38             # normally be using this module in a context where the stacktrace is already
39             # generated for you and you want to parse it, so you won't have to go through
40             # this step.
41             sub test3 { return Carp::longmess("Test"); }
42             sub test2 { return test3(); }
43             sub test1 { return test2(); }
44             my $stack_trace = test1();
45            
46             # Parse the Carp stack trace.
47             use Carp::Parse;
48             my $parsed_stack_trace = Carp::Parse::parse_stack_trace( $stack_trace );
49            
50             use Data::Dump qw( dump );
51             foreach my $caller_information ( @$parsed_stack_trace )
52             {
53             # Print the arguments for each caller.
54             say dump( $caller->get_arguments_list() );
55             }
56              
57              
58             =head1 FUNCTIONS
59              
60             =head2 parse_stack_trace()
61              
62             Parse a stack trace produced by C into an arrayref of
63             C objects.
64              
65             my $parsed_stack_trace = Carp::Parse::parse_stack_trace( $stack_trace );
66              
67             =cut
68              
69             sub parse_stack_trace
70             {
71 3     3 1 576 my ( $stack_trace ) = @_;
72            
73             # Verify parameters.
74 3 50 33     38 croak 'Specify a stack trace to parse as first argument'
75             if !defined( $stack_trace ) || ( $stack_trace eq '' );
76            
77 3         11 my $parsed_stack_trace = [];
78            
79             # The first part of the stack trace holds the message logged, which may
80             # include newlines so we need to parse it separately.
81 3         37 my ( $first_caller ) = $stack_trace =~ /^(.*?at.*?line\s*\d*\n)/sx;
82 3   50     21 $first_caller //= '';
83 3         94 $stack_trace =~ s/\Q$first_caller\E//;
84              
85 3         53 push(
86             @$parsed_stack_trace,
87             Carp::Parse::CallerInformation->new(
88             {
89             line => $first_caller,
90             }
91             ),
92             );
93            
94             # Parse the other lines, which is straightforward as Carp replaces newlines
95             # in the function arguments with \\x{a}.
96 3         45 foreach my $line ( split( /\n/, $stack_trace ) )
97             {
98 12         65 my ( $subroutine_arguments ) = $line =~ /\((.*)\)/;
99 12 100       47 next unless defined( $subroutine_arguments );
100            
101             # Why don't we eval() the string here into an array? This looks so simple!
102             # Unfortunately, subroutine arguments are not quoted correct by Carp in
103             # cases like the following:
104             # main::test_trace('password', 'thereisnotry', 'planet', 'degobah', 'ship_zip', 01138, 'username', 'yoda') called at test/lib/Sphorb/Utils/Logger/40-redacted.t line 47
105             # This would fail trying to eval 01138 as an octal due to the lack of quotes,
106             # but 8 is not a valid digit for that.
107 9         16 my @arguments = ();
108 9         19 my $parse_arguments = $subroutine_arguments;
109 9         14 my $arguments_count = 0;
110 9         1065 my $incorrect_arguments_format_detected = 0;
111 9   66     637 while (
      66        
      100        
112             defined( $parse_arguments )
113             && ( $parse_arguments ne '' )
114             && ( $arguments_count < $MAX_ARGUMENTS_PER_CALL )
115             && !$incorrect_arguments_format_detected
116             )
117             {
118 29         33 my ( $value );
119             # Note: we need to account for both single and double quotes here
120             # as Carp has changed its internals over time and the quoting style
121             # depends on the version of Carp.
122 29         55 my $first_character = substr( $parse_arguments, 0, 1 );
123 29 100 100     119 if ( $first_character eq '"' || $first_character eq "'" )
124             {
125             # If it starts with a quote, we use a negative lookbehind to find the
126             # matching closing quote, which should be a quote not preceded by a backslash
127             # (which would indicate an escaped quote that's part of the data).
128 27         537 ( $value ) = $parse_arguments =~ /^$first_character(.*?)(?
129 27 100       64 if ( defined( $value ) )
130             {
131 26         327 $parse_arguments =~ s/\Q$first_character$value$first_character\E//;
132             }
133             else
134             {
135 1         3 $incorrect_arguments_format_detected = 1;
136             }
137             }
138             else
139             {
140             # If it doesn't start with a quote, we just take all the following
141             # characters as long as they're not commas.
142 2         11 ( $value ) = $parse_arguments =~ /^([^,]*)/;
143 2 50       8 if ( defined( $value ) )
144             {
145 2         28 $parse_arguments =~ s/\Q$value\E//;
146             }
147             else
148             {
149 0         0 $incorrect_arguments_format_detected = 1;
150             }
151             }
152            
153 29 100       70 if ( !$incorrect_arguments_format_detected )
154             {
155 28         48 push( @arguments, $value );
156            
157             # Remove the comma that followed the argument (if it's not the last one).
158 28         102 $parse_arguments =~ s/^\s*,\s*//;
159            
160             # Make sure we never get into an infinite loop, in case the format of the
161             # stacktrace is somehow broken.
162 28         38 $arguments_count++;
163 28 50       270 carp "Max limit of arguments per call reached, showing the first $MAX_ARGUMENTS_PER_CALL only."
164             if $arguments_count == $MAX_ARGUMENTS_PER_CALL;
165             }
166             else
167             {
168 1         12 @arguments = ( '[incorrect arguments format]' );
169             }
170             }
171            
172             push(
173 9         65 @$parsed_stack_trace,
174             Carp::Parse::CallerInformation->new(
175             {
176             line => $line,
177             arguments_list => \@arguments,
178             arguments_string => $subroutine_arguments,
179             }
180             ),
181             );
182             }
183            
184 3         28 return $parsed_stack_trace;
185             }
186              
187              
188             =head1 AUTHOR
189              
190             Kate Kirby, C<< >>.
191              
192             Guillaume Aubert, C<< >>.
193              
194              
195             =head1 BUGS
196              
197             Please report any bugs or feature requests to C, or through
198             the web interface at L.
199             I will be notified, and then you'll automatically be notified of progress on
200             your bug as I make changes.
201              
202              
203             =head1 SUPPORT
204              
205             You can find documentation for this module with the perldoc command.
206              
207             perldoc Carp::Parse
208              
209              
210             You can also look for information at:
211              
212             =over 4
213              
214             =item * RT: CPAN's request tracker
215              
216             L
217              
218             =item * AnnoCPAN: Annotated CPAN documentation
219              
220             L
221              
222             =item * CPAN Ratings
223              
224             L
225              
226             =item * Search CPAN
227              
228             L
229              
230             =back
231              
232              
233             =head1 ACKNOWLEDGEMENTS
234              
235             Thanks to ThinkGeek (L) and its corporate overlords
236             at Geeknet (L), for footing the bill while we eat pizza
237             and write code for them!
238              
239              
240             =head1 COPYRIGHT & LICENSE
241              
242             Copyright 2012 Kate Kirby & Guillaume Aubert.
243              
244             This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3 as published by the Free Software Foundation.
245              
246             This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
247              
248             You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/
249              
250             =cut
251              
252             1;