File Coverage

blib/lib/XS/Check.pm
Criterion Covered Total %
statement 120 141 85.1
branch 27 40 67.5
condition 5 6 83.3
subroutine 25 28 89.2
pod 4 20 20.0
total 181 235 77.0


line stmt bran cond sub pod time code
1             package XS::Check;
2 5     5   301912 use warnings;
  5         44  
  5         174  
3 5     5   26 use strict;
  5         9  
  5         129  
4 5     5   26 use Carp;
  5         10  
  5         290  
5 5     5   30 use utf8;
  5         8  
  5         41  
6             our $VERSION = '0.12';
7 5     5   2910 use C::Tokenize '0.14', ':all';
  5         23663  
  5         1504  
8 5     5   2563 use Text::LineNumber;
  5         1812  
  5         172  
9 5     5   2468 use File::Slurper 'read_text';
  5         73396  
  5         335  
10 5     5   41 use Carp qw/croak carp cluck confess/;
  5         12  
  5         10743  
11              
12             # ____ _ _
13             # | _ \ _ __(_)_ ____ _| |_ ___
14             # | |_) | '__| \ \ / / _` | __/ _ \
15             # | __/| | | |\ V / (_| | || __/
16             # |_| |_| |_| \_/ \__,_|\__\___|
17             #
18              
19             sub debugmsg
20             {
21 0     0 0 0 my (undef, $file, $line) = caller ();
22 0         0 printf ("%s:%d: ", $file, $line);
23 0         0 print "@_\n";
24             }
25              
26             sub get_line_number
27             {
28 19     19 0 36 my ($o) = @_;
29 19         33 my $pos = pos ($o->{xs});
30 19 50       71 if (! defined ($pos)) {
31 0         0 confess "Bad pos for XS text";
32 0         0 return "unknown";
33             }
34 19         66 return $o->{tln}->off2lnr ($pos);
35             }
36              
37             # Report an error $message in $var
38              
39             sub report
40             {
41 19     19 0 60 my ($o, $message) = @_;
42 19         52 my $file = $o->get_file ();
43 19         60 my $line = $o->get_line_number ();
44 19 50       386 confess "No message" unless $message;
45 19 100       58 if (my $r = $o->{reporter}) {
46 1         5 &$r (file => $file, line => $line, message => $message);
47             }
48             else {
49 18         179 warn "$file$line: $message.\n";
50             }
51             }
52              
53             # Match a call to SvPV
54              
55             my $svpv_re = qr/
56             (
57             (?:$word_re(?:->|\.))*$word_re
58             )
59             \s*=[^;]*
60             (
61             SvPV(?:byte|utf8)?
62             (?:x|_(?:force|nolen))?
63             )
64             \s*\(\s*
65             ($word_re)
66             \s*,\s*
67             ($word_re)
68             \s*\)
69             /x;
70              
71             # Look for problems with calls to SvPV.
72              
73             sub check_svpv
74             {
75 18     18 0 73 my ($o) = @_;
76 18         982 while ($o->{xs} =~ /($svpv_re)/g) {
77 8         56 my ($match, $lvar, $svpv, $arg1, $arg2) = ($1, $2, $3, $4, $5);
78 8         22 my $lvar_type = $o->get_type ($lvar);
79 8         14 my $arg2_type = $o->get_type ($arg2);
80 8 50       18 if ($o->{verbose}) {
81 0         0 debugmsg ("<$match> $lvar_type $arg2_type");
82             }
83 8 100 66     66 if ($lvar_type && $lvar_type !~ /\bconst\b/) {
84 5         19 $o->report ("$lvar not a constant type");
85             }
86 8 100 100     82 if ($arg2_type && $arg2_type !~ /\bSTRLEN\b/) {
87 1         5 $o->report ("$arg2 is not a STRLEN variable ($arg2_type)");
88             }
89 8 100       54 if ($svpv !~ /bytes?|utf8/) {
90 5         11 $o->report ("Specify either SvPVbyte or SvPVutf8 to avoid ambiguity; see perldoc perlguts");
91             }
92             }
93             }
94              
95             # Best equivalents.
96              
97             my %equiv = (
98             # Newxc is for C++ programmers (cast malloc).
99             malloc => 'Newx/Newxc',
100             calloc => 'Newxz',
101             free => 'Safefree',
102             realloc => 'Renew',
103             );
104              
105             # Look for calls to malloc/calloc/realloc/free and suggest replacing
106             # them.
107              
108             sub check_malloc
109             {
110 18     18 0 34 my ($o) = @_;
111 18         134 while ($o->{xs} =~ /\b((?:m|c|re)alloc|free)\s*\(/g) {
112             # Bad function
113 1         3 my $badfun = $1;
114 1         2 my $equiv = $equiv{$badfun};
115 1 50       4 if (! $equiv) {
116 0         0 $o->report ("(BUG) No equiv for $badfun");
117             }
118             else {
119 1         5 $o->report ("Change $badfun to $equiv");
120             }
121             }
122             }
123              
124             # Look for a Perl_ prefix before functions.
125              
126             sub check_perl_prefix
127             {
128 18     18 0 30 my ($o) = @_;
129 18         283 while ($o->{xs} =~ /\b(Perl_$word_re)\b/g) {
130 2         11 $o->report ("Remove the 'Perl_' prefix from $1");
131             }
132             }
133              
134             # Regular expression to match a C declaration.
135              
136             my $declare_re = qr/
137             (
138             (
139             (?:
140             (?:$reserved_re|$word_re)
141             (?:\b|\s+)
142             |
143             \*\s*
144             )+
145             )
146             (
147             $word_re
148             )
149             )
150             # Match initial value.
151             \s*(?:=[^;]+)?;
152             /x;
153              
154             # Read the declarations.
155              
156             sub read_declarations
157             {
158 18     18 0 34 my ($o) = @_;
159 18         766 while ($o->{xs} =~ /$declare_re/g) {
160 21         55 my $type = $2;
161 21         33 my $var = $3;
162 21 50       43 if ($o->{verbose}) {
163 0         0 debugmsg ("type = $type for $var");
164             }
165 21 50       42 if ($o->{vars}{$type}) {
166             # This is very likely to produce false positives in a long
167             # file. A better way to do this would be to have variables
168             # associated with line numbers, so that x on line 10 is
169             # different from x on line 20.
170 0         0 warn "duplicate variable $var of type $type\n";
171             }
172 21         438 $o->{vars}{$var} = $type;
173             }
174             }
175              
176             # Get the type of variable $var.
177              
178             sub get_type
179             {
180 16     16 0 30 my ($o, $var) = @_;
181             # We currently do not have a way to store and retrieve types of
182             # structure members
183 16 50       55 if ($var =~ /->|\./) {
184 0         0 $o->report ("Cannot get type of $var, please check manually");
185 0         0 return undef;
186             }
187 16         31 my $type = $o->{vars}{$var};
188 16 100       30 if (! $type) {
189 1         6 $o->report ("(BUG) No type for $var");
190             }
191 16         36 return $type;
192             }
193              
194             # Set up the line numbering object.
195              
196             sub line_numbers
197             {
198 18     18 0 39 my ($o) = @_;
199 18         76 my $tln = Text::LineNumber->new ($o->{xs});
200 18         571 $o->{tln} = $tln;
201             }
202              
203             # This adds a colon to the end of the file, so it shouldn't really be
204             # user-visible.
205              
206             sub get_file
207             {
208 19     19 0 31 my ($o) = @_;
209 19 50       46 if (! $o->{file}) {
210 19         51 return '';
211             }
212 0         0 return "$o->{file}:";
213             }
214              
215             # Clear up old variables, inputs, etc. Don't delete everything since
216             # we want to keep at least the field "reporter" from one call to
217             # "check" to the next.
218              
219             sub cleanup
220             {
221 18     18 0 32 my ($o) = @_;
222 18         35 for (qw/vars xs file/) {
223 54         124 delete $o->{$_};
224             }
225             }
226              
227             # Regex to match (void) in XS function call.
228              
229             my $void_re = qr/
230             $word_re\s*
231             \(\s*void\s*\)\s*
232             (?=
233             # CODE:, PREINIT:, etc.
234             [A-Z]+:
235             # |
236             # Normal C function start
237             # \{
238             )
239             /xsm;
240              
241             # Look for (void) XS functions
242              
243             sub check_void_arg
244             {
245 18     18 0 32 my ($o) = @_;
246 18         174 while ($o->{xs} =~ /$void_re/g) {
247 1         3 $o->report ("Don't use (void) in function arguments");
248             }
249             }
250              
251             sub
252             check_hash_comments
253             {
254 18     18 0 28 my ($o) = @_;
255 18         51 while ($o->{xs} =~ /^#\s*(\w*)/gsm) {
256 3         15 my $hash = $1;
257 3 100       24 if ($hash !~ /^(?:
258             define|
259             else|
260             endif|
261             error|
262             ifdef|
263             ifndef|
264             if|
265             include|
266             line|
267             undef|
268             warning|
269             ZZZZZZZZZZZ)(\s+|$)/x) {
270 1         6 $o->report ("Put whitespace before # in comments");
271             }
272             }
273             }
274              
275             sub
276             check_c_pre
277             {
278 18     18 0 30 my ($o) = @_;
279 18         66 while ($o->{xs} =~ /^#\s*(\w*)/gsm) {
280 3         8 my $hash = $1;
281 3 50       18 if ($hash =~ /(?:if|else|endif)\s+/) {
282             # Complicated!
283             }
284             }
285             }
286              
287             sub check_fetch_deref
288             {
289 18     18 0 29 my ($o) = @_;
290 18         92 while ($o->{xs} =~ m!(\*\s*(?:a|h)v_fetch)!g) {
291 1         4 $o->report ("Dereference of av/hv_fetch");
292             }
293             }
294              
295             sub check_av_len
296             {
297 18     18 0 40 my ($o) = @_;
298 18         57 while ($o->{xs} =~ m!^(.*av_len\s*\([^\)]*\)(.*))!g) {
299 1         4 my $later = $2;
300 1 50       3 if ($later !~ /\+\s*1/) {
301 1         4 $o->report ("Add one to av_len");
302             }
303             }
304             }
305              
306             # _ _ _ _ _ _
307             # | | | |___ ___ _ __ __ _(_)___(_) |__ | | ___
308             # | | | / __|/ _ \ '__| \ \ / / / __| | '_ \| |/ _ \
309             # | |_| \__ \ __/ | \ V /| \__ \ | |_) | | __/
310             # \___/|___/\___|_| \_/ |_|___/_|_.__/|_|\___|
311             #
312              
313             sub new
314             {
315 18     18 1 13435 my ($class, %options) = @_;
316 18         44 my $o = bless {};
317 18 100       58 if (my $r = $options{reporter}) {
318 2 100       7 if (ref $r ne 'CODE') {
319 1         208 carp "reporter should be a code reference";
320             }
321             else {
322 1         13 $o->{reporter} = $r;
323             }
324             }
325 18 50       53 if (defined $options{verbose}) {
326 0         0 $o->{verbose} = $options{verbose};
327             }
328 18         45 return $o;
329             }
330              
331             sub set_file
332             {
333 0     0 1 0 my ($o, $file) = @_;
334 0 0       0 if (! $file) {
335 0         0 $file = undef;
336             }
337 0         0 $o->{file} = $file;
338             }
339              
340             # Check the XS.
341              
342             sub check
343             {
344 18     18 1 1341 my ($o, $xs) = @_;
345 18         54 $o->{xs} = $xs;
346 18         122 $o->{xs} = strip_comments ($o->{xs});
347 18         811 $o->line_numbers ();
348 18         54 $o->read_declarations ();
349 18         59 $o->check_svpv ();
350 18         108 $o->check_malloc ();
351 18         57 $o->check_perl_prefix ();
352 18         69 $o->check_void_arg ();
353 18         59 $o->check_c_pre ();
354 18         48 $o->check_hash_comments ();
355 18         43 $o->check_fetch_deref ();
356 18         50 $o->check_av_len ();
357             # Final line
358 18         44 $o->cleanup ();
359             }
360              
361             sub check_file
362             {
363 0     0 1   my ($o, $file) = @_;
364 0           $o->set_file ($file);
365 0           my $xs = read_text ($file);
366 0           $o->check ($xs);
367             }
368              
369             1;