//===-- esan_interceptors.cpp ---------------------------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file is a part of EfficiencySanitizer, a family of performance tuners.
//
// Interception routines for the esan run-time.
//===----------------------------------------------------------------------===//

#include "esan.h"
#include "esan_shadow.h"
#include "interception/interception.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_libc.h"
#include "sanitizer_common/sanitizer_linux.h"
#include "sanitizer_common/sanitizer_stacktrace.h"

using namespace __esan; // NOLINT

#define CUR_PC() (StackTrace::GetCurrentPc())

//===----------------------------------------------------------------------===//
// Interception via sanitizer common interceptors
//===----------------------------------------------------------------------===//

// Get the per-platform defines for what is possible to intercept
#include "sanitizer_common/sanitizer_platform_interceptors.h"

DECLARE_REAL_AND_INTERCEPTOR(void *, malloc, uptr)

// TODO(bruening): tsan disables several interceptors (getpwent, etc.) claiming
// that interception is a perf hit: should we do the same?

// We have no need to intercept:
#undef SANITIZER_INTERCEPT_TLS_GET_ADDR

// TODO(bruening): the common realpath interceptor assumes malloc is
// intercepted!  We should try to parametrize that, though we'll
// intercept malloc soon ourselves and can then remove this undef.
#undef SANITIZER_INTERCEPT_REALPATH

// We provide our own version:
#undef SANITIZER_INTERCEPT_SIGPROCMASK

#define COMMON_INTERCEPTOR_NOTHING_IS_INITIALIZED (!EsanIsInitialized)

#define COMMON_INTERCEPT_FUNCTION(name) INTERCEPT_FUNCTION(name)
#define COMMON_INTERCEPT_FUNCTION_VER(name, ver)                          \
  INTERCEPT_FUNCTION_VER(name, ver)

// We must initialize during early interceptors, to support tcmalloc.
// This means that for some apps we fully initialize prior to
// __esan_init() being called.
// We currently do not use ctx.
#define COMMON_INTERCEPTOR_ENTER(ctx, func, ...)                               \
  do {                                                                         \
    if (UNLIKELY(COMMON_INTERCEPTOR_NOTHING_IS_INITIALIZED)) {                 \
      if (!UNLIKELY(EsanDuringInit))                                           \
        initializeLibrary(__esan_which_tool);                                  \
      return REAL(func)(__VA_ARGS__);                                          \
    }                                                                          \
    ctx = nullptr;                                                             \
    (void)ctx;                                                                 \
  } while (false)

#define COMMON_INTERCEPTOR_ENTER_NOIGNORE(ctx, func, ...)                      \
  COMMON_INTERCEPTOR_ENTER(ctx, func, __VA_ARGS__)

#define COMMON_INTERCEPTOR_WRITE_RANGE(ctx, ptr, size)                         \
  processRangeAccess(CUR_PC(), (uptr)ptr, size, true)

#define COMMON_INTERCEPTOR_READ_RANGE(ctx, ptr, size)                          \
  processRangeAccess(CUR_PC(), (uptr)ptr, size, false)

// This is only called if the app explicitly calls exit(), not on
// a normal exit.
#define COMMON_INTERCEPTOR_ON_EXIT(ctx) finalizeLibrary()

#define COMMON_INTERCEPTOR_FILE_OPEN(ctx, file, path)                          \
  do {                                                                         \
    (void)(ctx);                                                               \
    (void)(file);                                                              \
    (void)(path);                                                              \
  } while (false)
#define COMMON_INTERCEPTOR_FILE_CLOSE(ctx, file)                               \
  do {                                                                         \
    (void)(ctx);                                                               \
    (void)(file);                                                              \
  } while (false)
#define COMMON_INTERCEPTOR_LIBRARY_LOADED(filename, handle)                    \
  do {                                                                         \
    (void)(filename);                                                          \
    (void)(handle);                                                            \
  } while (false)
#define COMMON_INTERCEPTOR_LIBRARY_UNLOADED()                                  \
  do {                                                                         \
  } while (false)
#define COMMON_INTERCEPTOR_ACQUIRE(ctx, u)                                     \
  do {                                                                         \
    (void)(ctx);                                                               \
    (void)(u);                                                                 \
  } while (false)
#define COMMON_INTERCEPTOR_RELEASE(ctx, u)                                     \
  do {                                                                         \
    (void)(ctx);                                                               \
    (void)(u);                                                                 \
  } while (false)
