#include <petsc/private/petscimpl.h>

static PetscInt petsc_checkpointer_intensity = 1;

/*@
  PetscCheckPointerSetIntensity - Set the intensity of debug pointer checks

  Not Collective

  Input Parameter:
. intensity - how much to check pointers for validity

  Options Database Key:
. -check_pointer_intensity - intensity (0, 1, or 2)

  Level: advanced

  Notes:
  An intense pointer check registers a signal handler and attempts to dereference to confirm
  whether the address is valid.  An intensity of 0 never uses signal handlers, 1 uses them when
  not in a "hot" function, and intensity of 2 always uses a signal handler.

.seealso: `PetscCheckPointer()`, `PetscFunctionBeginHot()`
@*/
PetscErrorCode PetscCheckPointerSetIntensity(PetscInt intensity)
{
  PetscFunctionBegin;
  PetscCheck((intensity >= 0) && (intensity <= 2), PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Intensity %" PetscInt_FMT " not in [0,2]", intensity);
  petsc_checkpointer_intensity = intensity;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/* ---------------------------------------------------------------------------------------*/

#if PetscDefined(HAVE_SETJMP_H)
  #include <setjmp.h>
static jmp_buf   PetscSegvJumpBuf;
static PetscBool PetscSegvJumpBuf_set;

/*@C
  PetscSignalSegvCheckPointerOrMpi - To be called from a signal handler for SIGSEGV.

  Not Collective, No Fortran Support

  Level: developer

  Note:
  If the signal was received while executing `PetscCheckPointer()`, this function longjmps back
  there, otherwise it returns with no effect. This function is called automatically by
  `PetscSignalHandlerDefault()`.

.seealso: `PetscPushSignalHandler()`
@*/
void PetscSignalSegvCheckPointerOrMpi(void)
{
  if (PetscSegvJumpBuf_set) longjmp(PetscSegvJumpBuf, 1);
}

/*@C
  PetscCheckPointer - Returns `PETSC_TRUE` if a pointer points to accessible data

  Not Collective, No Fortran Support

  Input Parameters:
+ ptr   - the pointer
- dtype - the type of data the pointer is suppose to point to

  Level: developer

  Notes:
  This is a non-standard PETSc function in that it returns the result and does not return an error code.

  This function always returns true when running under Valgrind, or when compiled with asan options.

.seealso: `PetscCheckPointerSetIntensity()`
@*/
PetscBool PetscCheckPointer(const void *ptr, PetscDataType dtype)
{
  if (PETSC_RUNNING_ON_VALGRIND) return PETSC_TRUE;
  if (!ptr) return PETSC_FALSE;
  if (petsc_checkpointer_intensity < 1) return PETSC_TRUE;
  if (PetscDefined(HAVE_SANITIZER)) return PETSC_TRUE;

  #if PetscDefined(USE_DEBUG) && !PetscDefined(HAVE_THREADSAFETY)
  /* Skip the verbose check if we are inside a hot function. */
  if (petscstack.hotdepth > 0 && petsc_checkpointer_intensity < 2) return PETSC_TRUE;
  #endif

  PetscSegvJumpBuf_set = PETSC_TRUE;

  if (setjmp(PetscSegvJumpBuf)) {
    /* A segv was triggered in the code below hence we return with an error code */
    PetscSegvJumpBuf_set = PETSC_FALSE;
    return PETSC_FALSE;
  } else {
    switch (dtype) {
    case PETSC_INT: {
      PETSC_UNUSED PetscInt x = *(volatile PetscInt *)ptr;
      break;
    }
  #if defined(PETSC_USE_COMPLEX)
    case PETSC_SCALAR: { /* C++ is seriously dysfunctional with volatile std::complex. */
    #if defined(PETSC_USE_CXXCOMPLEX)
      PetscReal                         xreal = ((volatile PetscReal *)ptr)[0], ximag = ((volatile PetscReal *)ptr)[1];
      PETSC_UNUSED volatile PetscScalar x = xreal + PETSC_i * ximag;
    #else
      PETSC_UNUSED PetscScalar x = *(volatile PetscScalar *)ptr;
    #endif
      break;
    }
  #endif
    case PETSC_REAL: {
      PETSC_UNUSED PetscReal x = *(volatile PetscReal *)ptr;
      break;
    }
    case PETSC_BOOL: {
      PETSC_UNUSED PetscBool x = *(volatile PetscBool *)ptr;
      break;
    }
    case PETSC_ENUM: {
      PETSC_UNUSED PetscEnum x = *(volatile PetscEnum *)ptr;
      break;
    }
    case PETSC_CHAR: {
      PETSC_UNUSED char x = *(volatile char *)ptr;
      break;
    }
    case PETSC_OBJECT: {
      PETSC_UNUSED volatile PetscClassId classid = ((PetscObject)ptr)->classid;
      break;
    }
    default:;
    }
  }
  PetscSegvJumpBuf_set = PETSC_FALSE;
  return PETSC_TRUE;
}
#endif
