File Coverage

blib/lib/Eval/Closure.pm
Criterion Covered Total %
statement 87 99 87.8
branch 34 40 85.0
condition 9 11 81.8
subroutine 18 19 94.7
pod 1 1 100.0
total 149 170 87.6


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