Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I override Perl functions, enabling multiple overrides?

Tags:

perl

some time ago, I asked This question about overriding building perl functions.

How do I do this in a way that allows multiple overrides? The following code yields an infinite recursion.

What's the proper way to do this? If I redefine a function, I don't want to step on someone else's redefinition.

package first;

my $orig_system1;
sub mysystem {
  my @args = @_;
  print("in first mysystem\n");
  return &{$orig_system1}(@args);
}

BEGIN {

  if (defined(my $orig = \&CORE::GLOBAL::system)) {
    $orig_system1 = $orig;
    *CORE::GLOBAL::system = \&first::mysystem;
    printf("first defined\n");
  } else {
    printf("no orig for first\n");
  }
}

package main;

system("echo hello world");
like image 796
mmccoo Avatar asked Jan 19 '10 19:01

mmccoo


1 Answers

The proper way to do it is not to do it. Find some other way to accomplish what you're doing. This technique has all the problems of a global variable, squared. Unless you get your rewrite of the function exactly right, you could break all sorts of code you never even knew existed. And while you might be polite in not blowing over an existing override, somebody else probably will not be.

Overriding system is particularly touchy because it does not have a proper prototype. This is because it does things not expressible in the prototype system. This means your override cannot do some things that system can. Namely...

system {$program} @args;

This is a valid way to call system, though you need to read the exec docs to do it. You might think "oh, well I just won't do that then", but if any module that you use does it, or any module it uses does it, then you're out of luck.

That said, there's little different from overriding any other function politely. You have to trap the existing function and be sure you call it in your new one. Whether you do it before or after is up to you.

The problem in your code is that the proper way to check if a function is defined is defined &function. Taking a code ref, even of an undefined function, will always return a true code ref. I'm not sure why, maybe its like how \undef will return a scalar ref. Why calling this code ref is causing mysystem() to go infinitely recursive is anyone's guess.

There's an additional complexity in that you can't take a reference to a core function. \&CORE::system doesn't do what you mean. Nor can you get at it with a symbolic reference. So if you want to call CORE::system or an existing override depending on which is defined you can't just assign one or the other to a code ref. You have to split your logic.

Here is one way to do it.

package first;

use strict;
use warnings;

sub override_system {
    my $after = shift;

    my $code;
    if( defined &CORE::GLOBAL::system ) {
        my $original = \&CORE::GLOBAL::system;

        $code = sub {
            my $exit = $original->(@_);
            return $after->($exit, @_);
        };
    }
    else {
        $code = sub {
            my $exit = CORE::system(@_);
            return $after->($exit, @_);
        };
    }

    no warnings 'redefine';
    *CORE::GLOBAL::system = $code;
}

sub mysystem {
    my($exit, @args) = @_;
    print("in first mysystem, got $exit and @args\n");
}

BEGIN { override_system(\&mysystem) }

package main;

system("echo hello world");

Note that I've changed mysystem() to merely be a hook that runs after the real system. It gets all the arguments and the exit code, and it can change the exit code, but it doesn't change what system() actually does. Adding before/after hooks is the only thing you can do if you want to honor an existing override. Its quite a bit safer anyway. The mess of overriding system is now in a subroutine to keep BEGIN from getting too cluttered.

You should be able to modify this for your needs.

like image 152
Schwern Avatar answered Oct 08 '22 14:10

Schwern