/*
-- This file is  free  software, which  comes  along  with  SmartEiffel. This
-- software  is  distributed  in the hope that it will be useful, but WITHOUT
-- ANY  WARRANTY;  without  even  the  implied warranty of MERCHANTABILITY or
-- FITNESS  FOR A PARTICULAR PURPOSE. You can modify it as you want, provided
-- this header is kept unaltered, and a notification of the changes is added.
-- You  are  allowed  to  redistribute  it and sell it, alone or as a part of
-- another product.
--       Copyright (C) 1994-2002 LORIA - INRIA - U.H.P. Nancy 1 - FRANCE
--          Dominique COLNET and Suzanne COLLIN - SmartEiffel@loria.fr
--                       http://SmartEiffel.loria.fr
--
*/
/*
  This file (SmartEiffel/sys/runtime/exception.c) is automatically included
  when exception handling is used somewhere in the live code.
*/

/*
  Miscellaneous Notes:
  We are currently using the old signal() system call instead of
  the preferred sigaction() call.

  On Linux systems at least, signal() is implemented using sigaction()
  so it doesn't really matter.  Need to investigate this on other
  Unix systems.

  James Graves (ansible@xnet.com)
*/

/*
   Top of the rescue context stack (or NULL if there is no current
   context) :
*/
struct rescue_context *rescue_context_top = NULL;

/*
   Set to nonzero if the exception was internally generated, as with an
   assertion violation.  It is Os_signal (11) for an OS signal.
*/
int internal_exception_number;
int original_internal_exception_number;

/*
   Set to nonzero if the exception was a signal (external).  It is 0
   for an internal exception.
*/
int signal_exception_number;

/*
   Used by some internal exceptions to print additional debugging
   information when the exception is not handled and a dump is done.
*/
char *additional_error_message;

static void critical_error_exit(void) {
/* This is called whenever a critical error in the SmartEiffel
   is encountered.  This kind of error cannot be handled by the
   exception handler.

   For instance, this routine is called if there is an error in the
   operation of the exception handler routines themselves.
*/
  fprintf(SE_ERR, "There was a critical error in the SmartEiffel runtime.\n");
  exit(EXIT_FAILURE);
}

