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

#include "vm/ProxyObject.h"

#include "jscompartment.h"

#include "proxy/DeadObjectProxy.h"
#include "proxy/ScriptedProxyHandler.h"

#include "jsobjinlines.h"

using namespace js;

/* 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();

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

    /*
     * 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()) {
        RootedObject protoObj(cx, proto.toObject());
        if (!JSObject::setNewGroupUnknown(cx, 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.isGCThing() && priv.toGCThing()->isTenured());
        newKind = SingletonObject;
    } else if ((priv.isGCThing() && priv.toGCThing()->isTenured()) ||
               !handler->canNurseryAllocate() ||
               !handler->finalizeInBackground(priv))
    {
        newKind = TenuredObject;
    }

    gc::AllocKind allocKind = gc::GetGCObjectKind(clasp);
    if (handler->finalizeInBackground(priv))
        allocKind = GetBackgroundAllocKind(allocKind);

    AutoSetNewObjectMetadata metadata(cx);
    // Note: this will initialize the object's |data| to strange values, but we
    // will immediately overwrite those below.
    RootedObject obj(cx, NewObjectWithGivenTaggedProto(cx, clasp, proto, allocKind,
                                                       newKind));
    if (!obj)
        return nullptr;

    Rooted<ProxyObject*> proxy(cx, &obj->as<ProxyObject>());
    new (proxy->data.values) detail::ProxyValueArray;
    proxy->data.handler = handler;
    proxy->setCrossCompartmentPrivate(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
{
    gc::AllocKind allocKind = gc::GetGCObjectKind(group()->clasp());
    if (data.handler->finalizeInBackground(const_cast<ProxyObject*>(this)->private_()))
        allocKind = GetBackgroundAllocKind(allocKind);
    return allocKind;
}

/* static */ size_t
ProxyObject::objectMovedDuringMinorGC(TenuringTracer* trc, JSObject* dst, JSObject* src)
{
    ProxyObject& psrc = src->as<ProxyObject>();
    ProxyObject& pdst = dst->as<ProxyObject>();

    // We're about to sweep the nursery heap, so migrate the inline
    // ProxyValueArray to the malloc heap if they were nursery allocated.
    if (trc->runtime()->gc.nursery.isInside(psrc.data.values))
        pdst.data.values = js_new<detail::ProxyValueArray>(*psrc.data.values);
    else
        trc->runtime()->gc.nursery.removeMallocedBuffer(psrc.data.values);
    return sizeof(detail::ProxyValueArray);
}

void
ProxyObject::setCrossCompartmentPrivate(const Value& priv)
{
    *slotOfPrivate() = priv;
}

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

void
ProxyObject::nuke()
{
    // When nuking scripted proxies, isCallable and isConstructor values for
    // the proxy needs to be preserved. Do this before clearing the target.
    uint32_t callable = handler()->isCallable(this)
                        ? ScriptedProxyHandler::IS_CALLABLE : 0;
    uint32_t constructor = handler()->isConstructor(this)
                           ? ScriptedProxyHandler::IS_CONSTRUCTOR : 0;
    setExtra(ScriptedProxyHandler::IS_CALLCONSTRUCT_EXTRA,
             PrivateUint32Value(callable | constructor));

    // Clear the target reference.
    setSameCompartmentPrivate(NullValue());

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

    // The proxy's extra 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_FRIEND_API(void)
js::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;
}
