/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * 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/. */

/*
 * JS execution context.
 */

#include "vm/JSContext-inl.h"

#include "mozilla/ArrayUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Sprintf.h"
#include "mozilla/Unused.h"

#include <ctype.h>
#include <stdarg.h>
#include <string.h>
#ifdef ANDROID
# include <android/log.h>
# include <fstream>
# include <string>
#endif // ANDROID
#ifdef XP_WIN
# include <processthreadsapi.h>
#endif // XP_WIN

#include "jsexn.h"
#include "jspubtd.h"
#include "jstypes.h"

#include "builtin/String.h"
#include "gc/FreeOp.h"
#include "gc/Marking.h"
#include "jit/Ion.h"
#include "jit/PcScriptCache.h"
#include "js/CharacterEncoding.h"
#include "js/Printf.h"
#include "util/DoubleToString.h"
#include "util/NativeStack.h"
#include "util/Windows.h"
#include "vm/BytecodeUtil.h"
#include "vm/ErrorReporting.h"
#include "vm/HelperThreads.h"
#include "vm/Iteration.h"
#include "vm/JSAtom.h"
#include "vm/JSFunction.h"
#include "vm/JSObject.h"
#include "vm/JSScript.h"
#include "vm/Realm.h"
#include "vm/Shape.h"

#include "vm/Compartment-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/Stack-inl.h"

using namespace js;
using namespace js::gc;

using mozilla::DebugOnly;
using mozilla::PodArrayZero;

bool
js::AutoCycleDetector::init()
{
    MOZ_ASSERT(cyclic);

    AutoCycleDetector::Vector& vector = cx->cycleDetectorVector();

    for (JSObject* obj2 : vector) {
        if (MOZ_UNLIKELY(obj == obj2))
            return true;
    }

    if (!vector.append(obj))
        return false;

    cyclic = false;
    return true;
}

js::AutoCycleDetector::~AutoCycleDetector()
{
    if (MOZ_LIKELY(!cyclic)) {
        AutoCycleDetector::Vector& vec = cx->cycleDetectorVector();
        MOZ_ASSERT(vec.back() == obj);
        if (vec.length() > 1) {
            vec.popBack();
        } else {
            // Avoid holding on to unused heap allocations.
            vec.clearAndFree();
        }
    }
}

bool
JSContext::init(ContextKind kind)
{
    // Skip most of the initialization if this thread will not be running JS.
    if (kind == ContextKind::MainThread) {
        if (!regexpStack.ref().init())
            return false;

        if (!fx.initInstance())
            return false;

#ifdef JS_SIMULATOR
        simulator_ = jit::Simulator::Create(this);
        if (!simulator_)
            return false;
#endif

        if (!wasm::EnsureSignalHandlers(this))
            return false;
    }

    // Set the ContextKind last, so that ProtectedData checks will allow us to
    // initialize this context before it becomes the runtime's active context.
    kind_ = kind;

    return true;
}

JSContext*
js::NewContext(uint32_t maxBytes, uint32_t maxNurseryBytes, JSRuntime* parentRuntime)
{
    AutoNoteSingleThreadedRegion anstr;

    MOZ_RELEASE_ASSERT(!TlsContext.get());

#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
    js::oom::SetThreadType(!parentRuntime ? js::THREAD_TYPE_MAIN
                                          : js::THREAD_TYPE_WORKER);
#endif

    JSRuntime* runtime = js_new<JSRuntime>(parentRuntime);
    if (!runtime)
        return nullptr;

    JSContext* cx = js_new<JSContext>(runtime, JS::ContextOptions());
    if (!cx) {
        js_delete(runtime);
        return nullptr;
    }

    if (!runtime->init(cx, maxBytes, maxNurseryBytes)) {
        runtime->destroyRuntime();
        js_delete(cx);
        js_delete(runtime);
        return nullptr;
    }

    if (!cx->init(ContextKind::MainThread)) {
        runtime->destroyRuntime();
        js_delete(cx);
        js_delete(runtime);
        return nullptr;
    }

    return cx;
}

static void
FreeJobQueueHandling(JSContext* cx)
{
    if (!cx->jobQueue)
        return;

    cx->jobQueue->reset();
    FreeOp* fop = cx->defaultFreeOp();
    fop->delete_(cx->jobQueue.ref());
    cx->getIncumbentGlobalCallback = nullptr;
    cx->enqueuePromiseJobCallback = nullptr;
    cx->enqueuePromiseJobCallbackData = nullptr;
}

void
js::DestroyContext(JSContext* cx)
{
    JS_AbortIfWrongThread(cx);

    if (cx->outstandingRequests != 0)
        MOZ_CRASH("Attempted to destroy a context while it is in a request.");

    cx->checkNoGCRooters();

    // Cancel all off thread Ion compiles. Completed Ion compiles may try to
    // interrupt this context. See HelperThread::handleIonWorkload.
    CancelOffThreadIonCompile(cx->runtime());

    FreeJobQueueHandling(cx);

    // Flush promise tasks executing in helper threads early, before any parts
    // of the JSRuntime that might be visible to helper threads are torn down.
    cx->runtime()->offThreadPromiseState.ref().shutdown(cx);

    // Destroy the runtime along with its last context.
    cx->runtime()->destroyRuntime();
    js_delete(cx->runtime());
    js_delete_poison(cx);
}

void
JS::RootingContext::checkNoGCRooters() {
#ifdef DEBUG
    for (auto const& stackRootPtr : stackRoots_)
        MOZ_ASSERT(stackRootPtr == nullptr);
#endif
}

bool
AutoResolving::alreadyStartedSlow() const
{
    MOZ_ASSERT(link);
    AutoResolving* cursor = link;
    do {
        MOZ_ASSERT(this != cursor);
        if (object.get() == cursor->object && id.get() == cursor->id && kind == cursor->kind)
            return true;
    } while (!!(cursor = cursor->link));
    return false;
}

