/* -*- 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 "vm/Compartment-inl.h"

#include "mozilla/MemoryReporting.h"

#include <stddef.h>

#include "jsfriendapi.h"

#include "debugger/DebugAPI.h"
#include "gc/GC.h"
#include "gc/Memory.h"
#include "gc/PublicIterators.h"
#include "gc/Zone.h"
#include "js/friend/StackLimits.h"  // js::AutoCheckRecursionLimit
#include "js/friend/WindowProxy.h"  // js::IsWindow, js::IsWindowProxy, js::ToWindowProxyIfWindow
#include "js/Proxy.h"
#include "js/RootingAPI.h"
#include "js/StableStringChars.h"
#include "js/Wrapper.h"
#include "js/WrapperCallbacks.h"
#include "proxy/DeadObjectProxy.h"
#include "proxy/DOMProxy.h"
#include "vm/JSContext.h"
#include "vm/WrapperObject.h"

#include "gc/Marking-inl.h"
#include "gc/WeakMap-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/Realm-inl.h"
#include "vm/StringType-inl.h"

using namespace js;

using JS::AutoStableStringChars;

Compartment::Compartment(Zone* zone, bool invisibleToDebugger)
    : zone_(zone),
      runtime_(zone->runtimeFromAnyThread()),
      invisibleToDebugger_(invisibleToDebugger),
      crossCompartmentObjectWrappers(zone, 0),
      realms_(zone) {}

#ifdef JSGC_HASH_TABLE_CHECKS

void Compartment::checkObjectWrappersAfterMovingGC() {
  for (ObjectWrapperEnum e(this); !e.empty(); e.popFront()) {
    auto key = e.front().key();
    CheckGCThingAfterMovingGC(key.get());  // Keys may be in a different zone.
    CheckGCThingAfterMovingGC(e.front().value().unbarrieredGet(), zone());
    CheckTableEntryAfterMovingGC(crossCompartmentObjectWrappers, e, key);
  }
}

#endif  // JSGC_HASH_TABLE_CHECKS

bool Compartment::putWrapper(JSContext* cx, JSObject* wrapped,
                             JSObject* wrapper) {
  MOZ_ASSERT(!js::IsProxy(wrapper) || js::GetProxyHandler(wrapper)->family() !=
                                          js::GetDOMRemoteProxyHandlerFamily());

  if (!crossCompartmentObjectWrappers.put(wrapped, wrapper)) {
    ReportOutOfMemory(cx);
    return false;
  }

  return true;
}

bool Compartment::putWrapper(JSContext* cx, JSString* wrapped,
                             JSString* wrapper) {
  if (!zone()->crossZoneStringWrappers().put(wrapped, wrapper)) {
    ReportOutOfMemory(cx);
    return false;
  }

  return true;
}

void Compartment::removeWrapper(js::ObjectWrapperMap::Ptr p) {
  JSObject* key = p->key();
  JSObject* value = p->value().unbarrieredGet();
  if (js::gc::detail::GetDelegate(value) == key) {
    key->zone()->beforeClearDelegate(value, key);
  }

  crossCompartmentObjectWrappers.remove(p);
}

JSString* js::CopyStringPure(JSContext* cx, JSString* str) {
  /*
   * Directly allocate the copy in the destination compartment, rather than
   * first flattening it (and possibly allocating in source compartment),
   * because we don't know whether the flattening will pay off later.
   */

  size_t len = str->length();
  JSString* copy;
  if (str->isLinear()) {
    // If the string has a refcounted StringBuffer, we can share it.
    if (str->hasStringBuffer()) {
      RefPtr<mozilla::StringBuffer> buffer(str->asLinear().stringBuffer());
      if (str->hasLatin1Chars()) {
        Rooted<JSString::OwnedChars<Latin1Char>> owned(cx, std::move(buffer),
                                                       len);
        return JSLinearString::newValidLength<CanGC, Latin1Char>(
            cx, &owned, gc::Heap::Default);
      }
      Rooted<JSString::OwnedChars<char16_t>> owned(cx, std::move(buffer), len);
      return JSLinearString::newValidLength<CanGC, char16_t>(cx, &owned,
                                                             gc::Heap::Default);
    }

    /* Only use AutoStableStringChars if the NoGC allocation fails. */
    if (str->hasLatin1Chars()) {
      JS::AutoCheckCannotGC nogc;
      copy = NewStringCopyN<NoGC>(cx, str->asLinear().latin1Chars(nogc), len);
    } else {
      JS::AutoCheckCannotGC nogc;
      copy = NewStringCopyNDontDeflate<NoGC>(
          cx, str->asLinear().twoByteChars(nogc), len);
    }
    if (copy) {
      return copy;
    }

    AutoStableStringChars chars(cx);
    if (!chars.init(cx, str)) {
      return nullptr;
    }

    return chars.isLatin1() ? NewStringCopyN<CanGC>(
                                  cx, chars.latin1Range().begin().get(), len)
                            : NewStringCopyNDontDeflate<CanGC>(
                                  cx, chars.twoByteRange().begin().get(), len);
  }

  if (str->hasLatin1Chars()) {
    UniquePtr<Latin1Char[], JS::FreePolicy> copiedChars =
        str->asRope().copyLatin1Chars(cx, js::StringBufferArena);
    if (!copiedChars) {
      return nullptr;
    }

    return NewString<CanGC>(cx, std::move(copiedChars), len);
  }

  UniqueTwoByteChars copiedChars =
      str->asRope().copyTwoByteChars(cx, js::StringBufferArena);
  if (!copiedChars) {
    return nullptr;
  }

  return NewStringDontDeflate<CanGC>(cx, std::move(copiedChars), len);
}

