The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Code::Splice - Injects the contents of one subroutine at a specified point elsewhere.

SYNOPSIS

  use Code::Splice;

  Code::Splice::inject(
    code => sub { print "fred\n"; }, 
    package => 'main', 
    method => 'foo', 
    precondition => sub { 
      my $op = shift; 
      my $line = shift;
      $line =~ m/print/ and $line =~ m/four/;
    },
    postcondition => sub { 
      my $op = shift; 
      my $line = shift;
      $line =~ m/print/ and $line =~ m/five/;
    },
  );

  sub foo {
    print "one\n";
    print "two\n";
    print "three\n";
    print "four\n";
    print "five\n";
  }

DESCRIPTION

Removes the contents of a subroutine (usually an anonymous subroutine created just for the purpose) and splices in into the program elsewhere.

Why, you ask?

Write stronger unit tests than the granularity of the API would otherwise allow
Write unit tests for nasty, interdependant speghetti code (my motivation -- hey, you gotta have tests before you can start refactoring, and if you can't write tests for the code, you're screwed)
Fix stupid bugs and remove stupid restrictions in other people's code in a way that's more resiliant across upgrades than editing files you don't own
Be what "aspects" should be
Screw with your cow-orkers by introducing monster heisenbugs
Play with self-modifying code
Write self-replicating code (but be nice, we're all friends here, right?)

The specifics:

The body of the code { } block are extracted from the subroutine and inserted in a place in the code specified by the call to the splice() function. Where the new code is spliced in, the old code is spliced out. The package and method arguments are required and tell the thing how to find the code to be modified. The code argument is required as it specifies the code to be spliced in. That same code block should not be used for anything else under penalty of coredump.

The rest of the argumets specify where the code is to be inserted. Any number of precondition and postcondition arguments provide callbacks to help locate the exact area to splice the code in at. Before the code can e spliced in, all of the precondition blocks must have returned true, and none of the postcondition blocks may have yet returned true. If a postcondition returns true before all of the precondition blocks have, an error is raised. Both blocks get called numerous times per line and get passed a reference to the B OP object currently under consideration and the text of the current line:

    precondition => sub { 
      my $op = shift; 
      my $line = shift;
      $line =~ m/print/ and $line =~ m/four/;
    },

... or...

    precondition => sub { my $op = shift; $op->name eq 'padsv' and $op->sv->sv =~ m/fred/; },

It's possible to insert code in the middle of an expression when testing ops, but when testing the text of the line of code, the spliced in code will always replace the whole line.

I'll probably drop sending in the opcode in a future version, at least for the precondition/postcondition blocks, or maybe I'll swap them to the 2nd arg so they're more optional.

Do not attempt to match text in comments as it won't be there. The code in $line is re-generated from the bytecode using B::Deparse and will vary from the original source code in a few ways, including changes to formatting, changes to some idioms and details of the expressions, and formatting of the code with regards to whitespace.

The splicing code will die if it fails for any reason. This will likely change in possible future versions.

There are also label and line arguments that create preconditions for you, for simple cases. Of course, you shouldn't use line for anything other than simple experimentation.

References to lexical variables in the code to be injected are replaced with references to the lexical variables of the same name in the location the code is inserted into. If a variable of the same name doesn't exist there, it's an error. ... but it probably shouldn't be an error, at least in the cases where the code being spliced in declares that lexical with my, or when the variable was initiailized entirely outside of the sub block being spliced in and was merely closed over by it.

See the comments in the source code (at the top, in a nice block) for my todo/desired features. Let me know if there are any features in there or yet unsuggested that you want. I won't promise them, but I would like to hear about them.

BUGS

The original code reference passed in cannot be used elsewhere. It can't be called, and it should not be passed back to inject() again. Failure to heed these warnings will result in coredumps and strange behaviors.

Until I get around to finishing reworking B::Generate, B::Generate-1.06 needs line 940 of B-Generate-1.06/lib/B/Generate.c changed to read o = Perl_fold_constants(o); (the word Perl and an understore should be inserted). This is in order to build B::Generate-1.06 on newer Perls. I have a fixed and slightly extended version in my area on CPAN, if you search for SWALTERS.

Should gracefully default to not fixing up lexicals where no direct equivilent exists.

Should repair the provided subroutine reference so that if were to be accidentally called, Perl wouldn't coredump.

HISTORY

0.1 -- initial release.

SEE ALSO

http://search.cpan.org/~swalters/B-Generate-1.06_1/ -- slightly updated B::Generate -- you'll need this

http://perldesignpatterns.com/?PerlAssembly attempts to document the Perl internals I'm prodding so bluntly.

AUTHORS

Scott Walters scott@slowass.net - http://slowass.net/

Brock Wilcox awwaiid@thelackthereof.org - http://thelackthereof.org/

Code lifted from various B modules...

COPYRIGHT AND LICENSE

Copyright (C) 2007 by Scott Walters and Brock Wilcox

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.8 or, at your option, any later version of Perl 5 you may have available.