Branch: refs/heads/yves/fix_20396
  Home:   https://github.com/Perl/perl5
  Commit: 5482530f849016385781df6c3086c276cf60b813
      
https://github.com/Perl/perl5/commit/5482530f849016385781df6c3086c276cf60b813
  Author: Yves Orton <demer...@gmail.com>
  Date:   2022-10-18 (Tue, 18 Oct 2022)

  Changed paths:
    M perl.c
    M pp_ctl.c
    M t/op/eval.t

  Log Message:
  -----------
  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.


  Commit: 3cc030d99f9b22dcb55cb227ba6017b3f1a1ce14
      
https://github.com/Perl/perl5/commit/3cc030d99f9b22dcb55cb227ba6017b3f1a1ce14
  Author: Yves Orton <demer...@gmail.com>
  Date:   2022-10-18 (Tue, 18 Oct 2022)

  Changed paths:
    M pp_ctl.c

  Log Message:
  -----------
  pp_ctl.c - in try_run_unitcheck() guard against leaking PL_restartop

If we die while executing a UNITCHECK inside of an eval we shouldn't
leave PL_restartop set, as we will execute PL_op->op_next anyway.
I couldn't get anything to SEGV with code related to this, but I could
get asserts() to trip to show the leaking PL_restartop. This maintains
equivalence with try_yyparse() which does need it for sure.


Compare: https://github.com/Perl/perl5/compare/78bb4e5a6f20...3cc030d99f9b

Reply via email to