/*++
Copyright (c) Microsoft Corporation

Module Name:

    error_handling

Abstract:

    Error handling in the OCaml API for Z3.

    The wrapper of each Z3 API routine that takes a Z3_context or a Z3_theory
    argument calls check_error_code before returning.  (These calls are added
    in generate_mlapi.cmd using the build.sed script.)

    There are two error handling schemes implemented, depending on whether
    (UN)SAFE_ERRORS is set.

    - SAFE_ERRORS checks Z3_error_code after each call and raises an OCaml
      exception in error conditions.  Z3_set_error_handler is not exposed by
      the SAFE_ERRORS version.

    - UNSAFE_ERRORS sets a Z3 error handler routine that either calls a
      globally registered OCaml function or, by default, raises an OCaml
      exception.  This avoids overhead of repeatedly checking
      Z3_get_error_code, but leaves Z3 in a broken state.

Notes:

    The current SAFE_ERRORS implementation interacts badly with theory plugin
    callbacks.  Z3 errors are converted into OCaml exceptions, which the
    wrappers of theory plugin callbacks are not expecting.  Therefore, if a
    theory plugin calls a Z3 API routine that triggers an error, an OCaml
    exception will be raised and bypass any C++ destructors pushed onto the
    stack by Z3 before the call to the plugin and after the preceding OCaml
    exception handler.  One solution to this would be to modify the theory
    plugin callback registration functions to wrap callbacks in an OCaml
    exception handler.  Since OCaml exceptions are cheap to raise at the
    expense of some cost to install a handler, this may not be desirable.
    Another solution would be to modify check_error_code to detect if it is
    executing in a plugin callback and simply maintain the Z3_error_code, or
    raise a C++ exception, instead of raising an OCaml exception.

Author:

    Josh Berdine (jjb) 2012-03-21

--*/


#if !defined(UNSAFE_ERRORS) && !defined(SAFE_ERRORS)
#define SAFE_ERRORS
#endif


#ifdef SAFE_ERRORS

quote(mlmli,"
(** Exceptions raised by Z3. It is safe to continue interacting with Z3 after
    catching [Error] exceptions.

    - {b See also}: {!get_error_msg}
*)
exception Error of context * error_code
");
quote(ml,"
/* Register dynamically-generated exception tag for use from C */
let _ = Callback.register_exception \"Z3.Error\" (Error (Obj.magic None, OK))
");

quote(c,"
value camlidl_c2ml_z3_Z3_error_code(Z3_error_code * _c2, camlidl_ctx _ctx);

/* Error checking routine that raises OCaml Error exceptions */
void check_error_code (Z3_context c)
{
  static struct camlidl_ctx_struct _ctxs = { CAMLIDL_TRANSIENT, NULL };
  value* exn_tag = NULL;
  value ctx_err[2];
  Z3_error_code e;
  e = Z3_get_error_code(c);
  if (e != Z3_OK) {
    ctx_err[0] = c2ml_Z3_context(&c);
    ctx_err[1] = camlidl_c2ml_z3_Z3_error_code(&e, &_ctxs);
    exn_tag = caml_named_value(\"Z3.Error\");
    if (*exn_tag == 0) {
      fprintf(stderr, \"Z3.Error not found\");
      exit(1);
    }
    caml_raise_with_args(*exn_tag, 2, ctx_err);
  }
}

/* Disable default error handler, all error checking is done by check_error_code */
void* error_handler_static = NULL;
");

#else

quote(mlmli,"
(** Exceptions raised by Z3.  {b Warning}: It is unsafe to continue
    interacting with Z3 after catching [Error] exceptions. To recover from
    error conditions, use {!set_error_handler} to set an error handler that
    does nothing, and then test {!get_error_code} after every call to Z3.

    - {b See also}: {!get_error_msg}
*)
exception Error of context * error_code
");
quote(ml,"
/* Register dynamically-generated exception tag for use from C */
let _ = Callback.register_exception \"Z3.Error\" (Error (Obj.magic None, OK))
");

quote(c,"
/* Error checking routine that does nothing */
void check_error_code(Z3_context c) {}

/* All contexts share the same handler */
static value caml_error_handler = 0;

static void error_handler_static (Z3_context c, Z3_error_code e)
{
  static struct camlidl_ctx_struct _ctxs = { CAMLIDL_TRANSIENT, NULL };
  value* exn_tag = NULL;
  value ctx_err[2];
  ctx_err[0] = c2ml_Z3_context(&c);
  ctx_err[1] = camlidl_c2ml_z3_Z3_error_code(&e, &_ctxs);
  if (caml_error_handler) {
    caml_callback2(caml_error_handler, ctx_err[0], ctx_err[1]);
  } else {
    /* if no handler set, raise OCaml Error exception */
    exn_tag = caml_named_value(\"Z3.Error\");
    if (*exn_tag == 0) {
      fprintf(stderr, \"Z3.Error not found\");
      exit(1);
    }
    caml_raise_with_args(*exn_tag, 2, ctx_err);
  }
}

void ml2c_Z3_error_handler (value ml_handler, void* c_handler)
{
  caml_error_handler = ml_handler;
  c_handler = (void*)error_handler_static;
}

/* Never called */
value c2ml_Z3_error_handler (void* _)
{
  return 0;
}
");

typedef [mltype("context -> error_code -> unit"),
         ml2c(ml2c_Z3_error_handler),
         c2ml(c2ml_Z3_error_handler)
        ] void Z3_error_handler;

quote(c,"#define Z3_error_handler void*");

#endif
