https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126568

            Bug ID: 126568
           Summary: [17 Regression] Missed handling of exceptions in
                    factor_out_conditional_operation
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Keywords: wrong-code
          Severity: normal
          Priority: P3
         Component: tree-optimization
          Assignee: unassigned at gcc dot gnu.org
          Reporter: ktkachov at gcc dot gnu.org
  Target Milestone: ---

/* factor_out_conditional_operation (gcc/tree-ssa-phiopt.cc:329) removes the
   two arm statements with gsi_remove (&gsi, true) and builds the replacement
   with maybe_push_res_to_seq.  Neither step transfers the EH region of the
   removed statements to the new one, and there is no
   maybe_clean_or_replace_eh_stmt / add_stmt_to_eh_lp anywhere in
   tree-ssa-phiopt.cc.

   The shape filter in execute_over_cond_phis only requires single_succ_p on
   the arm blocks.  That excludes lp_nr > 0 (a landing pad makes an EDGE_EH
   successor) but NOT lp_nr < 0, which is what record_stmt_eh_region stores
   for an ERT_MUST_NOT_THROW region.  So the two divisions of a `noexcept'
   body under -fnon-call-exceptions sit in single-successor blocks and reach
   the transform.

   Before phiopt the divisions carry lp_nr < 0, so convert_to_eh_region_ranges
   gives them action -2, "no call-site entry", which is how the personality
   routine is told to run std::terminate.  After phiopt the merged division
   has lp_nr == 0, gets an ordinary action-0 call-site entry (or no LSDA at
   all), and the exception unwinds straight out of the noexcept function.

   g++ -O2 -fnon-call-exceptions -fexceptions, x86_64 (needs a division that
   actually traps; AArch64 sdiv does not fault on a zero divisor).

   Correct: std::terminate.  Broken: the throw escapes divsel and main catches
   it.  */

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <exception>

static void sigfpe_handler (int) { throw 1; }
static void my_terminate () { printf ("TERMINATE\n"); exit (0); }

volatile int zero = 0;

/* Opaque possibly-throwing call.  It opens an EH range in main that also
   covers the call to divsel, so an escaping exception is observable.  */
__attribute__((noipa)) void maythrow (void) { if (zero) throw 2; }

__attribute__((noipa))
int divsel (int c, int a, int b, int d) noexcept
{
  int x;
  if (c)
    x = a / d;
  else
    x = b / d;
  return x;
}

int main (void)
{
  std::set_terminate (my_terminate);
  struct sigaction sa;
  sa.sa_handler = sigfpe_handler;
  sigemptyset (&sa.sa_mask);
  sa.sa_flags = SA_NODEFER;
  sigaction (SIGFPE, &sa, NULL);

  try
    {
      maythrow ();
      int r = divsel (1, 4, 8, 0);
      printf ("NO TRAP r=%d\n", r);
      __builtin_abort ();
    }
  catch (int)
    {
      printf ("ESCAPED noexcept, caught in main\n");
      __builtin_abort ();
    }
  return 0;
}

This ends with SIGSEGV on x86_64 with -O2 -fnon-call-exceptions -fexceptions

Reply via email to