Code-Splice
view release on metacpan or search on metacpan
lib/Code/Splice.pm view on Meta::CPAN
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";
}
=head1 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?
=over 1
=item Write stronger unit tests than the granularity of the API would otherwise allow
=item 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)
=item 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
=item Be what "aspects" should be
=item Screw with your cow-orkers by introducing monster heisenbugs
=item Play with self-modifying code
=item Write self-replicating code (but be nice, we're all friends here, right?)
=back
The specifics:
The body of the C<< code { } >> block are extracted from the subroutine and inserted in a place
in the code specified by the call to the C<splice()> function.
Where the new code is spliced in, the old code is spliced out.
The C<package> and C<method> arguments are required and tell the thing how to find the
code to be modified.
The C<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 C<precondition> and C<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 C<precondition> blocks must have returned
true, and none of the C<postcondition> blocks may have yet returned true.
If a C<postcondition> returns true before all of the C<precondition> blocks have,
an error is raised.
Both blocks get called numerous times per line and get passed a reference to the C<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 C<$line> is re-generated from the bytecode using F<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 C<die> if it fails for any reason.
This will likely change in possible future versions.
There are also C<label> and C<line> arguments that create preconditions for you, for
simple cases.
Of course, you shouldn't use C<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 C<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.
=head1 BUGS
The original code reference passed in cannot be used elsewhere.
It can't be called, and it should not be passed back to C<< inject() >> again.
Failure to heed these warnings will result in coredumps and strange behaviors.
Until I get around to finishing reworking C<B::Generate>, C<B::Generate-1.06> needs
line 940 of C<B-Generate-1.06/lib/B/Generate.c> changed to read
C<o = Perl_fold_constants(o);> (the word C<Perl> and an understore should be inserted).
This is in order to build C<B::Generate-1.06> on newer Perls.
I have a fixed and slightly extended version in my area on CPAN, if you search
( run in 1.110 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )