File Coverage

blib/lib/Closure/Explicit.pm
Criterion Covered Total %
statement 59 61 96.7
branch 8 14 57.1
condition n/a
subroutine 10 10 100.0
pod 2 2 100.0
total 79 87 90.8


line stmt bran cond sub pod time code
1             package Closure::Explicit;
2             # ABSTRACT: check coderefs for unintended lexical capture
3 5     5   225669 use strict;
  5         12  
  5         177  
4 5     5   28 use warnings;
  5         10  
  5         131  
5 5     5   26 use B;
  5         10  
  5         228  
6 5     5   4010 use PadWalker qw(closed_over peek_sub peek_my);
  5         4220  
  5         379  
7 5     5   33 use Scalar::Util ();
  5         11  
  5         256  
8              
9             our $VERSION = '0.001';
10              
11             =head1 NAME
12              
13             Closure::Explicit - check coderefs for variable capture
14              
15             =head1 VERSION
16              
17             version 0.001
18              
19             =head1 SYNOPSIS
20              
21             use Closure::Explicit qw(callback);
22              
23             {
24             package Example;
25             sub new { my $class = shift; bless {}, $class }
26             sub method { my $self = shift; print "In method\n" }
27             }
28             my $self = Example->new;
29             # This will raise an exception due to the reference to $self
30             eval {
31             my $code = callback {
32             $self->method;
33             };
34             };
35             # This will not raise the exception because $self is whitelisted
36             my $code = callback {
37             $self->method;
38             } [qw($self)];
39             # This will wrap the coderef so we can pass a weakened copy of $self
40             my $code = callback {
41             my $self = shift;
42             $self->method;
43             } weaken => [qw($self)];
44              
45             =head1 DESCRIPTION
46              
47             Attempts to provide some very basic protection against unintentional
48             capturing of lexicals in a closure.
49              
50             For example, code such as the following risks creating cycles which
51             mean the top-level object is never freed:
52              
53             sub some_method {
54             my $self = shift;
55             $self->{callback} = sub { $self->other_method }
56             }
57              
58             and this can in turn lead to memory leaks.
59              
60             =head1 API STABILITY
61              
62             The main L function is not expected to change in future versions,
63             so as long as you use this:
64              
65             use Closure::Explicit qw(callback);
66              
67             to import the function into your local namespace, or fully-qualify it using
68              
69             Closure::Explicit::callback { ... }
70              
71             then you should have no problems with future versions of this module.
72              
73             However, it is highly likely that a future version will also start exporting
74             a differently-named function with a better interface.
75              
76             =cut
77              
78 5     5   4821 use parent qw(Exporter);
  5         1629  
  5         29  
79             our @EXPORT_OK = qw(callback);
80              
81             # This is not documented, because turning it off will break
82             # the weaken behaviour.
83 5 50   5   516 use constant CLOSURE_CHECKS => exists($ENV{PERL_CLOSURE_EXPLICIT_CHECKS}) ? $ENV{PERL_CLOSURE_EXPLICIT_CHECKS} : 1;
  5         11  
  5         4358  