void setup_signal_handler() {
/*
  Sets up the reception of signals.  If exception handling is enabled
  (by the existance of a rescue clause somewhere), then all OS signals
  now go to exception_handler instead of se_print_run_time_stack().
*/
#ifdef SIG_ERR
/* Check signal() call for errors.  Posix compliant systems should
   define SIG_ERR which is returned by signal() on an error. All Unix
   signals are included except SIGABRT, SIGKILL and SIGSTOP.

   The other signals SmartEiffel traps for
   other OSs (like SIGBREAK) are not included here, but are below
   in the #else part, for non-Posix systems.
*/

#ifdef SIGHUP
   if ( SIG_ERR == signal( SIGHUP, signal_exception_handler ) )
      {
      fprintf(SE_ERR, "Error setting up signal handler for SIGHUP.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGINT
   if ( SIG_ERR == signal( SIGINT, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGINT.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGQUIT
   if ( SIG_ERR == signal( SIGQUIT, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGQUIT.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGILL
   if ( SIG_ERR == signal( SIGILL, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGILL.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGTRAP
   if ( SIG_ERR == signal( SIGTRAP, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGTRAP.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGIOT
   if ( SIG_ERR == signal( SIGIOT, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGIOT.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGBUS
   if ( SIG_ERR == signal( SIGBUS, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGBUS.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGFPE
   if ( SIG_ERR == signal( SIGFPE, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGFPE.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGUSR1
   if ( SIG_ERR == signal( SIGUSR1, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGUSR1.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGSEGV
   if ( SIG_ERR == signal( SIGSEGV, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGSEGV.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGUSR2
   if ( SIG_ERR == signal( SIGUSR2, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGUSR2.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGPIPE
   if ( SIG_ERR == signal( SIGPIPE, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGPIPE.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGALRM
   if ( SIG_ERR == signal( SIGALRM, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGALRM.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGTERM
   if ( SIG_ERR == signal( SIGTERM, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGTERM.\n");
      critical_error_exit();
      }
#endif

/* SIGCHLD is ignored, POSIX default action */

/* SIGCONT is ignored, POSIX default action */

#ifdef SIGTSTP
   if ( SIG_ERR == signal( SIGTSTP, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGTSTP.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGTTIN
   if ( SIG_ERR == signal( SIGTTIN, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGTTIN.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGTTOU
   if ( SIG_ERR == signal( SIGTTOU, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGTTOU.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGURG
   if ( SIG_ERR == signal( SIGURG, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGURG.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGXCPU
   if ( SIG_ERR == signal( SIGXCPU, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGXCPU.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGXFSZ
   if ( SIG_ERR == signal( SIGXFSZ, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGXFSZ.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGVTALRM
   if ( SIG_ERR == signal( SIGVTALRM, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGVTALRM.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGPROF
   if ( SIG_ERR == signal( SIGPROF, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGPROF.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGWINCH
   if ( SIG_ERR == signal( SIGWINCH, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGWINCH.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGIO
   if ( SIG_ERR == signal( SIGIO, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGIO.\n");
      critical_error_exit();
      }
#endif

#ifdef SIGPWR
   if ( SIG_ERR == signal( SIGPWR, signal_exception_handler ) ) {
      fprintf(SE_ERR, "Error setting up signal handler for SIGPWR.\n");
      critical_error_exit();
      }
#endif

#else   /* SIG_ERR not defined, non-Posix system? */

/* These don't check return code for the signal() OS call.  Bad practice,
   but what can be done?  */

#ifdef SIGBREAK
   /* This signal does not exist on Unix systems. */
   signal( SIGBREAK, signal_exception_handler );
#endif

#ifdef SIGHUP
   signal( SIGHUP, signal_exception_handler );
#endif

#ifdef SIGINT
   signal( SIGINT, signal_exception_handler );
#endif

#ifdef SIGQUIT
   signal( SIGQUIT, signal_exception_handler );
#endif

#ifdef SIGILL
   signal( SIGILL, signal_exception_handler );
#endif

#ifdef SIGTRAP
   signal( SIGTRAP, signal_exception_handler );
#endif

#ifdef SIGIOT
   signal( SIGIOT, signal_exception_handler );
#endif

#ifdef SIGBUS
   signal( SIGBUS, signal_exception_handler );
#endif

#ifdef SIGFPE
   signal( SIGFPE, signal_exception_handler );
#endif

#ifdef SIGUSR1
   signal( SIGUSR1, signal_exception_handler );
#endif

#ifdef SIGSEGV
   signal( SIGSEGV, signal_exception_handler );
#endif

#ifdef SIGUSR2
   signal( SIGUSR2, signal_exception_handler );
#endif

#ifdef SIGPIPE
   signal( SIGPIPE, signal_exception_handler );
#endif

#ifdef SIGALRM
   signal( SIGALRM, signal_exception_handler );
#endif

#ifdef SIGTERM
   signal( SIGTERM, signal_exception_handler );
#endif

#ifdef SIGCHLD
   signal( SIGCHLD, signal_exception_handler );
#endif

#ifdef SIGCONT
   signal( SIGCONT, signal_exception_handler );
#endif

#ifdef SIGSTOP
   /* This should also silently fail on all Unix systems, but it may
      be effective on other OSs. */
   signal( SIGSTOP, signal_exception_handler );
#endif

#ifdef SIGTSTP
   signal( SIGTSTP, signal_exception_handler );
#endif

#ifdef SIGTTIN
   signal( SIGTTIN, signal_exception_handler );
#endif

#ifdef SIGTTOU
   signal( SIGTTOU, signal_exception_handler );
#endif

#ifdef SIGURG
   signal( SIGURG, signal_exception_handler );
#endif

#ifdef SIGXCPU
   signal( SIGXCPU, signal_exception_handler );
#endif

#ifdef SIGXFSZ
   signal( SIGXFSZ, signal_exception_handler );
#endif

#ifdef SIGTALRM
   signal( SIGTALRM, signal_exception_handler );
#endif

#ifdef SIGPROF
   signal( SIGPROF, signal_exception_handler );
#endif

#ifdef SIGWINCH
   signal( SIGWINCH, signal_exception_handler );
#endif

#ifdef SIGIO
   signal( SIGIO, signal_exception_handler );
#endif

#ifdef SIGPWR
   signal( SIGPWR, signal_exception_handler );
#endif

#endif  /* ifdef SIG_ERR */

}

#ifndef SE_BOOST
static void reset_assertion_checking(struct rescue_context * current_context) {
  /* Unwind the dump stack, resetting assertion checking when a rescue
     clause is invoked.
     This function must be called just before the LONGJMP to the
     enclosing rescue context.
  */
  while(se_dst!=current_context->top_of_ds){
    if(se_dst->fd!=NULL)se_dst->fd->assertion_flag=1;
    se_dst = se_dst->caller;
  }
}
#endif

void signal_exception_handler(int signal_number) {
  /* Set up to be called whenever an OS signal has been received.
     Checks to see if there is a rescue clause active (somewhere on the
     call stack), and if so, transfer control to that.
  */
  struct rescue_context *current_context;

#ifdef SIG_ERR
  /* some OS implementations automatically block a signal while
   * executing the signal handler, but some do not. */
  if ( SIG_ERR == signal( signal_number, SIG_IGN ) ) {
    fprintf(SE_ERR, "In signal_exception_handler: ");
    fprintf(SE_ERR, "Error turning off signal %d.\n", signal_number );
    critical_error_exit();
  }
#else
  signal( signal_number, SIG_IGN );
#endif

  internal_exception_number = Os_signal;
  original_internal_exception_number = 0;
  signal_exception_number = signal_number;

  if ( rescue_context_top != NULL ) {
    current_context = rescue_context_top;
    rescue_context_top = rescue_context_top->next;

    /* now re-enable that signal */
#ifdef SIG_ERR
    if ( SIG_ERR == signal( signal_number, signal_exception_handler ) ) {
      fprintf(SE_ERR, "In signal_exception_handler: ");
      fprintf(SE_ERR, "Error turning on signal %d.\n", signal_number );
      critical_error_exit();
    }
#else
    signal( signal_number, signal_exception_handler );
#endif

#ifndef SE_BOOST
    /* Unwind dump_stack structures PRIOR to jumping :
     */
    reset_assertion_checking(current_context) ;
#endif
    LONGJMP( current_context->jb, internal_exception_number );
  }

  /* No current rescue clause, exit with a dump or debug: */
#ifdef SE_SEDB
  print_exception();
  error0("Exception not handled",0);
#else
  print_exception();
  se_print_run_time_stack();
  exit(EXIT_FAILURE);
#endif
}

void internal_exception_handler(int exception_number) {
  /* Called whenever an internal (to SmartEiffel) exception is to
     be raised (`raise' feature, assertion violation, etc.).

     Checks to see if there is a current rescue clause (somewhere
     in the call stack), and transfers control to it.
     Else exit with a stack trace (if enabled).
  */
  struct rescue_context *current_context;

  /* UNCOMMENT THIS PART TO DEBUG WITH EXCEPTION:
     print_exception(); se_print_run_time_stack();
  */



  /* If this is not a routine failure, clear out old exception
   * information. */
  if ( exception_number != Routine_failure ) {
    internal_exception_number = exception_number;
    original_internal_exception_number = 0;
    signal_exception_number = 0;
  }
  else {
    original_internal_exception_number = internal_exception_number;
  }

  if ( rescue_context_top != NULL ) {
    current_context = rescue_context_top;
    rescue_context_top = rescue_context_top->next;
#ifndef SE_BOOST
    /* Unwind dump_stack structures PRIOR to jumping :
     */
    reset_assertion_checking(current_context) ;
#endif
    LONGJMP( current_context->jb, exception_number );
  }

  /* No current rescue clause, exit with a dump : */
  print_exception();
  se_print_run_time_stack();
  exit(EXIT_FAILURE);
}

#ifndef SE_BOOST
static void print_exception_case( int ex_num ) {
  switch( ex_num ) {
  case Check_instruction:
    fprintf(SE_ERR, "Check instruction failed.\n");
    break;
  case Class_invariant:
    fprintf(SE_ERR, "Class invariant not maintained.\n");
    break;
  case Developer_exception:
    fprintf(SE_ERR, "Developer exception:\n");
    break;
  case Incorrect_inspect_value:
    fprintf(SE_ERR, "Incorrect inspect value.\n");
    break;
  case Loop_invariant:
    fprintf(SE_ERR, "Loop invariant failed.\n");
    break;
  case Loop_variant:
    fprintf(SE_ERR, "Loop variant failed to decrease.\n");
    break;
  case No_more_memory:
    fprintf(SE_ERR, "Failed to allocate additional memory.\n");
    break;
  case Postcondition:
    fprintf(SE_ERR, "Postcondition (ensure clause) failed.\n");
    break;
  case Precondition:
    fprintf(SE_ERR, "Precondition (require clause) failed.\n");
    break;
  case Routine_failure:
    fprintf(SE_ERR, "Routine failure.\n");
    break;
  case Os_signal:
    fprintf(SE_ERR, "OS Signal (%d) received.\n",
	    signal_exception_number );
    break;
  case Void_attached_to_expanded:
    fprintf(SE_ERR, "A Void became attached to an expanded object.\n");
    fprintf(SE_ERR, "Please report this problem to the SmartEiffel team.\n");
    break;
  case Void_call_target:
    fprintf(SE_ERR, "Feature call attempted on a Void reference.\n");
    break;
  case System_level_type_error:
    fprintf(SE_ERR, "System-level type error (probably a bad covariant call).\n");
    break;
  default:
    fprintf(SE_ERR, "There was an unknown exception.\n");
    fprintf(SE_ERR, "Please report this problem to the SmartEiffel team.\n");
  }
}
#endif

void print_exception(void) {
  /* Display some information about last not handled exception. */
#ifndef SE_BOOST
  fprintf(SE_ERR,"Exception number %d not handled.\n",internal_exception_number);
  if ( internal_exception_number == Routine_failure ) {
    fprintf(SE_ERR, "Routine failure.  Original exception: \n");
    print_exception_case( original_internal_exception_number );
  }
  else {
    print_exception_case( internal_exception_number );
  }
  if ( additional_error_message != NULL ) {
    fprintf(SE_ERR, "%s\n", additional_error_message );
  }
#endif
}
