/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "frontend/FrontendContext.h"

#ifdef _WIN32
#  include "util/WindowsWrapper.h"
#  include <process.h>  // GetCurrentThreadId
#else
#  include <pthread.h>  // pthread_self
#endif

#include "gc/GC.h"
#include "js/AllocPolicy.h"  // js::ReportOutOfMemory
#include "js/experimental/CompileScript.h"
#include "js/friend/StackLimits.h"  // js::ReportOverRecursed, js::MinimumStackLimitMargin, js::StackLimitMargin
#include "js/Modules.h"
#include "util/DifferentialTesting.h"
#include "util/NativeStack.h"  // GetNativeStackBase
#include "vm/JSContext.h"

using namespace js;

void FrontendErrors::clearErrors() {
  error.reset();
  warnings.clear();
  overRecursed = false;
  outOfMemory = false;
  allocationOverflow = false;
}

void FrontendErrors::clearWarnings() { warnings.clear(); }

void FrontendAllocator::reportAllocationOverflow() {
  fc_->onAllocationOverflow();
}

void* FrontendAllocator::onOutOfMemory(AllocFunction allocFunc,
                                       arena_id_t arena, size_t nbytes,
                                       void* reallocPtr) {
  return fc_->onOutOfMemory(allocFunc, arena, nbytes, reallocPtr);
}

FrontendContext::~FrontendContext() {
  if (ownNameCollectionPool_) {
    MOZ_ASSERT(nameCollectionPool_);
    js_delete(nameCollectionPool_);
  }
}

void FrontendContext::setStackQuota(JS::NativeStackSize stackSize) {
#ifdef __wasi__
  stackLimit_ = JS::WASINativeStackLimit;
#else   // __wasi__
  if (stackSize == 0) {
    stackLimit_ = JS::NativeStackLimitMax;
  } else {
    stackLimit_ = JS::GetNativeStackLimit(GetNativeStackBase(), stackSize - 1);
  }
#endif  // !__wasi__

#ifdef DEBUG
  setNativeStackLimitThread();
#endif
}

JS_PUBLIC_API void JS::SetNativeStackQuota(JS::FrontendContext* fc,
                                           JS::NativeStackSize stackSize) {
  fc->setStackQuota(stackSize);
}

JS_PUBLIC_API JS::NativeStackSize JS::ThreadStackQuotaForSize(
    size_t stackSize) {
  // Set the stack quota to 10% less that the actual size.
  static constexpr double RatioWithoutMargin = 0.9;

  MOZ_ASSERT(double(stackSize) * (1 - RatioWithoutMargin) >
             js::MinimumStackLimitMargin);

  return JS::NativeStackSize(double(stackSize) * RatioWithoutMargin);
}

bool FrontendContext::allocateOwnedPool() {
  MOZ_ASSERT(!nameCollectionPool_);

  nameCollectionPool_ = js_new<frontend::NameCollectionPool>();
  if (!nameCollectionPool_) {
    return false;
  }
  ownNameCollectionPool_ = true;
  return true;
}

bool FrontendContext::hadErrors() const {
  // All errors must be reported to FrontendContext.
  MOZ_ASSERT_IF(maybeCx_, !maybeCx_->isExceptionPending());

  return errors_.hadErrors();
}

JS_PUBLIC_API bool JS::HadFrontendErrors(JS::FrontendContext* fc) {
  return fc->hadErrors();
}

void FrontendContext::clearErrors() {
  MOZ_ASSERT(!maybeCx_);
  return errors_.clearErrors();
}

JS_PUBLIC_API void JS::ClearFrontendErrors(JS::FrontendContext* fc) {
  fc->clearErrors();
}

void FrontendContext::clearWarnings() { return errors_.clearWarnings(); }

void* FrontendContext::onOutOfMemory(AllocFunction allocFunc, arena_id_t arena,
                                     size_t nbytes, void* reallocPtr) {
  addPendingOutOfMemory();
  return nullptr;
}

void FrontendContext::onAllocationOverflow() {
  errors_.allocationOverflow = true;
}

