in reply to The safety of string eval and block eval.

There is no problem with string eval if your program controls the string that is going to be evaluated.

Sometimes, you actually need to evaluate strings hard coded in the program. As an example, let's start with a simple iterator in the spirit of Dominus's Higher Order Perl:

sub create_iterator{ my $val = shift; return sub { return $val += 2;} } my $iter_even = create_iterator(0); my $iter_odd = create_iterator(1);
This creates two closures which iterate on even or odd numbers.

Suppose now that you want to create a generic iterator in which you could pass an arbitrary function. If you try to do that, it might look like this:

sub create_iter { my ($code_ref, @rest) = @_; return sub {$code_ref->();} }
and be called with a callback function like this:
my $even = create_iter(sub {my $c; $c += 2; return $c;}, 0); # Doe +s NOT work
That does not work because, since the $c variable is created within the callback subroutine passed to the create_iter subroutine, create_iter will not close over the variable which is defined elsewhere.

Basically, this cannot work because the callback function is not defined within the environment that would make it a closure.

As far as I can tell, there is no solution with the techniques described above. So, we cannot make a generic iterator? Yes we can, if we use eval to create the callback function at the right place, using a string passed as a parameter. For example, the following code defines a generic iterator used to generate one by one Fibonacci numbers:

use strict; use warnings; sub create_iter { my ($code_string, @rest) = @_; eval $code_string; } my $fibonacci = create_iter ( ' return sub {my ($c, $d) = @rest; my $e = $c + $d; @rest = ($d, $e); return $e;} ', 1, 1); print "Fibonacci numbers: \n"; print $fibonacci->(), " ", for 1..7;
which will print:
Fibonacci: 2 3 5 8 13 21 34
The point is that using eval makes it possible to create the closure code in the right place for the subroutine to close over the variable.

I can now use the same generic iterator to generate factorials:

my $fact = create_iter (<<'EOS' return sub { $rest[0]++; $rest[1] *= $rest[0];}; EOS , 1, 1); print "\n\nFact: \n"; print $fact->(), " ", for 1..5;
which will print:
Fact: 2 6 24 120 720
The example might seem somewhat contrived, but that's the only way that I found to easily create a generic iterator when I needed one.

So, to me, string eval can in some cases be a very useful technique, and, as far as I can tell, there is no danger of malicious abuse, because the string used is actually hard-coded in the program.

Another example of useful string eval can be found in the Benchmark module, which offers the possibility to pass the function whose performance should be measured in the form of a string that is (I think) evaluated with eval.

In brief, my point is that string eval can be very useful and should not be ruled out, but, of course, can be dangerous if used, for example, with user input.

Replies are listed 'Best First'.
Re^2: The safety of string eval and block eval.
by choroba (Cardinal) on Aug 14, 2016 at 23:19 UTC
    I'm not sure string eval is needed to create a generic interator, I was able to reproduce all your examples with just anonymous subs and some @_ juggling:
    #!/usr/bin/perl use warnings; use strict; use feature qw{ say }; sub create_iter { my ($code_ref, @rest) = @_; sub { $code_ref->(@rest) } } my $iter = create_iter(sub { $_[0] += 2 }, 0); say $iter->() for 1 .. 5; my $fibo = create_iter(sub { @_[0, 1] = ($_[1], $_[0] + $_[1]); $_[1] +}, 1, 1); say $fibo->() for 1 .. 7; my $fact = create_iter(sub { $_[1] *= ++$_[0] }, 1, 1); say $fact->() for 1 .. 5;
    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
      Yes choroba, you're right, thanks a lot for your examples. It seems it can be done playing with @_, at least for those examples.

      Either I was simply not able to find the right syntax when I was playing with these things a couple of years ago, or my actual use cases were more complicated than the simple examples I gave in my previous post. I'd have to go back to what I was doing at the time to figure that out, but I do not have these things available with me today.

      My gut feeling, though, is that I probably was simply not able to find the right syntax.

        As a very generic rule of thumb: If you think that you need a string eval, you are doing it wrong.

        There are very few good reasons for using a string eval, and even fewer if the string eval has to work on input controlled by the user. use, require, and the rare do FILENAME, are of course string evals behind the scenes, and they are useful and the proper way to load perl code from files.

        In very rare cases, perl functions have to be generated from data at run time, and at that point, a string eval is actually the best way to solve the problem. But for tasks like parsing configuration files, "variable variable names", constructing variables at run-time, reading data back from stored files (e.g. from Data::Dumper), string eval is the wrong way. Configuration files are better stored as INI files, JSON or XML (all readable and all without executable code). "Variable variable names" and variables constructed at runtime are better implemented via arrays, hashes, or (worst case) symbol table manipulation. And data is better stored using JSON, XML, one of the various "universal" binary storage formats (see Re^4: Storing state of execution -- Sereal?), or the problematic (see Re: Perl Storable problem (i Think) but fast Storable.

        See also: Re^2: Storing state of execution, Re^4: Storing state of execution

        And of course, for a quick hack that runs on known data, string eval is ok if it does the job AND the quick hack does not leave your environment. Don't let users use any tool that uses string eval just because it needs less code than a proper solution.

        Alexander

        --
        Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)