84              
85             =head1 EXPORTS
86              
87             =cut
88              
89             =head2 callback
90              
91             Checks the given coderef for potential closure issues, raising an exception if any
92             are found and returning the coderef (or a wrapped version of it) if everything is
93             okay.
94              
95             The first parameter is the block of code to run. This is protoyped as C< & > so
96             you can replace the usual 'sub { ... }' with 'callback { ... }'. If you already
97             have a coderef, you can pass that using C< &callback($code, ...) >, but please
98             don't.
99              
100             Remaining parameters are optional - you can either pass a single array, containing
101             a list of the B of the variables that are safe to capture:
102              
103             callback { print "$x\n" } [qw($x)];
104              
105             or a list of named parameters:
106              
107             =over 4
108              
109             =item * weaken => [...] - list of B which will be copied, weakened
110             via L, then prepended to the parameter list available in @_
111             in your code block
112              
113             =item * allowed => [...] - list of B to ignore if used in the code,
114             same behaviour as passing a single arrayref
115              
116             =back
117              
118             For example, a method call might look like this:
119              
120             my $code = callback {
121             my $self = shift;
122             $self->method(@_);
123             } weaken => [qw($self)];
124              
125             although L would be a much cleaner alternative there:
126              
127             my $code = $self->curry::weak::method;
128              
129             You can mix C and C:
130              
131             my $x = 1;
132             my $code = callback {
133             shift->method(++$x);
134             } weaken => [qw($self)], allowed => [qw($x)];
135              
136             =cut
137              
138             sub callback(&;@) {
139 18     18 1 14024 if(CLOSURE_CHECKS) {
140 18         32 my $code = shift;
141 18 100       86 my %spec = (@_ > 1) ? (@_) : (allowed => shift);
142             # warn "Have " . join ',', keys %spec;
143 18 100       67 if(my @err = lint( $code => %spec )) {
144 6         56 warn "$_\n" for @err;
145 6         1523 die "Had " . @err . " error(s) in closure";
146             }
147 12         62 return $code
148             } else {
149             die "cannot disable closure checks when using weaken" if grep $_ eq 'weaken', @_;
150             return $_[0]
151             }
152             }
153              
154             =head2 lint
155              
156             Runs checks on the given coderef. This is used internally and not exported,
157             but if you just want to get a list of potential problems for a coderef,
158             call this:
159              
160             my @errors = lint($code, allowed => [qw($x)]);
161              
162             It's unlikely that the C parameter will work when calling this
163             function directly - this may be fixed in a future version.
164              
165             =cut
166              
167             sub lint {
168 18     18 1 44 my ($code, %spec) = @_;
169 18         93 my $cv = B::svref_2object($code);
170 18         308 my $details = sprintf '%s(%s:%d)', $cv->STASH->NAME, $cv->FILE, $cv->GV->LINE;
171              
172 18         46 my %closed = %{closed_over($code)};
  18         107  
173 11         74 my %closed_by_value = map {
174 18         52 ref($closed{$_}) eq 'REF'
175 11 50       56 ? (${$closed{$_}} => $_)
176             : ()
177             } keys %closed;
178              
179             # This is everything we declare in the sub
180 18         34 my @lexicals = grep !exists $closed{$_}, keys %{ peek_sub $code };
  18         153  
181              
182 18 100       73 if($spec{weaken}) {
183             # warn "weaken request: " . join ',', @{$spec{weaken}};
184 5         39 my $scope = peek_my(2);
185 5         10 my $real_code = $code;
186 5         11 my @extra = map ${ $scope->{$_} }, @{$spec{weaken}};
  5         64  
  5         17  
187 5         56 Scalar::Util::weaken($_) for @extra;
188 5     2   32 $code = $_[0] = sub { $real_code->(@extra, @_) };
  2         9  
189             }
190              
191             # That's it for the data collection, now run the tests
192 18         30 my @errors;
193 18         24 foreach my $var (@{$spec{declares}}) {
  18         54  
194 0 0       0 push @errors, "no $var declared in padlist" unless grep $_ eq $var, @lexicals;
195             }
196             # say " * We are capturing $_" for sort keys %closed;
197 18         51 my %allowed = map { $_ => 1 } @{$spec{allowed}};
  5         30  
  18         40  
198 18         100 push @errors, "$_ captured in closure, recommend checking for cycles" for sort grep !exists $allowed{$_}, keys %closed;
199              
200 18         33 foreach my $var (@{$spec{captures}}) {
  18         671  
201 0 0       0 push @errors, "$var captured in closure, recommend checking for cycles" if grep $_ eq $var, keys %closed;
202             }
203 18         614 push @errors, "blacklisted value found in closure: $_ ($closed_by_value{$_})" for grep exists $closed_by_value{$_}, @{$spec{values}};
  18         64  
204 18         1132 return map "$details - $_", @errors;
205             }
206              
207             1;
208              
209             __END__