#define COMMON_INTERCEPTOR_DIR_ACQUIRE(ctx, path)                              \
  do {                                                                         \
    (void)(ctx);                                                               \
    (void)(path);                                                              \
  } while (false)
#define COMMON_INTERCEPTOR_FD_ACQUIRE(ctx, fd)                                 \
  do {                                                                         \
    (void)(ctx);                                                               \
    (void)(fd);                                                                \
  } while (false)
#define COMMON_INTERCEPTOR_FD_RELEASE(ctx, fd)                                 \
  do {                                                                         \
    (void)(ctx);                                                               \
    (void)(fd);                                                                \
  } while (false)
#define COMMON_INTERCEPTOR_FD_ACCESS(ctx, fd)                                  \
  do {                                                                         \
    (void)(ctx);                                                               \
    (void)(fd);                                                                \
  } while (false)
#define COMMON_INTERCEPTOR_FD_SOCKET_ACCEPT(ctx, fd, newfd)                    \
  do {                                                                         \
    (void)(ctx);                                                               \
    (void)(fd);                                                                \
    (void)(newfd);                                                             \
  } while (false)
#define COMMON_INTERCEPTOR_SET_THREAD_NAME(ctx, name)                          \
  do {                                                                         \
    (void)(ctx);                                                               \
    (void)(name);                                                              \
  } while (false)
#define COMMON_INTERCEPTOR_SET_PTHREAD_NAME(ctx, thread, name)                 \
  do {                                                                         \
    (void)(ctx);                                                               \
    (void)(thread);                                                            \
    (void)(name);                                                              \
  } while (false)
#define COMMON_INTERCEPTOR_BLOCK_REAL(name) REAL(name)
#define COMMON_INTERCEPTOR_MUTEX_LOCK(ctx, m)                                  \
  do {                                                                         \
    (void)(ctx);                                                               \
    (void)(m);                                                                 \
  } while (false)
#define COMMON_INTERCEPTOR_MUTEX_UNLOCK(ctx, m)                                \
  do {                                                                         \
    (void)(ctx);                                                               \
    (void)(m);                                                                 \
  } while (false)
#define COMMON_INTERCEPTOR_MUTEX_REPAIR(ctx, m)                                \
  do {                                                                         \
    (void)(ctx);                                                               \
    (void)(m);                                                                 \
  } while (false)
#define COMMON_INTERCEPTOR_HANDLE_RECVMSG(ctx, msg)                            \
  do {                                                                         \
    (void)(ctx);                                                               \
    (void)(msg);                                                               \
  } while (false)
#define COMMON_INTERCEPTOR_USER_CALLBACK_START()                               \
  do {                                                                         \
  } while (false)
#define COMMON_INTERCEPTOR_USER_CALLBACK_END()                                 \
  do {                                                                         \
  } while (false)

#define COMMON_INTERCEPTOR_MMAP_IMPL(ctx, mmap, addr, sz, prot, flags, fd,     \
                                     off)                                      \
  do {                                                                         \
    if (!fixMmapAddr(&addr, sz, flags))                                        \
      return (void *)-1;                                                       \
    void *result = REAL(mmap)(addr, sz, prot, flags, fd, off);                 \
    return (void *)checkMmapResult((uptr)result, sz);                          \
  } while (false)

#include "sanitizer_common/sanitizer_common_interceptors.inc"

//===----------------------------------------------------------------------===//
// Syscall interception
//===----------------------------------------------------------------------===//

// We want the caller's PC b/c unlike the other function interceptors these
// are separate pre and post functions called around the app's syscall().

#define COMMON_SYSCALL_PRE_READ_RANGE(ptr, size)                               \
  processRangeAccess(GET_CALLER_PC(), (uptr)ptr, size, false)

#define COMMON_SYSCALL_PRE_WRITE_RANGE(ptr, size)                              \
  do {                                                                         \
    (void)(ptr);                                                               \
    (void)(size);                                                              \
  } while (false)

#define COMMON_SYSCALL_POST_READ_RANGE(ptr, size)                              \
  do {                                                                         \
    (void)(ptr);                                                               \
    (void)(size);                                                              \
  } while (false)

