//===-- tysan.cpp ---------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file is a part of TypeSanitizer.
//
// TypeSanitizer runtime.
//===----------------------------------------------------------------------===//

#include "sanitizer_common/sanitizer_atomic.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_flag_parser.h"
#include "sanitizer_common/sanitizer_flags.h"
#include "sanitizer_common/sanitizer_libc.h"
#include "sanitizer_common/sanitizer_report_decorator.h"
#include "sanitizer_common/sanitizer_stacktrace.h"
#include "sanitizer_common/sanitizer_symbolizer.h"

#include "tysan/tysan.h"

#include <string.h>

using namespace __sanitizer;
using namespace __tysan;

extern "C" SANITIZER_INTERFACE_ATTRIBUTE void
tysan_set_type_unknown(const void *addr, uptr size) {
  if (tysan_inited)
    internal_memset(shadow_for(addr), 0, size * sizeof(uptr));
}

extern "C" SANITIZER_INTERFACE_ATTRIBUTE void
tysan_copy_types(const void *daddr, const void *saddr, uptr size) {
  if (tysan_inited)
    internal_memmove(shadow_for(daddr), shadow_for(saddr), size * sizeof(uptr));
}

static const char *getDisplayName(const char *Name) {
  if (Name[0] == '\0')
    return "<anonymous type>";

  // Clang generates tags for C++ types that demangle as typeinfo. Remove the
  // prefix from the generated string.
  const char *TIPrefix = "typeinfo name for ";
  size_t TIPrefixLen = strlen(TIPrefix);

  const char *DName = Symbolizer::GetOrInit()->Demangle(Name);
  if (!internal_strncmp(DName, TIPrefix, TIPrefixLen))
    DName += TIPrefixLen;

  return DName;
}

static void printTDName(tysan_type_descriptor *td) {
  if (((sptr)td) <= 0) {
    Printf("<unknown type>");
    return;
  }

  switch (td->Tag) {
  default:
    CHECK(false && "invalid enum value");
    break;
  case TYSAN_MEMBER_TD:
    printTDName(td->Member.Access);
    if (td->Member.Access != td->Member.Base) {
      Printf(" (in ");
      printTDName(td->Member.Base);
      Printf(" at offset %zu)", td->Member.Offset);
    }
    break;
  case TYSAN_STRUCT_TD:
    Printf("%s", getDisplayName(
                     (char *)(td->Struct.Members + td->Struct.MemberCount)));
    break;
  }
}

static tysan_type_descriptor *getRootTD(tysan_type_descriptor *TD) {
  tysan_type_descriptor *RootTD = TD;

  do {
    RootTD = TD;

    if (TD->Tag == TYSAN_STRUCT_TD) {
      if (TD->Struct.MemberCount > 0)
        TD = TD->Struct.Members[0].Type;
      else
        TD = nullptr;
    } else if (TD->Tag == TYSAN_MEMBER_TD) {
      TD = TD->Member.Access;
    } else {
      CHECK(false && "invalid enum value");
      break;
    }
  } while (TD);

  return RootTD;
}

static bool isAliasingLegalUp(tysan_type_descriptor *TDA,
                              tysan_type_descriptor *TDB, int TDAOffset) {
  // Walk up the tree starting with TDA to see if we reach TDB.
  uptr OffsetA = 0, OffsetB = 0;
  if (TDB->Tag == TYSAN_MEMBER_TD) {
    OffsetB = TDB->Member.Offset;
    TDB = TDB->Member.Base;
  }

  if (TDA->Tag == TYSAN_MEMBER_TD) {
    OffsetA = TDA->Member.Offset - TDAOffset;
    TDA = TDA->Member.Base;
  }

  do {
    if (TDA == TDB)
      return OffsetA == OffsetB;

    if (TDA->Tag == TYSAN_STRUCT_TD) {
      // Reached root type descriptor.
      if (!TDA->Struct.MemberCount)
        break;

      uptr Idx = 0;
      for (; Idx < TDA->Struct.MemberCount - 1; ++Idx) {
        if (TDA->Struct.Members[Idx].Offset >= OffsetA)
          break;
      }

      // This offset can't be negative. Therefore we must be accessing something
      // before the current type (not legal) or partially inside the last type.
      // In the latter case, we adjust Idx.
      if (TDA->Struct.Members[Idx].Offset > OffsetA) {
        // Trying to access something before the current type.
        if (!Idx)
          return false;

        Idx -= 1;
      }

      OffsetA -= TDA->Struct.Members[Idx].Offset;
      TDA = TDA->Struct.Members[Idx].Type;
    } else {
      CHECK(false && "invalid enum value");
      break;
    }
  } while (TDA);

  return false;
}