void FrontendContext::onOutOfMemory() { addPendingOutOfMemory(); }

void FrontendContext::onOverRecursed() { errors_.overRecursed = true; }

void FrontendContext::recoverFromOutOfMemory() {
  MOZ_ASSERT_IF(maybeCx_, !maybeCx_->isThrowingOutOfMemory());

  errors_.outOfMemory = false;
}

const JSErrorFormatString* FrontendContext::gcSafeCallback(
    JSErrorCallback callback, void* userRef, const unsigned errorNumber) {
  mozilla::Maybe<gc::AutoSuppressGC> suppressGC;
  if (maybeCx_) {
    suppressGC.emplace(maybeCx_);
  }

  return callback(userRef, errorNumber);
}

void FrontendContext::reportError(CompileError&& err) {
  if (errors_.error) {
    errors_.error.reset();
  }

  // When compiling off thread, save the error so that the thread finishing the
  // parse can report it later.
  errors_.error.emplace(std::move(err));
}

bool FrontendContext::reportWarning(CompileError&& err) {
  if (!errors_.warnings.append(std::move(err))) {
    ReportOutOfMemory();
    return false;
  }

  return true;
}

void FrontendContext::ReportOutOfMemory() {
  /*
   * OOMs are non-deterministic, especially across different execution modes
   * (e.g. interpreter vs JIT). When doing differential testing, print to
   * stderr so that the fuzzers can detect this.
   */
  if (SupportDifferentialTesting()) {
    fprintf(stderr, "ReportOutOfMemory called\n");
  }

  addPendingOutOfMemory();
}

void FrontendContext::addPendingOutOfMemory() { errors_.outOfMemory = true; }

void FrontendContext::setCurrentJSContext(JSContext* cx) {
  MOZ_ASSERT(!nameCollectionPool_);

  maybeCx_ = cx;
  nameCollectionPool_ = &cx->frontendCollectionPool();
  scriptDataTableHolder_ = &cx->runtime()->scriptDataTableHolder();
  stackLimit_ = cx->stackLimitForCurrentPrincipal();

#ifdef DEBUG
  setNativeStackLimitThread();
#endif
}

bool FrontendContext::convertToRuntimeError(
    JSContext* cx, Warning warning /* = Warning::Report */) {
  // Report out of memory errors eagerly, or errors could be malformed.
  if (hadOutOfMemory()) {
    js::ReportOutOfMemory(cx);
    return false;
  }

  if (maybeError()) {
    if (!maybeError()->throwError(cx)) {
      return false;
    }
  }
  if (warning == Warning::Report) {
    for (CompileError& error : warnings()) {
      if (!error.throwError(cx)) {
        return false;
      }
    }
  }
  if (hadOverRecursed()) {
    js::ReportOverRecursed(cx);
  }
  if (hadAllocationOverflow()) {
    js::ReportAllocationOverflow(cx);
  }

  MOZ_ASSERT(!extraBindingsAreNotUsed(),
             "extraBindingsAreNotUsed shouldn't escape from FrontendContext");
  return true;
}

JS_PUBLIC_API bool JS::ConvertFrontendErrorsToRuntimeErrors(
    JSContext* cx, JS::FrontendContext* fc,
    const JS::ReadOnlyCompileOptions& options) {
  return fc->convertToRuntimeError(cx);
}

#ifdef DEBUG
static size_t GetTid() {
#  if defined(_WIN32)
  return size_t(GetCurrentThreadId());
#  elif defined(__wasm__)
  return 1;
#  else
  return size_t(pthread_self());
#  endif
}

void FrontendContext::setNativeStackLimitThread() {
  stackLimitThreadId_.emplace(GetTid());
}

void FrontendContext::assertNativeStackLimitThread() {
  if (!stackLimitThreadId_.isSome()) {
    return;
  }

  MOZ_ASSERT(*stackLimitThreadId_ == GetTid());
}
#endif

#ifdef __wasi__
void FrontendContext::incWasiRecursionDepth() {
  if (maybeCx_) {
    IncWasiRecursionDepth(maybeCx_);
  }
}

