File Coverage

lib/Sub/Contract/Compiler.pm
Criterion Covered Total %
statement 270 329 82.0
branch 125 166 75.3
condition 4 6 66.6
subroutine 22 22 100.0
pod 3 3 100.0
total 424 526 80.6


line stmt bran cond sub pod time code
1             #
2             # Sub::Contract::Compiler - Compile, enable and disable a contract
3             #
4             # $Id: Compiler.pm,v 1.22 2009/06/16 12:23:58 erwan_lemonnier Exp $
5             #
6              
7             package Sub::Contract::Compiler;
8              
9 39     39   1162 use strict;
  39         73  
  39         9178  
10 28     28   226 use warnings;
  28         50  
  28         3604  
11 27     27   161 use Carp qw(croak confess);
  27         388  
  27         5482  
12 25     25   149 use Data::Dumper;
  25         114  
  25         2376  
13 25     25   132 use Sub::Contract::Debug qw(debug);
  25         41  
  25         2070  
14 24     24   8914 use Sub::Name;
  24         10370  
  24         24894  
15              
16             our $VERSION = '0.12';
17              
18             #---------------------------------------------------------------
19             #
20             # enable - recompile contract and reenable it
21             #
22              
23             sub enable {
24 56     56 1 672 my $self = shift;
25              
26 56         189 debug(1,"Sub::Contract: enabling contract for [".$self->contractor."]");
27              
28 56 100       394 $self->disable if ($self->{is_enabled});
29              
30             # list all variables with same names in enable() as in _generate_code()
31 56         177 my $contractor = $self->contractor;
32 56         112 my $validator_in = $self->{in};
33 56         107 my $validator_out = $self->{out};
34 56         668 my $check_in = $self->{pre};
35 56         98 my $check_out = $self->{post};
36 56         96 my $invariant = $self->{invariant};
37 56         102 my $cache = $self->{cache};
38              
39 56         80 my @list_checks_in;
40             my %hash_checks_in;
41 56 100       263 if (defined $validator_in) {
42 30         51 @list_checks_in = @{$validator_in->list_checks};
  30         93  
43 30         170 %hash_checks_in = %{$validator_in->hash_checks};
  30         87  
44             }
45              
46 56         251 my @list_checks_out;
47             my %hash_checks_out;
48 56 100       224 if (defined $validator_out) {
49 21         30 @list_checks_out = @{$validator_out->list_checks};
  21         68  
50 21         98 %hash_checks_out = %{$validator_out->hash_checks};
  21         57  
51             }
52              
53             # compile code to validate pre and post constraints
54 56         649 my $str_pre = _generate_code('before',
55             $contractor,
56             $validator_in,
57             $check_in,
58             $invariant,
59             # a mapping to local variable names
60             {
61             contractor => "contractor",
62             validator => "validator_in",
63             check => "check_in",
64             invariant => "invariant",
65             list_check => "list_checks_in",
66             hash_check => "hash_checks_in",
67             },
68             );
69              
70 56         527 my $str_post = _generate_code('after',
71             $contractor,
72             $validator_out,
73             $check_out,
74             $invariant,
75             # a mapping to local variable names
76             {
77             contractor => "contractor",
78             validator => "validator_out",
79             check => "check_out",
80             invariant => "invariant",
81             list_check => "list_checks_out",
82             hash_check => "hash_checks_out",
83             },
84             );
85              
86 56         235 my $str_call_pre = "";
87 56         85 my $str_call_post = "";
88              
89 56 100       167 if ($str_pre) {
90 41         71 $str_call_pre = q{
91             &$cref_pre();
92             };
93             }
94              
95 56 100       137 if ($str_post) {
96 32         47 $str_call_post = q{
97             &$cref_post();
98             };
99             }
100              
101             # find contractor's code ref
102 56         208 my $cref = $self->contractor_cref;
103              
104             # add caching
105 56         182 my $str_cache_enter = "";
106 56         94 my $str_cache_return_array = "";
107 56         116 my $str_cache_return_scalar = "";
108              
109 56 100       150 if ($cache) {
110 8 100       25 $str_cache_enter = sprintf q{
    100          
111             if (!defined $Sub::Contract::wantarray) {
112             _croak "calling memoized subroutine %s in void context";
113             }
114              
115             if (grep({ ref $_; } @_)) {
116             _croak "cannot memoize result of %s when input arguments contain references";
117             }
118              
119             my $key = join(":", map( { (defined $_) ? $_ : "undef"; } ( ($Sub::Contract::wantarray) ? "array":"scalar"),@_));
120             if ($cache->has($key)) {
121             %s
122             if ($Sub::Contract::wantarray) {
123             return @{$cache->get($key)};
124             } else {
125             return $cache->get($key);
126             }
127             }
128             %s
129             },
130             $contractor,
131             $contractor,
132             (Sub::Contract::Memoizer::_is_profiler_on()) ? "Sub::Contract::Memoizer::_incr_hit(\"$contractor\");" : "",
133             (Sub::Contract::Memoizer::_is_profiler_on()) ? "Sub::Contract::Memoizer::_incr_miss(\"$contractor\");" : "";
134              
135 8 100       22 $str_cache_return_array = sprintf q{
136             $cache->set($key,\@Sub::Contract::results);
137             %s
138             },
139             (Sub::Contract::Memoizer::_is_profiler_on()) ? "Sub::Contract::Memoizer::_incr_max_reached(\"$contractor\");" : "";
140              
141 8 100       18 $str_cache_return_scalar = sprintf q{
142             $cache->set($key,$s);
143             %s
144             },
145             (Sub::Contract::Memoizer::_is_profiler_on()) ? "Sub::Contract::Memoizer::_incr_max_reached(\"$contractor\");" : "";
146             }
147              
148             # the context in which the contracted sub is called depends on
149             # whether we have conditions on return values
150 56         77 my $str_call;
151              
152 56 100       354 if (!defined $validator_out) {
153             # there are no constraints on return arguments so we can't assume
154             # anything on the context the sub expects to be called in
155             # we therefore propagate the same context as the call to the contract
156              
157 35         285 $str_call = sprintf q{
158              
159             local $Sub::Contract::wantarray = wantarray;
160              
161             %s
162              
163             # TODO: this code is not re-entrant. use local variables for args/wantarray/results. is local enough?
164              
165             local @Sub::Contract::args = @_;
166             local @Sub::Contract::results = ();
167              
168             if (!defined $Sub::Contract::wantarray) {
169             # void context
170             %s
171             &$cref(@Sub::Contract::args);
172             @Sub::Contract::results = ();
173             %s
174             return ();
175              
176             } elsif ($Sub::Contract::wantarray) {
177             # array context
178             %s
179             @Sub::Contract::results = &$cref(@Sub::Contract::args);
180             %s
181             %s
182             return @Sub::Contract::results;
183              
184             } else {
185             # scalar context
186             %s
187             my $s = &$cref(@Sub::Contract::args);
188             @Sub::Contract::results = ($s);
189             %s
190             %s
191             return $s;
192             }
193             },
194             $str_cache_enter,
195             $str_call_pre,
196             $str_call_post,
197             $str_call_pre,
198             $str_call_post,
199             $str_cache_return_array,
200             $str_call_pre,
201             $str_call_post,
202             $str_cache_return_scalar;
203              
204             } else {
205             # we have conditions set on the return values
206             # we have 3 cases:
207 21         74 my @checks = (@list_checks_out,%hash_checks_out);
208              
209 21 100       69 if (scalar @checks == 0) {
    100          
210             # the sub returns nothing. therefore it should
211             # only be called in void context. anything else
212             # is an error.
213              
214             # we shouldn't try caching this sub
215 10 50       26 if ($cache) {
216 0         0 croak "trying to cache a sub that returns nothing (according to ->out())";
217             }
218              
219 10         54 $str_call = sprintf q{
220              
221             local $Sub::Contract::wantarray = wantarray;
222              
223             if (defined $Sub::Contract::wantarray) {
224             _croak "calling %s in scalar or array context when its contract says it has no return values";
225             }
226              
227             local @Sub::Contract::args = @_;
228             local @Sub::Contract::results = ();
229              
230             # void context, but we call the sub in array context to check if we get something back
231             # (if we do, it's an error)
232             %s
233             @Sub::Contract::results = &$cref(@Sub::Contract::args);
234             %s
235             return;
236             },
237             $contractor,
238             $str_call_pre,
239             $str_call_post;
240              
241             } elsif (scalar @checks == 1) {
242             # the sub returns only 1 element.
243             # we don't know though whether it returns a scalar
244             # (most likely) or an array with just 1 element.
245             # returning a 1-element array instead of a scalar
246             # is a sign of bad programming so we just forbid
247             # this case by raising an error if called in array
248             # context.
249             # otherwise, we call the sub in scalar context,
250             # check the result and return it.
251              
252 5         38 $str_call = sprintf q{
253              
254             local $Sub::Contract::wantarray = wantarray;
255              
256             %s
257              
258             # TODO: this code is not re-entrant. use local variables for args/wantarray/results. is local enough?
259              
260             if ($Sub::Contract::wantarray) {
261             _croak "calling %s in array context when its contract says it returns a scalar";
262             }
263              
264             local @Sub::Contract::args = @_;
265             local @Sub::Contract::results = ();
266              
267             # call in scalar context, even if called from void context
268             %s
269             my $s = &$cref(@Sub::Contract::args);
270             @Sub::Contract::results = ($s);
271             %s
272             %s
273             return $s;
274              
275             },
276             $str_cache_enter,
277             $contractor,
278             $str_call_pre,
279             $str_call_post,
280             $str_cache_return_scalar;
281              
282             } else {
283             # the sub returns an array. we call it in array context,
284             # check the conditions and return an array as well
285              
286 6         33 $str_call = sprintf q{
287              
288             local $Sub::Contract::wantarray = wantarray;
289              
290             %s
291              
292             # TODO: this code is not re-entrant. use local variables for args/wantarray/results. is local enough?
293              
294             local @Sub::Contract::args = @_;
295             local @Sub::Contract::results = ();
296              
297             # call in array context, even if called from void or scalar context
298             %s
299             @Sub::Contract::results = &$cref(@Sub::Contract::args);
300             %s
301             %s
302             return @Sub::Contract::results;
303              
304             },
305             $str_cache_enter,
306             $str_call_pre,
307             $str_call_post,
308             $str_cache_return_array;
309             }
310             }
311              
312 56         628 my $str_contract = sprintf q{
313             use Carp;
314              
315             my $cref_pre = sub {
316             %s
317             };
318              
319             my $cref_post = sub {
320             %s
321             };
322              
323             $contract = sub {
324             %s
325             }
326             },
327             $str_pre,
328             $str_post,
329             $str_call;
330              
331             # compile code
332 56         1830 $str_contract =~ s/^\s+//gm;
333              
334 56         222 debug(2,join("\n",
335             "Sub::Contract: wrapping this code around [".$self->contractor."]:",
336             "-------------------------------------------------------",
337             $str_contract,
338             "-------------------------------------------------------"));
339              
340 56         217 my $contract;
341 56 100   198   4941 eval $str_contract;
  198 100   74   106922  
  198 50   48   715  
  152 100   24   9104  
  172 100   2   450  
  135 100   2   851  
  59 50       422  
  62 100       748  
  128 100       55331  
  79 100       573  
  67 50       187  
  64 50       2346  
  88 50       824  
  47 100       312  
  32 50       386  
  66 50       24209  
  51 50       171  
  45 50       84  
  44 50       156  
  35 50       494  
  14 50       146  
  4 100       150  
  32 50       10905  
  30 50       174  
  22 50       117  
  18 50       67  
  14 50       94  
  12 50       42  
  11 0       107  
  9 50       136  
  5 50       24  
  30 50       16046  
  30 50       77  
  1 50       4  
  27 50       36  
  44 50       117  
  1 50       5  
  26 100       51  
  68 50       165  
  26 50       83  
  20 50       48  
  20 50       40  
  0 50       0  
  0 50       0  
  20 50       55  
  6 100       16  
  6 50       16  
  6 50       18  
  6         17  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  6         19  
  6         114  
  6         19  
  6         19  
  6         21  
  13         201  
  13         31  
  0         0  
  13         19  
  13         41  
  0         0  
  13         28  
  26         72  
  13         36  
  1         3  
  1         4  
  0         0  
  0         0  
  1         4  
  12         38  
  12         30  
  12         17  
  12         30  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  12         26  
  12         214  
  12         35  
  12         30  
  12         47  
  1         587  
  1         4  
  0         0  
  1         2  
  3         8  
  0         0  
  1         4  
  4         13  
  1         12  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  1         5  
  1         3  
  1         2  
  1         5  
  0         0  
  0         0  
  0         0  
  1         5  
  1         23  
  1         3  
  1         6  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  2         641  
  2         7  
  0         0  
  2         3  
  6         15  
  0         0  
  2         5  
  8         22  
  2         8  
  1         4  
  1         11  
  1         3  
  1         4  
  0         0  
  1         5  
  1         3  
  1         3  
  1         4  
  0         0  
  0         0  
  0         0  
  1         4  
  1         23  
  1         3  
  1         7  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  2         5  
  2         6  
  0         0  
  2         3  
  4         11  
  0         0  
  2         7  
  6         17  
  2         7  
  1         4  
  1         3  
  0         0  
  0         0  
  1         4  
  1         4  
  1         3  
  1         2  
  1         4  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  1         4  
  1         19  
  1         3  
  1         3  
  1         6  
342              
343 56 50 33     497 if (defined $@ and $@ ne "") {
344 0         0 confess "BUG: failed to compile contract ($@)";
345             }
346              
347             # replace contractor with contract sub
348 56         131 $^W = 0;
349 22     22   169 no strict 'refs';
  22         57  
  22         976  
350 22     22   143 no warnings;
  22         37  
  22         5702  
351 56         88 *{ $self->contractor } = $contract;
  56         226  
352              
353 56         173 my $name = $self->contractor;
354 56         664 $name =~ s/::([^:]+)$/::contract_$1/;
355 56         410 subname $name, $contract;
356              
357 56         108 $self->{is_enabled} = 1;
358              
359 56         294 return $self;
360             }
361              
362             sub disable {
363 15     15 1 32 my $self = shift;
364 15 50       49 if ($self->{is_enabled}) {
365 15         47 debug(1,"Sub::Contract: disabling contract on [".$self->contractor."]");
366              
367             # restore original sub
368 15         35 $^W = 0;
369 22     22   125 no strict 'refs';
  22         42  
  22         627  
370 22     22   103 no warnings;
  22         45  
  22         21172  
371 15         32 *{ $self->contractor } = $self->{contractor_cref};
  15         48  
372              
373             # TODO: remove memoization
374 15         36 $self->{is_enabled} = 0;
375             }
376 15         33 return $self;
377              
378             }
379              
380             sub is_enabled {
381 12     12 1 91 return $_[0]->{is_enabled};
382             }
383              
384             #---------------------------------------------------------------
385             #
386             # _compile - generate the code to validate the contract before
387             # or after a call to the contractor function
388             #
389              
390             # TODO: insert _croak inline in compiled code
391             # croak from contract code, with proper stack level
392             sub _croak {
393 181     181   283 my $msg = shift;
394 181         266 local $Carp::CarpLevel = 2;
395 181         42154 confess "contract failed: $msg";
396             }
397              
398             # TODO: insert _run inline in compiled code
399             # run a condition, with proper stack level if croak
400             sub _run {
401 370     370   715 my ($func,@args) = @_;
402 370         515 local $Carp::CarpLevel = 4;
403 370         1167 my $res = $func->(@args);
404 359         83207 local $Carp::CarpLevel = 0; # is this needed? isn't local doing its job?
405 359         9865 return $res;
406             }
407              
408             # The strategy we use for building the contract validation sub is to
409             # to (quite horribly) build a string containing the code of the validation sub,
410             # then compiling this code with eval. We could instead use a closure,
411             # but that would mean that many things we can test at compile time would
412             # end up being tested each time the closure is called which would be a
413             # waste of cpu.
414              
415             sub _generate_code {
416 112     112   241 my ($state,$contractor,$validator,$check_condition,$check_invariant,$varnames) = @_;
417 112         140 my (@list_checks,%hash_checks);
418              
419 112 50       441 croak "BUG: wrong state" if ($state !~ /^before|after$/);
420              
421             # the code validating the pre or post-call part of the contract, as a string
422 112         200 my $str_code = "";
423              
424             # code validating the contract invariant
425 112 100       244 if (defined $check_invariant) {
426 18         103 $str_code .= sprintf q{
427             if (!_run($%s,@Sub::Contract::args)) {
428             _croak "invariant fails %s calling $%s";
429             }
430             }, $varnames->{invariant}, $state, $varnames->{contractor};
431             }
432              
433             # code validating the contract pre/post condition
434 112 100       239 if (defined $check_condition) {
435 9 100       37 if ($state eq 'before') {
436 4         29 $str_code .= sprintf q{
437             if (!_run($%s,@Sub::Contract::args)) {
438             _croak "pre-condition fails before calling $%s";
439             }
440             }, $varnames->{check}, $varnames->{contractor};
441             } else {
442             # if the contractor is called without context, the result is set to ()
443             # so we can't validate the returned arguments. maybe we should issue a warning?
444 5         29 $str_code .= sprintf q{
445             if (!_run($%s,@Sub::Contract::results)) {
446             _croak "post-condition fails after calling $%s";
447             }
448             }, $varnames->{check}, $varnames->{contractor};
449             }
450             }
451              
452             # compile the arguments validation code
453 112 100       254 if (defined $validator) {
454              
455 51         67 @list_checks = @{$validator->list_checks};
  51         149  
456 51         253 %hash_checks = %{$validator->hash_checks};
  51         166  
457              
458             # get args/@_ from right source
459 51 100       325 if ($state eq 'before') {
460 30         88 $str_code .= q{ my @args = @Sub::Contract::args; };
461             } else {
462 21         54 $str_code .= q{ my @args = @Sub::Contract::results; };
463             }
464              
465             # if arguments are list style only, check their count
466 51 100       169 if (!$validator->has_hash_args) {
467 39         248 my $count = scalar @list_checks;
468 39 100       115 if ($state eq 'before') {
469 22 100       223 $str_code .= sprintf q{
470             _croak "$%s expected %s input arguments but got ".(scalar @args) if (scalar @args != %s);
471             },
472             $varnames->{contractor},
473             ($count == 0) ? "no" : "exactly $count",
474             $count;
475             } else {
476 17 100       115 $str_code .= sprintf q{
477             _croak "$%s should return %s values but returned ".(scalar @args) if (scalar @args != %s);
478             },
479             $varnames->{contractor},
480             ($count == 0) ? "no" : "exactly $count",
481             $count;
482             }
483             }
484              
485             # do we have arguments to validate?
486 51 100 100     259 if ($validator->has_list_args || $validator->has_hash_args) {
487              
488             # add code validating heading arguments passed in list style
489 31         221 my $pos = 1;
490 31         116 for (my $i=0; $i
491 44 100       129 if (defined $list_checks[$i]) {
492 28 100       197 $str_code .= sprintf q{
493             _croak "%s number %s of $%s fails its constraint: ".((defined $args[0])?$args[0]:"undef") if (!_run($%s[%s], $args[0]));
494             },
495             ($state eq 'before') ? 'input argument' : 'return value',
496             $pos,
497             $varnames->{contractor},
498             $varnames->{list_check},
499             $i;
500             }
501              
502 44         76 $str_code .= q{
503             shift @args;
504             };
505 44         131 $pos++;
506             }
507              
508             # add code validating trailing arguments passed in hash style
509 31 100       101 if ($validator->has_hash_args) {
510              
511             # croak if odd number of elements
512 12 100       129 $str_code .= sprintf q{
513             _croak "odd number of hash-style %s in $%s" if (scalar @args %% 2);
514             my %%args = @args;
515             },
516             ($state eq 'before') ? 'input arguments' : 'return values',
517             $varnames->{contractor};
518              
519             # check the value of each key in the argument hash
520 12         69 while (my ($key,$check) = each %hash_checks) {
521 24 100       80 if (defined $check) {
522 16 100       205 $str_code .= sprintf q{
523             _croak "%s of $%s with key \'%s\' fails its constraint: %s = ".((defined $args{%s})?$args{%s}:"undef") if (!_run($%s{%s}, $args{%s}));
524             },
525             ($state eq 'before') ? 'input argument' : 'return value',
526             $varnames->{contractor},
527             $key,
528             $key,
529             $key,
530             $key,
531             $varnames->{hash_check},
532             $key,
533             $key;
534             }
535              
536 24         112 $str_code .= sprintf q{
537             delete $args{%s};
538             }, $key;
539             }
540             }
541             }
542              
543             # there should be no arguments left
544 51 100       522 if ($validator->has_hash_args) {
545 12 100       117 $str_code .= sprintf q{
546             _croak "$%s %s: ".join(" ",keys %%args) if (%%args);
547             },
548             $varnames->{contractor},
549             ($state eq 'before') ? 'got unexpected hash-style input arguments' : 'returned unexpected hash-style return values';
550             }
551             }
552              
553 112         556 return $str_code;
554             }
555              
556             1;
557              
558             __END__