static void
ReportError(JSContext* cx, JSErrorReport* reportp, JSErrorCallback callback,
            void* userRef)
{
    /*
     * Check the error report, and set a JavaScript-catchable exception
     * if the error is defined to have an associated exception.  If an
     * exception is thrown, then the JSREPORT_EXCEPTION flag will be set
     * on the error report, and exception-aware hosts should ignore it.
     */
    MOZ_ASSERT(reportp);
    if ((!callback || callback == GetErrorMessage) &&
        reportp->errorNumber == JSMSG_UNCAUGHT_EXCEPTION)
    {
        reportp->flags |= JSREPORT_EXCEPTION;
    }

    if (JSREPORT_IS_WARNING(reportp->flags)) {
        CallWarningReporter(cx, reportp);
        return;
    }

    ErrorToException(cx, reportp, callback, userRef);
}

/*
 * The given JSErrorReport object have been zeroed and must not outlive
 * cx->fp() (otherwise owned fields may become invalid).
 */
static void
PopulateReportBlame(JSContext* cx, JSErrorReport* report)
{
    JS::Realm* realm = cx->realm();
    if (!realm)
        return;

    /*
     * Walk stack until we find a frame that is associated with a non-builtin
     * rather than a builtin frame and which we're allowed to know about.
     */
    NonBuiltinFrameIter iter(cx, realm->principals());
    if (iter.done())
        return;

    report->filename = iter.filename();
    uint32_t column;
    report->lineno = iter.computeLine(&column);
    report->column = FixupColumnForDisplay(column);
    report->isMuted = iter.mutedErrors();
}

/*
 * Since memory has been exhausted, avoid the normal error-handling path which
 * allocates an error object, report and callstack. If code is running, simply
 * throw the static atom "out of memory". If code is not running, call the
 * error reporter directly.
 *
 * Furthermore, callers of ReportOutOfMemory (viz., malloc) assume a GC does
 * not occur, so GC must be avoided or suppressed.
 */
JS_FRIEND_API(void)
js::ReportOutOfMemory(JSContext* cx)
{
#ifdef JS_MORE_DETERMINISTIC
    /*
     * OOMs are non-deterministic, especially across different execution modes
     * (e.g. interpreter vs JIT). In more-deterministic builds, print to stderr
     * so that the fuzzers can detect this.
     */
    fprintf(stderr, "ReportOutOfMemory called\n");
#endif

    if (cx->helperThread())
        return cx->addPendingOutOfMemory();

    cx->runtime()->hadOutOfMemory = true;
    AutoSuppressGC suppressGC(cx);

    /* Report the oom. */
    if (JS::OutOfMemoryCallback oomCallback = cx->runtime()->oomCallback)
        oomCallback(cx, cx->runtime()->oomCallbackData);

    cx->setPendingException(StringValue(cx->names().outOfMemory));
}

mozilla::GenericErrorResult<OOM&>
js::ReportOutOfMemoryResult(JSContext* cx)
{
    ReportOutOfMemory(cx);
    return cx->alreadyReportedOOM();
}

void
js::ReportOverRecursed(JSContext* maybecx, unsigned errorNumber)
{
#ifdef JS_MORE_DETERMINISTIC
    /*
     * We cannot make stack depth deterministic across different
     * implementations (e.g. JIT vs. interpreter will differ in
     * their maximum stack depth).
     * However, we can detect externally when we hit the maximum
     * stack depth which is useful for external testing programs
     * like fuzzers.
     */
    fprintf(stderr, "ReportOverRecursed called\n");
#endif
    if (maybecx) {
        if (!maybecx->helperThread()) {
            JS_ReportErrorNumberASCII(maybecx, GetErrorMessage, nullptr, errorNumber);
            maybecx->overRecursed_ = true;
        } else {
            maybecx->addPendingOverRecursed();
        }
    }
}

JS_FRIEND_API(void)
js::ReportOverRecursed(JSContext* maybecx)
{
    ReportOverRecursed(maybecx, JSMSG_OVER_RECURSED);
}

void
js::ReportAllocationOverflow(JSContext* cx)
{
    if (!cx)
        return;

    if (cx->helperThread())
        return;

    AutoSuppressGC suppressGC(cx);
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ALLOC_OVERFLOW);
}

/*
 * Given flags and the state of cx, decide whether we should report an
 * error, a warning, or just continue execution normally.  Return
 * true if we should continue normally, without reporting anything;
 * otherwise, adjust *flags as appropriate and return false.
 */
static bool
checkReportFlags(JSContext* cx, unsigned* flags)
{
    if (JSREPORT_IS_STRICT(*flags)) {
        /* Warning/error only when JSOPTION_STRICT is set. */
        if (!cx->realm()->behaviors().extraWarnings(cx))
            return true;
    }

    /* Warnings become errors when JSOPTION_WERROR is set. */
    if (JSREPORT_IS_WARNING(*flags) && cx->options().werror())
        *flags &= ~JSREPORT_WARNING;

    return false;
}

bool
js::ReportErrorVA(JSContext* cx, unsigned flags, const char* format,
                  ErrorArgumentsType argumentsType, va_list ap)
{
    JSErrorReport report;

    if (checkReportFlags(cx, &flags))
        return true;

    UniqueChars message(JS_vsmprintf(format, ap));
    if (!message) {
        ReportOutOfMemory(cx);
        return false;
    }

    MOZ_ASSERT_IF(argumentsType == ArgumentsAreASCII, JS::StringIsASCII(message.get()));

    report.flags = flags;
    report.errorNumber = JSMSG_USER_DEFINED_ERROR;
    if (argumentsType == ArgumentsAreASCII || argumentsType == ArgumentsAreUTF8) {
        report.initOwnedMessage(message.release());
    } else {
        MOZ_ASSERT(argumentsType == ArgumentsAreLatin1);
        Latin1Chars latin1(message.get(), strlen(message.get()));
        UTF8CharsZ utf8(JS::CharsToNewUTF8CharsZ(cx, latin1));
        if (!utf8)
            return false;
        report.initOwnedMessage(reinterpret_cast<const char*>(utf8.get()));
    }
    PopulateReportBlame(cx, &report);

    bool warning = JSREPORT_IS_WARNING(report.flags);

    ReportError(cx, &report, nullptr, nullptr);
    return warning;
}

