eval()ing a pattern substitution under 'use strict' and lexical scope

B

bernd

Hi folks,

I have two perl progs, both expected to do the same, eval()ing a
pattern substitution expression using variables for the substitution
pattern and the replacement (to be used in a subroutine later on).

The first prog uses 'use strict' and lexical variables and looks like
this:

#!/usr/bin/perl -w

use strict;
my $old = 'test this one';
my $pat = 'this';
my $repl = 'that';
my $mods = '';
my $new;

eval('($new = $old) =~ s/$pat/$repl/ee . $mods');

print $new . "\n";

($mods can take a string containing one or more modifiers (like 'g')
to the s/// operator).

The output is

Use of uninitialized value in substitution iterator at (eval 1) line
1.
test one

In the second version I dispensed with 'use strict' and lexical
variables:

#!/usr/bin/perl -w

$old = 'test this one';
$pat = 'this';
$repl = 'that';
$mods = '';

eval('($new = $old) =~ s/$pat/$repl/ee . $mods');

print $new . "\n";

And the output is as expected:

Name "main::repl" used only once: possible typo at ./subst_nostrict.pl
line 5.
Name "main::new" used only once: possible typo at ./subst_nostrict.pl
line 10.
Name "main::eek:ld" used only once: possible typo at ./subst_nostrict.pl
line 3.
Name "main::pat" used only once: possible typo at ./subst_nostrict.pl
line 4.
Name "main::mods" used only once: possible typo at ./subst_nostrict.pl
line 6.
Unquoted string "that" may clash with future reserved word at (eval 2)
line 2.
test that one

Unfortunately, in the project in which the eval-stuff should be
integrated I have to use 'strict' and lexical scoping.

Can somebody explain the differences in the output and how I could
reach the desired output 'test that one'?

TIA


Bernd
 
B

bernd

Hi Ben,

thanks for the elaborate explanations.
Did you mean the '. $mods' to come outside the string?

Exactly, and in the original code it was like that. I changed this to
the statement posted during testing and "forgot" to change it back
since it seemed to have no effect anyway (but this was an erroneous
assumption, caused by the fact that $mods was the empty string during
all of these tests). So, the outer eval was intended to "glue" the
modifier string to the s///ee expression, what it actually does if
$mods is outside of the single quoted string.

My intention with this code was to use it in a subroutine which
executes pattern substitution with $pat and $repl read from a
configuration file. The double evaluation of the substitution operator
is strictly necessary since it could be possible that the replacement
read from the configuration file could contain backreference variables
($1, $2, ...).

So, what did I do after reading Your post?:

I changed back the eval-statement as mentioned above, introduced the
subroutine, in which I placed the code, with 'no use "subs"' (so that
'use "vars"' is still in effect and lexicals are forced) and switched
off warnings for the statement containing the pattern substitution
('no warnings'/'use warnings').
What if someone gives
you a $pat of
'//; system "rm -rf /"; s/'

Hm. The only users who could do something like this would be able to
execute 'rm -rf' on the UNIX command line anyway (and then they would
mess around with their own workplace ;-)). If a black hat from outside
would be able to do this we would have a much bigger problem with our
it-security and therefore this particular thread is neglectable. :->>

Cheers


Bernd
 
B

bernd

Quoth bernd <[email protected]>:




I don't know what you're trying to do, but I'm *certain* there's a
better way of doing it. Maybe if you explained why you're doing this
someone could suggest a less-insane alternative.

















OK, you have multiple levels of 'eval' here, so we need to step through
them carefully to understand what's going on.

First we eval the single-quoted string

    '($new = $old) =~ s/$pat/$repl/ee . $mods'

Since this is single-quoted there's no interpolation, so the eval has
exactly the same effect as writing the code out normally. (Did you mean
the '. $mods' to come outside the string?) So now we are executing

    ($new = $old) =~ s/$pat/$repl/ee . $mods

s///ee starts by interpolating $pat on the LHS, so we're searching for

    /this/

(as a regular expression). For each instance it finds, it runs

    eval "$repl"

(note the double-quotes, they're important). This is equivalent to

    eval 'that'

which is equivalent to

    that;

as a standalone Perl statement.

Since this is all under 'use strict "subs"', and there is no 'sub that'
in scope, this throws a 'Bareword "that" not allowed while "strict subs"
in use' error. The eval catches the error, stuffs it into $@, and
returns undef. The s///ee takes the undef and inserts it into $new in
place of the 'this' it found, which gives an 'uninitialized value'
warning.


The reason this 'worked' is not because you stopped using 'strict
"refs"' and lexicals, but because you stopped using 'strict "subs"'.

Perl 4 had a completely bizarre bit of behaviour: if it saw an
expression like

    that;

and there was no 'sub that' defined, it assumed you had meant

    'that';

and carried right on. This means that once the chain of evals above gets
that far, it simply turns the bareword back into a string (without
complaining) and you get the substitution you were expecting.

Now, how much of that did you want? I suspect you will get what you
meant if you just remove the 'ee' (and put the $mods outside the
string), but the whole thing is enormously unsafe. What if someone gives
you a $pat of

    '//; system "rm -rf /"; s/'

?

Ben
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top