Source code

Revision control

Copy as Markdown

Other Tools

# 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/.
""" A WebIDL parser. """
import copy
import math
import os
import re
import string
import traceback
from collections import OrderedDict, defaultdict
from itertools import chain
from ply import lex, yacc
# Machinery
def parseInt(literal):
string = literal
sign = 0
base = 0
if string[0] == "-":
sign = -1
string = string[1:]
else:
sign = 1
if string[0] == "0" and len(string) > 1:
if string[1] == "x" or string[1] == "X":
base = 16
string = string[2:]
else:
base = 8
string = string[1:]
else:
base = 10
value = int(string, base)
return value * sign
# This is surprisingly faster than using the enum.IntEnum type (which doesn't
# support 'base' anyway)
def enum(*names, base=None):
if base is not None:
names = base.attrs + names
class CustomEnumType(object):
attrs = names
def __setattr__(self, name, value): # this makes it read-only
raise NotImplementedError
for v, k in enumerate(names):
setattr(CustomEnumType, k, v)
return CustomEnumType()
class WebIDLError(Exception):
def __init__(self, message, locations, warning=False):
self.message = message
self.locations = [str(loc) for loc in locations]
self.warning = warning
def __str__(self):
return "%s: %s%s%s" % (
self.warning and "warning" or "error",
self.message,
", " if len(self.locations) != 0 else "",
"\n".join(self.locations),
)
class Location(object):
def __init__(self, lexer, lineno, lexpos, filename):
self._line = None
self._lineno = lineno
self._lexpos = lexpos
self._lexdata = lexer.lexdata
self.filename = filename if filename else "<unknown>"
def __eq__(self, other):
return self._lexpos == other._lexpos and self.filename == other.filename
def resolve(self):
if self._line:
return
startofline = self._lexdata.rfind("\n", 0, self._lexpos) + 1
endofline = self._lexdata.find("\n", self._lexpos, self._lexpos + 80)
if endofline != -1:
self._line = self._lexdata[startofline:endofline]
else:
self._line = self._lexdata[startofline:]
self._colno = self._lexpos - startofline
# Our line number seems to point to the start of self._lexdata
self._lineno += self._lexdata.count("\n", 0, startofline)
def get(self):
self.resolve()
return "%s line %s:%s" % (self.filename, self._lineno, self._colno)
def _pointerline(self):
return " " * self._colno + "^"
def __str__(self):
self.resolve()
return "%s line %s:%s\n%s\n%s" % (
self.filename,
self._lineno,
self._colno,
self._line,
self._pointerline(),
)
class BuiltinLocation(object):
__slots__ = "msg", "filename"
def __init__(self, text):
self.msg = text + "\n"
self.filename = "<builtin>"
def __eq__(self, other):
return isinstance(other, BuiltinLocation) and self.msg == other.msg
def resolve(self):
pass
def get(self):
return self.msg
def __str__(self):
return self.get()
# Data Model
class IDLObject(object):
__slots__ = "location", "userData", "filename"
def __init__(self, location):
self.location = location
self.userData = {}
self.filename = location and location.filename
def isInterface(self):
return False
def isNamespace(self):
return False
def isInterfaceMixin(self):
return False
def isEnum(self):
return False
def isCallback(self):
return False
def isType(self):
return False
def isDictionary(self):
return False
def isUnion(self):
return False
def isTypedef(self):
return False
def getUserData(self, key, default):
return self.userData.get(key, default)
def setUserData(self, key, value):
self.userData[key] = value
def addExtendedAttributes(self, attrs):
assert False # Override me!
def handleExtendedAttribute(self, attr):
assert False # Override me!
def _getDependentObjects(self):
assert False # Override me!
def getDeps(self, visited=None):
"""Return a set of files that this object depends on. If any of
these files are changed the parser needs to be rerun to regenerate
a new IDLObject.
The visited argument is a set of all the objects already visited.
We must test to see if we are in it, and if so, do nothing. This
prevents infinite recursion."""
# NB: We can't use visited=set() above because the default value is
# evaluated when the def statement is evaluated, not when the function
# is executed, so there would be one set for all invocations.
if visited is None:
visited = set()
if self in visited:
return set()
visited.add(self)
deps = set()
if self.filename != "<builtin>":
deps.add(self.filename)
for d in self._getDependentObjects():
deps.update(d.getDeps(visited))
return deps
class IDLScope(IDLObject):
__slots__ = "parentScope", "_name", "_dict", "globalNames", "globalNameMapping"
def __init__(self, location, parentScope, identifier):
IDLObject.__init__(self, location)
self.parentScope = parentScope
if identifier:
assert isinstance(identifier, IDLIdentifier)
self._name = identifier
else:
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)
def __str__(self):
return self.QName()
def QName(self):
# It's possible for us to be called before __init__ has been called, for
# the IDLObjectWithScope case. In that case, self._name won't be set yet.
if hasattr(self, "_name"):
name = self._name
else:
name = None
if name:
return name.QName() + "::"
return "::"
def ensureUnique(self, identifier, object):
"""
Ensure that there is at most one 'identifier' in scope ('self').
Note that object can be None. This occurs if we end up here for an
interface type we haven't seen yet.
"""
assert isinstance(identifier, IDLUnresolvedIdentifier)
assert not object or isinstance(object, IDLObjectWithIdentifier)
assert not object or object.identifier == identifier
if identifier.name in self._dict:
if not object:
return
# ensureUnique twice with the same object is not allowed
assert id(object) != id(self._dict[identifier.name])
replacement = self.resolveIdentifierConflict(
self, identifier, self._dict[identifier.name], object
)
self._dict[identifier.name] = replacement
return
self.addNewIdentifier(identifier, object)
def addNewIdentifier(self, identifier, object):
assert object
self._dict[identifier.name] = object
def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject):
if (
isinstance(originalObject, IDLExternalInterface)
and isinstance(newObject, IDLExternalInterface)
and originalObject.identifier.name == newObject.identifier.name
):
return originalObject
if isinstance(originalObject, IDLExternalInterface) or isinstance(
newObject, IDLExternalInterface
):
raise WebIDLError(
"Name collision between "
"interface declarations for identifier '%s' at '%s' and '%s'"
% (identifier.name, originalObject.location, newObject.location),
[],
)
if isinstance(originalObject, IDLDictionary) or isinstance(
newObject, IDLDictionary
):
raise WebIDLError(
"Name collision between dictionary declarations for "
"identifier '%s'.\n%s\n%s"
% (identifier.name, originalObject.location, newObject.location),
[],
)
# We do the merging of overloads here as opposed to in IDLInterface
# because we need to merge overloads of LegacyFactoryFunctions and we need to
# detect conflicts in those across interfaces. See also the comment in
# IDLInterface.addExtendedAttributes for "LegacyFactoryFunction".
if isinstance(originalObject, IDLMethod) and isinstance(newObject, IDLMethod):
return originalObject.addOverload(newObject)
# Default to throwing, derived classes can override.
raise self.createIdentifierConflictError(identifier, originalObject, newObject)
def createIdentifierConflictError(self, identifier, originalObject, newObject):
conflictdesc = "\n\t%s at %s\n\t%s at %s" % (
originalObject,
originalObject.location,
newObject,
newObject.location,
)
return WebIDLError(
"Multiple unresolvable definitions of identifier '%s' in scope '%s'%s"
% (identifier.name, str(self), conflictdesc),
[],
)
def _lookupIdentifier(self, identifier):
return self._dict[identifier.name]
def lookupIdentifier(self, identifier):
assert isinstance(identifier, IDLIdentifier)
assert identifier.scope == self
return self._lookupIdentifier(identifier)
def addIfaceGlobalNames(self, interfaceName, globalNames):
"""Record the global names (from |globalNames|) that can be used in
[Exposed] to expose things in a global named |interfaceName|"""
self.globalNames.update(globalNames)
for name in globalNames:
self.globalNameMapping[name].add(interfaceName)
class IDLIdentifier(IDLObject):
__slots__ = "name", "scope"
def __init__(self, location, scope, name):
IDLObject.__init__(self, location)
self.name = name
assert isinstance(scope, IDLScope)
self.scope = scope
def __str__(self):
return self.QName()
def QName(self):
return self.scope.QName() + self.name
def __hash__(self):
return self.QName().__hash__()
def __eq__(self, other):
return self.QName() == other.QName()
def object(self):
return self.scope.lookupIdentifier(self)
class IDLUnresolvedIdentifier(IDLObject):
__slots__ = ("name",)
def __init__(
self, location, name, allowDoubleUnderscore=False, allowForbidden=False
):
IDLObject.__init__(self, location)
assert name
if name == "__noSuchMethod__":
raise WebIDLError("__noSuchMethod__ is deprecated", [location])
if name[:2] == "__" and not allowDoubleUnderscore:
raise WebIDLError("Identifiers beginning with __ are reserved", [location])
if name[0] == "_" and not allowDoubleUnderscore:
name = name[1:]
if name in ["constructor", "toString"] and not allowForbidden:
raise WebIDLError(
"Cannot use reserved identifier '%s'" % (name), [location]
)
self.name = name
def __str__(self):
return self.QName()
def QName(self):
return "<unresolved scope>::" + self.name
def resolve(self, scope, object):
assert isinstance(scope, IDLScope)
assert not object or isinstance(object, IDLObjectWithIdentifier)
assert not object or object.identifier == self
scope.ensureUnique(self, object)
identifier = IDLIdentifier(self.location, scope, self.name)
if object:
object.identifier = identifier
return identifier
def finish(self):
assert False # Should replace with a resolved identifier first.
class IDLObjectWithIdentifier(IDLObject):
# no slots, incompatible with multiple inheritance
def __init__(self, location, parentScope, identifier):
IDLObject.__init__(self, location)
assert isinstance(identifier, IDLUnresolvedIdentifier)
self.identifier = identifier
if parentScope:
self.resolve(parentScope)
def resolve(self, parentScope):
assert isinstance(parentScope, IDLScope)
assert isinstance(self.identifier, IDLUnresolvedIdentifier)
self.identifier.resolve(parentScope, self)
class IDLObjectWithScope(IDLObjectWithIdentifier, IDLScope):
__slots__ = ()
def __init__(self, location, parentScope, identifier):
assert isinstance(identifier, IDLUnresolvedIdentifier)
IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier)
IDLScope.__init__(self, location, parentScope, self.identifier)
class IDLIdentifierPlaceholder(IDLObjectWithIdentifier):
__slots__ = ()
def __init__(self, location, identifier):
assert isinstance(identifier, IDLUnresolvedIdentifier)
IDLObjectWithIdentifier.__init__(self, location, None, identifier)
def finish(self, scope):
try:
scope._lookupIdentifier(self.identifier)
except Exception:
raise WebIDLError(
"Unresolved type '%s'." % self.identifier, [self.location]
)
obj = self.identifier.resolve(scope, None)
return scope.lookupIdentifier(obj)
class IDLExposureMixins:
# no slots, incompatible with multiple inheritance
def __init__(self, location):
# _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()
self._location = location
self._globalScope = None
def finish(self, scope):
assert scope.parentScope is None
self._globalScope = scope
if "*" in self._exposureGlobalNames:
self._exposureGlobalNames = scope.globalNames
else:
# 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]
)
# Verify that we are exposed _somwhere_ if we have some place to be
# exposed. We don't want to assert that we're definitely exposed
# because a lot of our parser tests have small-enough IDL snippets that
# they don't include any globals, and we don't really want to go through
# and add global interfaces and [Exposed] annotations to all those
# tests.
if len(scope.globalNames) != 0 and len(self._exposureGlobalNames) == 0:
raise WebIDLError(
(
"'%s' is not exposed anywhere even though we have "
"globals to be exposed to"
)
% self,
[self.location],
)
globalNameSetToExposureSet(scope, self._exposureGlobalNames, self.exposureSet)
def isExposedInWindow(self):
return "Window" in self.exposureSet
def isExposedInAnyWorker(self):
return len(self.getWorkerExposureSet()) > 0
def isExposedInWorkerDebugger(self):
return len(self.getWorkerDebuggerExposureSet()) > 0
def isExposedInAnyWorklet(self):
return len(self.getWorkletExposureSet()) > 0
def isExposedInSomeButNotAllWorkers(self):
"""
Returns true if the Exposed extended attribute for this interface
exposes it in some worker globals but not others. The return value does
not depend on whether the interface is exposed in Window or System
globals.
"""
if not self.isExposedInAnyWorker():
return False
workerScopes = self.parentScope.globalNameMapping["Worker"]
return len(workerScopes.difference(self.exposureSet)) > 0
def isExposedInShadowRealms(self):
return "ShadowRealmGlobalScope" in self.exposureSet
def getWorkerExposureSet(self):
workerScopes = self._globalScope.globalNameMapping["Worker"]
return workerScopes.intersection(self.exposureSet)
def getWorkletExposureSet(self):
workletScopes = self._globalScope.globalNameMapping["Worklet"]
return workletScopes.intersection(self.exposureSet)
def getWorkerDebuggerExposureSet(self):
workerDebuggerScopes = self._globalScope.globalNameMapping["WorkerDebugger"]
return workerDebuggerScopes.intersection(self.exposureSet)
class IDLExternalInterface(IDLObjectWithIdentifier):
__slots__ = ("parent",)
def __init__(self, location, parentScope, identifier):
assert isinstance(identifier, IDLUnresolvedIdentifier)
assert isinstance(parentScope, IDLScope)
self.parent = None
IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier)
IDLObjectWithIdentifier.resolve(self, parentScope)
def finish(self, scope):
pass
def validate(self):
pass
def isIteratorInterface(self):
return False
def isAsyncIteratorInterface(self):
return False
def isExternal(self):
return True
def isInterface(self):
return True
def addExtendedAttributes(self, attrs):
if len(attrs) != 0:
raise WebIDLError(
"There are no extended attributes that are "
"allowed on external interfaces",
[attrs[0].location, self.location],
)
def resolve(self, parentScope):
pass
def getJSImplementation(self):
return None
def isJSImplemented(self):
return False
def hasProbablyShortLivingWrapper(self):
return False
def _getDependentObjects(self):
return set()
class IDLPartialDictionary(IDLObject):
__slots__ = "identifier", "members", "_nonPartialDictionary", "_finished"
def __init__(self, location, name, members, nonPartialDictionary):
assert isinstance(name, IDLUnresolvedIdentifier)
IDLObject.__init__(self, location)
self.identifier = name
self.members = members
self._nonPartialDictionary = nonPartialDictionary
self._finished = False
nonPartialDictionary.addPartialDictionary(self)
def addExtendedAttributes(self, attrs):
pass
def finish(self, scope):
if self._finished:
return
self._finished = True
# Need to make sure our non-partial dictionary gets
# finished so it can report cases when we only have partial
# dictionaries.
self._nonPartialDictionary.finish(scope)
def validate(self):
pass
class IDLPartialInterfaceOrNamespace(IDLObject):
__slots__ = (
"identifier",
"members",
"propagatedExtendedAttrs",
"_haveSecureContextExtendedAttribute",
"_nonPartialInterfaceOrNamespace",
"_finished",
)
def __init__(self, location, name, members, nonPartialInterfaceOrNamespace):
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._haveSecureContextExtendedAttribute = False
self._nonPartialInterfaceOrNamespace = nonPartialInterfaceOrNamespace
self._finished = False
nonPartialInterfaceOrNamespace.addPartial(self)
def addExtendedAttributes(self, attrs):
for attr in attrs:
identifier = attr.identifier()
if identifier == "LegacyFactoryFunction":
self.propagatedExtendedAttrs.append(attr)
elif identifier == "SecureContext":
self._haveSecureContextExtendedAttribute = True
# This gets propagated to all our members.
for member in self.members:
if member.getExtendedAttribute("SecureContext"):
raise WebIDLError(
"[SecureContext] specified on both a "
"partial interface member and on the "
"partial interface itself",
[member.location, attr.location],
)
member.addExtendedAttributes([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
if (
not self._haveSecureContextExtendedAttribute
and self._nonPartialInterfaceOrNamespace.getExtendedAttribute(
"SecureContext"
)
):
# This gets propagated to all our members.
for member in self.members:
if member.getExtendedAttribute("SecureContext"):
raise WebIDLError(
"[SecureContext] specified on both a "
"partial interface member and on the "
"non-partial interface",
[
member.location,
self._nonPartialInterfaceOrNamespace.location,
],
)
member.addExtendedAttributes(
[
IDLExtendedAttribute(
self._nonPartialInterfaceOrNamespace.location,
("SecureContext",),
)
]
)
# Need to make sure our non-partial interface or namespace gets
# finished so it can report cases when we only have partial
# interfaces/namespaces.
self._nonPartialInterfaceOrNamespace.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])
# Because WebIDL allows static and regular operations with the same identifier
# we use a special class to be able to store them both in the scope for the
# same identifier.
class IDLOperations:
__slots__ = "static", "regular"
def __init__(self, static=None, regular=None):
self.static = static
self.regular = regular
class IDLInterfaceOrInterfaceMixinOrNamespace(IDLObjectWithScope, IDLExposureMixins):
__slots__ = (
"_finished",
"members",
"_partials",
"_extendedAttrDict",
"_isKnownNonPartial",
)
def __init__(self, location, parentScope, name):
assert isinstance(parentScope, IDLScope)
assert isinstance(name, IDLUnresolvedIdentifier)
self._finished = False
self.members = []
self._partials = []
self._extendedAttrDict = {}
self._isKnownNonPartial = False
IDLObjectWithScope.__init__(self, location, parentScope, name)
IDLExposureMixins.__init__(self, location)
def finish(self, scope):
if not self._isKnownNonPartial:
raise WebIDLError(
"%s does not have a non-partial declaration" % str(self),
[self.location],
)
IDLExposureMixins.finish(self, scope)
# Now go ahead and merge in our partials.
for partial in self._partials:
partial.finish(scope)
self.addExtendedAttributes(partial.propagatedExtendedAttrs)
self.members.extend(partial.members)
def addNewIdentifier(self, identifier, object):
if isinstance(object, IDLMethod):
if object.isStatic():
object = IDLOperations(static=object)
else:
object = IDLOperations(regular=object)
IDLScope.addNewIdentifier(self, identifier, object)
def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject):
assert isinstance(scope, IDLScope)
assert isinstance(newObject, IDLInterfaceMember)
# The identifier of a regular operation or static operation must not be
# the same as the identifier of a constant or attribute.
if isinstance(newObject, IDLMethod) != isinstance(
originalObject, IDLOperations
):
if isinstance(originalObject, IDLOperations):
if originalObject.regular is not None:
originalObject = originalObject.regular
else:
assert originalObject.static is not None
originalObject = originalObject.static
raise self.createIdentifierConflictError(
identifier, originalObject, newObject
)
if isinstance(newObject, IDLMethod):
originalOperations = originalObject
if newObject.isStatic():
if originalOperations.static is None:
originalOperations.static = newObject
return originalOperations
originalObject = originalOperations.static
else:
if originalOperations.regular is None:
originalOperations.regular = newObject
return originalOperations
originalObject = originalOperations.regular
assert isinstance(originalObject, IDLMethod)
else:
assert isinstance(originalObject, IDLInterfaceMember)
retval = IDLScope.resolveIdentifierConflict(
self, scope, identifier, originalObject, newObject
)
if isinstance(newObject, IDLMethod):
if newObject.isStatic():
originalOperations.static = retval
else:
originalOperations.regular = retval
retval = originalOperations
# Might be a ctor, which isn't in self.members
if newObject in self.members:
self.members.remove(newObject)
return retval
def typeName(self):
if self.isInterface():
return "interface"
if self.isNamespace():
return "namespace"
assert self.isInterfaceMixin()
return "interface mixin"
def getExtendedAttribute(self, name):
return self._extendedAttrDict.get(name, None)
def setNonPartial(self, location, members):
if self._isKnownNonPartial:
raise WebIDLError(
"Two non-partial definitions for the " "same %s" % self.typeName(),
[location, self.location],
)
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
# Put the new members at the beginning
self.members = members + self.members
def addPartial(self, partial):
assert self.identifier.name == partial.identifier.name
self._partials.append(partial)
def getPartials(self):
# Don't let people mutate our guts.
return list(self._partials)
def finishMembers(self, scope):
# Assuming we've merged in our partials, 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 or interface mixin
# that defined them" and "partial interfaces or interface mixins default
# to interface or interface mixin they're a partial for" rules from the
# spec.
for m in self.members:
# If m, or the partial 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)
if m.isAttr() and m.stringifier:
m.expand(self.members)
# resolve() will modify self.members, so we need to iterate
# over a copy of the member list here.
for member in list(self.members):
member.resolve(self)
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 or interface mixin member has "
"larger exposure set than its container",
[member.location, self.location],
)
def isExternal(self):
return False
class IDLInterfaceMixin(IDLInterfaceOrInterfaceMixinOrNamespace):
__slots__ = ("actualExposureGlobalNames",)
def __init__(self, location, parentScope, name, members, isKnownNonPartial):
self.actualExposureGlobalNames = set()
assert isKnownNonPartial or not members
IDLInterfaceOrInterfaceMixinOrNamespace.__init__(
self, location, parentScope, name
)
if isKnownNonPartial:
self.setNonPartial(location, members)
def __str__(self):
return "Interface mixin '%s'" % self.identifier.name
def isInterfaceMixin(self):
return True
def finish(self, scope):
if self._finished:
return
self._finished = True
# Expose to the globals of interfaces that includes this mixin if this
# mixin has no explicit [Exposed] so that its members can be exposed
# based on the base interface exposure set.
#
# Make sure this is done before IDLExposureMixins.finish call, since
# that converts our set of exposure global names to an actual exposure
# set.
hasImplicitExposure = len(self._exposureGlobalNames) == 0
if hasImplicitExposure:
self._exposureGlobalNames.update(self.actualExposureGlobalNames)
IDLInterfaceOrInterfaceMixinOrNamespace.finish(self, scope)
self.finishMembers(scope)
def validate(self):
for member in self.members:
if member.isAttr():
if member.inherit:
raise WebIDLError(
"Interface mixin member cannot include "
"an inherited attribute",
[member.location, self.location],
)
if member.isStatic():
raise WebIDLError(
"Interface mixin member cannot include " "a static member",
[member.location, self.location],
)
if member.isMethod():
if member.isStatic():
raise WebIDLError(
"Interface mixin member cannot include " "a static operation",
[member.location, self.location],
)
if (
member.isGetter()
or member.isSetter()
or member.isDeleter()
or member.isLegacycaller()
):
raise WebIDLError(
"Interface mixin member cannot include a " "special operation",
[member.location, self.location],
)
def addExtendedAttributes(self, attrs):
for attr in attrs:
identifier = attr.identifier()
if identifier == "SecureContext":
if not attr.noArguments():
raise WebIDLError(
"[%s] must take no arguments" % identifier, [attr.location]
)
# This gets propagated to all our members.
for member in self.members:
if member.getExtendedAttribute("SecureContext"):
raise WebIDLError(
"[SecureContext] specified on both "
"an interface mixin member and on"
"the interface mixin itself",
[member.location, attr.location],
)
member.addExtendedAttributes([attr])
elif identifier == "Exposed":
convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames)
else:
raise WebIDLError(
"Unknown extended attribute %s on interface" % identifier,
[attr.location],
)
attrlist = attr.listValue()
self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True
def _getDependentObjects(self):
return set(self.members)
class IDLInterfaceOrNamespace(IDLInterfaceOrInterfaceMixinOrNamespace):
__slots__ = (
"parent",
"_callback",
"maplikeOrSetlikeOrIterable",
"legacyFactoryFunctions",
"legacyWindowAliases",
"includedMixins",
"interfacesBasedOnSelf",
"_hasChildInterfaces",
"_isOnGlobalProtoChain",
"totalMembersInSlots",
"_ownMembersInSlots",
"iterableInterface",
"asyncIterableInterface",
"hasCrossOriginMembers",
"hasDescendantWithCrossOriginMembers",
)
def __init__(self, location, parentScope, name, parent, members, isKnownNonPartial):
assert isKnownNonPartial or not parent
assert isKnownNonPartial or not members
self.parent = None
self._callback = False
self.maplikeOrSetlikeOrIterable = None
# legacyFactoryFunctions needs deterministic ordering because bindings code
# outputs the constructs in the order that legacyFactoryFunctions enumerates
# them.
self.legacyFactoryFunctions = []
self.legacyWindowAliases = []
self.includedMixins = set()
# self.interfacesBasedOnSelf is the set of interfaces that inherit from
# self, including self itself.
# Used for distinguishability checking.
self.interfacesBasedOnSelf = {self}
self._hasChildInterfaces = False
self._isOnGlobalProtoChain = False
# Tracking of the number of reserved slots we need for our
# members and those of ancestor interfaces.
self.totalMembersInSlots = 0
# Tracking of the number of own own members we have in slots
self._ownMembersInSlots = 0
# If this is an iterator interface, we need to know what iterable
# interface we're iterating for in order to get its nativeType.
self.iterableInterface = None
self.asyncIterableInterface = None
# True if we have cross-origin members.
self.hasCrossOriginMembers = False
# True if some descendant (including ourselves) has cross-origin members
self.hasDescendantWithCrossOriginMembers = False
IDLInterfaceOrInterfaceMixinOrNamespace.__init__(
self, location, parentScope, name
)
if isKnownNonPartial:
self.setNonPartial(location, parent, members)
def ctor(self):
identifier = IDLUnresolvedIdentifier(
self.location, "constructor", allowForbidden=True
)
try:
return self._lookupIdentifier(identifier).static
except Exception:
return None
def isIterable(self):
return (
self.maplikeOrSetlikeOrIterable
and self.maplikeOrSetlikeOrIterable.isIterable()
)
def isAsyncIterable(self):
return (
self.maplikeOrSetlikeOrIterable
and self.maplikeOrSetlikeOrIterable.isAsyncIterable()
)
def isIteratorInterface(self):
return self.iterableInterface is not None
def isAsyncIteratorInterface(self):
return self.asyncIterableInterface is not None
def getClassName(self):
return self.identifier.name
def finish(self, scope):
if self._finished:
return
self._finished = True
IDLInterfaceOrInterfaceMixinOrNamespace.finish(self, scope)
if len(self.legacyWindowAliases) > 0:
if not self.hasInterfaceObject():
raise WebIDLError(
"Interface %s unexpectedly has [LegacyWindowAlias] "
"and [LegacyNoInterfaceObject] together" % self.identifier.name,
[self.location],
)
if not self.isExposedInWindow():
raise WebIDLError(
"Interface %s has [LegacyWindowAlias] "
"but not exposed in Window" % self.identifier.name,
[self.location],
)
# Generate maplike/setlike interface members. Since generated members
# need to be treated like regular interface members, do this before
# things like exposure setting.
for member in self.members:
if member.isMaplikeOrSetlikeOrIterable():
if self.isJSImplemented():
raise WebIDLError(
"%s declaration used on "
"interface that is implemented in JS"
% (member.maplikeOrSetlikeOrIterableType),
[member.location],
)
if member.valueType.isObservableArray() or (
member.hasKeyType() and member.keyType.isObservableArray()
):
raise WebIDLError(
"%s declaration uses ObservableArray as value or key type"
% (member.maplikeOrSetlikeOrIterableType),
[member.location],
)
# Check that we only have one interface declaration (currently
# there can only be one maplike/setlike declaration per
# interface)
if self.maplikeOrSetlikeOrIterable:
raise WebIDLError(
"%s declaration used on "
"interface that already has %s "
"declaration"
% (
member.maplikeOrSetlikeOrIterableType,
self.maplikeOrSetlikeOrIterable.maplikeOrSetlikeOrIterableType,
),
[self.maplikeOrSetlikeOrIterable.location, member.location],
)
self.maplikeOrSetlikeOrIterable = member
# If we've got a maplike or setlike declaration, we'll be building all of
# our required methods in Codegen. Generate members now.
self.maplikeOrSetlikeOrIterable.expand(self.members)
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):
raise WebIDLError(
"%s inherits from %s which does not have "
"a definition" % (self.identifier.name, self.parent.identifier.name),
[self.location],
)
if parent and not isinstance(parent, IDLInterface):
raise WebIDLError(
"%s inherits from %s which is not an interface "
% (self.identifier.name, self.parent.identifier.name),
[self.location, parent.location],
)
self.parent = parent
assert iter(self.members)
if self.isNamespace():
assert not self.parent
for m in self.members:
if m.isAttr() or m.isMethod():
if m.isStatic():
raise WebIDLError(
"Don't mark things explicitly static " "in namespaces",
[self.location, m.location],
)
# Just mark all our methods/attributes as static. The other
# option is to duplicate the relevant InterfaceMembers
# production bits but modified to produce static stuff to
# start with, but that sounds annoying.
m.forceStatic()
if self.parent:
self.parent.finish(scope)
self.parent._hasChildInterfaces = True
self.totalMembersInSlots = self.parent.totalMembersInSlots
# Interfaces with [Global] must not have anything inherit from them
if self.parent.getExtendedAttribute("Global"):
# Note: This is not a self.parent.isOnGlobalProtoChain() check
# because ancestors of a [Global] interface can have other
# descendants.
raise WebIDLError(
"[Global] interface has another interface " "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.
# XXXbz Can non-callbacks inherit from callbacks? Spec issue pending.
if self.isCallback():
if not self.parent.isCallback():
raise WebIDLError(
"Callback interface %s inheriting from "
"non-callback interface %s"
% (self.identifier.name, self.parent.identifier.name),
[self.location, self.parent.location],
)
elif self.parent.isCallback():
raise WebIDLError(
"Non-callback interface %s inheriting from "
"callback interface %s"
% (self.identifier.name, self.parent.identifier.name),
[self.location, self.parent.location],
)
# Interfaces which have interface objects can't inherit
# from [LegacyNoInterfaceObject] interfaces.
if self.parent.getExtendedAttribute(
"LegacyNoInterfaceObject"
) and not self.getExtendedAttribute("LegacyNoInterfaceObject"):
raise WebIDLError(
"Interface %s does not have "
"[LegacyNoInterfaceObject] but inherits from "
"interface %s which does"
% (self.identifier.name, self.parent.identifier.name),
[self.location, self.parent.location],
)
# Interfaces that are not [SecureContext] can't inherit
# from [SecureContext] interfaces.
if self.parent.getExtendedAttribute(
"SecureContext"
) and not self.getExtendedAttribute("SecureContext"):
raise WebIDLError(
"Interface %s does not have "
"[SecureContext] but inherits from "
"interface %s which does"
% (self.identifier.name, self.parent.identifier.name),
[self.location, self.parent.location],
)
for mixin in self.includedMixins:
mixin.finish(scope)
cycleInGraph = self.findInterfaceLoopPoint(self)
if cycleInGraph:
raise WebIDLError(
"Interface %s has itself as ancestor" % self.identifier.name,
[self.location, cycleInGraph.location],
)
self.finishMembers(scope)
ctor = self.ctor()
if ctor is not None:
if not self.hasInterfaceObject():
raise WebIDLError(
"Can't have both a constructor and [LegacyNoInterfaceObject]",
[self.location, ctor.location],
)
if self.globalNames:
raise WebIDLError(
"Can't have both a constructor and [Global]",
[self.location, ctor.location],
)
assert ctor._exposureGlobalNames == self._exposureGlobalNames
ctor._exposureGlobalNames.update(self._exposureGlobalNames)
# Remove the constructor operation from our member list so
# it doesn't get in the way later.
self.members.remove(ctor)
for ctor in self.legacyFactoryFunctions:
if self.globalNames:
raise WebIDLError(
"Can't have both a legacy factory function and [Global]",
[self.location, ctor.location],
)
assert len(ctor._exposureGlobalNames) == 0
ctor._exposureGlobalNames.update(self._exposureGlobalNames)
ctor.finish(scope)
# Make a copy of our member list, so things that implement us
# can get those without all the stuff we implement ourselves
# admixed.
self.originalMembers = list(self.members)
for mixin in sorted(self.includedMixins, key=lambda x: x.identifier.name):
for mixinMember in mixin.members:
for member in self.members:
if mixinMember.identifier.name == member.identifier.name and (
not mixinMember.isMethod()
or not member.isMethod()
or mixinMember.isStatic() == member.isStatic()
):
raise WebIDLError(
"Multiple definitions of %s on %s coming from 'includes' statements"
% (member.identifier.name, self),
[mixinMember.location, member.location],
)
self.members.extend(mixin.members)
for ancestor in self.getInheritedInterfaces():
ancestor.interfacesBasedOnSelf.add(self)
if (
ancestor.maplikeOrSetlikeOrIterable is not None
and self.maplikeOrSetlikeOrIterable is not None
):
raise WebIDLError(
"Cannot have maplike/setlike on %s that "
"inherits %s, which is already "
"maplike/setlike"
% (self.identifier.name, ancestor.identifier.name),
[
self.maplikeOrSetlikeOrIterable.location,
ancestor.maplikeOrSetlikeOrIterable.location,
],
)
# Deal with interfaces marked [LegacyUnforgeable], 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("LegacyUnforgeable"):
# 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(
"LegacyUnforgeable interface %s does not have a "
"stringifier" % self.identifier.name,
[self.location],
)
for m in self.members:
if m.identifier.name == "toJSON":
raise WebIDLError(
"LegacyUnforgeable interface %s has a "
"toJSON 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(
"LegacyUnforgeable 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() or member.isMethod())
and member.isLegacyUnforgeable()
and not hasattr(member, "originatingInterface")
):
member.originatingInterface = self
for member in self.members:
if (
member.isMethod() and member.getExtendedAttribute("CrossOriginCallable")
) or (
member.isAttr()
and (
member.getExtendedAttribute("CrossOriginReadable")
or member.getExtendedAttribute("CrossOriginWritable")
)
):
self.hasCrossOriginMembers = True
break
if self.hasCrossOriginMembers:
parent = self
while parent:
parent.hasDescendantWithCrossOriginMembers = True
parent = parent.parent
# Compute slot indices for our members before we pull in unforgeable
# members from our parent. Also, maplike/setlike declarations get a
# slot to hold their backing object.
for member in self.members:
if (
member.isAttr()
and (
member.getExtendedAttribute("StoreInSlot")
or member.getExtendedAttribute("Cached")
or member.type.isObservableArray()
)
) or member.isMaplikeOrSetlike():
if self.isJSImplemented() and not member.isMaplikeOrSetlike():
raise WebIDLError(
"Interface %s is JS-implemented and we "
"don't support [Cached] or [StoreInSlot] or ObservableArray "
"on JS-implemented interfaces" % self.identifier.name,
[self.location, member.location],
)
if member.slotIndices is None:
member.slotIndices = dict()
member.slotIndices[self.identifier.name] = self.totalMembersInSlots
self.totalMembersInSlots += 1
if member.getExtendedAttribute("StoreInSlot"):
self._ownMembersInSlots += 1
if self.parent:
# Make sure we don't shadow any of the [LegacyUnforgeable] attributes on our
# ancestor interfaces. We don't have to worry about mixins here, because
# those have already been imported into the relevant .members lists. And
# we don't have to worry about anything other than our parent, because it
# has already imported its ancestors' unforgeable attributes into its
# member list.
for unforgeableMember in (
member
for member in self.parent.members
if (member.isAttr() or member.isMethod())
and member.isLegacyUnforgeable()
):
shadows = [
m
for m in self.members
if (m.isAttr() or m.isMethod())
and not m.isStatic()
and m.identifier.name == unforgeableMember.identifier.name
]
if len(shadows) != 0:
locs = [unforgeableMember.location] + [s.location for s in shadows]
raise WebIDLError(
"Interface %s shadows [LegacyUnforgeable] "
"members of %s"
% (self.identifier.name, ancestor.identifier.name),
locs,
)
# 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/methods of ancestor interfaces, with their
# corresponding getters, on our interface, but that gets pretty
# complicated and seems unnecessary.
self.members.append(unforgeableMember)
# At this point, we have all of our members. If the current interface
# uses maplike/setlike, check for collisions anywhere in the current
# interface or higher in the inheritance chain.
if self.maplikeOrSetlikeOrIterable:
testInterface = self
isAncestor = False
while testInterface:
self.maplikeOrSetlikeOrIterable.checkCollisions(
testInterface.members, isAncestor
)
isAncestor = True
testInterface = testInterface.parent
# Ensure that there's at most one of each {named,indexed}
# {getter,setter,deleter}, at most one stringifier,
# and at most one legacycaller. Note that this last is not
# quite per spec, but in practice no one overloads
# legacycallers. Also note that in practice we disallow
# indexed deleters, but it simplifies some other code to
# treat deleter analogously to getter/setter by
# prefixing it with "named".
specialMembersSeen = {}
for member in self.members:
if not member.isMethod():
continue
if member.isGetter():
memberType = "getters"
elif member.isSetter():
memberType = "setters"
elif member.isDeleter():
memberType = "deleters"
elif member.isStringifier():
memberType = "stringifiers"
elif member.isLegacycaller():
memberType = "legacycallers"
else:
continue
if memberType != "stringifiers" and memberType != "legacycallers":
if member.isNamed():
memberType = "named " + memberType
else:
assert member.isIndexed()
memberType = "indexed " + memberType
if memberType in specialMembersSeen:
raise WebIDLError(
"Multiple " + memberType + " on %s" % (self),
[
self.location,
specialMembersSeen[memberType].location,
member.location,
],
)
specialMembersSeen[memberType] = member
if self.getExtendedAttribute("LegacyUnenumerableNamedProperties"):
# Check that we have a named getter.
if "named getters" not in specialMembersSeen:
raise WebIDLError(
"Interface with [LegacyUnenumerableNamedProperties] does "
"not have a named getter",
[self.location],
)
ancestor = self.parent
while ancestor:
if ancestor.getExtendedAttribute("LegacyUnenumerableNamedProperties"):
raise WebIDLError(
"Interface with [LegacyUnenumerableNamedProperties] "
"inherits from another interface with "
"[LegacyUnenumerableNamedProperties]",
[self.location, ancestor.location],
)
ancestor = ancestor.parent
if self._isOnGlobalProtoChain:
# Make sure we have no named setters or deleters
for memberType in ["setter", "deleter"]:
memberId = "named " + memberType + "s"
if memberId in specialMembersSeen:
raise WebIDLError(
"Interface with [Global] has a named %s" % memberType,
[self.location, specialMembersSeen[memberId].location],
)
# Make sure we're not [LegacyOverrideBuiltIns]
if self.getExtendedAttribute("LegacyOverrideBuiltIns"):
raise WebIDLError(
"Interface with [Global] also has " "[LegacyOverrideBuiltIns]",
[self.location],
)
# Mark all of our ancestors as being on the global's proto chain too
parent = self.parent
while parent:
# Must not inherit from an interface with [LegacyOverrideBuiltIns]
if parent.getExtendedAttribute("LegacyOverrideBuiltIns"):
raise WebIDLError(
"Interface with [Global] inherits from "
"interface with [LegacyOverrideBuiltIns]",
[self.location, parent.location],
)
parent._isOnGlobalProtoChain = True
parent = parent.parent
def validate(self):
def checkDuplicateNames(member, name, attributeName):
for m in self.members:
if m.identifier.name == name:
raise WebIDLError(
"[%s=%s] has same name as interface member"
% (attributeName, name),
[member.location, m.location],
)
if m.isMethod() and m != member and name in m.aliases:
raise WebIDLError(
"conflicting [%s=%s] definitions" % (attributeName, name),
[member.location, m.location],
)
if m.isAttr() and m != member and name in m.bindingAliases:
raise WebIDLError(
"conflicting [%s=%s] definitions" % (attributeName, name),
[member.location, m.location],
)
# We also don't support inheriting from unforgeable interfaces.
if self.getExtendedAttribute("LegacyUnforgeable") and self.hasChildInterfaces():
locations = [self.location] + list(
i.location for i in self.interfacesBasedOnSelf if i.parent == self
)
raise WebIDLError(
"%s is an unforgeable ancestor interface" % self.identifier.name,
locations,
)
ctor = self.ctor()
if ctor is not None:
ctor.validate()
for namedCtor in self.legacyFactoryFunctions:
namedCtor.validate()
indexedGetter = None
hasLengthAttribute = False
for member in self.members:
member.validate()
if self.isCallback() and member.getExtendedAttribute("Replaceable"):
raise WebIDLError(
"[Replaceable] used on an attribute on "
"interface %s which is a callback interface" % self.identifier.name,
[self.location, member.location],
)
# Check that PutForwards refers to another attribute and that no
# cycles exist in forwarded assignments. Also check for a
# integer-typed "length" attribute.
if member.isAttr():
if member.identifier.name == "length" and member.type.isInteger():
hasLengthAttribute = True
iface = self
attr = member
putForwards = attr.getExtendedAttribute("PutForwards")
if putForwards and self.isCallback():
raise WebIDLError(
"[PutForwards] used on an attribute "
"on interface %s which is a callback "
"interface" % self.identifier.name,
[self.location, member.location],
)
while putForwards is not None:
forwardIface = attr.type.unroll().inner
fowardAttr = None
for forwardedMember in forwardIface.members:
if (
not forwardedMember.isAttr()
or forwardedMember.identifier.name != putForwards[0]
):
continue
if forwardedMember == member:
raise WebIDLError(
"Cycle detected in forwarded "
"assignments for attribute %s on "
"%s" % (member.identifier.name, self),