void FrontendContext::decWasiRecursionDepth() {
  if (maybeCx_) {
    DecWasiRecursionDepth(maybeCx_);
  }
}

bool FrontendContext::checkWasiRecursionLimit() {
  if (maybeCx_) {
    return CheckWasiRecursionLimit(maybeCx_);
  }
  return true;
}

JS_PUBLIC_API void js::IncWasiRecursionDepth(FrontendContext* fc) {
  fc->incWasiRecursionDepth();
}

JS_PUBLIC_API void js::DecWasiRecursionDepth(FrontendContext* fc) {
  fc->decWasiRecursionDepth();
}

JS_PUBLIC_API bool js::CheckWasiRecursionLimit(FrontendContext* fc) {
  return fc->checkWasiRecursionLimit();
}
#endif  // __wasi__

FrontendContext* js::NewFrontendContext() {
  UniquePtr<FrontendContext> fc = MakeUnique<FrontendContext>();
  if (!fc) {
    return nullptr;
  }

  if (!fc->allocateOwnedPool()) {
    return nullptr;
  }

  return fc.release();
}

JS_PUBLIC_API FrontendContext* JS::NewFrontendContext() {
  MOZ_ASSERT(JS::detail::libraryInitState == JS::detail::InitState::Running,
             "must call JS_Init prior to creating any FrontendContexts");

  return js::NewFrontendContext();
}

void js::DestroyFrontendContext(FrontendContext* fc) { js_delete_poison(fc); }

JS_PUBLIC_API void JS::DestroyFrontendContext(FrontendContext* fc) {
  return js::DestroyFrontendContext(fc);
}

#ifdef DEBUG
void FrontendContext::checkAndUpdateFrontendContextRecursionLimit(void* sp) {
  // For the js::MinimumStackLimitMargin to be effective, it should be larger
  // than the largest stack space which might be consumed by successive calls
  // to AutoCheckRecursionLimit::check.
  //
  // This function asserts that this property holds by recalling the stack
  // pointer of the previous call and comparing the consumed stack size with
  // the minimum margin.
  //
  // If this property does not hold, either the stack limit should be increased
  // or more calls to check for recursion should be added.
  if (previousStackPointer_ != nullptr) {
#  if JS_STACK_GROWTH_DIRECTION > 0
    if (sp > previousStackPointer_) {
      size_t diff = uintptr_t(sp) - uintptr_t(previousStackPointer_);
      MOZ_ASSERT(diff < js::MinimumStackLimitMargin);
    }
#  else
    if (sp < previousStackPointer_) {
      size_t diff = uintptr_t(previousStackPointer_) - uintptr_t(sp);
      MOZ_ASSERT(diff < js::MinimumStackLimitMargin);
    }
#  endif
  }
  previousStackPointer_ = sp;
}

void js::CheckAndUpdateFrontendContextRecursionLimit(FrontendContext* fc,
                                                     void* sp) {
  fc->checkAndUpdateFrontendContextRecursionLimit(sp);
}
#endif

JS_PUBLIC_API const JSErrorReport* JS::GetFrontendErrorReport(
    JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options) {
  if (!fc->maybeError().isSome()) {
    return nullptr;
  }
  return fc->maybeError().ptr();
}

JS_PUBLIC_API bool JS::HadFrontendOverRecursed(JS::FrontendContext* fc) {
  return fc->hadOverRecursed();
}

JS_PUBLIC_API bool JS::HadFrontendOutOfMemory(JS::FrontendContext* fc) {
  return fc->hadOutOfMemory();
}

JS_PUBLIC_API bool JS::HadFrontendAllocationOverflow(JS::FrontendContext* fc) {
  return fc->hadAllocationOverflow();
}

JS_PUBLIC_API size_t JS::GetFrontendWarningCount(JS::FrontendContext* fc) {
  return fc->warnings().length();
}

JS_PUBLIC_API const JSErrorReport* JS::GetFrontendWarningAt(
    JS::FrontendContext* fc, size_t index,
    const JS::ReadOnlyCompileOptions& options) {
  return &fc->warnings()[index];
}
