On 2013-05-11 00:39, PJF via RT wrote:
Show quoted text> <URL:
https://rt.cpan.org/Ticket/Display.html?id=46984 >
>
> Okay, this may be crazy, or brilliant. I'm not sure. Also, I've got another mail from you (Niels) in my inbox, andI haven't read that yet. I'm sending this anyway. :)
>
> Currently:
>
> * User code calls the leakguard, which has a real prototype, and checks the filename is correct.
> * If the filename isn't correct, we call the CORE version (or whatever we had in that spot before)
> * If the filename *is* correct, we jump (goto &) the autodying/fatalised version of the code.
>
> There's one leakguard for each filename + sub combination.
>
> However we *should* be able to do this:
>
> * User calls a micro-guard (with real proto) for each filename + sub combination
> `-> This just puts the filename into a known place, and jumps to the beefy guard.
> * Beefy guard exists for each sub (but *not* each file) and do the heavy lifting (but see below).
>
> The microguards effectively end up looking like:
>
> sub rename($$) {
> local $autodie::_GUARDFILE = 'yourscript.pl';
> goto $autodie::beefy{rename};
> }
>
If that is going to be the "per-file" leakguard, we wouldn't need an
eval for that.
use Scalar::Util qw(set_prototype);
[...]
my $leak_guard = sub {
local $autodie::_GUARDFILE = $file;
goto $autodie::beefy{$call};
};
return set_prototype($leak_guard, $real_prototype);
So we can still take out some eval overhead here.
But if the beefy leak guard becomes a real sub (and not a per-prototype
guard), then don't have to use the "local $autodie::_GUARDFILE"-trick
(instead just pass it as argument). You suggest that later and I think
it is doable (see below).
Show quoted text> Right now, the beefy guards include an actual call to the core sub, which is *only* called if we leaked. It's *this* call that requires our package declaration.
> For reusable subs, we only need generate the beefy guard once (which
Niels' patch essentially does), but wouldn't it be great if we could
cache all the subs?
Show quoted text> In fact, wouldn't it be great if we could only build one beefy guard,
and use it for everything?
Show quoted text>
Should be doable. Assuming we don't leak, all leak guards appear to
behave the same (namely, goto \&wrapper). On leak, we would have two
cases; one for core subs and one for other subs.
For "non-core" subs, we just need a subref to the original one and we
can goto it. Fatal must keep a reference to it around, so that should
be fairly straight forward.
For "core" subs, we have to generate a function to call the core sub
correctly (because to knowledge, we cannot get a reference to them nor
goto to them). But we can generate these lazily and cache them. So if
you never leak a CORE sub, Fatal will never generate the trampoline for it.
We can always throw in more information into the micro-guard to make the
"follow-up" easier (as you suggested in the end of your mail).
Show quoted text> Maybe we can.
>
> How often do we *actually* leak? It's when someone loads another file (require/use) and that file doesn't include a package declaration (or declares itself in the same package).
> In well written code, that's not very often. So maybe, just maybe, we
could have a single beefy guard. It looks in a known place (local()
variables set by the microguard) for
Show quoted text> what filename it should be checking against, and also what (autodying)
subroutine it should go jumping to if we're in the correct file. The
microguard has the prototpe that we
Show quoted text> need to expose to the end user, and by using magic-goto we can
preserve the arguments on the stack when we jump to our autodying sub.
The autodying sub has the package information
Show quoted text> encapsulated into that, so the beefy sub doesn't need to know it.
>
I believe we still have to generate the call trampoline for (unchecked)
CORE subs (see above). But indeed if we can go a goto \&CORE::sub that
would be great.
Show quoted text> If that beefy guard discovers we *have* leaked, *and* we should be calling a core subroutine, then packages matter again. So we have it look in a third known location
> (maybe they all live inside a hash/array?) and then evaluates and
jumps to the code there:
Show quoted text>
> my $sub = eval $some_magic_location;
> goto &$sub;
>
Why would packages matter here? Ignoring globs for a moment, there is
no value in compiling a sub into the package of the leaking callsite.
As we "goto" rather than call the original subref, both the leak
guard(s) will not be in the call stack. We can include a reference to
the original subref in the micro-guard that exposes it to the "beefy"
leak guard, so we don't even need a table-look up or anything.
In fact, for user subs, we can create generic wrappers for user subs
by including original subref.
sub {
my $orig = shift;
my $on_error_info = shift;
if (!wantarray) {
my $scalar_ret;
if (@_ == 1) {
$scalar_ret = $orig->($_[0]);
} elsif (...) {
...
}
if (!$scalar_ref) {
[code-that-throws-and-uses-$on_error_info]
}
} else {
...
}
}
Sure, it would have to be string eval'ed but we can pretty much re-use
it for all usersubs having the same prototype (or in fact, even a
"compatible-call prototype")[1]. In my experience, most user subs do
not have a prototype at all so it would be a big win here if we can just
make a generic handler for that case.
For CORE subs, I think the package name only matters if we cannot
qualify globs correctly. But sadly, we cannot generate generic checking
wrapper functions for these (but we can lazily generate reusable ones
for them).
Show quoted text> Maybe there's some caching going on as well (we should be able to cache on package + subroutine).
>
> Advantages are:
>
> * Only one big guard needs to be eval'd. In fact, it might be possible to make it a static subroutine now.
> * Many less lines of eval'd code when calling autodie (all microguards).
> * A lot of leak-catching code may only get compiled if we actually hit a leak. (Most programs won't.)
>
\o/
Show quoted text> Bonus is that our microguard might be able to push/unshift the filename, autodying subroutine reference,
> and core/leak code onto @_, which removes the need for magic
locations. Of coure, we'd have to pull them
Show quoted text> right off again at the start of the beefy guard.
>
That or pull it via the "local $autodie::_EXTRA_ARG"-trick either way
should be doable. Admittedly, I would prefer using @_ as it seems "less
magical".
~Niels
[1] Example:
sub a($) { ... }
sub b(&) { ... }
sub c(_) { ... }
Those three subs will have the same "generated call" (see fill_protos).
So we could do a:
sub checked_X {...}
that could handle any of those 3 subs (instead of creating 1 checking
sub for each).