/* |callee| requires a usage string provided by JS_DefineFunctionsWithHelp. */
void
js::ReportUsageErrorASCII(JSContext* cx, HandleObject callee, const char* msg)
{
    RootedValue usage(cx);
    if (!JS_GetProperty(cx, callee, "usage", &usage))
        return;

    if (!usage.isString()) {
        JS_ReportErrorASCII(cx, "%s", msg);
    } else {
        RootedString usageStr(cx, usage.toString());
        JSAutoByteString str;
        if (!str.encodeUtf8(cx, usageStr))
            return;
        JS_ReportErrorUTF8(cx, "%s. Usage: %s", msg, str.ptr());
    }
}

enum class PrintErrorKind {
    Error,
    Warning,
    StrictWarning,
    Note
};

static void
PrintErrorLine(FILE* file, const char* prefix, JSErrorReport* report)
{
    if (const char16_t* linebuf = report->linebuf()) {
        size_t n = report->linebufLength();

        fputs(":\n", file);
        if (prefix)
            fputs(prefix, file);

        for (size_t i = 0; i < n; i++)
            fputc(static_cast<char>(linebuf[i]), file);

        // linebuf usually ends with a newline. If not, add one here.
        if (n == 0 || linebuf[n-1] != '\n')
            fputc('\n', file);

        if (prefix)
            fputs(prefix, file);

        n = report->tokenOffset();
        for (size_t i = 0, j = 0; i < n; i++) {
            if (linebuf[i] == '\t') {
                for (size_t k = (j + 8) & ~7; j < k; j++)
                    fputc('.', file);
                continue;
            }
            fputc('.', file);
            j++;
        }
        fputc('^', file);
    }
}

static void
PrintErrorLine(FILE* file, const char* prefix, JSErrorNotes::Note* note)
{
}

template <typename T>
static bool
PrintSingleError(JSContext* cx, FILE* file, JS::ConstUTF8CharsZ toStringResult,
                 T* report, PrintErrorKind kind)
{
    UniqueChars prefix;
    if (report->filename)
        prefix = JS_smprintf("%s:", report->filename);

    if (report->lineno) {
        prefix = JS_smprintf("%s%u:%u ", prefix ? prefix.get() : "", report->lineno,
                                        report->column);
    }

    if (kind != PrintErrorKind::Error) {
        const char* kindPrefix = nullptr;
        switch (kind) {
          case PrintErrorKind::Error:
            MOZ_CRASH("unreachable");
          case PrintErrorKind::Warning:
            kindPrefix = "warning";
            break;
          case PrintErrorKind::StrictWarning:
            kindPrefix = "strict warning";
            break;
          case PrintErrorKind::Note:
            kindPrefix = "note";
            break;
        }

        prefix = JS_smprintf("%s%s: ", prefix ? prefix.get() : "", kindPrefix);
    }

    const char* message = toStringResult ? toStringResult.c_str() : report->message().c_str();

    /* embedded newlines -- argh! */
    const char* ctmp;
    while ((ctmp = strchr(message, '\n')) != 0) {
        ctmp++;
        if (prefix)
            fputs(prefix.get(), file);
        mozilla::Unused << fwrite(message, 1, ctmp - message, file);
        message = ctmp;
    }

    /* If there were no filename or lineno, the prefix might be empty */
    if (prefix)
        fputs(prefix.get(), file);
    fputs(message, file);

    PrintErrorLine(file, prefix.get(), report);
    fputc('\n', file);

    fflush(file);
    return true;
}

bool
js::PrintError(JSContext* cx, FILE* file, JS::ConstUTF8CharsZ toStringResult,
               JSErrorReport* report, bool reportWarnings)
{
    MOZ_ASSERT(report);

    /* Conditionally ignore reported warnings. */
    if (JSREPORT_IS_WARNING(report->flags) && !reportWarnings)
        return false;

    PrintErrorKind kind = PrintErrorKind::Error;
    if (JSREPORT_IS_WARNING(report->flags)) {
        if (JSREPORT_IS_STRICT(report->flags))
            kind = PrintErrorKind::StrictWarning;
        else
            kind = PrintErrorKind::Warning;
    }
    PrintSingleError(cx, file, toStringResult, report, kind);

    if (report->notes) {
        for (auto&& note : *report->notes)
            PrintSingleError(cx, file, JS::ConstUTF8CharsZ(), note.get(), PrintErrorKind::Note);
    }

    return true;
}

class MOZ_RAII AutoMessageArgs
{
    size_t totalLength_;
    /* only {0} thru {9} supported */
    mozilla::Array<const char*, JS::MaxNumErrorArguments> args_;
    mozilla::Array<size_t, JS::MaxNumErrorArguments> lengths_;
    uint16_t count_;
    bool allocatedElements_ : 1;

  public:
    AutoMessageArgs()
      : totalLength_(0), count_(0), allocatedElements_(false)
    {
        PodArrayZero(args_);
    }

    ~AutoMessageArgs()
    {
        /* free the arguments only if we allocated them */
        if (allocatedElements_) {
            uint16_t i = 0;
            while (i < count_) {
                if (args_[i])
                    js_free((void*)args_[i]);
                i++;
            }
        }
    }

    const char* args(size_t i) const {
        MOZ_ASSERT(i < count_);
        return args_[i];
    }

    size_t totalLength() const {
        return totalLength_;
    }

    size_t lengths(size_t i) const {
        MOZ_ASSERT(i < count_);
        return lengths_[i];
    }

    uint16_t count() const {
        return count_;
    }