bool Compartment::wrap(JSContext* cx, MutableHandleString strp) {
  MOZ_ASSERT(cx->compartment() == this);

  /* If the string is already in this compartment, we are done. */
  JSString* str = strp;
  if (str->zoneFromAnyThread() == zone()) {
    return true;
  }

  /*
   * If the string is an atom, we don't have to copy, but we do need to mark
   * the atom as being in use by the new zone.
   */
  if (str->isAtom()) {
    cx->markAtom(&str->asAtom());
    return true;
  }

  /* Check the cache. */
  if (StringWrapperMap::Ptr p = lookupWrapper(str)) {
    strp.set(p->value().get());
    return true;
  }

  /* No dice. Make a copy, and cache it. */
  JSString* copy = CopyStringPure(cx, str);
  if (!copy) {
    return false;
  }
  if (!putWrapper(cx, strp, copy)) {
    return false;
  }

  strp.set(copy);
  return true;
}

bool Compartment::wrap(JSContext* cx, MutableHandleBigInt bi) {
  MOZ_ASSERT(cx->compartment() == this);

  if (bi->zone() == cx->zone()) {
    return true;
  }

  BigInt* copy = BigInt::copy(cx, bi);
  if (!copy) {
    return false;
  }
  bi.set(copy);
  return true;
}