static bool isAliasingLegal(tysan_type_descriptor *TDA,
                            tysan_type_descriptor *TDB, int TDAOffset = 0) {
  if (TDA == TDB || !TDB || !TDA)
    return true;

  // Aliasing is legal is the two types have different root nodes.
  if (getRootTD(TDA) != getRootTD(TDB))
    return true;

  // TDB may have been adjusted by offset TDAOffset in the caller to point to
  // the outer type. Check for aliasing with and without adjusting for this
  // offset.
  return isAliasingLegalUp(TDA, TDB, 0) || isAliasingLegalUp(TDB, TDA, 0) ||
         isAliasingLegalUp(TDA, TDB, TDAOffset);
}

namespace __tysan {
class Decorator : public __sanitizer::SanitizerCommonDecorator {
public:
  Decorator() : SanitizerCommonDecorator() {}
  const char *Warning() { return Red(); }
  const char *Name() { return Green(); }
  const char *End() { return Default(); }
};
} // namespace __tysan

ALWAYS_INLINE
static void reportError(void *Addr, int Size, tysan_type_descriptor *TD,
                        tysan_type_descriptor *OldTD, const char *AccessStr,
                        const char *DescStr, int Offset, uptr pc, uptr bp,
                        uptr sp) {
  Decorator d;
  Printf("%s", d.Warning());
  Report("ERROR: TypeSanitizer: type-aliasing-violation on address %p"
         " (pc %p bp %p sp %p tid %llu)\n",
         Addr, (void *)pc, (void *)bp, (void *)sp, GetTid());
  Printf("%s", d.End());
  Printf("%s of size %d at %p with type ", AccessStr, Size, Addr);

  Printf("%s", d.Name());
  printTDName(TD);
  Printf("%s", d.End());

  Printf(" %s of type ", DescStr);

  Printf("%s", d.Name());
  printTDName(OldTD);
  Printf("%s", d.End());

  if (Offset != 0)
    Printf(" that starts at offset %d\n", Offset);
  else
    Printf("\n");

  if (pc) {
    uptr top = 0;
    uptr bottom = 0;
    if (flags().print_stacktrace)
      GetThreadStackTopAndBottom(false, &top, &bottom);

    bool request_fast = StackTrace::WillUseFastUnwind(true);
    BufferedStackTrace ST;
    ST.Unwind(kStackTraceMax, pc, bp, 0, top, bottom, request_fast);
    ST.Print();
  } else {
    Printf("\n");
  }
}

extern "C" SANITIZER_INTERFACE_ATTRIBUTE void
__tysan_check(void *addr, int size, tysan_type_descriptor *td, int flags) {
  GET_CALLER_PC_BP_SP;

  bool IsRead = flags & 1;
  bool IsWrite = flags & 2;
  const char *AccessStr;
  if (IsRead && !IsWrite)
    AccessStr = "READ";
  else if (!IsRead && IsWrite)
    AccessStr = "WRITE";
  else
    AccessStr = "ATOMIC UPDATE";

  tysan_type_descriptor **OldTDPtr = shadow_for(addr);
  tysan_type_descriptor *OldTD = *OldTDPtr;
  if (((sptr)OldTD) < 0) {
    int i = -((sptr)OldTD);
    OldTDPtr -= i;
    OldTD = *OldTDPtr;

    if (!isAliasingLegal(td, OldTD, i))
      reportError(addr, size, td, OldTD, AccessStr,
                  "accesses part of an existing object", -i, pc, bp, sp);

    return;
  }

  if (!isAliasingLegal(td, OldTD)) {
    reportError(addr, size, td, OldTD, AccessStr, "accesses an existing object",
                0, pc, bp, sp);
    return;
  }

  // These types are allowed to alias (or the stored type is unknown), report
  // an error if we find an interior type.

  for (int i = 0; i < size; ++i) {
    OldTDPtr = shadow_for((void *)(((uptr)addr) + i));
    OldTD = *OldTDPtr;
    if (((sptr)OldTD) >= 0 && !isAliasingLegal(td, OldTD))
      reportError(addr, size, td, OldTD, AccessStr,
                  "partially accesses an object", i, pc, bp, sp);
  }
}

