/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8                               :vi │
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2020 Justine Alexandra Roberts Tunney                              │
│                                                                              │
│ Permission to use, copy, modify, and/or distribute this software for         │
│ any purpose with or without fee is hereby granted, provided that the         │
│ above copyright notice and this permission notice appear in all copies.      │
│                                                                              │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL                │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED                │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE             │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL         │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR        │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER               │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR             │
│ PERFORMANCE OF THIS SOFTWARE.                                                │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/intrin/ubsan.h"
#include "libc/calls/calls.h"
#include "libc/intrin/describebacktrace.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/pushpop.h"
#include "libc/intrin/strace.h"
#include "libc/intrin/weaken.h"
#include "libc/limits.h"
#include "libc/log/color.internal.h"
#include "libc/log/internal.h"
#include "libc/log/libfatal.internal.h"
#include "libc/log/log.h"
#include "libc/mem/reverse.internal.h"
#include "libc/nt/runtime.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/symbols.internal.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/fileno.h"
#include "libc/sysv/consts/nr.h"

#define kUbsanKindInt     0
#define kUbsanKindFloat   1
#define kUbsanKindUnknown 0xffff

typedef void __ubsan_die_f(void);

struct UbsanSourceLocation {
  const char *file;
  uint32_t line;
  uint32_t column;
};

struct UbsanTypeDescriptor {
  uint16_t kind; /* int,float,... */
  uint16_t info; /* if int bit 0 if signed, remaining bits are log2(sizeof*8) */
  char name[];
};

struct UbsanTypeMismatchInfo {
  struct UbsanSourceLocation location;
  struct UbsanTypeDescriptor *type;
  uintptr_t alignment;
  uint8_t type_check_kind;
};

struct UbsanTypeMismatchInfoClang {
  struct UbsanSourceLocation location;
  struct UbsanTypeDescriptor *type;
  unsigned char log_alignment; /* https://reviews.llvm.org/D28244 */
  uint8_t type_check_kind;
};

struct UbsanOutOfBoundsInfo {
  struct UbsanSourceLocation location;
  struct UbsanTypeDescriptor *array_type;
  struct UbsanTypeDescriptor *index_type;
};

struct UbsanUnreachableData {
  struct UbsanSourceLocation location;
};

struct UbsanVlaBoundData {
  struct UbsanSourceLocation location;
  struct UbsanTypeDescriptor *type;
};

struct UbsanNonnullArgData {
  struct UbsanSourceLocation location;
  struct UbsanSourceLocation attr_location;
};

struct UbsanCfiBadIcallData {
  struct UbsanSourceLocation location;
  struct UbsanTypeDescriptor *type;
};

struct UbsanNonnullReturnData {
  struct UbsanSourceLocation location;
  struct UbsanSourceLocation attr_location;
};

struct UbsanFunctionTypeMismatchData {
  struct UbsanSourceLocation location;
  struct UbsanTypeDescriptor *type;
};

struct UbsanInvalidValueData {
  struct UbsanSourceLocation location;
  struct UbsanTypeDescriptor *type;
};

struct UbsanOverflowData {
  struct UbsanSourceLocation location;
  struct UbsanTypeDescriptor *type;
};

struct UbsanDynamicTypeCacheMissData {
  struct UbsanSourceLocation location;
  struct UbsanTypeDescriptor *type;
  void *TypeInfo;
  unsigned char TypeCheckKind;
};

struct UbsanFloatCastOverflowData {
#if __GNUC__ + 0 >= 6
  struct UbsanSourceLocation location;
#endif
  struct UbsanTypeDescriptor *from_type;
  struct UbsanTypeDescriptor *to_type;
};

struct UbsanOutOfBoundsData {
  struct UbsanSourceLocation location;
  struct UbsanTypeDescriptor *arraytype;
  struct UbsanTypeDescriptor *index_type;
};

struct UbsanShiftOutOfBoundsInfo {
  struct UbsanSourceLocation location;
  struct UbsanTypeDescriptor *lhs_type;
  struct UbsanTypeDescriptor *rhs_type;
};

static const char kUbsanTypeCheckKinds[] = "\
load of\0\
store to\0\
reference binding to\0\
member access within\0\
member call on\0\
constructor call on\0\
downcast of\0\
downcast of\0\
upcast of\0\
cast to virtual base of\0\
\0";

