4 use Sub::Exporter -setup => {
5 exports => [qw(eval_closure)],
6 groups => { default => [qw(eval_closure)] },
8 # ABSTRACT: safely and cleanly create closures via string eval
12 use Scalar::Util qw(reftype);
15 use constant HAS_LEXICAL_SUBS => $] >= 5.018;
21 my $code = eval_closure(
22 source => 'sub { $foo++ }',
31 my $code2 = eval_closure(
32 source => 'sub { $code->() }',
33 ); # dies, $code isn't in scope
37 String eval is often used for dynamic code generation. For instance, C<Moose>
38 uses it heavily, to generate inlined versions of accessors and constructors,
39 which speeds code up at runtime by a significant amount. String eval is not
40 without its issues however - it's difficult to control the scope it's used in
41 (which determines which variables are in scope inside the eval), and it's easy
42 to miss compilation errors, since eval catches them and sticks them in $@
45 This module attempts to solve these problems. It provides an C<eval_closure>
46 function, which evals a string in a clean environment, other than a fixed list
47 of specified variables. Compilation errors are rethrown automatically.
51 =func eval_closure(%args)
53 This function provides the main functionality of this module. It is exported by
54 default. It takes a hash of parameters, with these keys being valid:
60 The string to be evaled. It should end by returning a code reference. It can
61 access any variable declared in the C<environment> parameter (and only those
62 variables). It can be either a string, or an arrayref of lines (which will be
63 joined with newlines to produce the string).
67 The environment to provide to the eval. This should be a hashref, mapping
68 variable names (including sigils) to references of the appropriate type. For
69 instance, a valid value for environment would be C<< { '@foo' => [] } >> (which
70 would allow the generated function to use an array named C<@foo>). Generally,
71 this is used to allow the generated function to access externally defined
72 variables (so you would pass in a reference to a variable that already exists).
74 In perl 5.18 and greater, the environment hash can contain variables with a
75 sigil of C<&>. This will create a lexical sub in the evaluated code (see
76 L<feature/The 'lexical_subs' feature>). Using a C<&> sigil on perl versions
77 before lexical subs were available will throw an error.
81 This lets you provide a bit more information in backtraces. Normally, when a
82 function that was generated through string eval is called, that stack frame
83 will show up as "(eval n)", where 'n' is a sequential identifier for every
84 string eval that has happened so far in the program. Passing a C<description>
85 parameter lets you override that to something more useful (for instance,
86 L<Moose> overrides the description for accessors to something like "accessor
87 foo at MyClass.pm, line 123").
91 This lets you override the particular line number that appears in backtraces,
92 much like the C<description> option. The default is 1.
96 Normally, this function appends the source code that failed to compile, and
97 prepends some explanatory text. Setting this option to true suppresses that
98 behavior so you get only the compilation error that Perl actually reported.
107 $args{source} = _canonicalize_source($args{source});
108 _validate_env($args{environment} ||= {});
110 $args{source} = _line_directive(@args{qw(line description)})
112 if defined $args{description} && !($^P & 0x10);
114 my ($code, $e) = _clean_eval_closure(@args{qw(source environment)});
117 if ($args{terse_error}) {
121 croak("Failed to compile source: $e\n\nsource:\n$args{source}")
128 sub _canonicalize_source {
131 if (defined($source)) {
133 if (reftype($source) eq 'ARRAY'
134 || overload::Method($source, '@{}')) {
135 return join "\n", @$source;
137 elsif (overload::Method($source, '""')) {
141 croak("The 'source' parameter to eval_closure must be a "
142 . "string or array reference");
150 croak("The 'source' parameter to eval_closure is required");
157 croak("The 'environment' parameter must be a hashref")
158 unless reftype($env) eq 'HASH';
160 for my $var (keys %$env) {
161 if (HAS_LEXICAL_SUBS) {
162 croak("Environment key '$var' should start with \@, \%, \$, or \&")
163 unless $var =~ /^([\@\%\$\&])/;
166 croak("Environment key '$var' should start with \@, \%, or \$")
167 unless $var =~ /^([\@\%\$])/;
169 croak("Environment values must be references, not $env->{$var}")
170 unless ref($env->{$var});
174 sub _line_directive {
175 my ($line, $description) = @_;
177 $line = 1 unless defined($line);
179 return qq{#line $line "$description"\n};
182 sub _clean_eval_closure {
183 my ($source, $captures) = @_;
185 my @capture_keys = sort keys %$captures;
187 if ($ENV{EVAL_CLOSURE_PRINT_SOURCE}) {
188 _dump_source(_make_compiler_source($source, @capture_keys));
191 my ($compiler, $e) = _make_compiler($source, @capture_keys);
193 if (defined $compiler) {
194 $code = $compiler->(@$captures{@capture_keys});
197 if (defined($code) && (!ref($code) || ref($code) ne 'CODE')) {
198 $e = "The 'source' parameter must return a subroutine reference, "
207 my $source = _make_compiler_source(@_);
209 return @{ _clean_eval($source) };
215 my $compiler = eval $_[0];
220 $Eval::Closure::SANDBOX_ID = 0;
222 sub _make_compiler_source {
223 my ($source, @capture_keys) = @_;
224 $Eval::Closure::SANDBOX_ID++;
227 "package Eval::Closure::Sandbox_$Eval::Closure::SANDBOX_ID;",
229 (map { _make_lexical_assignment($_, $i++) } @capture_keys),
235 sub _make_lexical_assignment {
236 my ($key, $index) = @_;
237 my $sigil = substr($key, 0, 1);
238 my $name = substr($key, 1);
239 if (HAS_LEXICAL_SUBS && $sigil eq '&') {
240 my $tmpname = '$__' . $name . '__' . $index;
241 return 'use feature "lexical_subs"; '
242 . 'no warnings "experimental::lexical_subs"; '
243 . 'my ' . $tmpname . ' = $_[' . $index . ']; '
244 . 'my sub ' . $name . ' { goto ' . $tmpname . ' }';
247 return 'my ' . $key . ' = ' . $sigil . '{$_[' . $index . ']};';
255 if (try { require Perl::Tidy }) {
256 Perl::Tidy::perltidy(
258 destination => \$output,
273 Please report any bugs to GitHub Issues at L<https://github.com/doy/eval-closure/issues>.
279 =item * L<Class::MOP::Method::Accessor>
281 This module is a factoring out of code that used to live here
287 You can find this documentation for this module with the perldoc command.
289 perldoc Eval::Closure
291 You can also look for information at:
297 L<https://metacpan.org/release/Reply>
301 L<https://github.com/doy/reply>
303 =item * RT: CPAN's request tracker
305 L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Reply>
309 L<http://cpanratings.perl.org/d/Reply>
315 Based on code from L<Class::MOP::Method::Accessor>, by Stevan Little and the