diff options
author | Ms2ger <ms2ger@gmail.com> | 2014-08-19 11:01:05 +0200 |
---|---|---|
committer | Ms2ger <ms2ger@gmail.com> | 2014-08-19 11:01:05 +0200 |
commit | 0bb0951265a3e141c56d4ce5338dcdda01a00106 (patch) | |
tree | 816e2c472be60206b36e88b5aacdb89125d5f95e /src/components/script/dom/bindings/codegen/parser/WebIDL.py | |
parent | eb6eee513f44ef10aaf0085054096c192153912a (diff) | |
download | servo-0bb0951265a3e141c56d4ce5338dcdda01a00106.tar.gz servo-0bb0951265a3e141c56d4ce5338dcdda01a00106.zip |
Update WebIDL.py.
Diffstat (limited to 'src/components/script/dom/bindings/codegen/parser/WebIDL.py')
-rw-r--r-- | src/components/script/dom/bindings/codegen/parser/WebIDL.py | 627 |
1 files changed, 560 insertions, 67 deletions
diff --git a/src/components/script/dom/bindings/codegen/parser/WebIDL.py b/src/components/script/dom/bindings/codegen/parser/WebIDL.py index 4d39f54159c..32f80e82c56 100644 --- a/src/components/script/dom/bindings/codegen/parser/WebIDL.py +++ b/src/components/script/dom/bindings/codegen/parser/WebIDL.py @@ -9,6 +9,7 @@ import re import os import traceback import math +from collections import defaultdict # Machinery @@ -219,6 +220,12 @@ class IDLScope(IDLObject): self._name = None self._dict = {} + self.globalNames = set() + # A mapping from global name to the set of global interfaces + # that have that global name. + self.globalNameMapping = defaultdict(set) + self.primaryGlobalAttr = None + self.primaryGlobalName = None def __str__(self): return self.QName() @@ -446,24 +453,85 @@ class IDLExternalInterface(IDLObjectWithIdentifier): raise WebIDLError("Servo does not support external interfaces.", [self.location]) +class IDLPartialInterface(IDLObject): + def __init__(self, location, name, members, nonPartialInterface): + assert isinstance(name, IDLUnresolvedIdentifier) + + IDLObject.__init__(self, location) + self.identifier = name + self.members = members + # propagatedExtendedAttrs are the ones that should get + # propagated to our non-partial interface. + self.propagatedExtendedAttrs = [] + self._nonPartialInterface = nonPartialInterface + self._finished = False + nonPartialInterface.addPartialInterface(self) + + def addExtendedAttributes(self, attrs): + for attr in attrs: + identifier = attr.identifier() + + if identifier in ["Constructor", "NamedConstructor"]: + self.propagatedExtendedAttrs.append(attr) + elif identifier == "Exposed": + # This just gets propagated to all our members. + for member in self.members: + if len(member._exposureGlobalNames) != 0: + raise WebIDLError("[Exposed] specified on both a " + "partial interface member and on the " + "partial interface itself", + [member.location, attr.location]) + member.addExtendedAttributes([attr]) + else: + raise WebIDLError("Unknown extended attribute %s on partial " + "interface" % identifier, + [attr.location]) + + def finish(self, scope): + if self._finished: + return + self._finished = True + # Need to make sure our non-partial interface gets finished so it can + # report cases when we only have partial interfaces. + self._nonPartialInterface.finish(scope) + + def validate(self): + pass + + +def convertExposedAttrToGlobalNameSet(exposedAttr, targetSet): + assert len(targetSet) == 0 + if exposedAttr.hasValue(): + targetSet.add(exposedAttr.value()) + else: + assert exposedAttr.hasArgs() + targetSet.update(exposedAttr.args()) + +def globalNameSetToExposureSet(globalScope, nameSet, exposureSet): + for name in nameSet: + exposureSet.update(globalScope.globalNameMapping[name]) + class IDLInterface(IDLObjectWithScope): def __init__(self, location, parentScope, name, parent, members, - isPartial): + isKnownNonPartial): assert isinstance(parentScope, IDLScope) assert isinstance(name, IDLUnresolvedIdentifier) - assert not isPartial or not parent + assert isKnownNonPartial or not parent + assert isKnownNonPartial or len(members) == 0 self.parent = None self._callback = False self._finished = False self.members = [] + self._partialInterfaces = [] + self._extendedAttrDict = {} # namedConstructors needs deterministic ordering because bindings code # outputs the constructs in the order that namedConstructors enumerates # them. self.namedConstructors = list() self.implementedInterfaces = set() self._consequential = False - self._isPartial = True + self._isKnownNonPartial = False # self.interfacesBasedOnSelf is the set of interfaces that inherit from # self or have self as a consequential interface, including self itself. # Used for distinguishability checking. @@ -478,14 +546,16 @@ class IDLInterface(IDLObjectWithScope): self.totalMembersInSlots = 0 # Tracking of the number of own own members we have in slots self._ownMembersInSlots = 0 + # _exposureGlobalNames are the global names listed in our [Exposed] + # extended attribute. exposureSet is the exposure set as defined in the + # Web IDL spec: it contains interface names. + self._exposureGlobalNames = set() + self.exposureSet = set() IDLObjectWithScope.__init__(self, location, parentScope, name) - if not isPartial: + if isKnownNonPartial: self.setNonPartial(location, parent, members) - else: - # Just remember our members for now - self.members = members def __str__(self): return "Interface '%s'" % self.identifier.name @@ -517,11 +587,42 @@ class IDLInterface(IDLObjectWithScope): self._finished = True - if self._isPartial: + if not self._isKnownNonPartial: raise WebIDLError("Interface %s does not have a non-partial " "declaration" % self.identifier.name, [self.location]) + # Verify that our [Exposed] value, if any, makes sense. + for globalName in self._exposureGlobalNames: + if globalName not in scope.globalNames: + raise WebIDLError("Unknown [Exposed] value %s" % globalName, + [self.location]) + + if len(self._exposureGlobalNames) == 0: + self._exposureGlobalNames.add(scope.primaryGlobalName) + + globalNameSetToExposureSet(scope, self._exposureGlobalNames, + self.exposureSet) + + # Now go ahead and merge in our partial interfaces. + for partial in self._partialInterfaces: + partial.finish(scope) + self.addExtendedAttributes(partial.propagatedExtendedAttrs) + self.members.extend(partial.members) + + # Now that we've merged in our partial interfaces, set the + # _exposureGlobalNames on any members that don't have it set yet. Note + # that any partial interfaces that had [Exposed] set have already set up + # _exposureGlobalNames on all the members coming from them, so this is + # just implementing the "members default to interface that defined them" + # and "partial interfaces default to interface they're a partial for" + # rules from the spec. + for m in self.members: + # If m, or the partial interface m came from, had [Exposed] + # specified, it already has a nonempty exposure global names set. + if len(m._exposureGlobalNames) == 0: + m._exposureGlobalNames.update(self._exposureGlobalNames) + assert not self.parent or isinstance(self.parent, IDLIdentifierPlaceholder) parent = self.parent.finish(scope) if self.parent else None if parent and isinstance(parent, IDLExternalInterface): @@ -543,8 +644,10 @@ class IDLInterface(IDLObjectWithScope): self.totalMembersInSlots = self.parent.totalMembersInSlots - # Interfaces with [Global] must not have anything inherit from them - if self.parent.getExtendedAttribute("Global"): + # Interfaces with [Global] or [PrimaryGlobal] must not + # have anything inherit from them + if (self.parent.getExtendedAttribute("Global") or + self.parent.getExtendedAttribute("PrimaryGlobal")): # Note: This is not a self.parent.isOnGlobalProtoChain() check # because ancestors of a [Global] interface can have other # descendants. @@ -552,6 +655,14 @@ class IDLInterface(IDLObjectWithScope): "inheriting from it", [self.location, self.parent.location]) + # Make sure that we're not exposed in places where our parent is not + if not self.exposureSet.issubset(self.parent.exposureSet): + raise WebIDLError("Interface %s is exposed in globals where its " + "parent interface %s is not exposed." % + (self.identifier.name, + self.parent.identifier.name), + [self.location, self.parent.location]) + # Callbacks must not inherit from non-callbacks or inherit from # anything that has consequential interfaces. # XXXbz Can non-callbacks inherit from callbacks? Spec issue pending. @@ -597,6 +708,14 @@ class IDLInterface(IDLObjectWithScope): for member in self.members: member.finish(scope) + # Now that we've finished our members, which has updated their exposure + # sets, make sure they aren't exposed in places where we are not. + for member in self.members: + if not member.exposureSet.issubset(self.exposureSet): + raise WebIDLError("Interface member has larger exposure set " + "than the interface itself", + [member.location, self.location]) + ctor = self.ctor() if ctor is not None: ctor.finish(scope) @@ -617,6 +736,12 @@ class IDLInterface(IDLObjectWithScope): key=lambda x: x.identifier.name): # Flag the interface as being someone's consequential interface iface.setIsConsequentialInterfaceOf(self) + # Verify that we're not exposed somewhere where iface is not exposed + if not self.exposureSet.issubset(iface.exposureSet): + raise WebIDLError("Interface %s is exposed in globals where its " + "consequential interface %s is not exposed." % + (self.identifier.name, iface.identifier.name), + [self.location, iface.location]) additionalMembers = iface.originalMembers; for additionalMember in additionalMembers: for member in self.members: @@ -633,8 +758,36 @@ class IDLInterface(IDLObjectWithScope): for ancestorConsequential in ancestor.getConsequentialInterfaces(): ancestorConsequential.interfacesBasedOnSelf.add(self) + # Deal with interfaces marked [Unforgeable], now that we have our full + # member list, except unforgeables pulled in from parents. We want to + # do this before we set "originatingInterface" on our unforgeable + # members. + if self.getExtendedAttribute("Unforgeable"): + # Check that the interface already has all the things the + # spec would otherwise require us to synthesize and is + # missing the ones we plan to synthesize. + if not any(m.isMethod() and m.isStringifier() for m in self.members): + raise WebIDLError("Unforgeable interface %s does not have a " + "stringifier" % self.identifier.name, + [self.location]) + + for m in self.members: + if ((m.isMethod() and m.isJsonifier()) or + m.identifier.name == "toJSON"): + raise WebIDLError("Unforgeable interface %s has a " + "jsonifier so we won't be able to add " + "one ourselves" % self.identifier.name, + [self.location, m.location]) + + if m.identifier.name == "valueOf" and not m.isStatic(): + raise WebIDLError("Unforgeable interface %s has a valueOf " + "member so we won't be able to add one " + "ourselves" % self.identifier.name, + [self.location, m.location]) + for member in self.members: - if (member.isAttr() and member.isUnforgeable() and + if ((member.isAttr() or member.isMethod()) and + member.isUnforgeable() and not hasattr(member, "originatingInterface")): member.originatingInterface = self @@ -657,16 +810,16 @@ class IDLInterface(IDLObjectWithScope): # worry about anything other than our parent, because it has already # imported its ancestors unforgeable attributes into its member # list. - for unforgeableAttr in (attr for attr in self.parent.members if - attr.isAttr() and not attr.isStatic() and - attr.isUnforgeable()): + for unforgeableMember in (member for member in self.parent.members if + (member.isAttr() or member.isMethod()) and + member.isUnforgeable()): shadows = [ m for m in self.members if (m.isAttr() or m.isMethod()) and not m.isStatic() and - m.identifier.name == unforgeableAttr.identifier.name ] + m.identifier.name == unforgeableMember.identifier.name ] if len(shadows) != 0: - locs = [unforgeableAttr.location] + [ s.location for s - in shadows ] + locs = [unforgeableMember.location] + [ s.location for s + in shadows ] raise WebIDLError("Interface %s shadows [Unforgeable] " "members of %s" % (self.identifier.name, @@ -675,10 +828,10 @@ class IDLInterface(IDLObjectWithScope): # And now just stick it in our members, since we won't be # inheriting this down the proto chain. If we really cared we # could try to do something where we set up the unforgeable - # attributes of ancestor interfaces, with their corresponding - # getters, on our interface, but that gets pretty complicated - # and seems unnecessary. - self.members.append(unforgeableAttr) + # attributes/methods of ancestor interfaces, with their + # corresponding getters, on our interface, but that gets pretty + # complicated and seems unnecessary. + self.members.append(unforgeableMember) # Ensure that there's at most one of each {named,indexed} # {getter,setter,creator,deleter}, at most one stringifier, @@ -749,6 +902,26 @@ class IDLInterface(IDLObjectWithScope): parent = parent.parent def validate(self): + # We don't support consequential unforgeable interfaces. Need to check + # this here, becaue in finish() an interface might not know yet that + # it's consequential. + if self.getExtendedAttribute("Unforgeable") and self.isConsequential(): + raise WebIDLError( + "%s is an unforgeable consequential interface" % + self.identifier.name, + [self.location] + + list(i.location for i in + (self.interfacesBasedOnSelf - { self }) )) + + # We also don't support inheriting from unforgeable interfaces. + if self.getExtendedAttribute("Unforgeable") and self.hasChildInterfaces(): + raise WebIDLError("%s is an unforgeable ancestor interface" % + self.identifier.name, + [self.location] + + list(i.location for i in + self.interfacesBasedOnSelf if i.parent == self)) + + for member in self.members: member.validate() @@ -791,6 +964,12 @@ class IDLInterface(IDLObjectWithScope): attr = fowardAttr putForwards = attr.getExtendedAttribute("PutForwards") + if (self.getExtendedAttribute("Pref") and + self._exposureGlobalNames != set([self.parentScope.primaryGlobalName])): + raise WebIDLError("[Pref] used on an member that is not %s-only" % + self.parentScope.primaryGlobalName, + [self.location]) + def isInterface(self): return True @@ -848,7 +1027,6 @@ class IDLInterface(IDLObjectWithScope): return not self.isCallback() and self.getUserData('hasConcreteDescendant', False) def addExtendedAttributes(self, attrs): - self._extendedAttrDict = {} for attr in attrs: identifier = attr.identifier() @@ -949,17 +1127,42 @@ class IDLInterface(IDLObjectWithScope): "an interface with inherited interfaces", [attr.location, self.location]) elif identifier == "Global": + if attr.hasValue(): + self.globalNames = [ attr.value() ] + elif attr.hasArgs(): + self.globalNames = attr.args() + else: + self.globalNames = [ self.identifier.name ] + self.parentScope.globalNames.update(self.globalNames) + for globalName in self.globalNames: + self.parentScope.globalNameMapping[globalName].add(self.identifier.name) + self._isOnGlobalProtoChain = True + elif identifier == "PrimaryGlobal": if not attr.noArguments(): - raise WebIDLError("[Global] must take no arguments", + raise WebIDLError("[PrimaryGlobal] must take no arguments", [attr.location]) + if self.parentScope.primaryGlobalAttr is not None: + raise WebIDLError( + "[PrimaryGlobal] specified twice", + [attr.location, + self.parentScope.primaryGlobalAttr.location]) + self.parentScope.primaryGlobalAttr = attr + self.parentScope.primaryGlobalName = self.identifier.name + self.parentScope.globalNames.add(self.identifier.name) + self.parentScope.globalNameMapping[self.identifier.name].add(self.identifier.name) self._isOnGlobalProtoChain = True elif (identifier == "NeedNewResolve" or identifier == "OverrideBuiltins" or - identifier == "ChromeOnly"): + identifier == "ChromeOnly" or + identifier == "Unforgeable" or + identifier == "LegacyEventInit"): # Known extended attributes that do not take values if not attr.noArguments(): raise WebIDLError("[%s] must take no arguments" % identifier, [attr.location]) + elif identifier == "Exposed": + convertExposedAttrToGlobalNameSet(attr, + self._exposureGlobalNames) elif (identifier == "Pref" or identifier == "JSImplementation" or identifier == "HeaderFile" or @@ -1036,11 +1239,11 @@ class IDLInterface(IDLObjectWithScope): def setNonPartial(self, location, parent, members): assert not parent or isinstance(parent, IDLIdentifierPlaceholder) - if not self._isPartial: + if self._isKnownNonPartial: raise WebIDLError("Two non-partial definitions for the " "same interface", [location, self.location]) - self._isPartial = False + self._isKnownNonPartial = True # Now make it look like we were parsed at this new location, since # that's the place where the interface is "really" defined self.location = location @@ -1049,6 +1252,10 @@ class IDLInterface(IDLObjectWithScope): # Put the new members at the beginning self.members = members + self.members + def addPartialInterface(self, partial): + assert self.identifier.name == partial.identifier.name + self._partialInterfaces.append(partial) + def getJSImplementation(self): classId = self.getExtendedAttribute("JSImplementation") if not classId: @@ -1276,6 +1483,7 @@ class IDLType(IDLObject): 'any', 'domstring', 'bytestring', + 'scalarvaluestring', 'object', 'date', 'void', @@ -1328,6 +1536,9 @@ class IDLType(IDLObject): def isDOMString(self): return False + def isScalarValueString(self): + return False + def isVoid(self): return self.name == "Void" @@ -1428,13 +1639,17 @@ class IDLType(IDLObject): raise TypeError("Can't tell whether a generic type is or is not " "distinguishable from other things") + def isExposedInAllOf(self, exposureSet): + return True + class IDLUnresolvedType(IDLType): """ Unresolved types are interface types """ - def __init__(self, location, name): + def __init__(self, location, name, promiseInnerType=None): IDLType.__init__(self, location, name) + self._promiseInnerType = promiseInnerType def isComplete(self): return False @@ -1455,8 +1670,11 @@ class IDLUnresolvedType(IDLType): obj = obj.complete(scope) return obj + if self._promiseInnerType and not self._promiseInnerType.isComplete(): + self._promiseInnerType = self._promiseInnerType.complete(scope) + name = self.name.resolve(scope, None) - return IDLWrapperType(self.location, obj) + return IDLWrapperType(self.location, obj, self._promiseInnerType) def isDistinguishableFrom(self, other): raise TypeError("Can't tell whether an unresolved type is or is not " @@ -1501,6 +1719,9 @@ class IDLNullableType(IDLType): def isDOMString(self): return self.inner.isDOMString() + def isScalarValueString(self): + return self.inner.isScalarValueString() + def isFloat(self): return self.inner.isFloat() @@ -1622,6 +1843,9 @@ class IDLSequenceType(IDLType): def isDOMString(self): return False + def isScalarValueString(self): + return False + def isVoid(self): return False @@ -1669,7 +1893,7 @@ class IDLSequenceType(IDLType): # Just forward to the union; it'll deal return other.isDistinguishableFrom(self) return (other.isPrimitive() or other.isString() or other.isEnum() or - other.isDate() or other.isNonCallbackInterface()) + other.isDate() or other.isNonCallbackInterface() or other.isMozMap()) def _getDependentObjects(self): return self.inner._getDependentObjects() @@ -1723,7 +1947,10 @@ class IDLMozMapType(IDLType): # Just forward to the union; it'll deal return other.isDistinguishableFrom(self) return (other.isPrimitive() or other.isString() or other.isEnum() or - other.isDate() or other.isNonCallbackInterface()) + other.isDate() or other.isNonCallbackInterface() or other.isSequence()) + + def isExposedInAllOf(self, exposureSet): + return self.inner.unroll().isExposedInAllOf(exposureSet) def _getDependentObjects(self): return self.inner._getDependentObjects() @@ -1835,6 +2062,14 @@ class IDLUnionType(IDLType): return False return True + def isExposedInAllOf(self, exposureSet): + # We could have different member types in different globals. Just make sure that each thing in exposureSet has one of our member types exposed in it. + for globalName in exposureSet: + if not any(t.unroll().isExposedInAllOf(set([globalName])) for t + in self.flatMemberTypes): + return False + return True + def _getDependentObjects(self): return set(self.memberTypes) @@ -1876,6 +2111,9 @@ class IDLArrayType(IDLType): def isDOMString(self): return False + def isScalarValueString(self): + return False + def isVoid(self): return False @@ -1971,6 +2209,9 @@ class IDLTypedefType(IDLType, IDLObjectWithIdentifier): def isDOMString(self): return self.inner.isDOMString() + def isScalarValueString(self): + return self.inner.isScalarValueString() + def isVoid(self): return self.inner.isVoid() @@ -2038,11 +2279,13 @@ class IDLTypedefType(IDLType, IDLObjectWithIdentifier): return self.inner._getDependentObjects() class IDLWrapperType(IDLType): - def __init__(self, location, inner): + def __init__(self, location, inner, promiseInnerType=None): IDLType.__init__(self, location, inner.identifier.name) self.inner = inner self._identifier = inner.identifier self.builtin = False + assert not promiseInnerType or inner.identifier.name == "Promise" + self._promiseInnerType = promiseInnerType def __eq__(self, other): return isinstance(other, IDLWrapperType) and \ @@ -2067,6 +2310,9 @@ class IDLWrapperType(IDLType): def isDOMString(self): return False + def isScalarValueString(self): + return False + def isVoid(self): return False @@ -2163,6 +2409,20 @@ class IDLWrapperType(IDLType): assert other.isObject() return False + def isExposedInAllOf(self, exposureSet): + if not self.isInterface(): + return True + iface = self.inner + if iface.isExternal(): + # Let's say true, though ideally we'd only do this when + # exposureSet contains the primary global's name. + return True + if (iface.identifier.name == "Promise" and + # Check the internal type + not self._promiseInnerType.unroll().isExposedInAllOf(exposureSet)): + return False + return iface.exposureSet.issuperset(exposureSet) + def _getDependentObjects(self): # NB: The codegen for an interface type depends on # a) That the identifier is in fact an interface (as opposed to @@ -2203,6 +2463,7 @@ class IDLBuiltinType(IDLType): 'any', 'domstring', 'bytestring', + 'scalarvaluestring', 'object', 'date', 'void', @@ -2237,6 +2498,7 @@ class IDLBuiltinType(IDLType): Types.any: IDLType.Tags.any, Types.domstring: IDLType.Tags.domstring, Types.bytestring: IDLType.Tags.bytestring, + Types.scalarvaluestring: IDLType.Tags.scalarvaluestring, Types.object: IDLType.Tags.object, Types.date: IDLType.Tags.date, Types.void: IDLType.Tags.void, @@ -2269,7 +2531,8 @@ class IDLBuiltinType(IDLType): def isString(self): return self._typeTag == IDLBuiltinType.Types.domstring or \ - self._typeTag == IDLBuiltinType.Types.bytestring + self._typeTag == IDLBuiltinType.Types.bytestring or \ + self._typeTag == IDLBuiltinType.Types.scalarvaluestring def isByteString(self): return self._typeTag == IDLBuiltinType.Types.bytestring @@ -2277,6 +2540,9 @@ class IDLBuiltinType(IDLType): def isDOMString(self): return self._typeTag == IDLBuiltinType.Types.domstring + def isScalarValueString(self): + return self._typeTag == IDLBuiltinType.Types.scalarvaluestring + def isInteger(self): return self._typeTag <= IDLBuiltinType.Types.unsigned_long_long @@ -2429,6 +2695,9 @@ BuiltinTypes = { IDLBuiltinType.Types.bytestring: IDLBuiltinType(BuiltinLocation("<builtin type>"), "ByteString", IDLBuiltinType.Types.bytestring), + IDLBuiltinType.Types.scalarvaluestring: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "ScalarValueString", + IDLBuiltinType.Types.scalarvaluestring), IDLBuiltinType.Types.object: IDLBuiltinType(BuiltinLocation("<builtin type>"), "Object", IDLBuiltinType.Types.object), @@ -2563,6 +2832,13 @@ class IDLValue(IDLObject): raise WebIDLError("Trying to convert unrestricted value %s to non-unrestricted" % self.value, [location]); return self + elif self.type.isString() and type.isScalarValueString(): + # Allow ScalarValueStrings to use default value just like + # DOMString. No coercion is required in this case as Codegen.py + # treats ScalarValueString just like DOMString, but with an + # extra normalization step. + assert self.type.isDOMString() + return self raise WebIDLError("Cannot coerce type %s to type %s." % (self.type, type), [location]) @@ -2598,6 +2874,34 @@ class IDLNullValue(IDLObject): def _getDependentObjects(self): return set() +class IDLEmptySequenceValue(IDLObject): + def __init__(self, location): + IDLObject.__init__(self, location) + self.type = None + self.value = None + + def coerceToType(self, type, location): + if type.isUnion(): + # We use the flat member types here, because if we have a nullable + # member type, or a nested union, we want the type the value + # actually coerces to, not the nullable or nested union type. + for subtype in type.unroll().flatMemberTypes: + try: + return self.coerceToType(subtype, location) + except: + pass + + if not type.isSequence(): + raise WebIDLError("Cannot coerce empty sequence value to type %s." % type, + [location]) + + emptySequenceValue = IDLEmptySequenceValue(self.location) + emptySequenceValue.type = type + return emptySequenceValue + + def _getDependentObjects(self): + return set() + class IDLUndefinedValue(IDLObject): def __init__(self, location): IDLObject.__init__(self, location) @@ -2633,6 +2937,11 @@ class IDLInterfaceMember(IDLObjectWithIdentifier): IDLObjectWithIdentifier.__init__(self, location, None, identifier) self.tag = tag self._extendedAttrDict = {} + # _exposureGlobalNames are the global names listed in our [Exposed] + # extended attribute. exposureSet is the exposure set as defined in the + # Web IDL spec: it contains interface names. + self._exposureGlobalNames = set() + self.exposureSet = set() def isMethod(self): return self.tag == IDLInterfaceMember.Tags.Method @@ -2655,6 +2964,22 @@ class IDLInterfaceMember(IDLObjectWithIdentifier): def getExtendedAttribute(self, name): return self._extendedAttrDict.get(name, None) + def finish(self, scope): + for globalName in self._exposureGlobalNames: + if globalName not in scope.globalNames: + raise WebIDLError("Unknown [Exposed] value %s" % globalName, + [self.location]) + globalNameSetToExposureSet(scope, self._exposureGlobalNames, + self.exposureSet) + self._scope = scope + + def validate(self): + if (self.getExtendedAttribute("Pref") and + self.exposureSet != set([self._scope.primaryGlobalName])): + raise WebIDLError("[Pref] used on an interface member that is not " + "%s-only" % self._scope.primaryGlobalName, + [self.location]) + class IDLConst(IDLInterfaceMember): def __init__(self, location, identifier, type, value): IDLInterfaceMember.__init__(self, location, identifier, @@ -2675,6 +3000,8 @@ class IDLConst(IDLInterfaceMember): return "'%s' const '%s'" % (self.type, self.identifier) def finish(self, scope): + IDLInterfaceMember.finish(self, scope) + if not self.type.isComplete(): type = self.type.complete(scope) if not type.isPrimitive() and not type.isString(): @@ -2693,7 +3020,23 @@ class IDLConst(IDLInterfaceMember): self.value = coercedValue def validate(self): - pass + IDLInterfaceMember.validate(self) + + def handleExtendedAttribute(self, attr): + identifier = attr.identifier() + if identifier == "Exposed": + convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames) + elif (identifier == "Pref" or + identifier == "ChromeOnly" or + identifier == "Func" or + identifier == "AvailableIn" or + identifier == "CheckPermissions"): + # Known attributes that we don't need to do anything with here + pass + else: + raise WebIDLError("Unknown extended attribute %s on constant" % identifier, + [attr.location]) + IDLInterfaceMember.handleExtendedAttribute(self, attr) def _getDependentObjects(self): return set([self.type, self.value]) @@ -2731,6 +3074,8 @@ class IDLAttribute(IDLInterfaceMember): return "'%s' attribute '%s'" % (self.type, self.identifier) def finish(self, scope): + IDLInterfaceMember.finish(self, scope) + if not self.type.isComplete(): t = self.type.complete(scope) @@ -2777,6 +3122,8 @@ class IDLAttribute(IDLInterfaceMember): "interface type as its type", [self.location]) def validate(self): + IDLInterfaceMember.validate(self) + if ((self.getExtendedAttribute("Cached") or self.getExtendedAttribute("StoreInSlot")) and not self.getExtendedAttribute("Constant") and @@ -2792,6 +3139,10 @@ class IDLAttribute(IDLInterfaceMember): "sequence-valued, dictionary-valued, and " "MozMap-valued attributes", [self.location]) + if not self.type.unroll().isExposedInAllOf(self.exposureSet): + raise WebIDLError("Attribute returns a type that is not exposed " + "everywhere where the attribute is exposed", + [self.location]) def handleExtendedAttribute(self, attr): identifier = attr.identifier() @@ -2824,9 +3175,6 @@ class IDLAttribute(IDLInterfaceMember): [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]) if self.isStatic(): raise WebIDLError("[Unforgeable] is only allowed on non-static " "attributes", [attr.location, self.location]) @@ -2886,7 +3234,7 @@ class IDLAttribute(IDLInterfaceMember): [attr.location, self.location]) elif (identifier == "CrossOriginReadable" or identifier == "CrossOriginWritable"): - if not attr.noArguments(): + if not attr.noArguments() and identifier == "CrossOriginReadable": raise WebIDLError("[%s] must take no arguments" % identifier, [attr.location]) if self.isStatic(): @@ -2897,6 +3245,8 @@ class IDLAttribute(IDLInterfaceMember): raise WebIDLError("[LenientThis] is not allowed in combination " "with [%s]" % identifier, [attr.location, self.location]) + elif identifier == "Exposed": + convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames) elif (identifier == "Pref" or identifier == "SetterThrows" or identifier == "Pure" or @@ -3183,6 +3533,7 @@ class IDLMethod(IDLInterfaceMember, IDLScope): assert isinstance(jsonifier, bool) self._jsonifier = jsonifier self._specialType = specialType + self._unforgeable = False if static and identifier.name == "prototype": raise WebIDLError("The identifier of a static operation must not be 'prototype'", @@ -3314,6 +3665,8 @@ class IDLMethod(IDLInterfaceMember, IDLScope): self._overloads] def finish(self, scope): + IDLInterfaceMember.finish(self, scope) + overloadWithPromiseReturnType = None overloadWithoutPromiseReturnType = None for overload in self._overloads: @@ -3385,6 +3738,8 @@ class IDLMethod(IDLInterfaceMember, IDLScope): if len(self.signaturesForArgCount(i)) != 0 ] def validate(self): + IDLInterfaceMember.validate(self) + # Make sure our overloads are properly distinguishable and don't have # different argument types before the distinguishing args. for argCount in self.allowedArgCounts: @@ -3404,6 +3759,12 @@ class IDLMethod(IDLInterfaceMember, IDLScope): distinguishingIndex), [self.location, overload.location]) + for overload in self._overloads: + if not overload.returnType.unroll().isExposedInAllOf(self.exposureSet): + raise WebIDLError("Overload returns a type that is not exposed " + "everywhere where the method is exposed", + [overload.location]) + def overloadsForArgCount(self, argc): return [overload for overload in self._overloads if len(overload.arguments) == argc or @@ -3458,9 +3819,10 @@ class IDLMethod(IDLInterfaceMember, IDLScope): "[SetterThrows]", [attr.location, self.location]) elif identifier == "Unforgeable": - raise WebIDLError("Methods must not be flagged as " - "[Unforgeable]", - [attr.location, self.location]) + if self.isStatic(): + raise WebIDLError("[Unforgeable] is only allowed on non-static " + "methods", [attr.location, self.location]) + self._unforgeable = True elif identifier == "SameObject": raise WebIDLError("Methods must not be flagged as [SameObject]", [attr.location, self.location]); @@ -3481,15 +3843,21 @@ class IDLMethod(IDLInterfaceMember, IDLScope): raise WebIDLError("[LenientFloat] used on an operation with no " "restricted float type arguments", [attr.location, self.location]) + elif identifier == "Exposed": + convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames) + elif (identifier == "Pure" or + identifier == "CrossOriginCallable" or + identifier == "WebGLHandlesContextLoss"): + # Known no-argument attributes. + if not attr.noArguments(): + raise WebIDLError("[%s] must take no arguments" % identifier, + [attr.location]) elif (identifier == "Throws" or identifier == "NewObject" or identifier == "ChromeOnly" or identifier == "Pref" or identifier == "Func" or identifier == "AvailableIn" or - identifier == "Pure" or - identifier == "CrossOriginCallable" or - identifier == "WebGLHandlesContextLoss" or identifier == "CheckPermissions"): # Known attributes that we don't need to do anything with here pass @@ -3501,6 +3869,9 @@ class IDLMethod(IDLInterfaceMember, IDLScope): def returnsPromise(self): return self._overloads[0].returnType.isPromise() + def isUnforgeable(self): + return self._unforgeable + def _getDependentObjects(self): deps = set() for overload in self._overloads: @@ -3612,7 +3983,7 @@ class Tokenizer(object): return t def t_IDENTIFIER(self, t): - r'[A-Z_a-z][0-9A-Z_a-z]*' + r'[A-Z_a-z][0-9A-Z_a-z-]*' t.type = self.keywords.get(t.value, 'IDENTIFIER') return t @@ -3668,6 +4039,7 @@ class Tokenizer(object): "Date": "DATE", "DOMString": "DOMSTRING", "ByteString": "BYTESTRING", + "ScalarValueString": "SCALARVALUESTRING", "any": "ANY", "boolean": "BOOLEAN", "byte": "BYTE", @@ -3677,6 +4049,7 @@ class Tokenizer(object): "object": "OBJECT", "octet": "OCTET", "optional": "OPTIONAL", + "Promise": "PROMISE", "sequence": "SEQUENCE", "MozMap": "MOZMAP", "short": "SHORT", @@ -3717,6 +4090,45 @@ class Tokenizer(object): lextab='webidllex', reflags=re.DOTALL) +class SqueakyCleanLogger(object): + errorWhitelist = [ + # Web IDL defines the WHITESPACE token, but doesn't actually + # use it ... so far. + "Token 'WHITESPACE' defined, but not used", + # And that means we have an unused token + "There is 1 unused token", + # Web IDL defines a OtherOrComma rule that's only used in + # ExtendedAttributeInner, which we don't use yet. + "Rule 'OtherOrComma' defined, but not used", + # And an unused rule + "There is 1 unused rule", + # And the OtherOrComma grammar symbol is unreachable. + "Symbol 'OtherOrComma' is unreachable", + # Which means the Other symbol is unreachable. + "Symbol 'Other' is unreachable", + ] + def __init__(self): + self.errors = [] + def debug(self, msg, *args, **kwargs): + pass + info = debug + def warning(self, msg, *args, **kwargs): + if msg == "%s:%d: Rule '%s' defined, but not used": + # Munge things so we don't have to hardcode filenames and + # line numbers in our whitelist. + whitelistmsg = "Rule '%s' defined, but not used" + whitelistargs = args[2:] + else: + whitelistmsg = msg + whitelistargs = args + if (whitelistmsg % whitelistargs) not in SqueakyCleanLogger.errorWhitelist: + self.errors.append(msg % args) + error = warning + + def reportGrammarErrors(self): + if self.errors: + raise WebIDLError("\n".join(self.errors), []) + class Parser(Tokenizer): def getLocation(self, p, i): return Location(self.lexer, p.lineno(i), p.lexpos(i), self._filename) @@ -3792,10 +4204,11 @@ class Parser(Tokenizer): parent = p[3] try: - if self.globalScope()._lookupIdentifier(identifier): - p[0] = self.globalScope()._lookupIdentifier(identifier) + existingObj = self.globalScope()._lookupIdentifier(identifier) + if existingObj: + p[0] = existingObj if not isinstance(p[0], IDLInterface): - raise WebIDLError("Partial interface has the same name as " + raise WebIDLError("Interface has the same name as " "non-interface object", [location, p[0].location]) p[0].setNonPartial(location, parent, members) @@ -3806,7 +4219,7 @@ class Parser(Tokenizer): pass p[0] = IDLInterface(location, self.globalScope(), identifier, parent, - members, isPartial=False) + members, isKnownNonPartial=True) def p_InterfaceForwardDecl(self, p): """ @@ -3839,26 +4252,26 @@ class Parser(Tokenizer): identifier = IDLUnresolvedIdentifier(self.getLocation(p, 3), p[3]) members = p[5] + nonPartialInterface = None try: - if self.globalScope()._lookupIdentifier(identifier): - p[0] = self.globalScope()._lookupIdentifier(identifier) - if not isinstance(p[0], IDLInterface): + nonPartialInterface = self.globalScope()._lookupIdentifier(identifier) + if nonPartialInterface: + if not isinstance(nonPartialInterface, IDLInterface): raise WebIDLError("Partial interface has the same name as " "non-interface object", - [location, p[0].location]) - # Just throw our members into the existing IDLInterface. If we - # have extended attributes, those will get added to it - # automatically. - p[0].members.extend(members) - return + [location, nonPartialInterface.location]) except Exception, ex: if isinstance(ex, WebIDLError): raise ex pass - p[0] = IDLInterface(location, self.globalScope(), identifier, None, - members, isPartial=True) - pass + if not nonPartialInterface: + nonPartialInterface = IDLInterface(location, self.globalScope(), + identifier, None, + [], isKnownNonPartial=False) + partialInterface = IDLPartialInterface(location, identifier, members, + nonPartialInterface) + p[0] = partialInterface def p_Inheritance(self, p): """ @@ -3921,7 +4334,7 @@ class Parser(Tokenizer): def p_DictionaryMember(self, p): """ - DictionaryMember : Type IDENTIFIER DefaultValue SEMICOLON + DictionaryMember : Type IDENTIFIER Default SEMICOLON """ # These quack a lot like optional arguments, so just treat them that way. t = p[1] @@ -3933,16 +4346,27 @@ class Parser(Tokenizer): defaultValue=defaultValue, variadic=False, dictionaryMember=True) - def p_DefaultValue(self, p): + def p_Default(self, p): """ - DefaultValue : EQUALS ConstValue - | + Default : EQUALS DefaultValue + | """ if len(p) > 1: p[0] = p[2] else: p[0] = None + def p_DefaultValue(self, p): + """ + DefaultValue : ConstValue + | LBRACKET RBRACKET + """ + if len(p) == 2: + p[0] = p[1] + else: + assert len(p) == 3 # Must be [] + p[0] = IDLEmptySequenceValue(self.getLocation(p, 1)) + def p_Exception(self, p): """ Exception : EXCEPTION IDENTIFIER Inheritance LBRACE ExceptionMembers RBRACE SEMICOLON @@ -4401,7 +4825,7 @@ class Parser(Tokenizer): def p_Argument(self, p): """ - Argument : ExtendedAttributeList Optional Type Ellipsis ArgumentName DefaultValue + Argument : ExtendedAttributeList Optional Type Ellipsis ArgumentName Default """ t = p[3] assert isinstance(t, IDLType) @@ -4512,6 +4936,7 @@ class Parser(Tokenizer): | ExtendedAttributeArgList | ExtendedAttributeIdent | ExtendedAttributeNamedArgList + | ExtendedAttributeIdentList """ p[0] = IDLExtendedAttribute(self.getLocation(p, 1), p[1]) @@ -4552,6 +4977,7 @@ class Parser(Tokenizer): | DATE | DOMSTRING | BYTESTRING + | SCALARVALUESTRING | ANY | ATTRIBUTE | BOOLEAN @@ -4685,6 +5111,20 @@ class Parser(Tokenizer): type = IDLNullableType(self.getLocation(p, 5), type) p[0] = type + # Note: Promise<void> is allowed, so we want to parametrize on + # ReturnType, not Type. Also, we want this to end up picking up + # the Promise interface for now, hence the games with IDLUnresolvedType. + def p_NonAnyTypePromiseType(self, p): + """ + NonAnyType : PROMISE LT ReturnType GT Null + """ + innerType = p[3] + promiseIdent = IDLUnresolvedIdentifier(self.getLocation(p, 1), "Promise") + type = IDLUnresolvedType(self.getLocation(p, 1), promiseIdent, p[3]) + if p[5]: + type = IDLNullableType(self.getLocation(p, 5), type) + p[0] = type + def p_NonAnyTypeMozMapType(self, p): """ NonAnyType : MOZMAP LT Type GT Null @@ -4701,6 +5141,11 @@ class Parser(Tokenizer): """ assert isinstance(p[1], IDLUnresolvedIdentifier) + if p[1].name == "Promise": + raise WebIDLError("Promise used without saying what it's " + "parametrized over", + [self.getLocation(p, 1)]) + type = None try: @@ -4805,6 +5250,12 @@ class Parser(Tokenizer): """ p[0] = IDLBuiltinType.Types.bytestring + def p_PrimitiveOrStringTypeScalarValueString(self, p): + """ + PrimitiveOrStringType : SCALARVALUESTRING + """ + p[0] = IDLBuiltinType.Types.scalarvaluestring + def p_UnsignedIntegerTypeUnsigned(self, p): """ UnsignedIntegerType : UNSIGNED IntegerType @@ -4960,6 +5411,34 @@ class Parser(Tokenizer): """ p[0] = (p[1], p[3], p[5]) + def p_ExtendedAttributeIdentList(self, p): + """ + ExtendedAttributeIdentList : IDENTIFIER EQUALS LPAREN IdentifierList RPAREN + """ + p[0] = (p[1], p[4]) + + def p_IdentifierList(self, p): + """ + IdentifierList : IDENTIFIER Identifiers + """ + idents = list(p[2]) + idents.insert(0, p[1]) + p[0] = idents + + def p_IdentifiersList(self, p): + """ + Identifiers : COMMA IDENTIFIER Identifiers + """ + idents = list(p[3]) + idents.insert(0, p[2]) + p[0] = idents + + def p_IdentifiersEmpty(self, p): + """ + Identifiers : + """ + p[0] = [] + def p_error(self, p): if not p: raise WebIDLError("Syntax Error at end of file. Possibly due to missing semicolon(;), braces(}) or both", @@ -4969,12 +5448,26 @@ class Parser(Tokenizer): def __init__(self, outputdir='', lexer=None): Tokenizer.__init__(self, outputdir, lexer) + + logger = SqueakyCleanLogger() self.parser = yacc.yacc(module=self, outputdir=outputdir, tabmodule='webidlyacc', - errorlog=yacc.NullLogger(), - picklefile='WebIDLGrammar.pkl') + errorlog=logger + # Pickling the grammar is a speedup in + # some cases (older Python?) but a + # significant slowdown in others. + # We're not pickling for now, until it + # becomes a speedup again. + # , picklefile='WebIDLGrammar.pkl' + ) + logger.reportGrammarErrors() + self._globalScope = IDLScope(BuiltinLocation("<Global Scope>"), None, None) + # To make our test harness work, pretend like we have a primary global already. Note that we _don't_ set _globalScope.primaryGlobalAttr, so we'll still be able to detect multiple PrimaryGlobal extended attributes. + self._globalScope.primaryGlobalName = "FakeTestPrimaryGlobal" + self._globalScope.globalNames.add("FakeTestPrimaryGlobal") + self._globalScope.globalNameMapping["FakeTestPrimaryGlobal"].add("FakeTestPrimaryGlobal") self._installBuiltins(self._globalScope) self._productions = [] |