bool Compartment::getNonWrapperObjectForCurrentCompartment(
    JSContext* cx, HandleObject origObj, MutableHandleObject obj) {
  // Ensure that we have entered a realm.
  MOZ_ASSERT(cx->global());

  // The object is already in the right compartment. Normally same-
  // compartment returns the object itself, however, windows are always
  // wrapped by a proxy, so we have to check for that case here manually.
  if (obj->compartment() == this) {
    obj.set(ToWindowProxyIfWindow(obj));
    return true;
  }

  // Note that if the object is same-compartment, but has been wrapped into a
  // different compartment, we need to unwrap it and return the bare same-
  // compartment object. Note again that windows are always wrapped by a
  // WindowProxy even when same-compartment so take care not to strip this
  // particular wrapper.
  RootedObject objectPassedToWrap(cx, obj);
  obj.set(UncheckedUnwrap(obj, /* stopAtWindowProxy = */ true));
  if (obj->compartment() == this) {
    MOZ_ASSERT(!IsWindow(obj));
    return true;
  }

  // Disallow creating new wrappers if we nuked the object's realm or the
  // current compartment.
  if (!AllowNewWrapper(this, obj)) {
    obj.set(NewDeadProxyObject(cx, obj));
    return !!obj;
  }

  // Use the WindowProxy instead of the Window here, so that we don't have to
  // deal with this in the rest of the wrapping code.
  if (IsWindow(obj)) {
    obj.set(ToWindowProxyIfWindow(obj));

    // ToWindowProxyIfWindow can return a CCW if |obj| was a navigated-away-from
    // Window. Strip any CCWs.
    obj.set(UncheckedUnwrap(obj));

    if (JS_IsDeadWrapper(obj)) {
      obj.set(NewDeadProxyObject(cx, obj));
      return !!obj;
    }

    MOZ_ASSERT(IsWindowProxy(obj) || IsDOMRemoteProxyObject(obj));

    // We crossed a compartment boundary there, so may now have a gray object.
    // This function is not allowed to return gray objects, so don't do that.
    ExposeObjectToActiveJS(obj);
  }

  // If the object is a dead wrapper, return a new dead wrapper rather than
  // trying to wrap it for a different compartment.
  if (JS_IsDeadWrapper(obj)) {
    obj.set(NewDeadProxyObject(cx, obj));
    return !!obj;
  }

  // Invoke the prewrap callback. The prewrap callback is responsible for
  // doing similar reification as above, but can account for any additional
  // embedder requirements.
  //
  // We're a bit worried about infinite recursion here, so we do a check -
  // see bug 809295.
  auto preWrap = cx->runtime()->wrapObjectCallbacks->preWrap;
  if (preWrap) {
    AutoCheckRecursionLimit recursion(cx);
    if (!recursion.checkSystem(cx)) {
      return false;
    }
    preWrap(cx, cx->global(), origObj, obj, objectPassedToWrap, obj);
    if (!obj) {
      return false;
    }
  }
  MOZ_ASSERT(!IsWindow(obj));

  return true;
}

bool Compartment::getOrCreateWrapper(JSContext* cx, HandleObject existing,
                                     MutableHandleObject obj) {
  // ScriptSourceObject is an internal object that we never need to wrap.
  MOZ_ASSERT(!obj->is<ScriptSourceObject>());

  // If we already have a wrapper for this value, use it.
  if (ObjectWrapperMap::Ptr p = lookupWrapper(obj)) {
    obj.set(p->value().get());
    MOZ_ASSERT(obj->is<CrossCompartmentWrapperObject>());
    return true;
  }

  // Ensure that the wrappee is exposed in case we are creating a new wrapper
  // for a gray object.
  ExposeObjectToActiveJS(obj);

  // If we're wrapping an object which emulates undefined then the runtime fuse
  // should already have been popped.
  MOZ_ASSERT_IF(
      obj->getClass()->emulatesUndefined(),
      !cx->runtime()->hasSeenObjectEmulateUndefinedFuse.ref().intact());

  // Create a new wrapper for the object.
  auto wrap = cx->runtime()->wrapObjectCallbacks->wrap;
  RootedObject wrapper(cx, wrap(cx, existing, obj));
  if (!wrapper) {
    return false;
  }

  // We maintain the invariant that the key in the cross-compartment wrapper
  // map is always directly wrapped by the value.
  MOZ_ASSERT(Wrapper::wrappedObject(wrapper) == obj);

  if (!putWrapper(cx, obj, wrapper)) {
    // Enforce the invariant that all cross-compartment wrapper object are
    // in the map by nuking the wrapper if we couldn't add it.
    // Unfortunately it's possible for the wrapper to still be marked if we
    // took this path, for example if the object metadata callback stashes a
    // reference to it.
    if (wrapper->is<CrossCompartmentWrapperObject>()) {
      NukeCrossCompartmentWrapper(cx, wrapper);
    }
    return false;
  }

  obj.set(wrapper);
  return true;
}

bool Compartment::wrap(JSContext* cx, MutableHandleObject obj) {
  MOZ_ASSERT(cx->compartment() == this);

  if (!obj) {
    return true;
  }

  AutoDisableProxyCheck adpc;

  // Anything we're wrapping has already escaped into script, so must have
  // been unmarked-gray at some point in the past.
  JS::AssertObjectIsNotGray(obj);

  // The passed object may already be wrapped, or may fit a number of special
  // cases that we need to check for and manually correct.
  if (!getNonWrapperObjectForCurrentCompartment(cx, /* origObj = */ nullptr,
                                                obj)) {
    return false;
  }

  // If the reification above did not result in a same-compartment object,
  // get or create a new wrapper object in this compartment for it.
  if (obj->compartment() != this) {
    if (!getOrCreateWrapper(cx, nullptr, obj)) {
      return false;
    }
  }

  // Ensure that the wrapper is also exposed.
  ExposeObjectToActiveJS(obj);
  return true;
}