uintptr_t __ubsan_vptr_type_cache[128];

static int __ubsan_bits(struct UbsanTypeDescriptor *t) {
  return 1 << (t->info >> 1);
}

static bool __ubsan_signed(struct UbsanTypeDescriptor *t) {
  return t->info & 1;
}

static bool __ubsan_negative(struct UbsanTypeDescriptor *t, uintptr_t x) {
  return __ubsan_signed(t) && (intptr_t)x < 0;
}

static char *__ubsan_itpcpy(char *p, struct UbsanTypeDescriptor *t,
                            uintptr_t x) {
  if (__ubsan_signed(t)) {
    return __intcpy(p, x);
  } else {
    return __uintcpy(p, x);
  }
}

static size_t __ubsan_strlen(const char *s) {
  size_t i = 0;
  while (s[i])
    ++i;
  return i;
}

static const char *__ubsan_dubnul(const char *s, unsigned i) {
  size_t n;
  while (i--) {
    if ((n = __ubsan_strlen(s))) {
      s += n + 1;
    } else {
      return NULL;
    }
  }
  return s;
}

static uintptr_t __ubsan_extend(struct UbsanTypeDescriptor *t, uintptr_t x) {
  int w;
  w = __ubsan_bits(t);
  if (w < sizeof(x) * CHAR_BIT) {
    x <<= sizeof(x) * CHAR_BIT - w;
    if (__ubsan_signed(t)) {
      x = (intptr_t)x >> w;
    } else {
      x >>= w;
    }
  }
  return x;
}

static wontreturn void __ubsan_unreachable(void) {
  for (;;)
    abort();
}

static void __ubsan_exit(void) {
  kprintf("your ubsan runtime needs\n"
          "\t__static_yoink(\"__die\");\n"
          "in order to show you backtraces\n");
  _Exit(99);
}

static char *__ubsan_stpcpy(char *d, const char *s) {
  size_t i;
  for (i = 0;; ++i) {
    if (!(d[i] = s[i])) {
      return d + i;
    }
  }
}

__wur static __ubsan_die_f *__ubsan_die(void) {
  if (_weaken(__die)) {
    return _weaken(__die);
  } else {
    return __ubsan_exit;
  }
}

static void __ubsan_warning(const struct UbsanSourceLocation *loc,
                            const char *description) {
  kprintf("%s:%d: %subsan warning: %s is undefined behavior%s\n"
          "cosmoaddr2line %s %s\n",
          loc->file, loc->line, SUBTLE, description, RESET, __argv[0],
          DescribeBacktrace(__builtin_frame_address(0)));
  if (__ubsan_strict)
    __ubsan_die()();
}

__wur __ubsan_die_f *__ubsan_abort(const struct UbsanSourceLocation *loc,
                                   const char *description) {
  kprintf("\n%s:%d: %subsan error%s: %s (tid %d)\n"
          "cosmoaddr2line %s %s\n",
          loc->file, loc->line, RED2, RESET, description, gettid(), __argv[0],
          DescribeBacktrace(__builtin_frame_address(0)));
  return __ubsan_die();
}

static const char *__ubsan_describe_shift(
    struct UbsanShiftOutOfBoundsInfo *info, uintptr_t lhs, uintptr_t rhs) {
  if (__ubsan_negative(info->rhs_type, rhs)) {
    return "negative shift exponent";
  } else if (rhs >= __ubsan_bits(info->lhs_type)) {
    return "shift exponent too large for type";
  } else if (__ubsan_negative(info->lhs_type, lhs)) {
    return "left shift of negative value";
  } else if (__ubsan_signed(info->lhs_type)) {
    return "signed left shift changed sign bit or overflowed";
  } else {
    return "wut shift out of bounds";
  }
}

static char *__ubsan_describe_shift_out_of_bounds(
    char buf[512], struct UbsanShiftOutOfBoundsInfo *info, uintptr_t lhs,
    uintptr_t rhs) {
  char *p = buf;
  lhs = __ubsan_extend(info->lhs_type, lhs);
  rhs = __ubsan_extend(info->rhs_type, rhs);
  p = __ubsan_stpcpy(p, __ubsan_describe_shift(info, lhs, rhs)), *p++ = ' ';
  p = __ubsan_itpcpy(p, info->lhs_type, lhs), *p++ = ' ';
  p = __ubsan_stpcpy(p, info->lhs_type->name), *p++ = ' ';
  p = __ubsan_itpcpy(p, info->rhs_type, rhs), *p++ = ' ';
  p = __ubsan_stpcpy(p, info->rhs_type->name);
  return buf;
}