    /* Gather the arguments into an array, and accumulate their sizes. */
    bool init(JSContext* cx, const char16_t** argsArg, uint16_t countArg,
              ErrorArgumentsType typeArg, va_list ap) {
        MOZ_ASSERT(countArg > 0);

        count_ = countArg;

        for (uint16_t i = 0; i < count_; i++) {
            switch (typeArg) {
              case ArgumentsAreASCII:
              case ArgumentsAreUTF8: {
                MOZ_ASSERT(!argsArg);
                args_[i] = va_arg(ap, char*);
                MOZ_ASSERT_IF(typeArg == ArgumentsAreASCII, JS::StringIsASCII(args_[i]));
                lengths_[i] = strlen(args_[i]);
                break;
              }
              case ArgumentsAreLatin1: {
                MOZ_ASSERT(!argsArg);
                const Latin1Char* latin1 = va_arg(ap, Latin1Char*);
                size_t len = strlen(reinterpret_cast<const char*>(latin1));
                mozilla::Range<const Latin1Char> range(latin1, len);
                char* utf8 = JS::CharsToNewUTF8CharsZ(cx, range).c_str();
                if (!utf8)
                    return false;

                args_[i] = utf8;
                lengths_[i] = strlen(utf8);
                allocatedElements_ = true;
                break;
              }
              case ArgumentsAreUnicode: {
                const char16_t* uc = argsArg ? argsArg[i] : va_arg(ap, char16_t*);
                size_t len = js_strlen(uc);
                mozilla::Range<const char16_t> range(uc, len);
                char* utf8 = JS::CharsToNewUTF8CharsZ(cx, range).c_str();
                if (!utf8)
                    return false;

                args_[i] = utf8;
                lengths_[i] = strlen(utf8);
                allocatedElements_ = true;
                break;
              }
            }
            totalLength_ += lengths_[i];
        }
        return true;
    }
};

static void
SetExnType(JSErrorReport* reportp, int16_t exnType)
{
    reportp->exnType = exnType;
}

static void
SetExnType(JSErrorNotes::Note* notep, int16_t exnType)
{
    // Do nothing for JSErrorNotes::Note.
}

/*
 * The arguments from ap need to be packaged up into an array and stored
 * into the report struct.
 *
 * The format string addressed by the error number may contain operands
 * identified by the format {N}, where N is a decimal digit. Each of these
 * is to be replaced by the Nth argument from the va_list. The complete
 * message is placed into reportp->message_.
 *
 * Returns true if the expansion succeeds (can fail if out of memory).
 */
template <typename T>
bool
ExpandErrorArgumentsHelper(JSContext* cx, JSErrorCallback callback,
                           void* userRef, const unsigned errorNumber,
                           const char16_t** messageArgs,
                           ErrorArgumentsType argumentsType,
                           T* reportp, va_list ap)
{
    const JSErrorFormatString* efs;

    if (!callback)
        callback = GetErrorMessage;

    {
        AutoSuppressGC suppressGC(cx);
        efs = callback(userRef, errorNumber);
    }

    if (efs) {
        SetExnType(reportp, efs->exnType);

        MOZ_ASSERT_IF(argumentsType == ArgumentsAreASCII, JS::StringIsASCII(efs->format));

        uint16_t argCount = efs->argCount;
        MOZ_RELEASE_ASSERT(argCount <= JS::MaxNumErrorArguments);
        if (argCount > 0) {
            /*
             * Parse the error format, substituting the argument X
             * for {X} in the format.
             */
            if (efs->format) {
                const char* fmt;
                char* out;
#ifdef DEBUG
                int expandedArgs = 0;
#endif
                size_t expandedLength;
                size_t len = strlen(efs->format);

                AutoMessageArgs args;
                if (!args.init(cx, messageArgs, argCount, argumentsType, ap))
                    return false;

                expandedLength = len
                                 - (3 * args.count()) /* exclude the {n} */
                                 + args.totalLength();

                /*
                * Note - the above calculation assumes that each argument
                * is used once and only once in the expansion !!!
                */
                char* utf8 = out = cx->pod_malloc<char>(expandedLength + 1);
                if (!out)
                    return false;

                fmt = efs->format;
                while (*fmt) {
                    if (*fmt == '{') {
                        if (isdigit(fmt[1])) {
                            int d = JS7_UNDEC(fmt[1]);
                            MOZ_RELEASE_ASSERT(d < args.count());
                            strncpy(out, args.args(d), args.lengths(d));
                            out += args.lengths(d);
                            fmt += 3;
#ifdef DEBUG
                            expandedArgs++;
#endif
                            continue;
                        }
                    }
                    *out++ = *fmt++;
                }
                MOZ_ASSERT(expandedArgs == args.count());
                *out = 0;

                reportp->initOwnedMessage(utf8);
            }
        } else {
            /* Non-null messageArgs should have at least one non-null arg. */
            MOZ_ASSERT(!messageArgs);
            /*
             * Zero arguments: the format string (if it exists) is the
             * entire message.
             */
            if (efs->format)
                reportp->initBorrowedMessage(efs->format);
        }
    }
    if (!reportp->message()) {
        /* where's the right place for this ??? */
        const char* defaultErrorMessage
            = "No error message available for error number %d";
        size_t nbytes = strlen(defaultErrorMessage) + 16;
        char* message = cx->pod_malloc<char>(nbytes);
        if (!message)
            return false;
        snprintf(message, nbytes, defaultErrorMessage, errorNumber);
        reportp->initOwnedMessage(message);
    }
    return true;
}

bool
js::ExpandErrorArgumentsVA(JSContext* cx, JSErrorCallback callback,
                           void* userRef, const unsigned errorNumber,
                           const char16_t** messageArgs,
                           ErrorArgumentsType argumentsType,
                           JSErrorReport* reportp, va_list ap)
{
    return ExpandErrorArgumentsHelper(cx, callback, userRef, errorNumber,
                                      messageArgs, argumentsType, reportp, ap);
}

bool
js::ExpandErrorArgumentsVA(JSContext* cx, JSErrorCallback callback,
                           void* userRef, const unsigned errorNumber,
                           const char16_t** messageArgs,
                           ErrorArgumentsType argumentsType,
                           JSErrorNotes::Note* notep, va_list ap)
{
    return ExpandErrorArgumentsHelper(cx, callback, userRef, errorNumber,
                                      messageArgs, argumentsType, notep, ap);
}