// The actual amount written is in post, not pre.
#define COMMON_SYSCALL_POST_WRITE_RANGE(ptr, size)                             \
  processRangeAccess(GET_CALLER_PC(), (uptr)ptr, size, true)

#define COMMON_SYSCALL_ACQUIRE(addr)                                           \
  do {                                                                         \
    (void)(addr);                                                              \
  } while (false)
#define COMMON_SYSCALL_RELEASE(addr)                                           \
  do {                                                                         \
    (void)(addr);                                                              \
  } while (false)
#define COMMON_SYSCALL_FD_CLOSE(fd)                                            \
  do {                                                                         \
    (void)(fd);                                                                \
  } while (false)
#define COMMON_SYSCALL_FD_ACQUIRE(fd)                                          \
  do {                                                                         \
    (void)(fd);                                                                \
  } while (false)
#define COMMON_SYSCALL_FD_RELEASE(fd)                                          \
  do {                                                                         \
    (void)(fd);                                                                \
  } while (false)
#define COMMON_SYSCALL_PRE_FORK()                                              \
  do {                                                                         \
  } while (false)
#define COMMON_SYSCALL_POST_FORK(res)                                          \
  do {                                                                         \
    (void)(res);                                                               \
  } while (false)

#include "sanitizer_common/sanitizer_common_syscalls.inc"
#include "sanitizer_common/sanitizer_syscalls_netbsd.inc"

//===----------------------------------------------------------------------===//
// Custom interceptors
//===----------------------------------------------------------------------===//

// TODO(bruening): move more of these to the common interception pool as they
// are shared with tsan and asan.
// While our other files match LLVM style, here we match sanitizer style as we
// expect to move these to the common pool.

INTERCEPTOR(char *, strcpy, char *dst, const char *src) { // NOLINT
  void *ctx;
  COMMON_INTERCEPTOR_ENTER(ctx, strcpy, dst, src);
  uptr srclen = internal_strlen(src);
  COMMON_INTERCEPTOR_WRITE_RANGE(ctx, dst, srclen + 1);
  COMMON_INTERCEPTOR_READ_RANGE(ctx, src, srclen + 1);
  return REAL(strcpy)(dst, src); // NOLINT
}

INTERCEPTOR(char *, strncpy, char *dst, char *src, uptr n) {
  void *ctx;
  COMMON_INTERCEPTOR_ENTER(ctx, strncpy, dst, src, n);
  uptr srclen = internal_strnlen(src, n);
  uptr copied_size = srclen + 1 > n ? n : srclen + 1;
  COMMON_INTERCEPTOR_WRITE_RANGE(ctx, dst, copied_size);
  COMMON_INTERCEPTOR_READ_RANGE(ctx, src, copied_size);
  return REAL(strncpy)(dst, src, n);
}

INTERCEPTOR(int, open, const char *name, int flags, int mode) {
  void *ctx;
  COMMON_INTERCEPTOR_ENTER(ctx, open, name, flags, mode);
  COMMON_INTERCEPTOR_READ_STRING(ctx, name, 0);
  return REAL(open)(name, flags, mode);
}

#if SANITIZER_LINUX
INTERCEPTOR(int, open64, const char *name, int flags, int mode) {
  void *ctx;
  COMMON_INTERCEPTOR_ENTER(ctx, open64, name, flags, mode);
  COMMON_INTERCEPTOR_READ_STRING(ctx, name, 0);
  return REAL(open64)(name, flags, mode);
}
#define ESAN_MAYBE_INTERCEPT_OPEN64 INTERCEPT_FUNCTION(open64)
#else
#define ESAN_MAYBE_INTERCEPT_OPEN64
#endif

INTERCEPTOR(int, creat, const char *name, int mode) {
  void *ctx;
  COMMON_INTERCEPTOR_ENTER(ctx, creat, name, mode);
  COMMON_INTERCEPTOR_READ_STRING(ctx, name, 0);
  return REAL(creat)(name, mode);
}

#if SANITIZER_LINUX
INTERCEPTOR(int, creat64, const char *name, int mode) {
  void *ctx;
  COMMON_INTERCEPTOR_ENTER(ctx, creat64, name, mode);
  COMMON_INTERCEPTOR_READ_STRING(ctx, name, 0);
  return REAL(creat64)(name, mode);
}
#define ESAN_MAYBE_INTERCEPT_CREAT64 INTERCEPT_FUNCTION(creat64)
#else
#define ESAN_MAYBE_INTERCEPT_CREAT64
#endif