void __ubsan_handle_shift_out_of_bounds(struct UbsanShiftOutOfBoundsInfo *info,
                                        uintptr_t lhs, uintptr_t rhs) {
  char buf[512];
  __ubsan_warning(&info->location,
                  __ubsan_describe_shift_out_of_bounds(buf, info, lhs, rhs));
}

void __ubsan_handle_shift_out_of_bounds_abort(
    struct UbsanShiftOutOfBoundsInfo *info, uintptr_t lhs, uintptr_t rhs) {
  char buf[512];
  __ubsan_abort(&info->location,
                __ubsan_describe_shift_out_of_bounds(buf, info, lhs, rhs))();
  __ubsan_unreachable();
}

void __ubsan_handle_out_of_bounds(struct UbsanOutOfBoundsInfo *info,
                                  uintptr_t index) {
  char buf[512], *p = buf;
  p = __ubsan_stpcpy(p, info->index_type->name);
  p = __ubsan_stpcpy(p, " index ");
  p = __ubsan_itpcpy(p, info->index_type, index);
  p = __ubsan_stpcpy(p, " into ");
  p = __ubsan_stpcpy(p, info->array_type->name);
  p = __ubsan_stpcpy(p, " out of bounds");
  __ubsan_abort(&info->location, buf)();
  __ubsan_unreachable();
}

void __ubsan_handle_out_of_bounds_abort(struct UbsanOutOfBoundsInfo *info,
                                        uintptr_t index) {
  __ubsan_handle_out_of_bounds(info, index);
}

static __ubsan_die_f *__ubsan_type_mismatch_handler(
    struct UbsanTypeMismatchInfo *info, uintptr_t pointer) {
  const char *kind;
  char buf[512], *p = buf;
  if (!pointer)
    return __ubsan_abort(&info->location, "null pointer access");
  kind = __ubsan_dubnul(kUbsanTypeCheckKinds, info->type_check_kind);
  if (info->alignment && (pointer & (info->alignment - 1))) {
    p = __ubsan_stpcpy(p, "unaligned ");
    p = __ubsan_stpcpy(p, kind), *p++ = ' ';
    p = __ubsan_stpcpy(p, info->type->name), *p++ = ' ', *p++ = '@';
    p = __ubsan_itpcpy(p, info->type, pointer);
    p = __ubsan_stpcpy(p, " align ");
    p = __intcpy(p, info->alignment);
  } else {
    p = __ubsan_stpcpy(p, "insufficient size ");
    p = __ubsan_stpcpy(p, kind);
    p = __ubsan_stpcpy(p, " address 0x");
    p = __fixcpy(p, pointer, sizeof(pointer) * CHAR_BIT);
    p = __ubsan_stpcpy(p, " with insufficient space for object of type ");
    p = __ubsan_stpcpy(p, info->type->name);
  }
  return __ubsan_abort(&info->location, buf);
}

void __ubsan_handle_type_mismatch(struct UbsanTypeMismatchInfo *info,
                                  uintptr_t pointer) {
  __ubsan_type_mismatch_handler(info, pointer)();
  __ubsan_unreachable();
}

void __ubsan_handle_type_mismatch_abort(struct UbsanTypeMismatchInfo *info,
                                        uintptr_t pointer) {
  __ubsan_type_mismatch_handler(info, pointer)();
  __ubsan_unreachable();
}

static __ubsan_die_f *__ubsan_type_mismatch_v1_handler(
    struct UbsanTypeMismatchInfoClang *type_mismatch, uintptr_t pointer) {
  struct UbsanTypeMismatchInfo mm;
  mm.location = type_mismatch->location;
  mm.type = type_mismatch->type;
  mm.alignment = 1u << type_mismatch->log_alignment;
  mm.type_check_kind = type_mismatch->type_check_kind;
  return __ubsan_type_mismatch_handler(&mm, pointer);
}

void __ubsan_handle_type_mismatch_v1(
    struct UbsanTypeMismatchInfoClang *type_mismatch, uintptr_t pointer) {
  __ubsan_type_mismatch_v1_handler(type_mismatch, pointer)();
  __ubsan_unreachable();
}

