File Coverage

blib/lib/Eval/Closure.pm
Criterion Covered Total %
statement 92 101 91.0
branch 33 38 86.8
condition 11 14 78.5
subroutine 19 21 90.4
pod 1 1 100.0
total 156 175 89.1


line stmt bran cond sub pod time code
1             package Eval::Closure;
2             BEGIN {
3 11     11   142899 $Eval::Closure::AUTHORITY = 'cpan:DOY';
4             }
5             $Eval::Closure::VERSION = '0.13';
6 11     10   58 use strict;
  11         164  
  10         206  
7 10     10   29 use warnings;
  10         37  
  10         254  
8             # ABSTRACT: safely and cleanly create closures via string eval
9              
10 10     10   30 use Exporter 'import';
  10         59  
  10         317  
11             @Eval::Closure::EXPORT = @Eval::Closure::EXPORT_OK = 'eval_closure';
12              
13 10     9   30 use Carp;
  10         39  
  9         423  
14 9     9   8476 use overload ();
  9         6571  
  9         176  
15 9     9   39 use Scalar::Util qw(reftype);
  9         9  
  9         617  
16 9     9   1674 use Try::Tiny;
  9         3728  
  9         437  
17              
18 9     9   37 use constant HAS_LEXICAL_SUBS => $] >= 5.018;
  9         9  
  9         7546  