bool Compartment::rewrap(JSContext* cx, MutableHandleObject obj,
                         HandleObject existingArg) {
  MOZ_ASSERT(cx->compartment() == this);
  MOZ_ASSERT(obj);
  MOZ_ASSERT(existingArg);
  MOZ_ASSERT(existingArg->compartment() == cx->compartment());
  MOZ_ASSERT(IsDeadProxyObject(existingArg));

  AutoDisableProxyCheck adpc;

  // It may not be possible to re-use existing; if so, clear it so that we
  // are forced to create a new wrapper. Note that this cannot call out to
  // |wrap| because of the different gray unmarking semantics.
  RootedObject existing(cx, existingArg);
  if (existing->hasStaticPrototype() ||
      // Note: Class asserted above, so all that's left to check is callability
      existing->isCallable() || obj->isCallable()) {
    existing.set(nullptr);
  }

  // The passed object may already be wrapped, or may fit a number of special
  // cases that we need to check for and manually correct. We pass in
  // |existingArg| instead of |existing|, because the purpose is to get the
  // address of the object we are transplanting onto, not to find a wrapper
  // to reuse.
  if (!getNonWrapperObjectForCurrentCompartment(cx, existingArg, obj)) {
    return false;
  }

  // If the reification above resulted in a same-compartment object, we do
  // not need to create or return an existing wrapper.
  if (obj->compartment() == this) {
    return true;
  }

  return getOrCreateWrapper(cx, existing, obj);
}

bool Compartment::wrap(JSContext* cx,
                       MutableHandle<JS::PropertyDescriptor> desc) {
  if (desc.hasGetter()) {
    if (!wrap(cx, desc.getter())) {
      return false;
    }
  }
  if (desc.hasSetter()) {
    if (!wrap(cx, desc.setter())) {
      return false;
    }
  }
  if (desc.hasValue()) {
    if (!wrap(cx, desc.value())) {
      return false;
    }
  }
  return true;
}

bool Compartment::wrap(JSContext* cx,
                       MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) {
  if (desc.isNothing()) {
    return true;
  }

  Rooted<PropertyDescriptor> desc_(cx, *desc);
  if (!wrap(cx, &desc_)) {
    return false;
  }
  desc.set(mozilla::Some(desc_.get()));
  return true;
}

bool Compartment::wrap(JSContext* cx, MutableHandle<GCVector<Value>> vec) {
  for (size_t i = 0; i < vec.length(); ++i) {
    if (!wrap(cx, vec[i])) {
      return false;
    }
  }
  return true;
}

static inline bool ShouldTraceWrapper(JSObject* wrapper,
                                      Compartment::EdgeSelector whichEdges) {
  switch (whichEdges) {
    case Compartment::AllEdges:
      return true;
    case Compartment::NonGrayEdges:
      return !wrapper->isMarkedGray();
    case Compartment::GrayEdges:
      return wrapper->isMarkedGray();
    case Compartment::BlackEdges:
      return wrapper->isMarkedBlack();
    default:
      MOZ_CRASH("Unexpected EdgeSelector value");
  }
}

void Compartment::traceWrapperTargetsInCollectedZones(JSTracer* trc,
                                                      EdgeSelector whichEdges) {
  // Trace cross compartment wrapper private pointers into collected zones to
  // either mark or update them. Wrapped object pointers are updated by
  // sweepCrossCompartmentObjectWrappers().

  MOZ_ASSERT(JS::RuntimeHeapIsMajorCollecting());
  MOZ_ASSERT(!zone()->isCollectingFromAnyThread() ||
             trc->runtime()->gc.isHeapCompacting());

  for (WrappedObjectCompartmentEnum c(this); !c.empty(); c.popFront()) {
    Zone* zone = c.front()->zone();
    if (!zone->isCollectingFromAnyThread()) {
      continue;
    }

    for (ObjectWrapperEnum e(this, c); !e.empty(); e.popFront()) {
      JSObject* obj = e.front().value().unbarrieredGet();
      ProxyObject* wrapper = &obj->as<ProxyObject>();
      if (ShouldTraceWrapper(wrapper, whichEdges)) {
        ProxyObject::traceEdgeToTarget(trc, wrapper);
      }
    }
  }
}

