Hack 72. Find Functions Safely


Look for code to execute without risking explosions.

The ultimate goal of designing reusable code is genericitybeing able to write useful pieces of code that allow future expansion without (much) difficulty or modification. Complete genericity is difficult, as your code has to make some assumptions somewhere.

Perl's very flexible about how you interact with other code. You can fold, spindle, mutilate, and mangle symbols in any package you want at almost any time. Although this flexibility makes it possible to find code in other packages, sometimes it makes it difficult to know if the function you want is really there, at least safely and without digging through symbol tables.

You can do it, though.

The Hack

If you can avoid the problem, avoid it.

One of the most common ways to interact with other code is to provide an interface you expect it to fulfill. This may be through suggesting that all plug-ins inherit from a base class that provides default methods to overload or through documenting that your code will always call plug-in methods and pass specified arguments.

Subclassing can be fragile, though, especially in Perl where your implementation choices affect everyone else who writes code. (See the implementation of HTTP::Daemon and how it stores instance data, for example.)

If you only need to know that plug-ins or extensions conform to an interface, consider using a Perl 6-ish module such as Class::Roles or Class::Trait. Though there's a little bit of theory to learn before you understand the code, you can make your code more flexible and generic without enforcing more on the extensions than you really need to enforce.

Get cozy with can( )

If you can't entirely force a separate interface, as in the case where you want to make some methods publicly callable from user requests on a web site and other methods private to the world, consider namespacing them. For example, imagine a web program that performs mathematical operations based on the contents of the action parameter:

sub dispatch_request {     my ($self, $q) = @_;     my $action     = $q->param( 'action' );     $self->$action( ); }

This technique isn't quite as bad as invoking $action directly as a symbolic reference, but it provides little safety. An attacker could provide an invalid action, at best crashing the program as Perl tries to invoke an unknown method, or provide the name of a private method somewhere that he really shouldn't call, revealing sensitive data or causing unexpected havoc.

To verify that the method exists somewhere, use the can( ) method (provided by the UNIVERSAL ancestor of all classes):

sub dispatch_request {     my ($self, $q) = @_;     my $action     = $q->param( 'action' );     return unless $self->can( $action );     $self->$action( ); }

That prevents attackers from calling undefined methods, but it's little advantage over wrapping the whole dispatch in an eval block. If you change the names of all valid methods to start (or end) with a known token, you can prevent calling private methods:

sub dispatch_request {     my ($self, $q) = @_;     my $action     = 'action_' . $q->param( 'action' );     return unless $self->can( $action );     $self->$action( ); }

Now when the user selects the login action, the request dispatches to the method action_login. For even further protection, see "Control Access to Remote Objects" [Hack #48].

Find functions, not methods!

That works for methods, but what about functions? Perl 5 at least makes very few internal distinctions between methods and subroutines. can( ) works just as well on package names to find subroutines as it does class names to find methods. If you know you've loaded a plug-in called Logger and want to know if it can register( ), TRy:

my $register_subref = Logger->can( 'register '); $register_subref->( ) if $register_subref;

can( ) returns a reference to the found function if it exists and undef otherwise.

This also works if you have the package name in a variable:

my $register_subref = $plugin->can( 'register '); $register_subref->( ) if $register_subref;

If you don't know for sure that $plugin contains a valid package name, wrap the can( ) method call in an eval block:[2]

[2] Some people recommend calling UNIVERSAL::can( ) as a function, not a method. That's silly; what if the package overrides can( )? You'll get the wrong answer!

my $register_subref = eval { $plugin->can( 'register ') }; $register_subref->( ) if $register_subref;

If the eval fails, $register_subref will be false.



Perl Hacks
Perl Hacks: Tips & Tools for Programming, Debugging, and Surviving
ISBN: 0596526741
EAN: 2147483647
Year: 2004
Pages: 141

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net