void __ubsan_handle_type_mismatch_v1_abort(
    struct UbsanTypeMismatchInfoClang *type_mismatch, uintptr_t pointer) {
  __ubsan_type_mismatch_v1_handler(type_mismatch, pointer)();
  __ubsan_unreachable();
}

void __ubsan_handle_float_cast_overflow(void *data_raw, void *from_raw) {
  struct UbsanFloatCastOverflowData *data =
      (struct UbsanFloatCastOverflowData *)data_raw;
#if __GNUC__ + 0 >= 6
  __ubsan_abort(&data->location, "float cast overflow")();
  __ubsan_unreachable();
#else
  const struct UbsanSourceLocation kUnknownLocation = {
      "<unknown file>",
      pushpop(0),
      pushpop(0),
  };
  __ubsan_abort(((void)data, &kUnknownLocation), "float cast overflow")();
  __ubsan_unreachable();
#endif
}

void __ubsan_handle_float_cast_overflow_abort(void *data_raw, void *from_raw) {
  __ubsan_handle_float_cast_overflow(data_raw, from_raw);
}

void __ubsan_handle_add_overflow(const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "add overflow")();
  __ubsan_unreachable();
}

void __ubsan_handle_add_overflow_abort(const struct UbsanSourceLocation *loc) {
  __ubsan_handle_add_overflow(loc);
}

void __ubsan_handle_alignment_assumption(
    const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "alignment assumption")();
  __ubsan_unreachable();
}

void __ubsan_handle_alignment_assumption_abort(
    const struct UbsanSourceLocation *loc) {
  __ubsan_handle_alignment_assumption(loc);
}

void __ubsan_handle_builtin_unreachable(const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "builtin unreachable")();
  __ubsan_unreachable();
}

void __ubsan_handle_builtin_unreachable_abort(
    const struct UbsanSourceLocation *loc) {
  __ubsan_handle_builtin_unreachable(loc);
}

void __ubsan_handle_cfi_bad_type(const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "cfi bad type")();
  __ubsan_unreachable();
}

void __ubsan_handle_cfi_bad_type_abort(const struct UbsanSourceLocation *loc) {
  __ubsan_handle_cfi_bad_type(loc);
}

void __ubsan_handle_cfi_check_fail(const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "cfi check fail")();
  __ubsan_unreachable();
}

void __ubsan_handle_cfi_check_fail_abort(
    const struct UbsanSourceLocation *loc) {
  __ubsan_handle_cfi_check_fail(loc);
}

void __ubsan_handle_divrem_overflow(const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "divrem overflow")();
  __ubsan_unreachable();
}

void __ubsan_handle_divrem_overflow_abort(
    const struct UbsanSourceLocation *loc) {
  __ubsan_handle_divrem_overflow(loc);
}

static bool HandleDynamicTypeCacheMiss(
    struct UbsanDynamicTypeCacheMissData *data, uintptr_t ptr, uintptr_t hash) {
  return false;  // TODO: implement me
}

void __ubsan_handle_dynamic_type_cache_miss(
    struct UbsanDynamicTypeCacheMissData *data, uintptr_t ptr, uintptr_t hash) {
  HandleDynamicTypeCacheMiss(data, ptr, hash);
}

void __ubsan_handle_dynamic_type_cache_miss_abort(
    struct UbsanDynamicTypeCacheMissData *data, uintptr_t ptr, uintptr_t hash) {
  if (HandleDynamicTypeCacheMiss(data, ptr, hash)) {
    __ubsan_abort(&data->location, "dynamic type cache miss")();
    __ubsan_unreachable();
  }
}

void __ubsan_handle_function_type_mismatch(
    const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "function type mismatch")();
  __ubsan_unreachable();
}

void __ubsan_handle_function_type_mismatch_abort(
    const struct UbsanSourceLocation *loc) {
  __ubsan_handle_function_type_mismatch(loc);
}

void __ubsan_handle_implicit_conversion(const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "implicit conversion")();
  __ubsan_unreachable();
}

void __ubsan_handle_implicit_conversion_abort(
    const struct UbsanSourceLocation *loc) {
  __ubsan_handle_implicit_conversion(loc);
}

