Bug 1353867 - Add code generation for array of cross origin properties. r=bzbarsky

Differential Revision: https://phabricator.services.mozilla.com/D12654

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Peter Van der Beken 2019-01-02 13:28:06 +00:00
parent 05c586c0a9
commit b816d68b07
2 changed files with 284 additions and 139 deletions

View File

@ -2088,6 +2088,75 @@ class PropertyDefiner:
interfaceMember.getExtendedAttribute("SecureContext") is not None,
nonExposureSet)
@staticmethod
def generatePrefableArrayValues(array, descriptor, specFormatter, specTerminator,
getCondition, getDataTuple,
switchToCondition=None):
"""
This method generates an array of spec entries for interface members. It returns
a tuple containing the array of spec entries and the maximum of the number of
spec entries per condition.
array is an array of interface members.
descriptor is the descriptor for the interface that array contains members of.
specFormatter is a function that takes a single argument, a tuple,
and returns a string, a spec array entry.
specTerminator is a terminator for the spec array (inserted every time
our controlling pref changes and at the end of the array).
getCondition is a callback function that takes an array entry and
returns the corresponding MemberCondition.
getDataTuple is a callback function that takes an array entry and
returns a tuple suitable to be passed to specFormatter.
switchToCondition is a function that takes a MemberCondition and an array of
previously generated spec entries. If None is passed for this function then all
the interface members should return the same value from getCondition.
"""
def unsupportedSwitchToCondition(condition, specs):
# If no specs have been added yet then this is just the first call to
# switchToCondition that we call to avoid putting a specTerminator at the
# front of the list.
if len(specs) == 0:
return
raise "Not supported"
if switchToCondition is None:
switchToCondition = unsupportedSwitchToCondition
specs = []
numSpecsInCurPrefable = 0
maxNumSpecsInPrefable = 0
# So we won't put a specTerminator at the very front of the list:
lastCondition = getCondition(array[0], descriptor)
switchToCondition(lastCondition, specs)
for member in array:
curCondition = getCondition(member, descriptor)
if lastCondition != curCondition:
# Terminate previous list
specs.append(specTerminator)
if numSpecsInCurPrefable > maxNumSpecsInPrefable:
maxNumSpecsInPrefable = numSpecsInCurPrefable
numSpecsInCurPrefable = 0
# And switch to our new condition
switchToCondition(curCondition, specs)
lastCondition = curCondition
# And the actual spec
specs.append(specFormatter(getDataTuple(member, descriptor)))
numSpecsInCurPrefable += 1
if numSpecsInCurPrefable > maxNumSpecsInPrefable:
maxNumSpecsInPrefable = numSpecsInCurPrefable
specs.append(specTerminator)
return (specs, maxNumSpecsInPrefable)
def generatePrefableArray(self, array, name, specFormatter, specTerminator,
specType, getCondition, getDataTuple):
"""
@ -2119,10 +2188,7 @@ class PropertyDefiner:
# pref control is added to members while still allowing us to define all
# the members in the smallest number of JSAPI calls.
assert len(array) != 0
# So we won't put a specTerminator at the very front of the list:
lastCondition = getCondition(array[0], self.descriptor)
specs = []
disablers = []
prefableSpecs = []
@ -2137,11 +2203,11 @@ class PropertyDefiner:
prefableWithoutDisablersTemplate = ' { nullptr, &%s_specs[%d] }'
prefCacheTemplate = '&%s[%d].disablers->enabled'
def switchToCondition(props, condition):
def switchToCondition(condition, specs):
# Remember the info about where our pref-controlled
# booleans live.
if condition.pref is not None:
props.prefCacheData.append(
self.prefCacheData.append(
(condition.pref,
prefCacheTemplate % (name, len(prefableSpecs))))
# Set up pointers to the new sets of specs inside prefableSpecs
@ -2157,28 +2223,9 @@ class PropertyDefiner:
prefableSpecs.append(prefableWithoutDisablersTemplate %
(name, len(specs)))
switchToCondition(self, lastCondition)
numSpecsInCurPrefable = 0
maxNumSpecsInPrefable = 0
for member in array:
curCondition = getCondition(member, self.descriptor)
if lastCondition != curCondition:
# Terminate previous list
specs.append(specTerminator)
if numSpecsInCurPrefable > maxNumSpecsInPrefable:
maxNumSpecsInPrefable = numSpecsInCurPrefable
numSpecsInCurPrefable = 0
# And switch to our new condition
switchToCondition(self, curCondition)
lastCondition = curCondition
# And the actual spec
specs.append(specFormatter(getDataTuple(member)))
numSpecsInCurPrefable += 1
specs.append(specTerminator)
if numSpecsInCurPrefable > maxNumSpecsInPrefable:
maxNumSpecsInPrefable = numSpecsInCurPrefable
specs, maxNumSpecsInPrefable = self.generatePrefableArrayValues(
array, self.descriptor, specFormatter, specTerminator, getCondition,
getDataTuple, switchToCondition)
prefableSpecs.append(" { nullptr, nullptr }")
specType = "const " + specType
@ -2266,7 +2313,7 @@ class MethodDefiner(PropertyDefiner):
"""
A class for defining methods on a prototype object.
"""
def __init__(self, descriptor, name, static, unforgeable=False):
def __init__(self, descriptor, name, crossOriginOnly, static, unforgeable=False):
assert not (static and unforgeable)
PropertyDefiner.__init__(self, descriptor, name)
@ -2279,6 +2326,7 @@ class MethodDefiner(PropertyDefiner):
methods = [m for m in descriptor.interface.members if
m.isMethod() and m.isStatic() == static and
MemberIsUnforgeable(m, descriptor) == unforgeable and
(not crossOriginOnly or m.getExtendedAttribute("CrossOriginCallable")) and
not m.isIdentifierLess()]
else:
methods = []
@ -2315,16 +2363,7 @@ class MethodDefiner(PropertyDefiner):
})
continue
method = {
"name": m.identifier.name,
"methodInfo": not m.isStatic(),
"length": methodLength(m),
"flags": EnumerabilityFlags(m),
"condition": PropertyDefiner.getControllingCondition(m, descriptor),
"allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"),
"returnsPromise": m.returnsPromise(),
"hasIteratorAlias": "@@iterator" in m.aliases
}
method = self.methodData(m, descriptor)
if m.isStatic():
method["nativeName"] = CppKeywords.checkMethodName(IDLToCIdentifier(m.identifier.name))
@ -2459,70 +2498,86 @@ class MethodDefiner(PropertyDefiner):
# non-static methods go on the interface prototype object
assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
@staticmethod
def methodData(m, descriptor, overrideFlags=None):
return {
"name": m.identifier.name,
"methodInfo": not m.isStatic(),
"length": methodLength(m),
"flags": EnumerabilityFlags(m) if (overrideFlags is None) else overrideFlags,
"condition": PropertyDefiner.getControllingCondition(m, descriptor),
"allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"),
"returnsPromise": m.returnsPromise(),
"hasIteratorAlias": "@@iterator" in m.aliases
}
@staticmethod
def formatSpec(fields):
if fields[0].startswith("@@"):
fields = (fields[0][2:],) + fields[1:]
return ' JS_SYM_FNSPEC(%s, %s, %s, %s, %s, %s)' % fields
return ' JS_FNSPEC("%s", %s, %s, %s, %s, %s)' % fields
@staticmethod
def specData(m, descriptor, unforgeable=False):
def flags(m, unforgeable):
unforgeable = " | JSPROP_PERMANENT | JSPROP_READONLY" if unforgeable else ""
return m["flags"] + unforgeable
if "selfHostedName" in m:
selfHostedName = '"%s"' % m["selfHostedName"]
assert not m.get("methodInfo", True)
accessor = "nullptr"
jitinfo = "nullptr"
else:
selfHostedName = "nullptr"
# When defining symbols, function name may not match symbol name
methodName = m.get("methodName", m["name"])
accessor = m.get("nativeName", IDLToCIdentifier(methodName))
if m.get("methodInfo", True):
if m.get("returnsPromise", False):
exceptionPolicy = "ConvertExceptionsToPromises"
else:
exceptionPolicy = "ThrowExceptions"
# Cast this in case the methodInfo is a
# JSTypedMethodJitInfo.
jitinfo = ("reinterpret_cast<const JSJitInfo*>(&%s_methodinfo)" % accessor)
if m.get("allowCrossOriginThis", False):
accessor = ("(GenericMethod<CrossOriginThisPolicy, %s>)" %
exceptionPolicy)
elif descriptor.interface.isOnGlobalProtoChain():
accessor = ("(GenericMethod<MaybeGlobalThisPolicy, %s>)" %
exceptionPolicy)
else:
accessor = ("(GenericMethod<NormalThisPolicy, %s>)" %
exceptionPolicy)
else:
if m.get("returnsPromise", False):
jitinfo = "&%s_methodinfo" % accessor
accessor = "StaticMethodPromiseWrapper"
else:
jitinfo = "nullptr"
return (m["name"], accessor, jitinfo, m["length"], flags(m, unforgeable), selfHostedName)
@staticmethod
def condition(m, d):
return m["condition"]
def generateArray(self, array, name):
if len(array) == 0:
return ""
def condition(m, d):
return m["condition"]
def flags(m):
unforgeable = " | JSPROP_PERMANENT | JSPROP_READONLY" if self.unforgeable else ""
return m["flags"] + unforgeable
def specData(m):
if "selfHostedName" in m:
selfHostedName = '"%s"' % m["selfHostedName"]
assert not m.get("methodInfo", True)
accessor = "nullptr"
jitinfo = "nullptr"
else:
selfHostedName = "nullptr"
# When defining symbols, function name may not match symbol name
methodName = m.get("methodName", m["name"])
accessor = m.get("nativeName", IDLToCIdentifier(methodName))
if m.get("methodInfo", True):
if m.get("returnsPromise", False):
exceptionPolicy = "ConvertExceptionsToPromises"
else:
exceptionPolicy = "ThrowExceptions"
# Cast this in case the methodInfo is a
# JSTypedMethodJitInfo.
jitinfo = ("reinterpret_cast<const JSJitInfo*>(&%s_methodinfo)" % accessor)
if m.get("allowCrossOriginThis", False):
accessor = ("(GenericMethod<CrossOriginThisPolicy, %s>)" %
exceptionPolicy)
elif self.descriptor.interface.isOnGlobalProtoChain():
accessor = ("(GenericMethod<MaybeGlobalThisPolicy, %s>)" %
exceptionPolicy)
else:
accessor = ("(GenericMethod<NormalThisPolicy, %s>)" %
exceptionPolicy)
else:
if m.get("returnsPromise", False):
jitinfo = "&%s_methodinfo" % accessor
accessor = "StaticMethodPromiseWrapper"
else:
jitinfo = "nullptr"
return (m["name"], accessor, jitinfo, m["length"], flags(m), selfHostedName)
def formatSpec(fields):
if fields[0].startswith("@@"):
fields = (fields[0][2:],) + fields[1:]
return ' JS_SYM_FNSPEC(%s, %s, %s, %s, %s, %s)' % fields
return ' JS_FNSPEC("%s", %s, %s, %s, %s, %s)' % fields
return self.generatePrefableArray(
array, name,
formatSpec,
self.formatSpec,
' JS_FS_END',
'JSFunctionSpec',
condition, specData)
self.condition, functools.partial(self.specData, unforgeable=self.unforgeable))
def IsCrossOriginWritable(attr, descriptor):
def isCrossOriginWritable(attr, descriptor):
"""
Return whether the IDLAttribute in question is cross-origin writable on the
interface represented by descriptor. This is needed to handle the fact that
@ -2543,29 +2598,27 @@ def isNonExposedNavigatorObjectGetter(attr, descriptor):
not descriptor.getDescriptor(attr.type.inner.identifier.name).register)
class AttrDefiner(PropertyDefiner):
def __init__(self, descriptor, name, static, unforgeable=False):
def __init__(self, descriptor, name, crossOriginOnly, static, unforgeable=False):
assert not (static and unforgeable)
PropertyDefiner.__init__(self, descriptor, name)
self.name = name
# Ignore non-static attributes for interfaces without a proto object
if descriptor.interface.hasInterfacePrototypeObject() or static:
attributes = [m for m in descriptor.interface.members if
m.isAttr() and m.isStatic() == static and
MemberIsUnforgeable(m, descriptor) == unforgeable and
not isNonExposedNavigatorObjectGetter(m, descriptor)]
idlAttrs = [m for m in descriptor.interface.members if
m.isAttr() and m.isStatic() == static and
MemberIsUnforgeable(m, descriptor) == unforgeable and
(not crossOriginOnly or m.getExtendedAttribute("CrossOriginReadable") or
isCrossOriginWritable(m, descriptor)) and
not isNonExposedNavigatorObjectGetter(m, descriptor)]
else:
attributes = []
attributes = [
{"name": name, "attr": attr}
for attr in attributes
for name in [attr.identifier.name] + attr.bindingAliases
]
idlAttrs = []
attributes = []
for attr in idlAttrs:
attributes.extend(self.attrData(attr, unforgeable))
self.chrome = [m for m in attributes if isChromeOnly(m["attr"])]
self.regular = [m for m in attributes if not isChromeOnly(m["attr"])]
self.static = static
self.unforgeable = unforgeable
if static:
if not descriptor.interface.hasInterfaceObject():
@ -2576,19 +2629,25 @@ class AttrDefiner(PropertyDefiner):
# non-static attributes go on the interface prototype object
assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
def generateArray(self, array, name):
if len(array) == 0:
return ""
@staticmethod
def attrData(attr, unforgeable=False, overrideFlags=None):
if overrideFlags is None:
permanent = " | JSPROP_PERMANENT" if unforgeable else ""
flags = EnumerabilityFlags(attr) + permanent
else:
flags = overrideFlags
return ({"name": name, "attr": attr, "flags": flags} for name in [attr.identifier.name] + attr.bindingAliases)
def condition(m, d):
return PropertyDefiner.getControllingCondition(m["attr"], d)
def flags(attr):
unforgeable = " | JSPROP_PERMANENT" if self.unforgeable else ""
return EnumerabilityFlags(attr) + unforgeable
@staticmethod
def condition(m, d):
return PropertyDefiner.getControllingCondition(m["attr"], d)
@staticmethod
def specData(entry, descriptor, static=False, crossOriginOnly=False):
def getter(attr):
if self.static:
if crossOriginOnly and not attr.getExtendedAttribute("CrossOriginReadable"):
return "nullptr, nullptr"
if static:
if attr.type.isPromise():
raise TypeError("Don't know how to handle "
"static Promise-returning "
@ -2614,7 +2673,7 @@ class AttrDefiner(PropertyDefiner):
elif attr.getExtendedAttribute("CrossOriginReadable"):
accessor = ("GenericGetter<CrossOriginThisPolicy, %s>" %
exceptionPolicy)
elif self.descriptor.interface.isOnGlobalProtoChain():
elif descriptor.interface.isOnGlobalProtoChain():
accessor = ("GenericGetter<MaybeGlobalThisPolicy, %s>" %
exceptionPolicy)
else:
@ -2631,20 +2690,22 @@ class AttrDefiner(PropertyDefiner):
attr.getExtendedAttribute("Replaceable") is None and
attr.getExtendedAttribute("LenientSetter") is None):
return "nullptr, nullptr"
if self.static:
if crossOriginOnly and not isCrossOriginWritable(attr, descriptor):
return "nullptr, nullptr"
if static:
accessor = 'set_' + IDLToCIdentifier(attr.identifier.name)
jitinfo = "nullptr"
else:
if attr.hasLenientThis():
if IsCrossOriginWritable(attr, self.descriptor):
if isCrossOriginWritable(attr, descriptor):
raise TypeError("Can't handle lenient cross-origin "
"writable attribute %s.%s" %
(self.descriptor.name,
(descriptor.name,
attr.identifier.name))
accessor = "GenericSetter<LenientThisPolicy>"
elif IsCrossOriginWritable(attr, self.descriptor):
elif isCrossOriginWritable(attr, descriptor):
accessor = "GenericSetter<CrossOriginThisPolicy>"
elif self.descriptor.interface.isOnGlobalProtoChain():
elif descriptor.interface.isOnGlobalProtoChain():
accessor = "GenericSetter<MaybeGlobalThisPolicy>"
else:
accessor = "GenericSetter<NormalThisPolicy>"
@ -2652,16 +2713,22 @@ class AttrDefiner(PropertyDefiner):
return "%s, %s" % \
(accessor, jitinfo)
def specData(entry):
name, attr = entry["name"], entry["attr"]
return (name, flags(attr), getter(attr), setter(attr))
name, attr, flags = entry["name"], entry["attr"], entry["flags"]
return (name, flags, getter(attr), setter(attr))
@staticmethod
def formatSpec(fields):
return ' { "%s", %s, %s, %s }' % fields
def generateArray(self, array, name):
if len(array) == 0:
return ""
return self.generatePrefableArray(
array, name,
lambda fields: ' { "%s", %s, %s, %s }' % fields,
' { nullptr, 0, nullptr, nullptr, nullptr, nullptr }',
array, name, self.formatSpec,
' JS_PS_END',
'JSPropertySpec',
condition, specData)
self.condition, functools.partial(self.specData, static=self.static))
class ConstDefiner(PropertyDefiner):
@ -2679,7 +2746,7 @@ class ConstDefiner(PropertyDefiner):
if len(array) == 0:
return ""
def specData(const):
def specData(const, descriptor):
return (const.identifier.name,
convertConstIDLValueToJSVal(const.value))
@ -2692,17 +2759,19 @@ class ConstDefiner(PropertyDefiner):
class PropertyArrays():
def __init__(self, descriptor):
self.staticMethods = MethodDefiner(descriptor, "StaticMethods",
def __init__(self, descriptor, crossOriginOnly=False):
self.staticMethods = MethodDefiner(descriptor, "StaticMethods", crossOriginOnly,
static=True)
self.staticAttrs = AttrDefiner(descriptor, "StaticAttributes",
self.staticAttrs = AttrDefiner(descriptor, "StaticAttributes", crossOriginOnly,
static=True)
self.methods = MethodDefiner(descriptor, "Methods", static=False)
self.attrs = AttrDefiner(descriptor, "Attributes", static=False)
self.methods = MethodDefiner(descriptor, "Methods", crossOriginOnly, static=False)
self.attrs = AttrDefiner(descriptor, "Attributes", crossOriginOnly, static=False)
self.unforgeableMethods = MethodDefiner(descriptor, "UnforgeableMethods",
static=False, unforgeable=True)
crossOriginOnly, static=False,
unforgeable=True)
self.unforgeableAttrs = AttrDefiner(descriptor, "UnforgeableAttributes",
static=False, unforgeable=True)
crossOriginOnly, static=False,
unforgeable=True)
self.consts = ConstDefiner(descriptor, "Constants")
@staticmethod
@ -4081,6 +4150,77 @@ class CGClearCachedValueMethod(CGAbstractMethod):
regetMember=regetMember)
class CGCrossOriginProperties(CGThing):
def __init__(self, descriptor):
attrs = []
methods = []
for m in descriptor.interface.members:
if m.isAttr() and (m.getExtendedAttribute("CrossOriginReadable") or isCrossOriginWritable(m, descriptor)):
if m.isStatic():
raise TypeError("Don't know how to deal with static method %s" %
m.identifier.name)
if PropertyDefiner.getControllingCondition(m, descriptor).hasDisablers():
raise TypeError("Don't know how to deal with disabler for %s" %
m.identifier.name)
if len(m.bindingAliases) > 0:
raise TypeError("Don't know how to deal with aliases for %s" %
m.identifier.name)
attrs.extend(AttrDefiner.attrData(m, overrideFlags="0"))
elif m.isMethod() and m.getExtendedAttribute("CrossOriginCallable"):
if m.isStatic():
raise TypeError("Don't know how to deal with static method %s" %
m.identifier.name)
if PropertyDefiner.getControllingCondition(m, descriptor).hasDisablers():
raise TypeError("Don't know how to deal with disabler for %s" %
m.identifier.name)
if len(m.aliases) > 0:
raise TypeError("Don't know how to deal with aliases for %s" %
m.identifier.name)
methods.append(MethodDefiner.methodData(m, descriptor, overrideFlags="JSPROP_READONLY"))
if len(attrs) > 0:
self.attributeSpecs, _ = PropertyDefiner.generatePrefableArrayValues(
attrs, descriptor, AttrDefiner.formatSpec, ' JS_PS_END\n',
AttrDefiner.condition, functools.partial(AttrDefiner.specData, crossOriginOnly=True))
else:
self.attributeSpecs = [' JS_PS_END\n']
if len(methods) > 0:
self.methodSpecs, _ = PropertyDefiner.generatePrefableArrayValues(
methods, descriptor, MethodDefiner.formatSpec, ' JS_FS_END\n',
MethodDefiner.condition, MethodDefiner.specData)
else:
self.methodSpecs = [' JS_FS_END\n']
def declare(self):
return fill("""
extern JSPropertySpec sCrossOriginAttributes[${attributesLength}];
extern JSFunctionSpec sCrossOriginMethods[${methodsLength}];
""",
attributesLength=len(self.attributeSpecs),
methodsLength=len(self.methodSpecs))
def define(self):
return fill(
"""
// We deliberately use brace-elision to make Visual Studio produce better initalization code.
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmissing-braces"
#endif
JSPropertySpec sCrossOriginAttributes[] = {
$*{attributeSpecs}
};
JSFunctionSpec sCrossOriginMethods[] = {
$*{methodSpecs}
};
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
""",
attributeSpecs=",\n".join(self.attributeSpecs),
methodSpecs=",\n".join(self.methodSpecs))
class CGIsPermittedMethod(CGAbstractMethod):
"""
crossOriginGetters/Setters/Methods are sets of names of the relevant members.
@ -12398,14 +12538,14 @@ def memberProperties(m, descriptor):
props.isCrossOriginGetter = True
if not m.readonly:
if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject():
if IsCrossOriginWritable(m, descriptor):
if isCrossOriginWritable(m, descriptor):
props.isCrossOriginSetter = True
elif m.getExtendedAttribute("PutForwards"):
if IsCrossOriginWritable(m, descriptor):
if isCrossOriginWritable(m, descriptor):
props.isCrossOriginSetter = True
elif (m.getExtendedAttribute("Replaceable") or
m.getExtendedAttribute("LenientSetter")):
if IsCrossOriginWritable(m, descriptor):
if isCrossOriginWritable(m, descriptor):
props.isCrossOriginSetter = True
return props
@ -12648,6 +12788,7 @@ class CGDescriptor(CGThing):
# See whether we need we need to generate an IsPermitted method
if crossOriginGetters or crossOriginSetters or crossOriginMethods:
cgThings.append(CGCrossOriginProperties(descriptor))
cgThings.append(CGIsPermittedMethod(descriptor,
crossOriginGetters,
crossOriginSetters,

View File

@ -5138,6 +5138,10 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
if not attr.noArguments():
raise WebIDLError("[%s] must take no arguments" % identifier,
[attr.location])
if identifier == "CrossOriginCallable" and self.isStatic():
raise WebIDLError("[CrossOriginCallable] is only allowed on non-static "
"attributes"
[attr.location, self.location])
elif identifier == "Pure":
if not attr.noArguments():
raise WebIDLError("[Pure] must take no arguments",