bool
js::ReportErrorNumberVA(JSContext* cx, unsigned flags, JSErrorCallback callback,
                        void* userRef, const unsigned errorNumber,
                        ErrorArgumentsType argumentsType, va_list ap)
{
    JSErrorReport report;
    bool warning;

    if (checkReportFlags(cx, &flags))
        return true;
    warning = JSREPORT_IS_WARNING(flags);

    report.flags = flags;
    report.errorNumber = errorNumber;
    PopulateReportBlame(cx, &report);

    if (!ExpandErrorArgumentsVA(cx, callback, userRef, errorNumber,
                                nullptr, argumentsType, &report, ap)) {
        return false;
    }

    ReportError(cx, &report, callback, userRef);

    return warning;
}

static bool
ExpandErrorArguments(JSContext* cx, JSErrorCallback callback,
                     void* userRef, const unsigned errorNumber,
                     const char16_t** messageArgs,
                     ErrorArgumentsType argumentsType,
                     JSErrorReport* reportp, ...)
{
    va_list ap;
    va_start(ap, reportp);
    bool expanded = js::ExpandErrorArgumentsVA(cx, callback, userRef, errorNumber,
                                               messageArgs, argumentsType, reportp, ap);
    va_end(ap);
    return expanded;
}

bool
js::ReportErrorNumberUCArray(JSContext* cx, unsigned flags, JSErrorCallback callback,
                             void* userRef, const unsigned errorNumber,
                             const char16_t** args)
{
    if (checkReportFlags(cx, &flags))
        return true;
    bool warning = JSREPORT_IS_WARNING(flags);

    JSErrorReport report;
    report.flags = flags;
    report.errorNumber = errorNumber;
    PopulateReportBlame(cx, &report);

    if (!ExpandErrorArguments(cx, callback, userRef, errorNumber,
                              args, ArgumentsAreUnicode, &report))
    {
        return false;
    }

    ReportError(cx, &report, callback, userRef);

    return warning;
}

void
js::ReportIsNotDefined(JSContext* cx, HandleId id)
{
    JSAutoByteString printable;
    if (!ValueToPrintableUTF8(cx, IdToValue(id), &printable))
        return;
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_NOT_DEFINED, printable.ptr());
}

void
js::ReportIsNotDefined(JSContext* cx, HandlePropertyName name)
{
    RootedId id(cx, NameToId(name));
    ReportIsNotDefined(cx, id);
}

void
js::ReportIsNullOrUndefined(JSContext* cx, int spindex, HandleValue v)
{
    MOZ_ASSERT(v.isNullOrUndefined());

    UniqueChars bytes = DecompileValueGenerator(cx, spindex, v, nullptr);
    if (!bytes)
        return;

    if (strcmp(bytes.get(), js_undefined_str) == 0 || strcmp(bytes.get(), js_null_str) == 0) {
        JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_NO_PROPERTIES,
                                   bytes.get());
    } else if (v.isUndefined()) {
        JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
                                   bytes.get(), js_undefined_str);
    } else {
        MOZ_ASSERT(v.isNull());
        JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
                                   bytes.get(), js_null_str);
    }
}

void
js::ReportMissingArg(JSContext* cx, HandleValue v, unsigned arg)
{
    char argbuf[11];
    UniqueChars bytes;

    SprintfLiteral(argbuf, "%u", arg);
    if (IsFunctionObject(v)) {
        RootedAtom name(cx, v.toObject().as<JSFunction>().explicitName());
        bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, name);
        if (!bytes)
            return;
    }
    JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
                               JSMSG_MISSING_FUN_ARG,
                               argbuf, bytes ? bytes.get() : "");
}

bool
js::ReportValueErrorFlags(JSContext* cx, unsigned flags, const unsigned errorNumber,
                          int spindex, HandleValue v, HandleString fallback,
                          const char* arg1, const char* arg2)
{
    MOZ_ASSERT(js_ErrorFormatString[errorNumber].argCount >= 1);
    MOZ_ASSERT(js_ErrorFormatString[errorNumber].argCount <= 3);
    UniqueChars bytes = DecompileValueGenerator(cx, spindex, v, fallback);
    if (!bytes)
        return false;

    return JS_ReportErrorFlagsAndNumberLatin1(cx, flags, GetErrorMessage, nullptr, errorNumber,
                                              bytes.get(), arg1, arg2);
}

JSObject*
js::CreateErrorNotesArray(JSContext* cx, JSErrorReport* report)
{
    RootedArrayObject notesArray(cx, NewDenseEmptyArray(cx));
    if (!notesArray)
        return nullptr;

    if (!report->notes)
        return notesArray;

    for (auto&& note : *report->notes) {
        RootedPlainObject noteObj(cx, NewBuiltinClassInstance<PlainObject>(cx));
        if (!noteObj)
            return nullptr;

        RootedString messageStr(cx, note->newMessageString(cx));
        if (!messageStr)
            return nullptr;
        RootedValue messageVal(cx, StringValue(messageStr));
        if (!DefineDataProperty(cx, noteObj, cx->names().message, messageVal))
            return nullptr;

        RootedValue filenameVal(cx);
        if (note->filename) {
            RootedString filenameStr(cx, NewStringCopyZ<CanGC>(cx, note->filename));
            if (!filenameStr)
                return nullptr;
            filenameVal = StringValue(filenameStr);
        }
        if (!DefineDataProperty(cx, noteObj, cx->names().fileName, filenameVal))
            return nullptr;

        RootedValue linenoVal(cx, Int32Value(note->lineno));
        if (!DefineDataProperty(cx, noteObj, cx->names().lineNumber, linenoVal))
            return nullptr;
        RootedValue columnVal(cx, Int32Value(note->column));
        if (!DefineDataProperty(cx, noteObj, cx->names().columnNumber, columnVal))
            return nullptr;

        if (!NewbornArrayPush(cx, notesArray, ObjectValue(*noteObj)))
            return nullptr;
    }

    return notesArray;
}

