This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
pp_ctl.c - in try_yyparse do not leak PL_restartop from compile that dies
authorYves Orton <demerphq@gmail.com>
Wed, 12 Oct 2022 16:50:25 +0000 (18:50 +0200)
committerYves Orton <demerphq@gmail.com>
Mon, 24 Oct 2022 12:33:55 +0000 (14:33 +0200)
commitdd66b1d793c73fea9309c1d12a879369bf55bb83
treeab0b36a53a25e4a41f5b385402fecbac9765d03a
parentc8b9c03cda0319a9e817d1723e0f641f48a3a114
pp_ctl.c - in try_yyparse do not leak PL_restartop from compile that dies

Fix GH Issue #20396, try_yyparse() breaks Attribute::Handlers.

Reduced test case is:

    perl -e'CHECK { eval "]" }'

which should not assert or segfault.

In c304acb49 we made it so that when doeval_compile() is executed and it
calls yyparse() inside of an eval any exceptions that occur during the
parse process are trapped by try_yyparse() so that exection would return
to doeval_compile(). This was done so that post eval compilation cleanup
logic could be handled similarly regardless of whether Perl_croak() was
called or not. However the logic to setup PL_restartop was not adjusted
accordingly.

The opcode that calls doeval_compile() setups an eval context data
before it calls doeval_compile(). This data includes the "retop" which
is used to return control to after the eval should it die and is set to
the be the evaling opcodes op_next. When Perl_die_unwind() is called it
sets PL_restartop to be the "retop" of the of the current eval frame,
and then does a longjmp, on the assumption it will end up inside of a
"run loop enabled jump enviornment", where it restarts the run loop
based on the value of PL_restartop, zeroing it aftewards.

After c304acb49 however, a die inside of try_yyparse the die_unwind
returns control back to the try_yyparse, which ignores PL_restartop, and
leaves it set. Code then goes through the "compilation failed" branch
and execution returns to PL_restartop /anyway/, as PL_op hasn't changed
and pp_entereval returns control to PL_op->op_next, which is what we
pushed into the eval context anyway for the PL_restartop.

The end result of this however is that PL_restartop remains set when we
enter perl_run() for the first time. perl_run() is a "run loop enabled
jump enviornment" which uses run_body() to do its business, such that
when PL_restartop is NULL it executes the just compiled body of the
program, and when PL_restartop is not null it assumes it must be in the
eval handler from an eval from the main body and it should recontinue.
The leaked PL_restartop is thus executed instead of the main program
body and things go horribly wrong.

This patch changes it so that when try_yyparse traps an exception we
restore PL_restartop back to its old value. Same for its partner
PL_restartjmpenv. This is fine as they have been set to the values from
the beginning of the eval frame which we are part of, which is now over.
perl.c
pp_ctl.c
t/op/eval.t