js/src/vm/ProxyObject.cpp
author Jon Coppeard <jcoppeard@mozilla.com>
Thu, 06 Dec 2018 16:28:14 -0500
changeset 450672 42f073dedf5fd708e118833b4ddf63a19907485a
parent 448963 66eb1f485c1a3ea81372758bc92292c9428b17cd
child 450701 117411b9bc37722a3be46e020345a4d939dd8e34
permissions -rw-r--r--
Bug 1463462 - Delay gray marking assertions when we are doing incremental gray marking r=sfink

/* -*- 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/Allocator.h"
#include "gc/GCTrace.h"
#include "proxy/DeadObjectProxy.h"
#include "vm/Realm.h"

#include "gc/ObjectKind-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/TypeInference-inl.h"

using namespace js;

static gc::AllocKind GetProxyGCObjectKind(const Class* clasp,
                                          const BaseProxyHandler* handler,
                                          const Value& priv) {
  MOZ_ASSERT(clasp->isProxy());

  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);

  MOZ_ASSERT(
      js::detail::ProxyValueArray::sizeOf(nreserved) % sizeof(Value) == 0,
      "ProxyValueArray must be a multiple of Value");

  uint32_t nslots =
      js::detail::ProxyValueArray::sizeOf(nreserved) / sizeof(Value);
  MOZ_ASSERT(nslots <= NativeObject::MAX_FIXED_SLOTS);

  gc::AllocKind kind = gc::GetGCObjectKind(nslots);
  if (handler->finalizeInBackground(priv)) {
    kind = GetBackgroundAllocKind(kind);
  }

  return kind;
}

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

  const Class* clasp = options.clasp();

#ifdef DEBUG
  MOZ_ASSERT(isValidProxyClass(clasp));
  MOZ_ASSERT(clasp->shouldDelayMetadataBuilder());
  MOZ_ASSERT_IF(proto.isObject(),
                cx->compartment() == proto.toObject()->compartment());
  MOZ_ASSERT(clasp->hasFinalize());
  if (priv.isGCThing()) {
    JS::AssertCellIsNotGray(priv.toGCThing());
  }
#endif

  /*
   * Eagerly mark properties unknown for proxies, so we don't try to track
   * their properties and so that we don't need to walk the compartment if
   * their prototype changes later.  But don't do this for DOM proxies,
   * because we want to be able to keep track of them in typesets in useful
   * ways.
   */
  if (proto.isObject() && !options.singleton() && !clasp->isDOMClass()) {
    ObjectGroupRealm& realm = ObjectGroupRealm::getForNewObject(cx);
    RootedObject protoObj(cx, proto.toObject());
    if (!JSObject::setNewGroupUnknown(cx, realm, clasp, protoObj)) {
      return nullptr;
    }
  }

  // Ensure that the wrapper has the same lifetime assumptions as the
  // wrappee. Prefer to allocate in the nursery, when possible.
  NewObjectKind newKind = NurseryAllocatedProxy;
  if (options.singleton()) {
    MOZ_ASSERT(priv.isNull() ||
               (priv.isGCThing() && priv.toGCThing()->isTenured()));
    newKind = SingletonObject;
  } else if ((priv.isGCThing() && priv.toGCThing()->isTenured()) ||
             !handler->canNurseryAllocate()) {
    newKind = TenuredObject;
  }

  gc::AllocKind allocKind = GetProxyGCObjectKind(clasp, handler, priv);

  AutoSetNewObjectMetadata metadata(cx);
  // Note: this will initialize the object's |data| to strange values, but we
  // will immediately overwrite those below.
  ProxyObject* proxy;
  JS_TRY_VAR_OR_RETURN_NULL(cx, proxy,
                            create(cx, clasp, proto, allocKind, newKind));

  proxy->setInlineValueArray();

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

  proxy->data.handler = handler;
  if (IsCrossCompartmentWrapper(proxy)) {
    proxy->setCrossCompartmentPrivate(priv);
  } else {
    proxy->setSameCompartmentPrivate(priv);
  }

  /* Don't track types of properties of non-DOM and non-singleton proxies. */
  if (newKind != SingletonObject && !clasp->isDOMClass()) {
    MarkObjectGroupUnknownProperties(cx, proxy->group());
  }

  return proxy;
}

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

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) {
  MOZ_ASSERT_IF(IsMarkedBlack(this) && priv.isGCThing(),
                !JS::GCThingIsMarkedGray(JS::GCCellPtr(priv)));
  *slotOfPrivate() = priv;
}

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

  // 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.
}

/* static */ JS::Result<ProxyObject*, JS::OOM&> ProxyObject::create(
    JSContext* cx, const Class* clasp, Handle<TaggedProto> proto,
    gc::AllocKind allocKind, NewObjectKind newKind) {
  MOZ_ASSERT(clasp->isProxy());

  Realm* realm = cx->realm();
  RootedObjectGroup group(cx);
  RootedShape shape(cx);

  // Try to look up the group and shape in the NewProxyCache.
  if (!realm->newProxyCache.lookup(clasp, proto, group.address(),
                                   shape.address())) {
    group = ObjectGroup::defaultNewGroup(cx, clasp, proto, nullptr);
    if (!group) {
      return cx->alreadyReportedOOM();
    }

    shape = EmptyShape::getInitialShape(cx, clasp, proto, /* nfixed = */ 0);
    if (!shape) {
      return cx->alreadyReportedOOM();
    }

    MOZ_ASSERT(group->realm() == realm);
    realm->newProxyCache.add(group, shape);
  }

  gc::InitialHeap heap = GetInitialHeap(newKind, clasp);
  debugCheckNewObject(group, shape, allocKind, heap);

  JSObject* obj = js::Allocate<JSObject>(cx, allocKind, /* nDynamicSlots = */ 0,
                                         heap, clasp);
  if (!obj) {
    return cx->alreadyReportedOOM();
  }

  ProxyObject* pobj = static_cast<ProxyObject*>(obj);
  pobj->initGroup(group);
  pobj->initShape(shape);

  MOZ_ASSERT(clasp->shouldDelayMetadataBuilder());
  cx->realm()->setObjectPendingMetadata(cx, pobj);

  js::gc::gcTracer.traceCreateObject(pobj);

  if (newKind == SingletonObject) {
    Rooted<ProxyObject*> pobjRoot(cx, pobj);
    if (!JSObject::setSingleton(cx, pobjRoot)) {
      return cx->alreadyReportedOOM();
    }
    pobj = pobjRoot;
  }

  return pobj;
}

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