const JSErrorFormatString js_ErrorFormatString[JSErr_Limit] = {
#define MSG_DEF(name, count, exception, format) \
    { #name, format, count, exception } ,
#include "js.msg"
#undef MSG_DEF
};

JS_FRIEND_API(const JSErrorFormatString*)
js::GetErrorMessage(void* userRef, const unsigned errorNumber)
{
    if (errorNumber > 0 && errorNumber < JSErr_Limit)
        return &js_ErrorFormatString[errorNumber];
    return nullptr;
}

void
JSContext::recoverFromOutOfMemory()
{
    if (helperThread()) {
        // Keep in sync with addPendingOutOfMemory.
        if (ParseTask* task = helperThread()->parseTask())
            task->outOfMemory = false;
    } else {
        if (isExceptionPending()) {
            MOZ_ASSERT(isThrowingOutOfMemory());
            clearPendingException();
        }
    }
}

static bool
InternalEnqueuePromiseJobCallback(JSContext* cx, JS::HandleObject job,
                                  JS::HandleObject allocationSite,
                                  JS::HandleObject incumbentGlobal, void* data)
{
    MOZ_ASSERT(job);
    return cx->jobQueue->append(job);
}

namespace {
class MOZ_STACK_CLASS ReportExceptionClosure : public ScriptEnvironmentPreparer::Closure
{
  public:
    explicit ReportExceptionClosure(HandleValue exn)
        : exn_(exn)
    {
    }

    bool operator()(JSContext* cx) override
    {
        cx->setPendingException(exn_);
        return false;
    }

  private:
    HandleValue exn_;
};
} // anonymous namespace

JS_FRIEND_API(bool)
js::UseInternalJobQueues(JSContext* cx)
{
    // Internal job queue handling must be set up very early. Self-hosting
    // initialization is as good a marker for that as any.
    MOZ_RELEASE_ASSERT(!cx->runtime()->hasInitializedSelfHosting(),
                       "js::UseInternalJobQueues must be called early during runtime startup.");
    MOZ_ASSERT(!cx->jobQueue);
    auto* queue = js_new<PersistentRooted<JobQueue>>(cx, JobQueue(SystemAllocPolicy()));
    if (!queue)
        return false;

    cx->jobQueue = queue;

    cx->runtime()->offThreadPromiseState.ref().initInternalDispatchQueue();
    MOZ_ASSERT(cx->runtime()->offThreadPromiseState.ref().initialized());

    JS::SetEnqueuePromiseJobCallback(cx, InternalEnqueuePromiseJobCallback);

    return true;
}

JS_FRIEND_API(bool)
js::EnqueueJob(JSContext* cx, JS::HandleObject job)
{
    MOZ_ASSERT(cx->jobQueue);
    if (!cx->jobQueue->append(job)) {
        ReportOutOfMemory(cx);
        return false;
    }

    return true;
}

JS_FRIEND_API(void)
js::StopDrainingJobQueue(JSContext* cx)
{
    MOZ_ASSERT(cx->jobQueue);
    cx->stopDrainingJobQueue = true;
}

JS_FRIEND_API(void)
js::RunJobs(JSContext* cx)
{
    MOZ_ASSERT(cx->jobQueue);

    if (cx->drainingJobQueue || cx->stopDrainingJobQueue)
        return;

    while (true) {
        cx->runtime()->offThreadPromiseState.ref().internalDrain(cx);

        // It doesn't make sense for job queue draining to be reentrant. At the
        // same time we don't want to assert against it, because that'd make
        // drainJobQueue unsafe for fuzzers. We do want fuzzers to test this,
        // so we simply ignore nested calls of drainJobQueue.
        cx->drainingJobQueue = true;

        RootedObject job(cx);
        JS::HandleValueArray args(JS::HandleValueArray::empty());
        RootedValue rval(cx);

        // Execute jobs in a loop until we've reached the end of the queue.
        // Since executing a job can trigger enqueuing of additional jobs,
        // it's crucial to re-check the queue length during each iteration.
        for (size_t i = 0; i < cx->jobQueue->length(); i++) {
            // A previous job might have set this flag. E.g., the js shell
            // sets it if the `quit` builtin function is called.
            if (cx->stopDrainingJobQueue)
                break;

            job = cx->jobQueue->get()[i];

            // It's possible that queue draining was interrupted prematurely,
            // leaving the queue partly processed. In that case, slots for
            // already-executed entries will contain nullptrs, which we should
            // just skip.
            if (!job)
                continue;

            cx->jobQueue->get()[i] = nullptr;
            AutoRealm ar(cx, job);
            {
                if (!JS::Call(cx, UndefinedHandleValue, job, args, &rval)) {
                    // Nothing we can do about uncatchable exceptions.
                    if (!cx->isExceptionPending())
                        continue;
                    RootedValue exn(cx);
                    if (cx->getPendingException(&exn)) {
                        /*
                         * Clear the exception, because
                         * PrepareScriptEnvironmentAndInvoke will assert that we don't
                         * have one.
                         */
                        cx->clearPendingException();
                        ReportExceptionClosure reportExn(exn);
                        PrepareScriptEnvironmentAndInvoke(cx, cx->global(), reportExn);
                    }
                }
            }
        }

        cx->drainingJobQueue = false;

        if (cx->stopDrainingJobQueue) {
            cx->stopDrainingJobQueue = false;
            break;
        }

        cx->jobQueue->clear();

        // It's possible a job added a new off-thread promise task.
        if (!cx->runtime()->offThreadPromiseState.ref().internalHasPending())
            break;
    }
}

JS::Error JSContext::reportedError;
JS::OOM JSContext::reportedOOM;

mozilla::GenericErrorResult<OOM&>
JSContext::alreadyReportedOOM()
{
#ifdef DEBUG
    if (helperThread()) {
        // Keep in sync with addPendingOutOfMemory.
        if (ParseTask* task = helperThread()->parseTask())
            MOZ_ASSERT(task->outOfMemory);
    } else {
        MOZ_ASSERT(isThrowingOutOfMemory());
    }
#endif
    return mozilla::Err(reportedOOM);
}

mozilla::GenericErrorResult<JS::Error&>
JSContext::alreadyReportedError()
{
#ifdef DEBUG
    if (!helperThread())
        MOZ_ASSERT(isExceptionPending());
#endif
    return mozilla::Err(reportedError);
}

JSContext::JSContext(JSRuntime* runtime, const JS::ContextOptions& options)
  : runtime_(runtime),
    kind_(ContextKind::HelperThread),
    helperThread_(nullptr),
    options_(options),
    arenas_(nullptr),
    jitActivation(nullptr),
    activation_(nullptr),
    profilingActivation_(nullptr),
    nativeStackBase(GetNativeStackBase()),
    entryMonitor(nullptr),
    noExecuteDebuggerTop(nullptr),
    activityCallback(nullptr),
    activityCallbackArg(nullptr),
    requestDepth(0),
#ifdef DEBUG
    checkRequestDepth(0),
    inUnsafeCallWithABI(false),
    hasAutoUnsafeCallWithABI(false),
#endif
#ifdef JS_SIMULATOR
    simulator_(nullptr),
#endif
#ifdef JS_TRACE_LOGGING
    traceLogger(nullptr),
#endif
    autoFlushICache_(nullptr),
    dtoaState(nullptr),
    suppressGC(0),
#ifdef DEBUG
    ionCompiling(false),
    ionCompilingSafeForMinorGC(false),
    performingGC(false),
    gcSweeping(false),
    gcHelperStateThread(false),
    isTouchingGrayThings(false),
    noGCOrAllocationCheck(0),
    noNurseryAllocationCheck(0),
    disableStrictProxyCheckingCount(0),
#endif
#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
    runningOOMTest(false),
#endif
    enableAccessValidation(false),
    inUnsafeRegion(0),
    generationalDisabled(0),
    compactingDisabledCount(0),
    suppressProfilerSampling(false),
    tempLifoAlloc_((size_t)TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
    debuggerMutations(0),
    ionPcScriptCache(nullptr),
    throwing(false),
    overRecursed_(false),
    propagatingForcedReturn_(false),
    liveVolatileJitFrameIter_(nullptr),
    reportGranularity(JS_DEFAULT_JITREPORT_GRANULARITY),
    resolvingList(nullptr),
#ifdef DEBUG
    enteredPolicy(nullptr),
#endif
    generatingError(false),
    cycleDetectorVector_(this),
    data(nullptr),
    outstandingRequests(0),
    jitIsBroken(false),
    asyncCauseForNewActivations(nullptr),
    asyncCallIsExplicit(false),
    interruptCallbackDisabled(false),
    interruptBits_(0),
    osrTempData_(nullptr),
    ionReturnOverride_(MagicValue(JS_ARG_POISON)),
    jitStackLimit(UINTPTR_MAX),
    jitStackLimitNoInterrupt(UINTPTR_MAX),
    getIncumbentGlobalCallback(nullptr),
    enqueuePromiseJobCallback(nullptr),
    enqueuePromiseJobCallbackData(nullptr),
    jobQueue(nullptr),
    drainingJobQueue(false),
    stopDrainingJobQueue(false),
    promiseRejectionTrackerCallback(nullptr),
    promiseRejectionTrackerCallbackData(nullptr)
{
    MOZ_ASSERT(static_cast<JS::RootingContext*>(this) ==
               JS::RootingContext::get(this));

    MOZ_ASSERT(!TlsContext.get());
    TlsContext.set(this);

    for (size_t i = 0; i < mozilla::ArrayLength(nativeStackQuota); i++)
        nativeStackQuota[i] = 0;
}

JSContext::~JSContext()
{
    // Clear the ContextKind first, so that ProtectedData checks will allow us to
    // destroy this context even if the runtime is already gone.
    kind_ = ContextKind::HelperThread;

    /* Free the stuff hanging off of cx. */
    MOZ_ASSERT(!resolvingList);

    js_delete(ionPcScriptCache.ref());

    if (dtoaState)
        DestroyDtoaState(dtoaState);

    fx.destroyInstance();
    freeOsrTempData();

#ifdef JS_SIMULATOR
    js::jit::Simulator::Destroy(simulator_);
#endif

#ifdef JS_TRACE_LOGGING
    if (traceLogger)
        DestroyTraceLogger(traceLogger);
#endif

    MOZ_ASSERT(TlsContext.get() == this);
    TlsContext.set(nullptr);
}

void
JSContext::setRuntime(JSRuntime* rt)
{
    MOZ_ASSERT(!resolvingList);
    MOZ_ASSERT(!compartment());
    MOZ_ASSERT(!activation());
    MOZ_ASSERT(!unwrappedException_.ref().initialized());
    MOZ_ASSERT(!asyncStackForNewActivations_.ref().initialized());

    runtime_ = rt;
}

bool
JSContext::getPendingException(MutableHandleValue rval)
{
    MOZ_ASSERT(throwing);
    rval.set(unwrappedException());
    if (zone()->isAtomsZone())
        return true;
    bool wasOverRecursed = overRecursed_;
    clearPendingException();
    if (!compartment()->wrap(this, rval))
        return false;
    assertSameCompartment(this, rval);
    setPendingException(rval);
    overRecursed_ = wasOverRecursed;
    return true;
}

bool
JSContext::isThrowingOutOfMemory()
{
    return throwing && unwrappedException() == StringValue(names().outOfMemory);
}

bool
JSContext::isClosingGenerator()
{
    return throwing && unwrappedException().isMagic(JS_GENERATOR_CLOSING);
}

bool
JSContext::isThrowingDebuggeeWouldRun()
{
    return throwing &&
           unwrappedException().isObject() &&
           unwrappedException().toObject().is<ErrorObject>() &&
           unwrappedException().toObject().as<ErrorObject>().type() == JSEXN_DEBUGGEEWOULDRUN;
}

static bool
ComputeIsJITBroken()
{
#if !defined(ANDROID)
    return false;
#else  // ANDROID
    if (getenv("JS_IGNORE_JIT_BROKENNESS")) {
        return false;
    }

    std::string line;

    // Check for the known-bad kernel version (2.6.29).
    std::ifstream osrelease("/proc/sys/kernel/osrelease");
    std::getline(osrelease, line);
    __android_log_print(ANDROID_LOG_INFO, "Gecko", "Detected osrelease `%s'",
                        line.c_str());

    if (line.npos == line.find("2.6.29")) {
        // We're using something other than 2.6.29, so the JITs should work.
        __android_log_print(ANDROID_LOG_INFO, "Gecko", "JITs are not broken");
        return false;
    }

    // We're using 2.6.29, and this causes trouble with the JITs on i9000.
    line = "";
    bool broken = false;
    std::ifstream cpuinfo("/proc/cpuinfo");
    do {
        if (0 == line.find("Hardware")) {
            static const char* const blacklist[] = {
                "SCH-I400",     // Samsung Continuum
                "SGH-T959",     // Samsung i9000, Vibrant device
                "SGH-I897",     // Samsung i9000, Captivate device
                "SCH-I500",     // Samsung i9000, Fascinate device
                "SPH-D700",     // Samsung i9000, Epic device
                "GT-I9000",     // Samsung i9000, UK/Europe device
                nullptr
            };
            for (const char* const* hw = &blacklist[0]; *hw; ++hw) {
                if (line.npos != line.find(*hw)) {
                    __android_log_print(ANDROID_LOG_INFO, "Gecko",
                                        "Blacklisted device `%s'", *hw);
                    broken = true;
                    break;
                }
            }
            break;
        }
        std::getline(cpuinfo, line);
    } while(!cpuinfo.fail() && !cpuinfo.eof());

    __android_log_print(ANDROID_LOG_INFO, "Gecko", "JITs are %sbroken",
                        broken ? "" : "not ");

    return broken;
#endif  // ifndef ANDROID
}

static bool
IsJITBrokenHere()
{
    static bool computedIsBroken = false;
    static bool isBroken = false;
    if (!computedIsBroken) {
        isBroken = ComputeIsJITBroken();
        computedIsBroken = true;
    }
    return isBroken;
}

void
JSContext::updateJITEnabled()
{
    jitIsBroken = IsJITBrokenHere();
}

size_t
JSContext::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
{
    /*
     * There are other JSContext members that could be measured; the following
     * ones have been found by DMD to be worth measuring.  More stuff may be
     * added later.
     */
    return cycleDetectorVector().sizeOfExcludingThis(mallocSizeOf);
}

#ifdef DEBUG
bool
JSContext::inAtomsZone() const
{
    return zone_->isAtomsZone();
}
#endif

void
JSContext::trace(JSTracer* trc)
{
    cycleDetectorVector().trace(trc);
    geckoProfiler().trace(trc);

    if (trc->isMarkingTracer() && realm_)
        realm_->mark();
}

void*
JSContext::stackLimitAddressForJitCode(JS::StackKind kind)
{
#ifdef JS_SIMULATOR
    return addressOfSimulatorStackLimit();
#else
    return stackLimitAddress(kind);
#endif
}

uintptr_t
JSContext::stackLimitForJitCode(JS::StackKind kind)
{
#ifdef JS_SIMULATOR
    return simulator()->stackLimit();
#else
    return stackLimit(kind);
#endif
}

void
JSContext::resetJitStackLimit()
{
    // Note that, for now, we use the untrusted limit for ion. This is fine,
    // because it's the most conservative limit, and if we hit it, we'll bail
    // out of ion into the interpreter, which will do a proper recursion check.
#ifdef JS_SIMULATOR
    jitStackLimit = jit::Simulator::StackLimit();
#else
    jitStackLimit = nativeStackLimit[JS::StackForUntrustedScript];
#endif
    jitStackLimitNoInterrupt = jitStackLimit;
}

void
JSContext::initJitStackLimit()
{
    resetJitStackLimit();
}

void
JSContext::updateMallocCounter(size_t nbytes)
{
    if (!zone()) {
        runtime()->updateMallocCounter(nbytes);
        return;
    }

    zone()->updateMallocCounter(nbytes);
}

#ifdef DEBUG

JS::AutoCheckRequestDepth::AutoCheckRequestDepth(JSContext* cxArg)
  : cx(cxArg->helperThread() ? nullptr : cxArg)
{
    if (cx) {
        MOZ_ASSERT(cx->requestDepth || JS::RuntimeHeapIsBusy());
        MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
        cx->checkRequestDepth++;
    }
}

JS::AutoCheckRequestDepth::~AutoCheckRequestDepth()
{
    if (cx) {
        MOZ_ASSERT(cx->checkRequestDepth != 0);
        cx->checkRequestDepth--;
    }
}

#endif

#ifdef JS_CRASH_DIAGNOSTICS
void
CompartmentChecker::check(InterpreterFrame* fp)
{
    if (fp)
        check(fp->environmentChain());
}

void
CompartmentChecker::check(AbstractFramePtr frame)
{
    if (frame)
        check(frame.environmentChain());
}
#endif

void
AutoEnterOOMUnsafeRegion::crash(const char* reason)
{
    char msgbuf[1024];
    js::NoteIntentionalCrash();
    SprintfLiteral(msgbuf, "[unhandlable oom] %s", reason);
    MOZ_ReportAssertionFailure(msgbuf, __FILE__, __LINE__);
    MOZ_CRASH();
}

AutoEnterOOMUnsafeRegion::AnnotateOOMAllocationSizeCallback
AutoEnterOOMUnsafeRegion::annotateOOMSizeCallback = nullptr;

void
AutoEnterOOMUnsafeRegion::crash(size_t size, const char* reason)
{
    {
        JS::AutoSuppressGCAnalysis suppress;
        if (annotateOOMSizeCallback)
            annotateOOMSizeCallback(size);
    }
    crash(reason);
}

#ifdef DEBUG
AutoUnsafeCallWithABI::AutoUnsafeCallWithABI()
  : cx_(TlsContext.get()),
    nested_(cx_->hasAutoUnsafeCallWithABI),
    nogc(cx_)
{
    cx_->hasAutoUnsafeCallWithABI = true;
}

AutoUnsafeCallWithABI::~AutoUnsafeCallWithABI()
{
    MOZ_ASSERT(cx_->hasAutoUnsafeCallWithABI);
    if (!nested_) {
        cx_->hasAutoUnsafeCallWithABI = false;
        cx_->inUnsafeCallWithABI = false;
    }
}
#endif