INTERCEPTOR(int, unlink, char *path) {
  void *ctx;
  COMMON_INTERCEPTOR_ENTER(ctx, unlink, path);
  COMMON_INTERCEPTOR_READ_STRING(ctx, path, 0);
  return REAL(unlink)(path);
}

INTERCEPTOR(int, puts, const char *s) {
  void *ctx;
  COMMON_INTERCEPTOR_ENTER(ctx, puts, s);
  COMMON_INTERCEPTOR_READ_RANGE(ctx, s, internal_strlen(s));
  return REAL(puts)(s);
}

INTERCEPTOR(int, rmdir, char *path) {
  void *ctx;
  COMMON_INTERCEPTOR_ENTER(ctx, rmdir, path);
  COMMON_INTERCEPTOR_READ_STRING(ctx, path, 0);
  return REAL(rmdir)(path);
}

//===----------------------------------------------------------------------===//
// Signal-related interceptors
//===----------------------------------------------------------------------===//

#if SANITIZER_LINUX
typedef void (*signal_handler_t)(int);
INTERCEPTOR(signal_handler_t, signal, int signum, signal_handler_t handler) {
  void *ctx;
  COMMON_INTERCEPTOR_ENTER(ctx, signal, signum, handler);
  signal_handler_t result;
  if (!processSignal(signum, handler, &result))
    return result;
  else
    return REAL(signal)(signum, handler);
}
#define ESAN_MAYBE_INTERCEPT_SIGNAL INTERCEPT_FUNCTION(signal)
#else
#error Platform not supported
#define ESAN_MAYBE_INTERCEPT_SIGNAL
#endif

#if SANITIZER_LINUX
DECLARE_REAL(int, sigaction, int signum, const struct sigaction *act,
             struct sigaction *oldact)
INTERCEPTOR(int, sigaction, int signum, const struct sigaction *act,
            struct sigaction *oldact) {
  void *ctx;
  COMMON_INTERCEPTOR_ENTER(ctx, sigaction, signum, act, oldact);
  if (!processSigaction(signum, act, oldact))
    return 0;
  else
    return REAL(sigaction)(signum, act, oldact);
}

// This is required to properly use internal_sigaction.
namespace __sanitizer {
int real_sigaction(int signum, const void *act, void *oldact) {
  if (REAL(sigaction) == nullptr) {
    // With an instrumented allocator, this is called during interceptor init
    // and we need a raw syscall solution.
    return internal_sigaction_syscall(signum, act, oldact);
  }
  return REAL(sigaction)(signum, (const struct sigaction *)act,
                         (struct sigaction *)oldact);
}
} // namespace __sanitizer

#define ESAN_MAYBE_INTERCEPT_SIGACTION INTERCEPT_FUNCTION(sigaction)
#else
#error Platform not supported
#define ESAN_MAYBE_INTERCEPT_SIGACTION
#endif

#if SANITIZER_LINUX
INTERCEPTOR(int, sigprocmask, int how, __sanitizer_sigset_t *set,
            __sanitizer_sigset_t *oldset) {
  void *ctx;
  COMMON_INTERCEPTOR_ENTER(ctx, sigprocmask, how, set, oldset);
  int res = 0;
  if (processSigprocmask(how, set, oldset))
    res = REAL(sigprocmask)(how, set, oldset);
  if (!res && oldset)
    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, oldset, sizeof(*oldset));
  return res;
}
#define ESAN_MAYBE_INTERCEPT_SIGPROCMASK INTERCEPT_FUNCTION(sigprocmask)
#else
#define ESAN_MAYBE_INTERCEPT_SIGPROCMASK
#endif

#if !SANITIZER_WINDOWS
INTERCEPTOR(int, pthread_sigmask, int how, __sanitizer_sigset_t *set,
            __sanitizer_sigset_t *oldset) {
  void *ctx;
  COMMON_INTERCEPTOR_ENTER(ctx, pthread_sigmask, how, set, oldset);
  int res = 0;
  if (processSigprocmask(how, set, oldset))
    res = REAL(sigprocmask)(how, set, oldset);
  if (!res && oldset)
    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, oldset, sizeof(*oldset));
  return res;
}
#define ESAN_MAYBE_INTERCEPT_PTHREAD_SIGMASK INTERCEPT_FUNCTION(pthread_sigmask)
#else
#define ESAN_MAYBE_INTERCEPT_PTHREAD_SIGMASK
#endif

