As reported in other thread, I've seen mod_perl emit panic warnings such
as the following ever since 5.19.7:

 panic: attempt to copy freed scalar 7fb2264ffde0 to 7fb2264ffd08 at
/Users/mschout/Sync/Apache-TryCatch/blib/lib/Apache/TryCatch.pm line 20.

I've also seen random segementation faults caused by innocent looking
"return" statements in a large code base.

I've created a test mod perl handler that demonstrates this behaviour:

------------------
package Apache::TryCatch;

use Apache2::Const -compile => qw(OK);
use TryCatch;
use CGI;

sub handler :method {
    my ($self, $r) = @_;

    $r->content_type('text/plain');

    print $self->crash($r);

    return Apache2::Const::OK;
}

sub crash {
    my $cgi = CGI->new;

    try { $cgi->param(x => 'x') }

    return "Hello World!";
}

1;
------------------

configured in httpd.conf as:

PerlModule Apache::TryCatch

PerlSwitches -I@ServerRoot/../lib

<Location /crashme>
    SetHandler perl-script
    PerlResponseHandler Apache::TryCatch
</Location>
------------------

Simply accessing /crashme causes a 500 internal server error and the
panic warning in the httpd error log.

I bisected against perl git, using mod_perl 2.0.9 and apache 2.2.29 as
the mod_perl environment, and installing TryCatch from cpan, and
discovered that the following perl commit is the culprit:

> commit 437e3a7dac994ebace1195549170c81f474d9c20
> Author: Matthew Horsfall <wolfs...@gmail.com>
> Date:   Wed Dec 11 18:28:21 2013 -0500
> 
>     Optimise out PUSHMARK/RETURN if return is the last statement in a sub.
>     
>     This makes:
>     
>       sub baz { return $cat; }
>     
>     Behave like:
>     
>       sub baz { $cat; }
>     
>     Which is notably faster.
>     
>     Unpatched:
>     
>       ./perl -Ilib/ ~/stuff/bench.pl
>       Benchmark: timing 40000000 iterations of normal, ret...
>         normal:  3 wallclock secs ( 1.60 usr +  0.01 sys =  1.61 CPU) @ 
> 24844720.50/s (n=40000000)
>            ret:  3 wallclock secs ( 2.08 usr +  0.00 sys =  2.08 CPU) @ 
> 19230769.23/s (n=40000000)
>     
>     Patched:
>     
>       ./perl -Ilib ~/stuff/bench.pl
>       Benchmark: timing 40000000 iterations of aret, normal...
>         normal:  2 wallclock secs ( 1.72 usr +  0.00 sys =  1.72 CPU) @ 
> 23255813.95/s (n=40000000)
>            ret:  2 wallclock secs ( 1.72 usr +  0.00 sys =  1.72 CPU) @ 
> 23255813.95/s (n=40000000)
>     
>     The difference in OP trees can be seen here:
>     
>     Unpatched:
>     
>       $ perl  -MO=Concise,baz -e 'sub baz { return $cat }'
>       main::baz:
>       5  <1> leavesub[1 ref] K/REFC,1 ->(end)
>       -     <@> lineseq KP ->5
>       1        <;> nextstate(main 1 -e:1) v ->2
>       4        <@> return K ->5
>       2           <0> pushmark s ->3
>       -           <1> ex-rv2sv sK/1 ->4
>       3              <#> gvsv[*cat] s ->4
>       -e syntax OK
>     
>     Patched:
>     
>       $ ./perl -Ilib  -MO=Concise,baz -e 'sub baz { return $cat }'
>       main::baz:
>       3  <1> leavesub[1 ref] K/REFC,1 ->(end)
>       -     <@> lineseq KP ->3
>       1        <;> nextstate(main 1 -e:1) v ->2
>       -        <@> return K ->-
>       -           <0> pushmark s ->2
>       -           <1> ex-rv2sv sK/1 ->-
>       2              <$> gvsv(*cat) s ->3
>       -e syntax OK
>     
>     (Includes some modifications from Steffen)
> 
>  ext/B/t/optree_samples.t | 18 +++++++++++++++++-
>  op.c                     | 31 +++++++++++++++++++++++++++++++
>  op.h                     |  6 ++++++
>  3 files changed, 54 insertions(+), 1 deletion(-)

Reverting the change to op.c completely fixes the problem.  I cannot
reproduce the panic after this change, and I do not get any segmentation
faults either in a large codebase that I am also testing against.

This problem seems to be mod_perl specific as the try { $cgi->param() }
does not cause a panic when run outside of mod_perl.

In addition, not all try { } blocks cause the panic.  It seems that
calling CGI.pm's param() method to set a value *does* cause it however.

If anyone has any ideas on what might be causing behaviour in mod_perl,
I'm all ears.  I may try to dig into this, but I'm not familiar with
mod_perl internals so that may take quite some time.  Admittedly,
TryCatch depends on Devel::Declare, which as I understand it makes
changes to the optree at runtime.  I'm guessing there is some
interaction with the optree in mod_perl that TryCatch along with perl's
optimization that removes the "return" ops that is causing this.

I have put the test case as a module up on github so that anyone can
clone it to reproduce the bug.  The repository is here:

https://github.com/mschout/modperl-trycatch-bug.git

The readme explains how to run it, but essentially its install a perl >=
5.19.7, install mod_perl, install TryCatch, and then the usual
"perl Makefile.PL && make test"

Regards,
Michael Schout

Reply via email to