/* -*- 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/ProxyObject.h"

#include "gc/GCProbes.h"
#include "gc/Marking.h"
#include "gc/Zone.h"
#include "proxy/DeadObjectProxy.h"
#include "vm/Compartment.h"
#include "vm/Realm.h"

#include "gc/ObjectKind-inl.h"
#include "vm/JSContext-inl.h"

using namespace js;

static gc::AllocKind GetProxyGCObjectKind(const JSClass* clasp,
                                          const BaseProxyHandler* handler,
                                          const Value& priv,
                                          bool withInlineValues) {
  MOZ_ASSERT(clasp->isProxyObject());

  uint32_t nreserved = JSCLASS_RESERVED_SLOTS(clasp);

  // For now assert each Proxy Class has at least 1 reserved slot. This is
  // not a hard requirement, but helps catch Classes that need an explicit
  // JSCLASS_HAS_RESERVED_SLOTS since bug 1360523.
  MOZ_ASSERT(nreserved > 0);

  uint32_t nslots = 0;
  if (withInlineValues) {
    nslots = detail::ProxyValueArray::allocCount(nreserved);
  }

  MOZ_ASSERT(nslots <= NativeObject::MAX_FIXED_SLOTS);
  gc::AllocKind kind = gc::GetGCObjectKind(nslots);
  gc::FinalizeKind finalizeKind;

  // Bug 1957589: Support non-finalized proxies as well.
  if (handler->finalizeInBackground(priv)) {
    finalizeKind = gc::FinalizeKind::Background;
  } else {
    finalizeKind = gc::FinalizeKind::Foreground;
  }

  return gc::GetFinalizedAllocKind(kind, finalizeKind);
}

void ProxyObject::init(const BaseProxyHandler* handler, HandleValue priv,
                       JSContext* cx) {
  setInlineValueArray();

  detail::ProxyValueArray* values = detail::GetProxyDataLayout(this)->values();
  values->init(numReservedSlots());

  data.handler = handler;

  if (IsCrossCompartmentWrapper(this)) {
    MOZ_ASSERT(cx->global() == &cx->compartment()->globalForNewCCW());
    setCrossCompartmentPrivate(priv);
  } else {
    setSameCompartmentPrivate(priv);
  }

  // The expando slot is nullptr until required by the installation of
  // a private field.
  setExpando(nullptr);
}

/* static */
ProxyObject* ProxyObject::New(JSContext* cx, const BaseProxyHandler* handler,
                              HandleValue priv, TaggedProto proto_,
                              const JSClass* clasp) {
  Rooted<TaggedProto> proto(cx, proto_);

  MOZ_ASSERT(!clasp->isNativeObject());
  MOZ_ASSERT(clasp->isProxyObject());
  MOZ_ASSERT(isValidProxyClass(clasp));
  MOZ_ASSERT(clasp->shouldDelayMetadataBuilder());
  MOZ_ASSERT_IF(proto.isObject(),
                cx->compartment() == proto.toObject()->compartment());
  MOZ_ASSERT(clasp->hasFinalize());

#ifdef DEBUG
  if (priv.isGCThing()) {
    JS::AssertCellIsNotGray(priv.toGCThing());
  }
#endif

  gc::AllocKind allocKind = GetProxyGCObjectKind(clasp, handler, priv,
                                                 /* withInlineValues = */ true);

  Realm* realm = cx->realm();

  AutoSetNewObjectMetadata metadata(cx);
  // Try to look up the shape in the NewProxyCache.
  Rooted<Shape*> shape(cx);
  if (!realm->newProxyCache.lookup(clasp, proto, shape.address())) {
    shape = ProxyShape::getShape(cx, clasp, realm, proto, ObjectFlags());
    if (!shape) {
      return nullptr;
    }

    realm->newProxyCache.add(shape);
  }

  MOZ_ASSERT(shape->realm() == realm);
  MOZ_ASSERT(!IsAboutToBeFinalizedUnbarriered(shape.get()));

  // Ensure that the wrapper has the same lifetime assumptions as the
  // wrappee. Prefer to allocate in the nursery, when possible.
  gc::Heap heap;
  if ((priv.isGCThing() && priv.toGCThing()->isTenured()) ||
      !handler->canNurseryAllocate()) {
    heap = gc::Heap::Tenured;
  } else {
    heap = gc::Heap::Default;
  }

  debugCheckNewObject(shape, allocKind, heap);

  ProxyObject* proxy = cx->newCell<ProxyObject>(allocKind, heap, clasp);
  if (!proxy) {
    return nullptr;
  }

  proxy->initShape(shape);

  MOZ_ASSERT(clasp->shouldDelayMetadataBuilder());
  realm->setObjectPendingMetadata(proxy);

  gc::gcprobes::CreateObject(proxy);

  proxy->init(handler, priv, cx);

  return proxy;
}

gc::AllocKind ProxyObject::allocKindForTenure() const {
  Value priv = private_();
  return GetProxyGCObjectKind(getClass(), data.handler, priv,
                              usingInlineValueArray());
}

void ProxyObject::setCrossCompartmentPrivate(const Value& priv) {
  setPrivate(priv);
}

void ProxyObject::setSameCompartmentPrivate(const Value& priv) {
  MOZ_ASSERT(IsObjectValueInCompartment(priv, compartment()));
  setPrivate(priv);
}

inline void ProxyObject::setPrivate(const Value& priv) {
#ifdef DEBUG
  JS::AssertValueIsNotGray(priv);
#endif
  *slotOfPrivate() = priv;
}

void ProxyObject::setExpando(JSObject* expando) {
  // Ensure we're in the same compartment as the proxy object: Don't want the
  // expando to end up as a CCW.
  MOZ_ASSERT_IF(expando, expando->compartment() == compartment());

  // Ensure that we don't accidentally end up pointing to a
  // grey object, which would violate GC invariants.
  MOZ_ASSERT_IF(!zone()->isGCPreparing() && isMarkedBlack() && expando,
                !JS::GCThingIsMarkedGray(JS::GCCellPtr(expando)));

  *slotOfExpando() = ObjectOrNullValue(expando);
}

void ProxyObject::nuke() {
  // Notify the zone that a delegate is no longer a delegate. Be careful not to
  // expose this pointer, because it has already been removed from the wrapper
  // map yet we have assertions during tracing that will verify that it is
  // still present.
  JSObject* delegate = UncheckedUnwrapWithoutExpose(this);
  if (delegate != this) {
    delegate->zone()->beforeClearDelegate(this, delegate);
  }

  // Clear the target reference and replaced it with a value that encodes
  // various information about the original target.
  setSameCompartmentPrivate(DeadProxyTargetValue(this));

  // Clear out the expando
  setExpando(nullptr);

  // Update the handler to make this a DeadObjectProxy.
  setHandler(&DeadObjectProxy::singleton);

  // The proxy's reserved slots are not cleared and will continue to be
  // traced. This avoids the possibility of triggering write barriers while
  // nuking proxies in dead compartments which could otherwise cause those
  // compartments to be kept alive. Note that these are slots cannot hold
  // cross compartment pointers, so this cannot cause the target compartment
  // to leak.
}

JS_PUBLIC_API void js::detail::SetValueInProxy(Value* slot,
                                               const Value& value) {
  // Slots in proxies are not GCPtr<Value>s, so do a cast whenever assigning
  // values to them which might trigger a barrier.
  *reinterpret_cast<GCPtr<Value>*>(slot) = value;
}