19              
20              
21              
22             sub eval_closure {
23 29     29 1 7417 my (%args) = @_;
24              
25             # default to copying environment
26 29 100       87 $args{alias} = 0 if !exists $args{alias};
27              
28 29         57 $args{source} = _canonicalize_source($args{source});
29 27   100     122 _validate_env($args{environment} ||= {});
30              
31 25 100 100     79 $args{source} = _line_directive(@args{qw(line description)})
32             . $args{source}
33             if defined $args{description} && !($^P & 0x10);
34              
35 25         66 my ($code, $e) = _clean_eval_closure(@args{qw(source environment alias)});
36              
37 24 100       45 if (!$code) {
38 6 100       11 if ($args{terse_error}) {
39 1         5 die "$e\n";
40             }
41             else {
42 5         545 croak("Failed to compile source: $e\n\nsource:\n$args{source}")
43             }
44             }
45              
46 18         52 return $code;
47             }
48              
49             sub _canonicalize_source {
50 29     29   33 my ($source) = @_;
51              
52 29 100       53 if (defined($source)) {
53 28 100       48 if (ref($source)) {
54 2 100 66     16 if (reftype($source) eq 'ARRAY'
    50          
55             || overload::Method($source, '@{}')) {
56 1         4 return join "\n", @$source;
57             }
58             elsif (overload::Method($source, '""')) {
59 0         0 return "$source";
60             }
61             else {
62 1         108 croak("The 'source' parameter to eval_closure must be a "
63             . "string or array reference");
64             }
65             }
66             else {
67 26         45 return $source;
68             }
69             }
70             else {
71 1         124 croak("The 'source' parameter to eval_closure is required");
72             }
73             }
74              
75             sub _validate_env {
76 27     27   26 my ($env) = @_;
77              
78 27 50       115 croak("The 'environment' parameter must be a hashref")
79             unless reftype($env) eq 'HASH';
80              
81 27         78 for my $var (keys %$env) {
82 8         10 if (HAS_LEXICAL_SUBS) {
83 8 100       153 croak("Environment key '$var' should start with \@, \%, \$, or \&")
84             unless $var =~ /^([\@\%\$\&])/;
85             }
86             else {
87             croak("Environment key '$var' should start with \@, \%, or \$")
88             unless $var =~ /^([\@\%\$])/;
89             }
90 7 100       103 croak("Environment values must be references, not $env->{$var}")
91             unless ref($env->{$var});
92             }
93             }
94              
95             sub _line_directive {
96 2     2   2 my ($line, $description) = @_;
97              
98 2 100       3 $line = 1 unless defined($line);
99              
100 2         7 return qq{#line $line "$description"\n};
101             }
102              
103             sub _clean_eval_closure {
104 25     25   30 my ($source, $captures, $alias) = @_;
105              
106 25         64 my @capture_keys = sort keys %$captures;
107              
108 25 50       56 if ($ENV{EVAL_CLOSURE_PRINT_SOURCE}) {
109 0         0 _dump_source(_make_compiler_source($source, $alias, @capture_keys));
110             }
111              
112 25         53 my ($compiler, $e) = _make_compiler($source, $alias, @capture_keys);
113 25         46 my $code;
114 25 100       56 if (defined $compiler) {
115 20         377 $code = $compiler->(@$captures{@capture_keys});
116             }
117              
118 24 100 66     149 if (defined($code) && (!ref($code) || ref($code) ne 'CODE')) {
      66        
119 1         3 $e = "The 'source' parameter must return a subroutine reference, "
120             . "not $code";
121 1         2 undef $code;
122             }
123              
124 24 100       41 if ($alias) {
125 1         7 require Devel::LexAlias;
126             Devel::LexAlias::lexalias($code, $_, $captures->{$_})
127 1         13 for grep !/^\&/, keys %$captures;
128             }
129              
130 24         70 return ($code, $e);
131             }
132              
133             sub _make_compiler {
134 25     25   47 my $source = _make_compiler_source(@_);
135              
136 25         25 return @{ _clean_eval($source) };
  25         43  
137             }
138              
139             sub _clean_eval {
140 25     25   21 local $@;
141 25         65 local $SIG{__DIE__};
142 3     3   23 my $compiler = eval $_[0];
  3         2  
  3         206  
  25         2227  
143 25         60 my $e = $@;
144 25         163 [ $compiler, $e ];
145             }
146              
147             $Eval::Closure::SANDBOX_ID = 0;
148              
149             sub _make_compiler_source {
150 25     25   32 my ($source, $alias, @capture_keys) = @_;
151 25         26 $Eval::Closure::SANDBOX_ID++;
152 25         24 my $i = 0;
153 6         18 return join "\n", (
154             "package Eval::Closure::Sandbox_$Eval::Closure::SANDBOX_ID;",
155             'sub {',
156 25         83 (map { _make_lexical_assignment($_, $i++, $alias) } @capture_keys),
157             $source,
158             '}',
159             );
160             }
161              
162             sub _make_lexical_assignment {
163 6     6   7 my ($key, $index, $alias) = @_;
164 6         11 my $sigil = substr($key, 0, 1);
165 6         10 my $name = substr($key, 1);
166 6 100       91 if (HAS_LEXICAL_SUBS && $sigil eq '&') {
167 1         3 my $tmpname = '$__' . $name . '__' . $index;
168 1         11 return 'use feature "lexical_subs"; '
169             . 'no warnings "experimental::lexical_subs"; '
170             . 'my ' . $tmpname . ' = $_[' . $index . ']; '
171             . 'my sub ' . $name . ' { goto ' . $tmpname . ' }';
172             }
173 5 100       8 if ($alias) {
174 1         43 return 'my ' . $key . ';';
175             }
176             else {
177 4         21 return 'my ' . $key . ' = ' . $sigil . '{$_[' . $index . ']};';
178             }
179             }
180              
181             sub _dump_source {
182 0     0     my ($source) = @_;
183              
184 0           my $output;
185 0 0   0     if (try { require Perl::Tidy }) {
  0            
186 0           Perl::Tidy::perltidy(
187             source => \$source,
188             destination => \$output,
189             argv => [],
190             );
191             }
192             else {
193 0           $output = $source;
194             }
195              
196 0           warn "$output\n";
197             }
198              
199              
200             1;
201              
202             __END__
203              
204             =pod
205              
206             =encoding UTF-8
207              
208             =head1 NAME
209              
210             Eval::Closure - safely and cleanly create closures via string eval
211              
212             =head1 VERSION
213              
214             version 0.13
215              
216             =head1 SYNOPSIS
217              
218             use Eval::Closure;
219              
220             my $code = eval_closure(
221             source => 'sub { $foo++ }',
222             environment => {
223             '$foo' => \1,
224             },
225             );
226              
227             warn $code->(); # 1
228             warn $code->(); # 2
229              
230             my $code2 = eval_closure(
231             source => 'sub { $code->() }',
232             ); # dies, $code isn't in scope
233              
234             =head1 DESCRIPTION
235              
236             String eval is often used for dynamic code generation. For instance, C<Moose>
237             uses it heavily, to generate inlined versions of accessors and constructors,
238             which speeds code up at runtime by a significant amount. String eval is not
239             without its issues however - it's difficult to control the scope it's used in
240             (which determines which variables are in scope inside the eval), and it's easy
241             to miss compilation errors, since eval catches them and sticks them in $@
242             instead.
243              
244             This module attempts to solve these problems. It provides an C<eval_closure>
245             function, which evals a string in a clean environment, other than a fixed list
246             of specified variables. Compilation errors are rethrown automatically.
247              
248             =head1 FUNCTIONS
249              
250             =head2 eval_closure(%args)
251              
252             This function provides the main functionality of this module. It is exported by
253             default. It takes a hash of parameters, with these keys being valid:
254              
255             =over 4
256              
257             =item source
258              
259             The string to be evaled. It should end by returning a code reference. It can
260             access any variable declared in the C<environment> parameter (and only those
261             variables). It can be either a string, or an arrayref of lines (which will be
262             joined with newlines to produce the string).
263              
264             =item environment
265              
266             The environment to provide to the eval. This should be a hashref, mapping
267             variable names (including sigils) to references of the appropriate type. For
268             instance, a valid value for environment would be C<< { '@foo' => [] } >> (which
269             would allow the generated function to use an array named C<@foo>). Generally,
270             this is used to allow the generated function to access externally defined
271             variables (so you would pass in a reference to a variable that already exists).
272              
273             In perl 5.18 and greater, the environment hash can contain variables with a
274             sigil of C<&>. This will create a lexical sub in the evaluated code (see
275             L<feature/The 'lexical_subs' feature>). Using a C<&> sigil on perl versions
276             before lexical subs were available will throw an error.
277              
278             =item alias
279              
280             If set to true, the coderef returned closes over the variables referenced in
281             the environment hashref. (This feature requires L<Devel::LexAlias>.) If set to
282             false, the coderef closes over a I<< shallow copy >> of the variables.
283              
284             If this argument is omitted, Eval::Closure will currently assume false, but
285             this assumption may change in a future version.
286              
287             =item description
288              
289             This lets you provide a bit more information in backtraces. Normally, when a
290             function that was generated through string eval is called, that stack frame
291             will show up as "(eval n)", where 'n' is a sequential identifier for every
292             string eval that has happened so far in the program. Passing a C<description>
293             parameter lets you override that to something more useful (for instance,
294             L<Moose> overrides the description for accessors to something like "accessor
295             foo at MyClass.pm, line 123").
296              
297             =item line
298              
299             This lets you override the particular line number that appears in backtraces,
300             much like the C<description> option. The default is 1.
301              
302             =item terse_error
303              
304             Normally, this function appends the source code that failed to compile, and
305             prepends some explanatory text. Setting this option to true suppresses that
306             behavior so you get only the compilation error that Perl actually reported.
307              
308             =back
309              
310             =head1 BUGS
311              
312             No known bugs.
313              
314             Please report any bugs to GitHub Issues at
315             L<https://github.com/doy/eval-closure/issues>.
316              
317             =head1 SEE ALSO
318              
319             =over 4
320              
321             =item * L<Class::MOP::Method::Accessor>
322              
323             This module is a factoring out of code that used to live here
324              
325             =back
326              
327             =head1 SUPPORT
328              
329             You can find this documentation for this module with the perldoc command.
330              
331             perldoc Eval::Closure
332              
333             You can also look for information at:
334              
335             =over 4
336              
337             =item * MetaCPAN
338              
339             L<https://metacpan.org/release/Eval-Closure>
340              
341             =item * Github
342              
343             L<https://github.com/doy/eval-closure>
344              
345             =item * RT: CPAN's request tracker
346              
347             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Eval-Closure>
348              
349             =item * CPAN Ratings
350              
351             L<http://cpanratings.perl.org/d/Eval-Closure>
352              
353             =back
354              
355             =head1 NOTES
356              
357             Based on code from L<Class::MOP::Method::Accessor>, by Stevan Little and the
358             Moose Cabal.
359              
360             =head1 AUTHOR
361              
362             Jesse Luehrs <doy@tozt.net>
363              
364             =head1 COPYRIGHT AND LICENSE
365              
366             This software is copyright (c) 2015 by Jesse Luehrs.
367              
368             This is free software; you can redistribute it and/or modify it under
369             the same terms as the Perl 5 programming language system itself.
370              
371             =cut