void __ubsan_handle_invalid_builtin(const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "invalid builtin")();
  __ubsan_unreachable();
}

void __ubsan_handle_invalid_builtin_abort(
    const struct UbsanSourceLocation *loc) {
  __ubsan_handle_invalid_builtin(loc);
}

void __ubsan_handle_load_invalid_value(const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "load invalid value (uninitialized? bool∌[01]?)")();
  __ubsan_unreachable();
}

void __ubsan_handle_load_invalid_value_abort(
    const struct UbsanSourceLocation *loc) {
  __ubsan_handle_load_invalid_value(loc);
}

void __ubsan_handle_missing_return(const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "missing return")();
  __ubsan_unreachable();
}

void __ubsan_handle_missing_return_abort(
    const struct UbsanSourceLocation *loc) {
  __ubsan_handle_missing_return(loc);
}

void __ubsan_handle_mul_overflow(const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "multiply overflow")();
  __ubsan_unreachable();
}

void __ubsan_handle_mul_overflow_abort(const struct UbsanSourceLocation *loc) {
  __ubsan_handle_mul_overflow(loc);
}

void __ubsan_handle_negate_overflow(const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "negate overflow")();
  __ubsan_unreachable();
}

void __ubsan_handle_negate_overflow_abort(
    const struct UbsanSourceLocation *loc) {
  __ubsan_handle_negate_overflow(loc);
}

void __ubsan_handle_nonnull_arg(const struct UbsanSourceLocation *loc) {
  __ubsan_warning(loc, "null argument here");
}

void __ubsan_handle_nonnull_arg_abort(const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "nonnull argument")();
  __ubsan_unreachable();
}

void __ubsan_handle_nonnull_return_v1(const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "non-null return (v1)")();
  __ubsan_unreachable();
}

void __ubsan_handle_nonnull_return_v1_abort(
    const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "non-null return (v1)")();
  __ubsan_unreachable();
}

void __ubsan_handle_nullability_arg(const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "nullability arg")();
  __ubsan_unreachable();
}

void __ubsan_handle_nullability_arg_abort(
    const struct UbsanSourceLocation *loc) {
  __ubsan_handle_nullability_arg(loc);
}

void __ubsan_handle_nullability_return_v1(
    const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "nullability return (v1)")();
  __ubsan_unreachable();
}

void __ubsan_handle_nullability_return_v1_abort(
    const struct UbsanSourceLocation *loc) {
  __ubsan_handle_nullability_return_v1(loc);
}

void __ubsan_handle_pointer_overflow(const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "pointer overflow")();
  __ubsan_unreachable();
}

void __ubsan_handle_pointer_overflow_abort(
    const struct UbsanSourceLocation *loc) {
  __ubsan_handle_pointer_overflow(loc);
}

void __ubsan_handle_sub_overflow(const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "subtract overflow")();
  __ubsan_unreachable();
}

void __ubsan_handle_sub_overflow_abort(const struct UbsanSourceLocation *loc) {
  __ubsan_handle_sub_overflow(loc);
}

void __ubsan_handle_vla_bound_not_positive(
    const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "vla bound not positive")();
  __ubsan_unreachable();
}

void __ubsan_handle_vla_bound_not_positive_abort(
    const struct UbsanSourceLocation *loc) {
  __ubsan_handle_vla_bound_not_positive(loc);
}

void __ubsan_handle_nonnull_return(const struct UbsanSourceLocation *loc) {
  __ubsan_abort(loc, "nonnull return")();
  __ubsan_unreachable();
}

void __ubsan_handle_nonnull_return_abort(
    const struct UbsanSourceLocation *loc) {
  __ubsan_handle_nonnull_return(loc);
}

void __ubsan_default_options(void) {
}

void __ubsan_on_report(void) {
}

void *__ubsan_get_current_report_data(void) {
  return 0;
}

__attribute__((__constructor__(90))) static textstartup void ubsan_init() {
  STRACE(" _   _ ____ ____    _    _   _");
  STRACE("| | | | __ ) ___|  / \\  | \\ | |");
  STRACE("| | | |  _ \\___ \\ / _ \\ |  \\| |");
  STRACE("| |_| | |_) |__) / ___ \\| |\\  |");
  STRACE(" \\___/|____/____/_/   \\_\\_| \\_|");
  STRACE("cosmopolitan behavior module initialized");
}
