mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
ba1d6a9c71
While adding the CPOW flag for xray waivers, I discovered a bunch of inconsistency and sloppiness with respect to our handling of object ids, and a general lack of clarity about when the id included flags or not. Given the fact that I'm removing static callability for CPOWs, we _could_ just get rid of the flags, and store the xray waiver state on the answer-side only. But I eventually decided that these kinds of flags (which are accessible to both the Answer _and_ the Owner) had enough potential utility that they were worth cleaning up. It's worth noting that that utility comes with the large caveat that the flags can't be trusted for security-sensitive decisions (at least in the parent->child case), since they could be forged by a compromised child.
583 lines
14 KiB
C++
583 lines
14 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* vim: set ts=4 sw=4 et 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 "JavaScriptShared.h"
|
|
#include "mozilla/dom/BindingUtils.h"
|
|
#include "mozilla/dom/TabChild.h"
|
|
#include "jsfriendapi.h"
|
|
#include "xpcprivate.h"
|
|
#include "mozilla/Preferences.h"
|
|
|
|
using namespace js;
|
|
using namespace JS;
|
|
using namespace mozilla;
|
|
using namespace mozilla::jsipc;
|
|
|
|
IdToObjectMap::IdToObjectMap()
|
|
: table_(SystemAllocPolicy())
|
|
{
|
|
}
|
|
|
|
bool
|
|
IdToObjectMap::init()
|
|
{
|
|
if (table_.initialized())
|
|
return true;
|
|
return table_.init(32);
|
|
}
|
|
|
|
void
|
|
IdToObjectMap::trace(JSTracer *trc)
|
|
{
|
|
for (Table::Range r(table_.all()); !r.empty(); r.popFront()) {
|
|
DebugOnly<JSObject *> prior = r.front().value().get();
|
|
JS_CallObjectTracer(trc, &r.front().value(), "ipc-object");
|
|
}
|
|
}
|
|
|
|
void
|
|
IdToObjectMap::sweep()
|
|
{
|
|
for (Table::Enum e(table_); !e.empty(); e.popFront()) {
|
|
JS::Heap<JSObject *> *objp = &e.front().value();
|
|
JS_UpdateWeakPointerAfterGC(objp);
|
|
if (!*objp)
|
|
e.removeFront();
|
|
}
|
|
}
|
|
|
|
JSObject *
|
|
IdToObjectMap::find(ObjectId id)
|
|
{
|
|
Table::Ptr p = table_.lookup(id);
|
|
if (!p)
|
|
return nullptr;
|
|
return p->value();
|
|
}
|
|
|
|
bool
|
|
IdToObjectMap::add(ObjectId id, JSObject *obj)
|
|
{
|
|
return table_.put(id, obj);
|
|
}
|
|
|
|
void
|
|
IdToObjectMap::remove(ObjectId id)
|
|
{
|
|
table_.remove(id);
|
|
}
|
|
|
|
ObjectToIdMap::ObjectToIdMap()
|
|
: table_(nullptr)
|
|
{
|
|
}
|
|
|
|
ObjectToIdMap::~ObjectToIdMap()
|
|
{
|
|
if (table_) {
|
|
dom::AddForDeferredFinalization<Table, nsAutoPtr>(table_);
|
|
table_ = nullptr;
|
|
}
|
|
}
|
|
|
|
bool
|
|
ObjectToIdMap::init()
|
|
{
|
|
if (table_)
|
|
return true;
|
|
|
|
table_ = new Table(SystemAllocPolicy());
|
|
return table_ && table_->init(32);
|
|
}
|
|
|
|
void
|
|
ObjectToIdMap::trace(JSTracer *trc)
|
|
{
|
|
for (Table::Enum e(*table_); !e.empty(); e.popFront()) {
|
|
JSObject *obj = e.front().key();
|
|
JS_CallUnbarrieredObjectTracer(trc, &obj, "ipc-object");
|
|
if (obj != e.front().key())
|
|
e.rekeyFront(obj);
|
|
}
|
|
}
|
|
|
|
void
|
|
ObjectToIdMap::sweep()
|
|
{
|
|
for (Table::Enum e(*table_); !e.empty(); e.popFront()) {
|
|
JSObject *obj = e.front().key();
|
|
JS_UpdateWeakPointerAfterGCUnbarriered(&obj);
|
|
if (!obj)
|
|
e.removeFront();
|
|
else if (obj != e.front().key())
|
|
e.rekeyFront(obj);
|
|
}
|
|
}
|
|
|
|
ObjectId
|
|
ObjectToIdMap::find(JSObject *obj)
|
|
{
|
|
Table::Ptr p = table_->lookup(obj);
|
|
if (!p)
|
|
return ObjectId::nullId();
|
|
return p->value();
|
|
}
|
|
|
|
bool
|
|
ObjectToIdMap::add(JSContext *cx, JSObject *obj, ObjectId id)
|
|
{
|
|
if (!table_->put(obj, id))
|
|
return false;
|
|
JS_StoreObjectPostBarrierCallback(cx, keyMarkCallback, obj, table_);
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* This function is called during minor GCs for each key in the HashMap that has
|
|
* been moved.
|
|
*/
|
|
/* static */ void
|
|
ObjectToIdMap::keyMarkCallback(JSTracer *trc, JSObject *key, void *data)
|
|
{
|
|
Table *table = static_cast<Table*>(data);
|
|
JSObject *prior = key;
|
|
JS_CallUnbarrieredObjectTracer(trc, &key, "ObjectIdCache::table_ key");
|
|
table->rekeyIfMoved(prior, key);
|
|
}
|
|
|
|
void
|
|
ObjectToIdMap::remove(JSObject *obj)
|
|
{
|
|
table_->remove(obj);
|
|
}
|
|
|
|
bool JavaScriptShared::sLoggingInitialized;
|
|
bool JavaScriptShared::sLoggingEnabled;
|
|
bool JavaScriptShared::sStackLoggingEnabled;
|
|
|
|
JavaScriptShared::JavaScriptShared(JSRuntime *rt)
|
|
: rt_(rt),
|
|
refcount_(1),
|
|
nextSerialNumber_(1)
|
|
{
|
|
if (!sLoggingInitialized) {
|
|
sLoggingInitialized = true;
|
|
|
|
if (PR_GetEnv("MOZ_CPOW_LOG")) {
|
|
sLoggingEnabled = true;
|
|
sStackLoggingEnabled = true;
|
|
} else {
|
|
Preferences::AddBoolVarCache(&sLoggingEnabled,
|
|
"dom.ipc.cpows.log.enabled", false);
|
|
Preferences::AddBoolVarCache(&sStackLoggingEnabled,
|
|
"dom.ipc.cpows.log.stack", false);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
JavaScriptShared::init()
|
|
{
|
|
if (!objects_.init())
|
|
return false;
|
|
if (!cpows_.init())
|
|
return false;
|
|
if (!objectIds_.init())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
JavaScriptShared::decref()
|
|
{
|
|
refcount_--;
|
|
if (!refcount_)
|
|
delete this;
|
|
}
|
|
|
|
void
|
|
JavaScriptShared::incref()
|
|
{
|
|
refcount_++;
|
|
}
|
|
|
|
bool
|
|
JavaScriptShared::convertIdToGeckoString(JSContext *cx, JS::HandleId id, nsString *to)
|
|
{
|
|
RootedValue idval(cx);
|
|
if (!JS_IdToValue(cx, id, &idval))
|
|
return false;
|
|
|
|
RootedString str(cx, ToString(cx, idval));
|
|
if (!str)
|
|
return false;
|
|
|
|
return AssignJSString(cx, *to, str);
|
|
}
|
|
|
|
bool
|
|
JavaScriptShared::convertGeckoStringToId(JSContext *cx, const nsString &from, JS::MutableHandleId to)
|
|
{
|
|
RootedString str(cx, JS_NewUCStringCopyN(cx, from.BeginReading(), from.Length()));
|
|
if (!str)
|
|
return false;
|
|
|
|
return JS_StringToId(cx, str, to);
|
|
}
|
|
|
|
bool
|
|
JavaScriptShared::toVariant(JSContext *cx, JS::HandleValue from, JSVariant *to)
|
|
{
|
|
switch (JS_TypeOfValue(cx, from)) {
|
|
case JSTYPE_VOID:
|
|
*to = UndefinedVariant();
|
|
return true;
|
|
|
|
case JSTYPE_OBJECT:
|
|
case JSTYPE_FUNCTION:
|
|
{
|
|
RootedObject obj(cx, from.toObjectOrNull());
|
|
if (!obj) {
|
|
MOZ_ASSERT(from == JSVAL_NULL);
|
|
*to = NullVariant();
|
|
return true;
|
|
}
|
|
|
|
if (xpc_JSObjectIsID(cx, obj)) {
|
|
JSIID iid;
|
|
const nsID *id = xpc_JSObjectToID(cx, obj);
|
|
ConvertID(*id, &iid);
|
|
*to = iid;
|
|
return true;
|
|
}
|
|
|
|
ObjectVariant objVar;
|
|
if (!toObjectVariant(cx, obj, &objVar))
|
|
return false;
|
|
*to = objVar;
|
|
return true;
|
|
}
|
|
|
|
case JSTYPE_STRING:
|
|
{
|
|
nsAutoJSString autoStr;
|
|
if (!autoStr.init(cx, from))
|
|
return false;
|
|
*to = autoStr;
|
|
return true;
|
|
}
|
|
|
|
case JSTYPE_NUMBER:
|
|
if (from.isInt32())
|
|
*to = double(from.toInt32());
|
|
else
|
|
*to = from.toDouble();
|
|
return true;
|
|
|
|
case JSTYPE_BOOLEAN:
|
|
*to = from.toBoolean();
|
|
return true;
|
|
|
|
default:
|
|
MOZ_ASSERT(false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool
|
|
JavaScriptShared::fromVariant(JSContext *cx, const JSVariant &from, MutableHandleValue to)
|
|
{
|
|
switch (from.type()) {
|
|
case JSVariant::TUndefinedVariant:
|
|
to.set(UndefinedValue());
|
|
return true;
|
|
|
|
case JSVariant::TNullVariant:
|
|
to.set(NullValue());
|
|
return true;
|
|
|
|
case JSVariant::TObjectVariant:
|
|
{
|
|
JSObject *obj = fromObjectVariant(cx, from.get_ObjectVariant());
|
|
if (!obj)
|
|
return false;
|
|
to.set(ObjectValue(*obj));
|
|
return true;
|
|
}
|
|
|
|
case JSVariant::Tdouble:
|
|
to.set(JS_NumberValue(from.get_double()));
|
|
return true;
|
|
|
|
case JSVariant::Tbool:
|
|
to.set(BOOLEAN_TO_JSVAL(from.get_bool()));
|
|
return true;
|
|
|
|
case JSVariant::TnsString:
|
|
{
|
|
const nsString &old = from.get_nsString();
|
|
JSString *str = JS_NewUCStringCopyN(cx, old.BeginReading(), old.Length());
|
|
if (!str)
|
|
return false;
|
|
to.set(StringValue(str));
|
|
return true;
|
|
}
|
|
|
|
case JSVariant::TJSIID:
|
|
{
|
|
nsID iid;
|
|
const JSIID &id = from.get_JSIID();
|
|
ConvertID(id, &iid);
|
|
|
|
JSCompartment *compartment = GetContextCompartment(cx);
|
|
RootedObject global(cx, JS_GetGlobalForCompartmentOrNull(cx, compartment));
|
|
JSObject *obj = xpc_NewIDObject(cx, global, iid);
|
|
if (!obj)
|
|
return false;
|
|
to.set(ObjectValue(*obj));
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* static */ void
|
|
JavaScriptShared::ConvertID(const nsID &from, JSIID *to)
|
|
{
|
|
to->m0() = from.m0;
|
|
to->m1() = from.m1;
|
|
to->m2() = from.m2;
|
|
to->m3_0() = from.m3[0];
|
|
to->m3_1() = from.m3[1];
|
|
to->m3_2() = from.m3[2];
|
|
to->m3_3() = from.m3[3];
|
|
to->m3_4() = from.m3[4];
|
|
to->m3_5() = from.m3[5];
|
|
to->m3_6() = from.m3[6];
|
|
to->m3_7() = from.m3[7];
|
|
}
|
|
|
|
/* static */ void
|
|
JavaScriptShared::ConvertID(const JSIID &from, nsID *to)
|
|
{
|
|
to->m0 = from.m0();
|
|
to->m1 = from.m1();
|
|
to->m2 = from.m2();
|
|
to->m3[0] = from.m3_0();
|
|
to->m3[1] = from.m3_1();
|
|
to->m3[2] = from.m3_2();
|
|
to->m3[3] = from.m3_3();
|
|
to->m3[4] = from.m3_4();
|
|
to->m3[5] = from.m3_5();
|
|
to->m3[6] = from.m3_6();
|
|
to->m3[7] = from.m3_7();
|
|
}
|
|
|
|
JSObject *
|
|
JavaScriptShared::findObjectById(JSContext *cx, const ObjectId &objId)
|
|
{
|
|
RootedObject obj(cx, findObjectById(objId));
|
|
if (!obj) {
|
|
JS_ReportError(cx, "operation not possible on dead CPOW");
|
|
return nullptr;
|
|
}
|
|
|
|
// Each process has a dedicated compartment for CPOW targets. All CPOWs
|
|
// from the other process point to objects in this scope. From there, they
|
|
// can access objects in other compartments using cross-compartment
|
|
// wrappers.
|
|
JSAutoCompartment ac(cx, scopeForTargetObjects());
|
|
if (!JS_WrapObject(cx, &obj))
|
|
return nullptr;
|
|
return obj;
|
|
}
|
|
|
|
static const uint64_t DefaultPropertyOp = 1;
|
|
static const uint64_t UnknownPropertyOp = 2;
|
|
|
|
bool
|
|
JavaScriptShared::fromDescriptor(JSContext *cx, Handle<JSPropertyDescriptor> desc,
|
|
PPropertyDescriptor *out)
|
|
{
|
|
out->attrs() = desc.attributes();
|
|
if (!toVariant(cx, desc.value(), &out->value()))
|
|
return false;
|
|
|
|
JS_ASSERT(desc.object());
|
|
if (!toObjectVariant(cx, desc.object(), &out->obj()))
|
|
return false;
|
|
|
|
if (!desc.getter()) {
|
|
out->getter() = 0;
|
|
} else if (desc.hasGetterObject()) {
|
|
JSObject *getter = desc.getterObject();
|
|
ObjectVariant objVar;
|
|
if (!toObjectVariant(cx, getter, &objVar))
|
|
return false;
|
|
out->getter() = objVar;
|
|
} else {
|
|
if (desc.getter() == JS_PropertyStub)
|
|
out->getter() = DefaultPropertyOp;
|
|
else
|
|
out->getter() = UnknownPropertyOp;
|
|
}
|
|
|
|
if (!desc.setter()) {
|
|
out->setter() = 0;
|
|
} else if (desc.hasSetterObject()) {
|
|
JSObject *setter = desc.setterObject();
|
|
ObjectVariant objVar;
|
|
if (!toObjectVariant(cx, setter, &objVar))
|
|
return false;
|
|
out->setter() = objVar;
|
|
} else {
|
|
if (desc.setter() == JS_StrictPropertyStub)
|
|
out->setter() = DefaultPropertyOp;
|
|
else
|
|
out->setter() = UnknownPropertyOp;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
UnknownPropertyStub(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp)
|
|
{
|
|
JS_ReportError(cx, "getter could not be wrapped via CPOWs");
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
UnknownStrictPropertyStub(JSContext *cx, HandleObject obj, HandleId id, bool strict, MutableHandleValue vp)
|
|
{
|
|
JS_ReportError(cx, "setter could not be wrapped via CPOWs");
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
JavaScriptShared::toDescriptor(JSContext *cx, const PPropertyDescriptor &in,
|
|
MutableHandle<JSPropertyDescriptor> out)
|
|
{
|
|
out.setAttributes(in.attrs());
|
|
if (!fromVariant(cx, in.value(), out.value()))
|
|
return false;
|
|
Rooted<JSObject*> obj(cx);
|
|
obj = fromObjectVariant(cx, in.obj());
|
|
if (!obj)
|
|
return false;
|
|
out.object().set(obj);
|
|
|
|
if (in.getter().type() == GetterSetter::Tuint64_t && !in.getter().get_uint64_t()) {
|
|
out.setGetter(nullptr);
|
|
} else if (in.attrs() & JSPROP_GETTER) {
|
|
Rooted<JSObject*> getter(cx);
|
|
getter = fromObjectVariant(cx, in.getter().get_ObjectVariant());
|
|
if (!getter)
|
|
return false;
|
|
out.setGetter(JS_DATA_TO_FUNC_PTR(JSPropertyOp, getter.get()));
|
|
} else {
|
|
if (in.getter().get_uint64_t() == DefaultPropertyOp)
|
|
out.setGetter(JS_PropertyStub);
|
|
else
|
|
out.setGetter(UnknownPropertyStub);
|
|
}
|
|
|
|
if (in.setter().type() == GetterSetter::Tuint64_t && !in.setter().get_uint64_t()) {
|
|
out.setSetter(nullptr);
|
|
} else if (in.attrs() & JSPROP_SETTER) {
|
|
Rooted<JSObject*> setter(cx);
|
|
setter = fromObjectVariant(cx, in.setter().get_ObjectVariant());
|
|
if (!setter)
|
|
return false;
|
|
out.setSetter(JS_DATA_TO_FUNC_PTR(JSStrictPropertyOp, setter.get()));
|
|
} else {
|
|
if (in.setter().get_uint64_t() == DefaultPropertyOp)
|
|
out.setSetter(JS_StrictPropertyStub);
|
|
else
|
|
out.setSetter(UnknownStrictPropertyStub);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CpowIdHolder::ToObject(JSContext *cx, JS::MutableHandleObject objp)
|
|
{
|
|
return js_->Unwrap(cx, cpows_, objp);
|
|
}
|
|
|
|
bool
|
|
JavaScriptShared::Unwrap(JSContext *cx, const InfallibleTArray<CpowEntry> &aCpows,
|
|
JS::MutableHandleObject objp)
|
|
{
|
|
objp.set(nullptr);
|
|
|
|
if (!aCpows.Length())
|
|
return true;
|
|
|
|
RootedObject obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
|
|
if (!obj)
|
|
return false;
|
|
|
|
RootedValue v(cx);
|
|
RootedString str(cx);
|
|
for (size_t i = 0; i < aCpows.Length(); i++) {
|
|
const nsString &name = aCpows[i].name();
|
|
|
|
if (!fromVariant(cx, aCpows[i].value(), &v))
|
|
return false;
|
|
|
|
if (!JS_DefineUCProperty(cx,
|
|
obj,
|
|
name.BeginReading(),
|
|
name.Length(),
|
|
v,
|
|
JSPROP_ENUMERATE))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
objp.set(obj);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
JavaScriptShared::Wrap(JSContext *cx, HandleObject aObj, InfallibleTArray<CpowEntry> *outCpows)
|
|
{
|
|
if (!aObj)
|
|
return true;
|
|
|
|
AutoIdArray ids(cx, JS_Enumerate(cx, aObj));
|
|
if (!ids)
|
|
return false;
|
|
|
|
RootedId id(cx);
|
|
RootedValue v(cx);
|
|
for (size_t i = 0; i < ids.length(); i++) {
|
|
id = ids[i];
|
|
|
|
nsString str;
|
|
if (!convertIdToGeckoString(cx, id, &str))
|
|
return false;
|
|
|
|
if (!JS_GetPropertyById(cx, aObj, id, &v))
|
|
return false;
|
|
|
|
JSVariant var;
|
|
if (!toVariant(cx, v, &var))
|
|
return false;
|
|
|
|
outCpows->AppendElement(CpowEntry(str, var));
|
|
}
|
|
|
|
return true;
|
|
}
|