Flags __tysan::flags_data;

SANITIZER_INTERFACE_ATTRIBUTE uptr __tysan_shadow_memory_address;
SANITIZER_INTERFACE_ATTRIBUTE uptr __tysan_app_memory_mask;

#ifdef TYSAN_RUNTIME_VMA
// Runtime detected VMA size.
int __tysan::vmaSize;
#endif

void Flags::SetDefaults() {
#define TYSAN_FLAG(Type, Name, DefaultValue, Description) Name = DefaultValue;
#include "tysan_flags.inc"
#undef TYSAN_FLAG
}

static void RegisterTySanFlags(FlagParser *parser, Flags *f) {
#define TYSAN_FLAG(Type, Name, DefaultValue, Description)                      \
  RegisterFlag(parser, #Name, Description, &f->Name);
#include "tysan_flags.inc"
#undef TYSAN_FLAG
}

static void InitializeFlags() {
  SetCommonFlagsDefaults();
  {
    CommonFlags cf;
    cf.CopyFrom(*common_flags());
    cf.external_symbolizer_path = GetEnv("TYSAN_SYMBOLIZER_PATH");
    OverrideCommonFlags(cf);
  }

  flags().SetDefaults();

  FlagParser parser;
  RegisterCommonFlags(&parser);
  RegisterTySanFlags(&parser, &flags());
  parser.ParseString(GetEnv("TYSAN_OPTIONS"));
  InitializeCommonFlags();
  if (Verbosity())
    ReportUnrecognizedFlags();
  if (common_flags()->help)
    parser.PrintFlagDescriptions();
}

static void TySanInitializePlatformEarly() {
  AvoidCVE_2016_2143();
#ifdef TYSAN_RUNTIME_VMA
  vmaSize = (MostSignificantSetBitIndex(GET_CURRENT_FRAME()) + 1);
#if defined(__aarch64__) && !SANITIZER_APPLE
  if (vmaSize != 39 && vmaSize != 42 && vmaSize != 48) {
    Printf("FATAL: TypeSanitizer: unsupported VMA range\n");
    Printf("FATAL: Found %d - Supported 39, 42 and 48\n", vmaSize);
    Die();
  }
#endif
#endif

  __sanitizer::InitializePlatformEarly();

  __tysan_shadow_memory_address = ShadowAddr();
  __tysan_app_memory_mask = AppMask();
}

namespace __tysan {
bool tysan_inited = false;
bool tysan_init_is_running;
} // namespace __tysan

extern "C" SANITIZER_INTERFACE_ATTRIBUTE void __tysan_init() {
  CHECK(!tysan_init_is_running);
  if (tysan_inited)
    return;
  tysan_init_is_running = true;

  InitializeFlags();
  TySanInitializePlatformEarly();

  InitializeInterceptors();

  if (!MmapFixedNoReserve(ShadowAddr(), AppAddr() - ShadowAddr()))
    Die();

  tysan_init_is_running = false;
  tysan_inited = true;
}

#if SANITIZER_CAN_USE_PREINIT_ARRAY
__attribute__((section(".preinit_array"),
               used)) static void (*tysan_init_ptr)() = __tysan_init;
#endif
