Bug 793267. Add support for [Unforgeable] in WebIDL. r=peterv

Unforgeable attributes are defined directly on the object, not on the
prototype.  So we keep them in a separate spec array and define them
during object creation as needed.

This means that we have to pass that separate spec array to the Xray
helpers, unfortunately, which somewhat complicates those.
This commit is contained in:
Boris Zbarsky 2012-10-24 16:10:49 -04:00
parent e76c2ec778
commit fbeee7dfd0
8 changed files with 255 additions and 67 deletions

View File

@ -88,6 +88,14 @@ DefinePrefable(JSContext* cx, JSObject* obj, Prefable<T>* props)
return true;
}
bool
DefineUnforgeableAttributes(JSContext* cx, JSObject* obj,
Prefable<JSPropertySpec>* props)
{
return DefinePrefable(cx, obj, props);
}
// We should use JSFunction objects for interface objects, but we need a custom
// hasInstance hook because we have new interface objects on prototype chains of
// old (XPConnect-based) bindings. Because Function.prototype.toString throws if
@ -499,49 +507,58 @@ XrayResolveProperty(JSContext* cx, JSObject* wrapper, jsid id,
}
}
if (nativeProperties->attributes) {
Prefable<JSPropertySpec>* attr;
for (attr = nativeProperties->attributes; attr->specs; ++attr) {
if (attr->enabled) {
// Set i to be the index into our full list of ids/specs that we're
// looking at now.
size_t i = attr->specs - nativeProperties->attributeSpecs;
for ( ; nativeProperties->attributeIds[i] != JSID_VOID; ++i) {
if (id == nativeProperties->attributeIds[i]) {
JSPropertySpec& attrSpec = nativeProperties->attributeSpecs[i];
// Because of centralization, we need to make sure we fault in the
// JitInfos as well. At present, until the JSAPI changes, the easiest
// way to do this is wrap them up as functions ourselves.
desc->attrs = attrSpec.flags & ~JSPROP_NATIVE_ACCESSORS;
// They all have getters, so we can just make it.
JSObject *global = JS_GetGlobalForObject(cx, wrapper);
JSFunction *fun = JS_NewFunction(cx, (JSNative)attrSpec.getter.op,
0, 0, global, nullptr);
if (!fun)
return false;
SET_JITINFO(fun, attrSpec.getter.info);
JSObject *funobj = JS_GetFunctionObject(fun);
desc->getter = js::CastAsJSPropertyOp(funobj);
desc->attrs |= JSPROP_GETTER;
if (attrSpec.setter.op) {
// We have a setter! Make it.
fun = JS_NewFunction(cx, (JSNative)attrSpec.setter.op, 1, 0,
global, nullptr);
JSPropertySpec* attributeSpecs = nativeProperties->attributeSpecs;
Prefable<JSPropertySpec>* attr = nativeProperties->attributes;
jsid* attributeIds = nativeProperties->attributeIds;
// Do the attribute stuff for attributes, then for unforgeable attributes
for (int attrIteration = 0; attrIteration < 2; ++attrIteration) {
if (attr) {
for (; attr->specs; ++attr) {
if (attr->enabled) {
// Set i to be the index into our full list of ids/specs that we're
// looking at now.
size_t i = attr->specs - attributeSpecs;
for ( ; attributeIds[i] != JSID_VOID; ++i) {
if (id == attributeIds[i]) {
JSPropertySpec& attrSpec = attributeSpecs[i];
// Because of centralization, we need to make sure we fault in the
// JitInfos as well. At present, until the JSAPI changes, the easiest
// way to do this is wrap them up as functions ourselves.
desc->attrs = attrSpec.flags & ~JSPROP_NATIVE_ACCESSORS;
// They all have getters, so we can just make it.
JSObject *global = JS_GetGlobalForObject(cx, wrapper);
JSFunction *fun = JS_NewFunction(cx, (JSNative)attrSpec.getter.op,
0, 0, global, nullptr);
if (!fun)
return false;
SET_JITINFO(fun, attrSpec.setter.info);
funobj = JS_GetFunctionObject(fun);
desc->setter = js::CastAsJSStrictPropertyOp(funobj);
desc->attrs |= JSPROP_SETTER;
} else {
desc->setter = nullptr;
SET_JITINFO(fun, attrSpec.getter.info);
JSObject *funobj = JS_GetFunctionObject(fun);
desc->getter = js::CastAsJSPropertyOp(funobj);
desc->attrs |= JSPROP_GETTER;
if (attrSpec.setter.op) {
// We have a setter! Make it.
fun = JS_NewFunction(cx, (JSNative)attrSpec.setter.op, 1, 0,
global, nullptr);
if (!fun)
return false;
SET_JITINFO(fun, attrSpec.setter.info);
funobj = JS_GetFunctionObject(fun);
desc->setter = js::CastAsJSStrictPropertyOp(funobj);
desc->attrs |= JSPROP_SETTER;
} else {
desc->setter = nullptr;
}
desc->obj = wrapper;
return true;
}
desc->obj = wrapper;
return true;
}
}
}
}
attributeSpecs = nativeProperties->unforgeableAttributeSpecs;
attr = nativeProperties->unforgeableAttributes;
attributeIds = nativeProperties->unforgeableAttributeIds;
}
if (nativeProperties->constants) {
@ -608,21 +625,30 @@ XrayEnumerateProperties(JS::AutoIdVector& props,
}
}
if (nativeProperties->attributes) {
Prefable<JSPropertySpec>* attr;
for (attr = nativeProperties->attributes; attr->specs; ++attr) {
if (attr->enabled) {
// Set i to be the index into our full list of ids/specs that we're
// looking at now.
size_t i = attr->specs - nativeProperties->attributeSpecs;
for ( ; nativeProperties->attributeIds[i] != JSID_VOID; ++i) {
if ((nativeProperties->attributeSpecs[i].flags & JSPROP_ENUMERATE) &&
!props.append(nativeProperties->attributeIds[i])) {
return false;
JSPropertySpec* attributeSpecs = nativeProperties->attributeSpecs;
Prefable<JSPropertySpec>* attr = nativeProperties->attributes;
jsid* attributeIds = nativeProperties->attributeIds;
// Do the attribute stuff for attributes, then for unforgeable attributes
for (int attrIteration = 0; attrIteration < 2; ++attrIteration) {
if (attr) {
for (; attr->specs; ++attr) {
if (attr->enabled) {
// Set i to be the index into our full list of ids/specs that we're
// looking at now.
size_t i = attr->specs - attributeSpecs;
for ( ; attributeIds[i] != JSID_VOID; ++i) {
if ((attributeSpecs[i].flags & JSPROP_ENUMERATE) &&
!props.append(attributeIds[i])) {
return false;
}
}
}
}
}
attributeSpecs = nativeProperties->unforgeableAttributeSpecs;
attr = nativeProperties->unforgeableAttributes;
attributeIds = nativeProperties->unforgeableAttributeIds;
}
if (nativeProperties->constants) {

View File

@ -350,6 +350,9 @@ struct NativeProperties
Prefable<JSPropertySpec>* attributes;
jsid* attributeIds;
JSPropertySpec* attributeSpecs;
Prefable<JSPropertySpec>* unforgeableAttributes;
jsid* unforgeableAttributeIds;
JSPropertySpec* unforgeableAttributeSpecs;
Prefable<ConstantSpec>* constants;
jsid* constantIds;
ConstantSpec* constantSpecs;
@ -402,6 +405,13 @@ CreateInterfaceObjects(JSContext* cx, JSObject* global, JSObject* receiver,
const NativeProperties* chromeProperties,
const char* name);
/*
* Define the unforgeable attributes on an object.
*/
bool
DefineUnforgeableAttributes(JSContext* cx, JSObject* obj,
Prefable<JSPropertySpec>* props);
inline bool
MaybeWrapValue(JSContext* cx, JSObject* obj, JS::Value* vp)
{

View File

@ -136,7 +136,7 @@ DOMJSClass Class = {
%s, /* trace */
JSCLASS_NO_INTERNAL_MEMBERS
},
%s
%s
};
""" % (self.descriptor.interface.identifier.name,
ADDPROPERTY_HOOK_NAME if self.descriptor.concrete and not self.descriptor.workers and self.descriptor.wrapperCache else 'JS_PropertyStub',
@ -892,6 +892,7 @@ class PropertyDefiner:
# We only need Xrays for methods, attributes and constants, but in
# workers there are no Xrays.
return (self.name is "Methods" or self.name is "Attributes" or
self.name is "UnforgeableAttributes" or
self.name is "Constants") and not self.descriptor.workers
def __str__(self):
@ -1084,19 +1085,30 @@ class MethodDefiner(PropertyDefiner):
pref, specData, doIdArrays)
class AttrDefiner(PropertyDefiner):
def __init__(self, descriptor, name):
def __init__(self, descriptor, name, unforgeable):
PropertyDefiner.__init__(self, descriptor, name)
self.name = name
attributes = [m for m in descriptor.interface.members if m.isAttr()]
attributes = [m for m in descriptor.interface.members
if m.isAttr() and m.isUnforgeable() == unforgeable]
self.chrome = [m for m in attributes if isChromeOnly(m)]
self.regular = [m for m in attributes if not isChromeOnly(m)]
self.unforgeable = unforgeable
if unforgeable and len(attributes) != 0 and descriptor.proxy:
raise TypeError("Unforgeable properties are not supported on "
"proxy bindings without [NamedPropertiesObject]. "
"And not even supported on the ones with "
"[NamedPropertiesObject] yet, but we should fix "
"that, since they're safe there.")
def generateArray(self, array, name, doIdArrays):
if len(array) == 0:
return ""
def flags(attr):
return "JSPROP_SHARED | JSPROP_ENUMERATE | JSPROP_NATIVE_ACCESSORS"
unforgeable = " | JSPROP_PERMANENT" if self.unforgeable else ""
return ("JSPROP_SHARED | JSPROP_ENUMERATE | JSPROP_NATIVE_ACCESSORS" +
unforgeable)
def getter(attr):
native = ("genericLenientGetter" if attr.hasLenientThis()
@ -1155,16 +1167,19 @@ class PropertyArrays():
def __init__(self, descriptor):
self.staticMethods = MethodDefiner(descriptor, "StaticMethods", True)
self.methods = MethodDefiner(descriptor, "Methods", False)
self.attrs = AttrDefiner(descriptor, "Attributes")
self.attrs = AttrDefiner(descriptor, "Attributes", unforgeable=False)
self.unforgeableAttrs = AttrDefiner(descriptor, "UnforgeableAttributes",
unforgeable=True)
self.consts = ConstDefiner(descriptor, "Constants")
@staticmethod
def arrayNames():
return [ "staticMethods", "methods", "attrs", "consts" ]
return [ "staticMethods", "methods", "attrs", "unforgeableAttrs",
"consts" ]
@staticmethod
def xrayRelevantArrayNames():
return [ "methods", "attrs", "consts" ]
return [ "methods", "attrs", "unforgeableAttrs", "consts" ]
def hasChromeOnly(self):
return any(getattr(self, a).hasChromeOnly() for a in self.arrayNames())
@ -1311,10 +1326,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
else:
properties = "nullptr"
if self.properties.hasChromeOnly():
if self.descriptor.workers:
accessCheck = "mozilla::dom::workers::GetWorkerPrivateFromContext(aCx)->IsChromeWorker()"
else:
accessCheck = "xpc::AccessCheck::isChrome(aGlobal)"
accessCheck = GetAccessCheck(self.descriptor, "aGlobal")
chromeProperties = accessCheck + " ? &sChromeOnlyNativeProperties : nullptr"
else:
chromeProperties = "nullptr"
@ -1514,14 +1526,59 @@ def CreateBindingJSObject(descriptor, parent):
"""
return create % parent
def GetAccessCheck(descriptor, globalName):
"""
globalName is the name of the global JSObject*
returns a string
"""
if descriptor.workers:
accessCheck = "mozilla::dom::workers::GetWorkerPrivateFromContext(aCx)->IsChromeWorker()"
else:
accessCheck = "xpc::AccessCheck::isChrome(%s)" % globalName
return accessCheck
def InitUnforgeableProperties(descriptor, properties):
"""
properties is a PropertyArrays instance
"""
defineUnforgeables = ("if (!DefineUnforgeableAttributes(aCx, obj, %s)) {\n"
" return nullptr;\n"
"}")
unforgeableAttrs = properties.unforgeableAttrs
unforgeables = []
if unforgeableAttrs.hasNonChromeOnly():
unforgeables.append(CGGeneric(defineUnforgeables %
unforgeableAttrs.variableName(False)))
if unforgeableAttrs.hasChromeOnly():
unforgeables.append(
CGIfWrapper(CGGeneric(defineUnforgeables %
unforgeableAttrs.variableName(True)),
GetAccessCheck(descriptor, "global")))
return CGIndenter(CGWrapper(
CGList(unforgeables, "\n"),
pre=("\n"
"// Important: do unforgeable property setup after we have handed\n"
"// over ownership of the C++ object to obj as needed, so that if\n"
"// we fail and it ends up GCed it won't have problems in the\n"
"// finalizer trying to drop its ownership of the C++ object.\n"),
post="\n")).define() if len(unforgeables) > 0 else ""
class CGWrapWithCacheMethod(CGAbstractMethod):
def __init__(self, descriptor):
"""
Create a wrapper JSObject for a given native that implements nsWrapperCache.
properties should be a PropertyArrays instance.
"""
def __init__(self, descriptor, properties):
assert descriptor.interface.hasInterfacePrototypeObject()
args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aScope'),
Argument(descriptor.nativeType + '*', 'aObject'),
Argument('nsWrapperCache*', 'aCache'),
Argument('bool*', 'aTriedToWrap')]
CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'JSObject*', args)
self.properties = properties
def definition_body(self):
if self.descriptor.workers:
@ -1544,11 +1601,12 @@ class CGWrapWithCacheMethod(CGAbstractMethod):
}
%s
%s
aCache->SetWrapper(obj);
return obj;""" % (CheckPref(self.descriptor, "global", "*aTriedToWrap", "NULL", "aCache"),
CreateBindingJSObject(self.descriptor, "parent"))
CreateBindingJSObject(self.descriptor, "parent"),
InitUnforgeableProperties(self.descriptor, self.properties))
class CGWrapMethod(CGAbstractMethod):
def __init__(self, descriptor):
@ -1562,12 +1620,19 @@ class CGWrapMethod(CGAbstractMethod):
return " return Wrap(aCx, aScope, aObject, aObject, aTriedToWrap);"
class CGWrapNonWrapperCacheMethod(CGAbstractMethod):
def __init__(self, descriptor):
"""
Create a wrapper JSObject for a given native that does not implement
nsWrapperCache.
properties should be a PropertyArrays instance.
"""
def __init__(self, descriptor, properties):
# XXX can we wrap if we don't have an interface prototype object?
assert descriptor.interface.hasInterfacePrototypeObject()
args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aScope'),
Argument(descriptor.nativeType + '*', 'aObject')]
CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'JSObject*', args)
self.properties = properties
def definition_body(self):
return """
@ -1578,8 +1643,9 @@ class CGWrapNonWrapperCacheMethod(CGAbstractMethod):
}
%s
return obj;""" % CreateBindingJSObject(self.descriptor, "global")
%s
return obj;""" % (CreateBindingJSObject(self.descriptor, "global"),
InitUnforgeableProperties(self.descriptor, self.properties))
builtinNames = {
IDLType.Tags.bool: 'bool',
@ -5331,10 +5397,11 @@ class CGDescriptor(CGThing):
cgThings.append(CGDOMJSClass(descriptor))
if descriptor.wrapperCache:
cgThings.append(CGWrapWithCacheMethod(descriptor))
cgThings.append(CGWrapWithCacheMethod(descriptor, properties))
cgThings.append(CGWrapMethod(descriptor))
else:
cgThings.append(CGWrapNonWrapperCacheMethod(descriptor))
cgThings.append(CGWrapNonWrapperCacheMethod(descriptor,
properties))
cgThings = CGList((CGIndenter(t, declareOnly=True) for t in cgThings), "\n")
cgThings = CGWrapper(cgThings, pre='\n', post='\n')

View File

@ -517,6 +517,22 @@ class IDLInterface(IDLObjectWithScope):
self.parent.identifier.name),
[self.location, self.parent.location])
# Now make sure our parent doesn't have any [Unforgeable]
# attributes. We don't need to check its ancestors, because it has
# already checked those. We don't need to check its consequential
# interfaces, because it has already imported those into its
# .members.
unforgeableParentMembers = [
attr for attr in parent.members
if attr.isAttr() and attr.isUnforgeable() ]
if len(unforgeableParentMembers) != 0:
locs = [self.location, parent.location]
locs.extend(attr.location for attr in unforgeableParentMembers)
raise WebIDLError("Interface %s inherits from %s, which has "
"[Unforgeable] members" %
(self.identifier.name, parent.identifier.name),
locs)
for iface in self.implementedInterfaces:
iface.finish(scope)
@ -1946,6 +1962,7 @@ class IDLAttribute(IDLInterfaceMember):
self.inherit = inherit
self.static = static
self.lenientThis = False
self._unforgeable = False
if readonly and inherit:
raise WebIDLError("An attribute cannot be both 'readonly' and 'inherit'",
@ -2007,6 +2024,11 @@ class IDLAttribute(IDLInterfaceMember):
raise WebIDLError("[LenientThis] is only allowed on non-static "
"attributes", [attr.location, self.location])
self.lenientThis = True
elif identifier == "Unforgeable":
if not self.readonly:
raise WebIDLError("[Unforgeable] is only allowed on readonly "
"attributes", [attr.location, self.location])
self._unforgeable = True
IDLInterfaceMember.handleExtendedAttribute(self, attr)
def resolve(self, parentScope):
@ -2021,6 +2043,9 @@ class IDLAttribute(IDLInterfaceMember):
def hasLenientThis(self):
return self.lenientThis
def isUnforgeable(self):
return self._unforgeable
class IDLArgument(IDLObjectWithIdentifier):
def __init__(self, location, identifier, type, optional=False, defaultValue=None, variadic=False, dictionaryMember=False):
IDLObjectWithIdentifier.__init__(self, location, None, identifier)
@ -2469,10 +2494,14 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
raise WebIDLError("Methods must not be flagged as "
"[GetterInfallible]",
[attr.location, self.location])
if identifier == "SetterInfallible":
elif identifier == "SetterInfallible":
raise WebIDLError("Methods must not be flagged as "
"[SetterInfallible]",
[attr.location, self.location])
elif identifier == "Unforgeable":
raise WebIDLError("Methods must not be flagged as "
"[Unforgeable]",
[attr.location, self.location])
IDLInterfaceMember.handleExtendedAttribute(self, attr)
class IDLImplementsStatement(IDLObject):

View File

@ -0,0 +1,50 @@
def WebIDLTest(parser, harness):
threw = False
try:
parser.parse("""
interface Child : Parent {
};
interface Parent {
[Unforgeable] readonly attribute long foo;
};
""")
results = parser.finish()
except:
threw = True
harness.ok(threw, "Should have thrown.")
parser = parser.reset();
threw = False
try:
parser.parse("""
interface Child : Parent {
};
interface Parent {};
interface Consequential {
[Unforgeable] readonly attribute long foo;
};
Parent implements Consequential;
""")
results = parser.finish()
except:
threw = True
harness.ok(threw, "Should have thrown.")
parser = parser.reset();
threw = False
try:
parser.parse("""
interface iface {
[Unforgeable] attribute long foo;
};
""")
results = parser.finish()
except:
threw = True
harness.ok(threw, "Should have thrown.")

View File

@ -415,6 +415,8 @@ public:
// Miscellania
int32_t AttrWithLenientThis();
void SetAttrWithLenientThis(int32_t);
uint32_t UnforgeableAttr();
uint32_t UnforgeableAttr2();
// Methods and properties imported via "implements"
bool ImplementedProperty();

View File

@ -333,6 +333,8 @@ interface TestInterface {
// Miscellania
[LenientThis] attribute long attrWithLenientThis;
[Unforgeable] readonly attribute long unforgeableAttr;
[Unforgeable, ChromeOnly] readonly attribute long unforgeableAttr2;
// If you add things here, add them to TestExampleGen as well
};

View File

@ -303,6 +303,8 @@ interface TestExampleInterface {
// Miscellania
[LenientThis] attribute long attrWithLenientThis;
[Unforgeable] readonly attribute long unforgeableAttr;
[Unforgeable, ChromeOnly] readonly attribute long unforgeableAttr2;
// If you add things here, add them to TestCodeGen as well
};