Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?

Source filters in eval/require

by liz (Monsignor)
on Oct 02, 2003 at 21:12 UTC ( #296091=perlquestion: print w/replies, xml ) Need Help??

liz has asked for the wisdom of the Perl Monks concerning the following question:

Executive Summary: how can I get a source filter applied to required or evalled source code?

This came up while investigating some more features of load, when I suddenly realised that source filters have problems with AUTOLOADer modules, such as AutoLoader and SelfLoader (and of course The problem basically is that they're not applied to require'd or eval'ed source. For example, take this module which basically uppercases any strings between double quotes:

# package Foo; use Filter::Util::Call (); 1; sub import { Filter::Util::Call::filter_add( sub { my $status; if (($status = Filter::Util::Call::filter_read()) > 0) { s#("[^"]+")#\U$1\E#g; } $status; } ); } #import

This module is called in a script called "bar":

# bar use Foo; print "Hello World",$/; #require 'bar2'; # source is not filtered ;-( { local $/; open my $handle,'<','bar2' or die $!; $source = <$handle>; } #eval $source; # source is not filtered ;-( eval 'use Foo;'.$source; # even with importing again, source is not f +iltered ;-(

and the external file "bar2":

# bar2 print "Just Another Perl Hacker",$/;

and alas, the output is always:

Just Another Perl Hacker
whereas you would hope and/or expect:

These nodes seem related to this subject, but refer to other aspects of this problem I think.

I've been thinking a lot about this, but I guess I'm missing the right inspiration. I hope someone in the Monastery will have the right inspiration to fix this problem so I can add more features to


Replies are listed 'Best First'.
Re: Source filters in eval/require
by BrowserUk (Patriarch) on Oct 03, 2003 at 01:03 UTC

    If you prefix eval strings with

    'delete $INC{"Foo"}; use Foo;'
    in your filter, then your filter will be re-loaded when the string is eval'd.

    However, when I tried this -- using Filter::Simple as I thought that I could use it's separation of code and strings to my advantage -- it gets very confusing about what stage you are at. Everytime I thought I was close to having something work, another gotcha popped up.

    So, I thought I pass the basic idea along and see if it give you better inspiration than I. :)

    Update: dws pointed out that the above code wasn't responsible for my percieved partial success. The use Foo; will happen before the delete $INC{'Foo'}. The delete $INC{'Foo'} needs to delete $INC{''} anyway.

    The thing fooled me was a redundant piece of test code in the external file....D'oh.

    Anyway, having re-examined it, I did get a little closer, but still way short of anything that could be called successful.

    The filter

    package Filter::Test; use Filter::Simple; FILTER_ONLY code => sub { # print'C: ', $_; s[eval\s*($Filter::Simple::placeholder)]['<<<<<1>>>>>' eval '< +<<<<2>>>>>' . $1 ]; # print'C: ', $_; $_; }, quotelike => sub { # print 'Q: ', $_; $_ = uc $_; s['<<<<<1>>>>>'][delete \$INC{'Filter/'};]; s['<<<<<2>>>>>']["BEGIN{ require Filter::Test; Filter::Test::i +mport}"]; # print 'Q: ', $_; $_; }, ; print 'Filter::Test loaded';

    The test program

    #! perl -slw use strict; use Filter::Test; print 'Hello World from ', __PACKAGE__; eval "print 'Just another perl hacker'"; #require 'My/';

    The external program

    package My::filtertest; print 'Hello World'; eval "print 'Just another perl hacker';"; 1;

    The output

    P:\test>296091 Filter::Test loaded HELLO WORLD FROM main P:\test>296091 Filter::Test loaded HELLO WORLD FROM main Filter::Test loaded Use of uninitialized value in concatenation (.) or string at d:/Perl/l +ib/Filter/ line 156. Use of uninitialized value in concatenation (.) or string at d:/Perl/l +ib/Filter/ line 156. String found where operator expected at (eval 5) line 1, near "PRINT ' +JUST ANOTHER PERL HACKER'" (Do you need to predeclare PRINT?) P:\test>

    As you can see, the Filter is being loaded twice, and the text within the evald string has bee uppercased (too much of it, but that's a detail :).

    I did have the require half working at one point, by using eval 'use Filter::Test;' do{ local *ARGV = 'file'; <> };, but it starts getting very complicated when you start trying to filter an eval'd require file that contains evals that contains requires....

    Recurse; See Recurse; 8^o.

    I can't wrap my brain around it any longer, but maybe it will inspire you, or convince you to try a different route, which might be an equally valid assist :)

    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller
    If I understand your problem, I can solve it! Of course, the same can be said for you.

      ...I can't wrap my brain around it any longer...

      That describes my feelings quite a bit before posting ;-)

      ...maybe it will inspire you, or convince you to try a different route, which might be an equally valid assist :)

      I'm considering stealing Filter::Util::Call::filter_add and Filter::Util::Call::filter_del. Problem only is that I would need to steal these before they're getting loaded. ;-( Does anyone have any idea about that?


Re: Source filters in eval/require
by tilly (Archbishop) on Oct 05, 2003 at 16:57 UTC
    I don't have the energy to work out all of the details of how to do it, but here is an approach that I know can work.

    It is now possible to put anonymous subroutines in @INC. They get 2 arguments, the first being itself, the second being the module to load. It is supposed to return undef if it won't handle the request, and a filehandle otherwise. It can also look at caller to figure out who is asking. See perlfunc in 5.8 or better for details. (It was there earlier, but not documented IIRC.)

    So put a subroutine there that examines caller, and if the caller is one that you think should get your filtering, find and read the file you are requiring, slap your filter on the start, and return a filehandle to that in-memory filter.

    Trapping eval is also doable with this strategy. Replace eval with a call to a function that puts the code to eval in a given variable, and does a require that the sub above will decide to handle by slapping its stuff on the variable in question, and returning a filehandle to it. Your choice on how to get sub and require to cooperate.

    I would lean towards only putting one closure in @INC when your module is loaded, and then make import (and unimport if you want) work by manipulating lexical data which your import subroutine will examine in making its decision about whether it should trap any given require. (This because your import will potentially get called a lot, adding to @INC each time is..overkill.)

    I also think that it would be good if source-filters had some easy way to trap evals. But generally I would prefer that they not trap requires unless I ask for it because that makes it tricky and potentially dangerous to load existing code from within a filtered piece of code. (Furthermore the results cannot be guaranteed - what if the require'ed code was loaded before yours? Then it isn't filtered.) Therefore I might limit your hack to just evals and/or require's that you somehow know are intended to be yours.

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://296091]
Approved by jdtoronto
Front-paged by broquaint
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others rifling through the Monastery: (4)
As of 2022-08-18 08:33 GMT
Find Nodes?
    Voting Booth?

    No recent polls found