Source code

Revision control

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. """
from __future__ import print_function
from ply import lex, yacc
import re
import os
import traceback
import math
import string
from collections import defaultdict, OrderedDict
from itertools import chain
# 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
def enum(*names, **kw):
class Foo(object):
attrs = OrderedDict()
def __init__(self, names):
for v, k in enumerate(names):
self.attrs[k] = v
def __getattr__(self, attr):
if attr in self.attrs:
return self.attrs[attr]
raise AttributeError
def __setattr__(self, name, value): # this makes it read-only
raise NotImplementedError
if "base" not in kw:
return Foo(names)
return Foo(chain(kw["base"].attrs.keys(), names))
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._file = filename if filename else "<unknown>"
def __eq__(self, other):
return self._lexpos == other._lexpos and self._file == other._file
def filename(self):
return self._file
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._file, 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._file,
self._lineno,
self._colno,
self._line,
self._pointerline(),
)
class BuiltinLocation(object):
def __init__(self, text):
self.msg = text + "\n"
def __eq__(self, other):
return isinstance(other, BuiltinLocation) and self.msg == other.msg
def filename(self):
return "<builtin>"
def resolve(self):
pass
def get(self):
return self.msg
def __str__(self):
return self.get()
# Data Model
class IDLObject(object):
def __init__(self, location):
self.location = location
self.userData = dict()
def filename(self):
return self.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):
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
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.
conflictdesc = "\n\t%s at %s\n\t%s at %s" % (
originalObject,
originalObject.location,
newObject,
newObject.location,
)
raise 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):
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):
def __init__(
self, location, name, allowDoubleUnderscore=False, allowForbidden=False
):
IDLObject.__init__(self, location)
assert len(name) > 0
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):
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):
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):
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:
raise WebIDLError(
"Unresolved type '%s'." % self.identifier, [self.location]
)
obj = self.identifier.resolve(scope, None)
return scope.lookupIdentifier(obj)
class IDLExposureMixins:
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
# 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:
if 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 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):
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 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):
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):
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])
class IDLInterfaceOrInterfaceMixinOrNamespace(IDLObjectWithScope, IDLExposureMixins):
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 resolveIdentifierConflict(self, scope, identifier, originalObject, newObject):
assert isinstance(scope, IDLScope)
assert isinstance(originalObject, IDLInterfaceMember)
assert isinstance(newObject, IDLInterfaceMember)
retval = IDLScope.resolveIdentifierConflict(
self, scope, identifier, originalObject, newObject
)
# 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):
def __init__(self, location, parentScope, name, members, isKnownNonPartial):
self.actualExposureGlobalNames = set()
assert isKnownNonPartial or len(members) == 0
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):
def __init__(self, location, parentScope, name, parent, members, isKnownNonPartial):
assert isKnownNonPartial or not parent
assert isKnownNonPartial or len(members) == 0
self.parent = None
self._callback = False
self.maplikeOrSetlikeOrIterable = None
# namedConstructors needs deterministic ordering because bindings code
# outputs the constructs in the order that namedConstructors enumerates
# them.
self.legacyFactoryFunctions = list()
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 = set([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
# 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)
except:
return None
def isIterable(self):
return (
self.maplikeOrSetlikeOrIterable
and self.maplikeOrSetlikeOrIterable.isIterable()
)
def isIteratorInterface(self):
return self.iterableInterface 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],
)
# 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:
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.isMaplikeOrSetlike():
if self.isJSImplemented() and not member.isMaplikeOrSetlike():
raise WebIDLError(
"Interface %s is JS-implemented and we "
"don't support [Cached] or [StoreInSlot] "
"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),
[member.location],
)
fowardAttr = forwardedMember
break
if fowardAttr is None:
raise WebIDLError(
"Attribute %s on %s forwards to "
"missing attribute %s"
% (attr.identifier.name, iface, putForwards),
[attr.location],
)
iface = forwardIface
attr = fowardAttr
putForwards = attr.getExtendedAttribute("PutForwards")
# Check that the name of an [Alias] doesn't conflict with an
# interface member and whether we support indexed properties.
if member.isMethod():
if member.isGetter() and member.isIndexed():
indexedGetter = member
for alias in member.aliases:
if self.isOnGlobalProtoChain():
raise WebIDLError(
"[Alias] must not be used on a "
"[Global] interface operation",
[member.location],
)
if (
member.getExtendedAttribute("Exposed")
or member.getExtendedAttribute("ChromeOnly")
or member.getExtendedAttribute("Pref")
or member.getExtendedAttribute("Func")
or member.getExtendedAttribute("SecureContext")
):
raise WebIDLError(
"[Alias] must not be used on a "
"conditionally exposed operation",
[member.location],
)
if member.isStatic():
raise WebIDLError(
"[Alias] must not be used on a " "static operation",
[member.location],
)
if member.isIdentifierLess():
raise WebIDLError(
"[Alias] must not be used on an "
"identifierless operation",
[member.location],
)
if member.isLegacyUnforgeable():
raise WebIDLError(
"[Alias] must not be used on an "
"[LegacyUnforgeable] operation",
[member.location],
)
checkDuplicateNames(member, alias, "Alias")
# Check that the name of a [BindingAlias] doesn't conflict with an
# interface member.
if member.isAttr():
for bindingAlias in member.bindingAliases:
checkDuplicateNames(member, bindingAlias, "BindingAlias")
# Conditional exposure makes no sense for interfaces with no
# interface object.
# And SecureContext makes sense for interfaces with no interface object,
# since it is also propagated to interface members.
if (
self.isExposedConditionally(exclusions=["SecureContext"])
and not self.hasInterfaceObject()
):
raise WebIDLError(
"Interface with no interface object is " "exposed conditionally",
[self.location],
)
# Value iterators are only allowed on interfaces with indexed getters,
# and pair iterators are only allowed on interfaces without indexed
# getters.
if self.isIterable():
iterableDecl = self.maplikeOrSetlikeOrIterable
if iterableDecl.isValueIterator():
if not indexedGetter:
raise WebIDLError(
"Interface with value iterator does not "
"support indexed properties",
[self.location, iterableDecl.location],
)
if iterableDecl.valueType != indexedGetter.signatures()[0][0]:
raise WebIDLError(
"Iterable type does not match indexed " "getter type",
[iterableDecl.location, indexedGetter.location],
)
if not hasLengthAttribute:
raise WebIDLError(
"Interface with value iterator does not "
'have an integer-typed "length" attribute',
[self.location, iterableDecl.location],
)
else:
assert iterableDecl.isPairIterator()
if indexedGetter:
raise WebIDLError(
"Interface with pair iterator supports " "indexed properties",
[self.location, iterableDecl.location, indexedGetter.location],
)
if indexedGetter and not hasLengthAttribute:
raise WebIDLError(
"Interface with an indexed getter does not have "
'an integer-typed "length" attribute',
[self.location, indexedGetter.location],
)
def setCallback(self, value):
self._callback = value
def isCallback(self):
return self._callback
def isSingleOperationInterface(self):
assert self.isCallback() or self.isJSImplemented()
return (
# JS-implemented things should never need the
# this-handling weirdness of single-operation interfaces.
not self.isJSImplemented()
and
# Not inheriting from another interface
not self.parent
and
# No attributes of any kinds
not any(m.isAttr() for m in self.members)
and
# There is at least one regular operation, and all regular
# operations have the same identifier
len(
set(
m.identifier.name
for m in self.members
if m.isMethod() and not m.isStatic()
)
)
== 1
)
def inheritanceDepth(self):
depth = 0
parent = self.parent
while parent:
depth = depth + 1
parent = parent.parent
return depth
def hasConstants(self):
return any(m.isConst() for m in self.members)
def hasInterfaceObject(self):
if self.isCallback():
return self.hasConstants()
return not hasattr(self, "_noInterfaceObject")
def hasInterfacePrototypeObject(self):
return (
not self.isCallback()
and not self.isNamespace()
and self.getUserData("hasConcreteDescendant", False)
)
def addIncludedMixin(self, includedMixin):
assert isinstance(includedMixin, IDLInterfaceMixin)
self.includedMixins.add(includedMixin)
def getInheritedInterfaces(self):
"""
Returns a list of the interfaces this interface inherits from
(not including this interface itself). The list is in order
from most derived to least derived.
"""
assert self._finished
if not self.parent:
return []
parentInterfaces = self.parent.getInheritedInterfaces()
parentInterfaces.insert(0, self.parent)
return parentInterfaces
def findInterfaceLoopPoint(self, otherInterface):
"""
Finds an interface amongst our ancestors that inherits from otherInterface.
If there is no such interface, returns None.
"""
if self.parent:
if self.parent == otherInterface:
return self
loopPoint = self.parent.findInterfaceLoopPoint(otherInterface)
if loopPoint:
return loopPoint
return None
def setNonPartial(self, location, parent, members):
assert not parent or isinstance(parent, IDLIdentifierPlaceholder)
IDLInterfaceOrInterfaceMixinOrNamespace.setNonPartial(self, location, members)
assert not self.parent
self.parent = parent
def getJSImplementation(self):
classId = self.getExtendedAttribute("JSImplementation")
if not classId:
return classId
assert isinstance(classId, list)
assert len(classId) == 1
return classId[0]
def isJSImplemented(self):
return bool(self.getJSImplementation())
def hasProbablyShortLivingWrapper(self):
current = self
while current:
if current.getExtendedAttribute("ProbablyShortLivingWrapper"):
return True
current = current.parent
return False
def hasChildInterfaces(self):
return self._hasChildInterfaces
def isOnGlobalProtoChain(self):
return self._isOnGlobalProtoChain
def _getDependentObjects(self):
deps = set(self.members)
deps.update(self.includedMixins)
if self.parent:
deps.add(self.parent)
return deps
def hasMembersInSlots(self):
return self._ownMembersInSlots != 0
conditionExtendedAttributes = ["Pref", "ChromeOnly", "Func", "SecureContext"]
def isExposedConditionally(self, exclusions=[]):
return any(
((not a in exclusions) and self.getExtendedAttribute(a))
for a in self.conditionExtendedAttributes
)
class IDLInterface(IDLInterfaceOrNamespace):
def __init__(
self,
location,
parentScope,
name,
parent,
members,
isKnownNonPartial,
classNameOverride=None,
):
IDLInterfaceOrNamespace.__init__(
self, location, parentScope, name, parent, members, isKnownNonPartial
)
self.classNameOverride = classNameOverride
def __str__(self):
return "Interface '%s'" % self.identifier.name
def isInterface(self):
return True
def getClassName(self):
if self.classNameOverride:
return self.classNameOverride
return IDLInterfaceOrNamespace.getClassName(self)
def addExtendedAttributes(self, attrs):
for attr in attrs:
identifier = attr.identifier()
# Special cased attrs
if identifier == "TreatNonCallableAsNull":
raise WebIDLError(
"TreatNonCallableAsNull cannot be specified on interfaces",
[attr.location, self.location],
)
if identifier == "LegacyTreatNonObjectAsNull":
raise WebIDLError(
"LegacyTreatNonObjectAsNull cannot be specified on interfaces",
[attr.location, self.location],