/* static */
void Compartment::traceIncomingCrossCompartmentEdgesForZoneGC(
    JSTracer* trc, EdgeSelector whichEdges) {
  MOZ_ASSERT(JS::RuntimeHeapIsMajorCollecting());

  for (ZonesIter zone(trc->runtime(), SkipAtoms); !zone.done(); zone.next()) {
    if (zone->isCollectingFromAnyThread()) {
      continue;
    }

    for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
      c->traceWrapperTargetsInCollectedZones(trc, whichEdges);
    }
  }

  // Currently we trace all debugger edges as black.
  if (whichEdges != GrayEdges) {
    DebugAPI::traceCrossCompartmentEdges(trc);
  }
}

void Compartment::sweepAfterMinorGC(JSTracer* trc) {
  crossCompartmentObjectWrappers.sweepAfterMinorGC(trc);

  for (RealmsInCompartmentIter r(this); !r.done(); r.next()) {
    r->sweepAfterMinorGC(trc);
  }
}

// Remove dead wrappers from the table or update pointers to moved objects.
void Compartment::traceCrossCompartmentObjectWrapperEdges(JSTracer* trc) {
  crossCompartmentObjectWrappers.traceWeak(trc);
}

void Compartment::fixupCrossCompartmentObjectWrappersAfterMovingGC(
    JSTracer* trc) {
  MOZ_ASSERT(trc->runtime()->gc.isHeapCompacting());

  // Sweep the wrapper map to update keys (wrapped values) in other
  // compartments that may have been moved.
  traceCrossCompartmentObjectWrapperEdges(trc);

  // Trace the wrappers in the map to update their cross-compartment edges
  // to wrapped values in other compartments that may have been moved.
  traceWrapperTargetsInCollectedZones(trc, AllEdges);
}

void Compartment::fixupAfterMovingGC(JSTracer* trc) {
  MOZ_ASSERT(zone()->isGCCompacting());

  for (RealmsInCompartmentIter r(this); !r.done(); r.next()) {
    r->fixupAfterMovingGC(trc);
  }

  // Sweep the wrapper map to update values (wrapper objects) in this
  // compartment that may have been moved.
  traceCrossCompartmentObjectWrapperEdges(trc);
}

void Compartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                         size_t* compartmentObjects,
                                         size_t* crossCompartmentWrappersTables,
                                         size_t* compartmentsPrivateData) {
  *compartmentObjects += mallocSizeOf(this);
  *crossCompartmentWrappersTables +=
      crossCompartmentObjectWrappers.sizeOfExcludingThis(mallocSizeOf);

  if (auto callback = runtime_->sizeOfIncludingThisCompartmentCallback) {
    *compartmentsPrivateData += callback(mallocSizeOf, this);
  }
}

GlobalObject& Compartment::firstGlobal() const {
  for (Realm* realm : realms_) {
    if (!realm->hasInitializedGlobal()) {
      continue;
    }
    GlobalObject* global = realm->maybeGlobal();
    ExposeObjectToActiveJS(global);
    return *global;
  }
  MOZ_CRASH("If all our globals are dead, why is someone expecting a global?");
}

JS_PUBLIC_API JSObject* js::GetFirstGlobalInCompartment(JS::Compartment* comp) {
  return &comp->firstGlobal();
}

JS_PUBLIC_API bool js::CompartmentHasLiveGlobal(JS::Compartment* comp) {
  MOZ_ASSERT(comp);
  for (Realm* r : comp->realms()) {
    if (r->hasLiveGlobal()) {
      return true;
    }
  }
  return false;
}

void Compartment::traceWeakNativeIterators(JSTracer* trc) {
  /* Sweep list of native iterators. */
  NativeIteratorListIter iter(&enumerators_);
  while (!iter.done()) {
    NativeIterator* ni = iter.next();
    JSObject* iterObj = ni->iterObj();
    if (!TraceManuallyBarrieredWeakEdge(trc, &iterObj,
                                        "Compartment::enumerators_")) {
      ni->unlink();
    }
    MOZ_ASSERT(ni->objectBeingIterated()->compartment() == this);
  }
}