//===----------------------------------------------------------------------===//
// Malloc interceptors
//===----------------------------------------------------------------------===//

static const uptr early_alloc_buf_size = 4096;
static uptr allocated_bytes;
static char early_alloc_buf[early_alloc_buf_size];

static bool isInEarlyAllocBuf(const void *ptr) {
  return ((uptr)ptr >= (uptr)early_alloc_buf &&
          ((uptr)ptr - (uptr)early_alloc_buf) < sizeof(early_alloc_buf));
}

static void *handleEarlyAlloc(uptr size) {
  // If esan is initialized during an interceptor (which happens with some
  // tcmalloc implementations that call pthread_mutex_lock), the call from
  // dlsym to calloc will deadlock.
  // dlsym may also call malloc before REAL(malloc) is retrieved from dlsym.
  // We work around it by using a static buffer for the early malloc/calloc
  // requests.
  // This solution will also allow us to deliberately intercept malloc & family
  // in the future (to perform tool actions on each allocation, without
  // replacing the allocator), as it also solves the problem of intercepting
  // calloc when it will itself be called before its REAL pointer is
  // initialized.
  // We do not handle multiple threads here.  This only happens at process init
  // time, and while it's possible for a shared library to create early threads
  // that race here, we consider that to be a corner case extreme enough that
  // it's not worth the effort to handle.
  void *mem = (void *)&early_alloc_buf[allocated_bytes];
  allocated_bytes += size;
  CHECK_LT(allocated_bytes, early_alloc_buf_size);
  return mem;
}

INTERCEPTOR(void*, calloc, uptr size, uptr n) {
  if (EsanDuringInit && REAL(calloc) == nullptr)
    return handleEarlyAlloc(size * n);
  void *ctx;
  COMMON_INTERCEPTOR_ENTER(ctx, calloc, size, n);
  void *res = REAL(calloc)(size, n);
  // The memory is zeroed and thus is all written.
  COMMON_INTERCEPTOR_WRITE_RANGE(nullptr, (uptr)res, size * n);
  return res;
}

INTERCEPTOR(void*, malloc, uptr size) {
  if (EsanDuringInit && REAL(malloc) == nullptr)
    return handleEarlyAlloc(size);
  void *ctx;
  COMMON_INTERCEPTOR_ENTER(ctx, malloc, size);
  return REAL(malloc)(size);
}

INTERCEPTOR(void, free, void *p) {
  void *ctx;
  // There are only a few early allocation requests, so we simply skip the free.
  if (isInEarlyAllocBuf(p))
    return;
  COMMON_INTERCEPTOR_ENTER(ctx, free, p);
  REAL(free)(p);
}

namespace __esan {

void initializeInterceptors() {
  InitializeCommonInterceptors();

  INTERCEPT_FUNCTION(strcpy); // NOLINT
  INTERCEPT_FUNCTION(strncpy);

  INTERCEPT_FUNCTION(open);
  ESAN_MAYBE_INTERCEPT_OPEN64;
  INTERCEPT_FUNCTION(creat);
  ESAN_MAYBE_INTERCEPT_CREAT64;
  INTERCEPT_FUNCTION(unlink);
  INTERCEPT_FUNCTION(fread);
  INTERCEPT_FUNCTION(fwrite);
  INTERCEPT_FUNCTION(puts);
  INTERCEPT_FUNCTION(rmdir);

  ESAN_MAYBE_INTERCEPT_SIGNAL;
  ESAN_MAYBE_INTERCEPT_SIGACTION;
  ESAN_MAYBE_INTERCEPT_SIGPROCMASK;
  ESAN_MAYBE_INTERCEPT_PTHREAD_SIGMASK;

  INTERCEPT_FUNCTION(calloc);
  INTERCEPT_FUNCTION(malloc);
  INTERCEPT_FUNCTION(free);

  // TODO(bruening): intercept routines that other sanitizers intercept that
  // are not in the common pool or here yet, ideally by adding to the common
  // pool.  Examples include wcslen and bcopy.

  // TODO(bruening): there are many more libc routines that read or write data
  // structures that no sanitizer is intercepting: sigaction, strtol, etc.
}

} // namespace __esan
