summaryrefslogtreecommitdiffstats
path: root/ipc/ipdl/ipdl/type.py
diff options
context:
space:
mode:
Diffstat (limited to 'ipc/ipdl/ipdl/type.py')
-rw-r--r--ipc/ipdl/ipdl/type.py1748
1 files changed, 1748 insertions, 0 deletions
diff --git a/ipc/ipdl/ipdl/type.py b/ipc/ipdl/ipdl/type.py
new file mode 100644
index 0000000000..76f908dd41
--- /dev/null
+++ b/ipc/ipdl/ipdl/type.py
@@ -0,0 +1,1748 @@
+# vim: set ts=4 sw=4 tw=99 et:
+# 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/.
+
+import os
+import sys
+
+from ipdl.ast import CxxInclude, Decl, Loc, QualifiedId, StructDecl
+from ipdl.ast import UnionDecl, UsingStmt, Visitor, StringLiteral
+from ipdl.ast import ASYNC, SYNC, INTR
+from ipdl.ast import IN, OUT, INOUT
+from ipdl.ast import NOT_NESTED, INSIDE_SYNC_NESTED, INSIDE_CPOW_NESTED
+from ipdl.ast import priorityList
+import ipdl.builtin as builtin
+from ipdl.util import hash_str
+
+_DELETE_MSG = "__delete__"
+
+
+class TypeVisitor:
+ def __init__(self):
+ self.visited = set()
+
+ def defaultVisit(self, node, *args):
+ raise Exception(
+ "INTERNAL ERROR: no visitor for node type `%s'" % (node.__class__.__name__)
+ )
+
+ def visitVoidType(self, v, *args):
+ pass
+
+ def visitBuiltinCType(self, b, *args):
+ pass
+
+ def visitImportedCxxType(self, t, *args):
+ pass
+
+ def visitMessageType(self, m, *args):
+ for param in m.params:
+ param.accept(self, *args)
+ for ret in m.returns:
+ ret.accept(self, *args)
+ if m.cdtype is not None:
+ m.cdtype.accept(self, *args)
+
+ def visitProtocolType(self, p, *args):
+ # NB: don't visit manager and manages. a naive default impl
+ # could result in an infinite loop
+ pass
+
+ def visitActorType(self, a, *args):
+ a.protocol.accept(self, *args)
+
+ def visitStructType(self, s, *args):
+ if s in self.visited:
+ return
+
+ self.visited.add(s)
+ for field in s.fields:
+ field.accept(self, *args)
+
+ def visitUnionType(self, u, *args):
+ if u in self.visited:
+ return
+
+ self.visited.add(u)
+ for component in u.components:
+ component.accept(self, *args)
+
+ def visitArrayType(self, a, *args):
+ a.basetype.accept(self, *args)
+
+ def visitMaybeType(self, m, *args):
+ m.basetype.accept(self, *args)
+
+ def visitUniquePtrType(self, m, *args):
+ m.basetype.accept(self, *args)
+
+ def visitNotNullType(self, m, *args):
+ m.basetype.accept(self, *args)
+
+ def visitShmemType(self, s, *args):
+ pass
+
+ def visitByteBufType(self, s, *args):
+ pass
+
+ def visitShmemChmodType(self, c, *args):
+ c.shmem.accept(self)
+
+ def visitFDType(self, s, *args):
+ pass
+
+ def visitEndpointType(self, s, *args):
+ pass
+
+ def visitManagedEndpointType(self, s, *args):
+ pass
+
+
+class Type:
+ def __cmp__(self, o):
+ return cmp(self.fullname(), o.fullname())
+
+ def __eq__(self, o):
+ return self.__class__ == o.__class__ and self.fullname() == o.fullname()
+
+ def __hash__(self):
+ return hash_str(self.fullname())
+
+ # Is this a C++ type?
+ def isCxx(self):
+ return False
+
+ # Is this an IPDL type?
+
+ def isIPDL(self):
+ return False
+
+ # Is this type neither compound nor an array?
+
+ def isAtom(self):
+ return False
+
+ def isRefcounted(self):
+ return False
+
+ # Should this type be wrapped in `NotNull<T>` unless marked `nullable`?
+
+ def supportsNullable(self):
+ return False
+
+ def typename(self):
+ return self.__class__.__name__
+
+ def name(self):
+ raise NotImplementedError()
+
+ def fullname(self):
+ raise NotImplementedError()
+
+ def accept(self, visitor, *args):
+ visit = getattr(visitor, "visit" + self.__class__.__name__, None)
+ if visit is None:
+ return getattr(visitor, "defaultVisit")(self, *args)
+ return visit(self, *args)
+
+
+class VoidType(Type):
+ def isCxx(self):
+ return True
+
+ def isIPDL(self):
+ return False
+
+ def isAtom(self):
+ return True
+
+ def name(self):
+ return "void"
+
+ def fullname(self):
+ return "void"
+
+
+VOID = VoidType()
+
+# --------------------
+
+
+class BuiltinCType(Type):
+ def __init__(self, name):
+ self._name = name
+
+ def isCxx(self):
+ return True
+
+ def isAtom(self):
+ return True
+
+ def isSendMoveOnly(self):
+ return False
+
+ def isDataMoveOnly(self):
+ return False
+
+ def name(self):
+ return self._name
+
+ def fullname(self):
+ return self._name
+
+
+class ImportedCxxType(Type):
+ def __init__(self, qname, refcounted, sendmoveonly, datamoveonly):
+ assert isinstance(qname, QualifiedId)
+ self.loc = qname.loc
+ self.qname = qname
+ self.refcounted = refcounted
+ self.sendmoveonly = sendmoveonly
+ self.datamoveonly = datamoveonly
+
+ def isCxx(self):
+ return True
+
+ def isAtom(self):
+ return True
+
+ def isRefcounted(self):
+ return self.refcounted
+
+ def supportsNullable(self):
+ return self.refcounted
+
+ def isSendMoveOnly(self):
+ return self.sendmoveonly
+
+ def isDataMoveOnly(self):
+ return self.datamoveonly
+
+ def name(self):
+ return self.qname.baseid
+
+ def fullname(self):
+ return str(self.qname)
+
+
+# --------------------
+
+
+class IPDLType(Type):
+ def isIPDL(self):
+ return True
+
+ def isMessage(self):
+ return False
+
+ def isProtocol(self):
+ return False
+
+ def isActor(self):
+ return False
+
+ def isStruct(self):
+ return False
+
+ def isUnion(self):
+ return False
+
+ def isArray(self):
+ return False
+
+ def isMaybe(self):
+ return False
+
+ def isUniquePtr(self):
+ return False
+
+ def isNotNull(self):
+ return False
+
+ def isAtom(self):
+ return True
+
+ def isCompound(self):
+ return False
+
+ def isShmem(self):
+ return False
+
+ def isByteBuf(self):
+ return False
+
+ def isFD(self):
+ return False
+
+ def isEndpoint(self):
+ return False
+
+ def isManagedEndpoint(self):
+ return False
+
+ def hasBaseType(self):
+ return False
+
+
+class SendSemanticsType(IPDLType):
+ def __init__(self, nestedRange, sendSemantics):
+ self.nestedRange = nestedRange
+ self.sendSemantics = sendSemantics
+
+ def isAsync(self):
+ return self.sendSemantics == ASYNC
+
+ def isSync(self):
+ return self.sendSemantics == SYNC
+
+ def isInterrupt(self):
+ return self.sendSemantics is INTR
+
+ def sendSemanticsSatisfiedBy(self, greater):
+ def _unwrap(nr):
+ if isinstance(nr, dict):
+ return _unwrap(nr["nested"])
+ elif isinstance(nr, int):
+ return nr
+ else:
+ raise ValueError("Got unexpected nestedRange value: %s" % nr)
+
+ lesser = self
+ lnr0, gnr0, lnr1, gnr1 = (
+ _unwrap(lesser.nestedRange[0]),
+ _unwrap(greater.nestedRange[0]),
+ _unwrap(lesser.nestedRange[1]),
+ _unwrap(greater.nestedRange[1]),
+ )
+ if lnr0 < gnr0 or lnr1 > gnr1:
+ return False
+
+ # Protocols that use intr semantics are not allowed to use
+ # message nesting.
+ if greater.isInterrupt() and lesser.nestedRange != (NOT_NESTED, NOT_NESTED):
+ return False
+
+ if lesser.isAsync():
+ return True
+ elif lesser.isSync() and not greater.isAsync():
+ return True
+ elif greater.isInterrupt():
+ return True
+
+ return False
+
+
+class MessageType(SendSemanticsType):
+ def __init__(
+ self,
+ nested,
+ prio,
+ replyPrio,
+ sendSemantics,
+ direction,
+ ctor=False,
+ dtor=False,
+ cdtype=None,
+ compress=False,
+ tainted=False,
+ lazySend=False,
+ ):
+ assert not (ctor and dtor)
+ assert not (ctor or dtor) or cdtype is not None
+
+ SendSemanticsType.__init__(self, (nested, nested), sendSemantics)
+ self.nested = nested
+ self.prio = prio
+ self.replyPrio = replyPrio
+ self.direction = direction
+ self.params = []
+ self.returns = []
+ self.ctor = ctor
+ self.dtor = dtor
+ self.cdtype = cdtype
+ self.compress = compress
+ self.tainted = tainted
+ self.lazySend = lazySend
+
+ def isMessage(self):
+ return True
+
+ def isCtor(self):
+ return self.ctor
+
+ def isDtor(self):
+ return self.dtor
+
+ def constructedType(self):
+ return self.cdtype
+
+ def isIn(self):
+ return self.direction is IN
+
+ def isOut(self):
+ return self.direction is OUT
+
+ def isInout(self):
+ return self.direction is INOUT
+
+ def hasReply(self):
+ return len(self.returns) or self.isSync() or self.isInterrupt()
+
+ def hasImplicitActorParam(self):
+ return self.isCtor()
+
+
+class ProtocolType(SendSemanticsType):
+ def __init__(self, qname, nested, sendSemantics, refcounted, needsotherpid):
+ SendSemanticsType.__init__(self, (NOT_NESTED, nested), sendSemantics)
+ self.qname = qname
+ self.managers = [] # ProtocolType
+ self.manages = []
+ self.hasDelete = False
+ self.refcounted = refcounted
+ self.needsotherpid = needsotherpid
+
+ def isProtocol(self):
+ return True
+
+ def isRefcounted(self):
+ return self.refcounted
+
+ def hasOtherPid(self):
+ return all(top.needsotherpid for top in self.toplevels())
+
+ def name(self):
+ return self.qname.baseid
+
+ def fullname(self):
+ return str(self.qname)
+
+ def addManager(self, mgrtype):
+ assert mgrtype.isIPDL() and mgrtype.isProtocol()
+ self.managers.append(mgrtype)
+
+ def managedBy(self, mgr):
+ self.managers = list(mgr)
+
+ def toplevel(self):
+ if self.isToplevel():
+ return self
+ for mgr in self.managers:
+ if mgr is not self:
+ return mgr.toplevel()
+
+ def toplevels(self):
+ if self.isToplevel():
+ return [self]
+ toplevels = list()
+ for mgr in self.managers:
+ if mgr is not self:
+ toplevels.extend(mgr.toplevels())
+ return set(toplevels)
+
+ def isManagerOf(self, pt):
+ for managed in self.manages:
+ if pt is managed:
+ return True
+ return False
+
+ def isManagedBy(self, pt):
+ return pt in self.managers
+
+ def isManager(self):
+ return len(self.manages) > 0
+
+ def isManaged(self):
+ return 0 < len(self.managers)
+
+ def isToplevel(self):
+ return not self.isManaged()
+
+ def manager(self):
+ assert 1 == len(self.managers)
+ for mgr in self.managers:
+ return mgr
+
+
+class ActorType(IPDLType):
+ def __init__(self, protocol):
+ self.protocol = protocol
+
+ def isActor(self):
+ return True
+
+ def isRefcounted(self):
+ return self.protocol.isRefcounted()
+
+ def supportsNullable(self):
+ return True
+
+ def name(self):
+ return self.protocol.name()
+
+ def fullname(self):
+ return self.protocol.fullname()
+
+
+class _CompoundType(IPDLType):
+ def __init__(self):
+ self.defined = False # bool
+ self.mutualRec = set() # set(_CompoundType | ArrayType)
+
+ def isAtom(self):
+ return False
+
+ def isCompound(self):
+ return True
+
+ def itercomponents(self):
+ raise Exception('"pure virtual" method')
+
+ def mutuallyRecursiveWith(self, t, exploring=None):
+ """|self| is mutually recursive with |t| iff |self| and |t|
+ are in a cycle in the type graph rooted at |self|. This function
+ looks for such a cycle and returns True if found."""
+ if exploring is None:
+ exploring = set()
+
+ if t.isAtom():
+ return False
+ elif t is self or t in self.mutualRec:
+ return True
+ elif t.hasBaseType():
+ isrec = self.mutuallyRecursiveWith(t.basetype, exploring)
+ if isrec:
+ self.mutualRec.add(t)
+ return isrec
+ elif t in exploring:
+ return False
+
+ exploring.add(t)
+ for c in t.itercomponents():
+ if self.mutuallyRecursiveWith(c, exploring):
+ self.mutualRec.add(c)
+ return True
+ exploring.remove(t)
+
+ return False
+
+
+class StructType(_CompoundType):
+ def __init__(self, qname, fields):
+ _CompoundType.__init__(self)
+ self.qname = qname
+ self.fields = fields # [ Type ]
+
+ def isStruct(self):
+ return True
+
+ def itercomponents(self):
+ for f in self.fields:
+ yield f
+
+ def name(self):
+ return self.qname.baseid
+
+ def fullname(self):
+ return str(self.qname)
+
+
+class UnionType(_CompoundType):
+ def __init__(self, qname, components):
+ _CompoundType.__init__(self)
+ self.qname = qname
+ self.components = components # [ Type ]
+
+ def isUnion(self):
+ return True
+
+ def itercomponents(self):
+ for c in self.components:
+ yield c
+
+ def name(self):
+ return self.qname.baseid
+
+ def fullname(self):
+ return str(self.qname)
+
+
+class ArrayType(IPDLType):
+ def __init__(self, basetype):
+ self.basetype = basetype
+
+ def isAtom(self):
+ return False
+
+ def isArray(self):
+ return True
+
+ def hasBaseType(self):
+ return True
+
+ def name(self):
+ return self.basetype.name() + "[]"
+
+ def fullname(self):
+ return self.basetype.fullname() + "[]"
+
+
+class MaybeType(IPDLType):
+ def __init__(self, basetype):
+ self.basetype = basetype
+
+ def isAtom(self):
+ return False
+
+ def isMaybe(self):
+ return True
+
+ def hasBaseType(self):
+ return True
+
+ def name(self):
+ return self.basetype.name() + "?"
+
+ def fullname(self):
+ return self.basetype.fullname() + "?"
+
+
+class ShmemType(IPDLType):
+ def __init__(self, qname):
+ self.qname = qname
+
+ def isShmem(self):
+ return True
+
+ def name(self):
+ return self.qname.baseid
+
+ def fullname(self):
+ return str(self.qname)
+
+
+class ByteBufType(IPDLType):
+ def __init__(self, qname):
+ self.qname = qname
+
+ def isByteBuf(self):
+ return True
+
+ def name(self):
+ return self.qname.baseid
+
+ def fullname(self):
+ return str(self.qname)
+
+
+class FDType(IPDLType):
+ def __init__(self, qname):
+ self.qname = qname
+
+ def isFD(self):
+ return True
+
+ def name(self):
+ return self.qname.baseid
+
+ def fullname(self):
+ return str(self.qname)
+
+
+class EndpointType(IPDLType):
+ def __init__(self, qname, actor):
+ self.qname = qname
+ self.actor = actor
+
+ def isEndpoint(self):
+ return True
+
+ def name(self):
+ return self.qname.baseid
+
+ def fullname(self):
+ return str(self.qname)
+
+
+class ManagedEndpointType(IPDLType):
+ def __init__(self, qname, actor):
+ self.qname = qname
+ self.actor = actor
+
+ def isManagedEndpoint(self):
+ return True
+
+ def name(self):
+ return self.qname.baseid
+
+ def fullname(self):
+ return str(self.qname)
+
+
+class UniquePtrType(IPDLType):
+ def __init__(self, basetype):
+ self.basetype = basetype
+
+ def isAtom(self):
+ return False
+
+ def isUniquePtr(self):
+ return True
+
+ def hasBaseType(self):
+ return True
+
+ def name(self):
+ return "UniquePtr<" + self.basetype.name() + ">"
+
+ def fullname(self):
+ return "mozilla::UniquePtr<" + self.basetype.fullname() + ">"
+
+
+class NotNullType(IPDLType):
+ def __init__(self, basetype):
+ self.basetype = basetype
+
+ def isAtom(self):
+ return False
+
+ def isNotNull(self):
+ return True
+
+ def hasBaseType(self):
+ return True
+
+ def name(self):
+ return "NotNull<" + self.basetype.name() + ">"
+
+ def fullname(self):
+ return "mozilla::NotNull<" + self.basetype.fullname() + ">"
+
+
+def iteractortypes(t, visited=None):
+ """Iterate over any actor(s) buried in |type|."""
+ if visited is None:
+ visited = set()
+
+ # XXX |yield| semantics makes it hard to use TypeVisitor
+ if not t.isIPDL():
+ return
+ elif t.isActor():
+ yield t
+ elif t.hasBaseType():
+ for actor in iteractortypes(t.basetype, visited):
+ yield actor
+ elif t.isCompound() and t not in visited:
+ visited.add(t)
+ for c in t.itercomponents():
+ for actor in iteractortypes(c, visited):
+ yield actor
+
+
+def hasshmem(type):
+ """Return true iff |type| is shmem or has it buried within."""
+
+ class found(BaseException):
+ pass
+
+ class findShmem(TypeVisitor):
+ def visitShmemType(self, s):
+ raise found()
+
+ try:
+ type.accept(findShmem())
+ except found:
+ return True
+ return False
+
+
+# --------------------
+_builtinloc = Loc("<builtin>", 0)
+
+
+def makeBuiltinUsing(tname):
+ quals = tname.split("::")
+ base = quals.pop()
+ quals = quals[0:]
+ return UsingStmt(_builtinloc, QualifiedId(_builtinloc, base, quals))
+
+
+builtinUsing = [makeBuiltinUsing(t) for t in builtin.Types]
+builtinHeaderIncludes = [CxxInclude(_builtinloc, f) for f in builtin.HeaderIncludes]
+
+
+def errormsg(loc, fmt, *args):
+ while not isinstance(loc, Loc):
+ if loc is None:
+ loc = Loc.NONE
+ else:
+ loc = loc.loc
+ return "%s: error: %s" % (str(loc), fmt % args)
+
+
+# --------------------
+
+
+class SymbolTable:
+ def __init__(self, errors):
+ self.errors = errors
+ self.scopes = [{}] # stack({})
+ self.currentScope = self.scopes[0]
+
+ def enterScope(self):
+ assert isinstance(self.scopes[0], dict)
+ assert isinstance(self.currentScope, dict)
+
+ self.scopes.append({})
+ self.currentScope = self.scopes[-1]
+
+ def exitScope(self):
+ symtab = self.scopes.pop()
+ assert self.currentScope is symtab
+
+ self.currentScope = self.scopes[-1]
+
+ assert isinstance(self.scopes[0], dict)
+ assert isinstance(self.currentScope, dict)
+
+ def lookup(self, sym):
+ # NB: since IPDL doesn't allow any aliased names of different types,
+ # it doesn't matter in which order we walk the scope chain to resolve
+ # |sym|
+ for scope in self.scopes:
+ decl = scope.get(sym, None)
+ if decl is not None:
+ return decl
+ return None
+
+ def declare(self, decl):
+ assert decl.progname or decl.shortname or decl.fullname
+ assert decl.loc
+ assert decl.type
+
+ def tryadd(name):
+ olddecl = self.lookup(name)
+ if olddecl is not None:
+ self.errors.append(
+ errormsg(
+ decl.loc,
+ "redeclaration of symbol `%s', first declared at %s",
+ name,
+ olddecl.loc,
+ )
+ )
+ return
+ self.currentScope[name] = decl
+ decl.scope = self.currentScope
+
+ if decl.progname:
+ tryadd(decl.progname)
+ if decl.shortname:
+ tryadd(decl.shortname)
+ if decl.fullname:
+ tryadd(decl.fullname)
+
+
+class TypeCheck:
+ """This pass sets the .decl attribute of AST nodes for which that is relevant;
+ a decl says where, with what type, and under what name(s) a node was
+ declared.
+
+ With this information, it type checks the AST."""
+
+ def __init__(self):
+ # NB: no IPDL compile will EVER print a warning. A program has
+ # one of two attributes: it is either well typed, or not well typed.
+ self.errors = [] # [ string ]
+
+ def check(self, tu, errout=sys.stderr):
+ def runpass(tcheckpass):
+ tu.accept(tcheckpass)
+ if len(self.errors):
+ self.reportErrors(errout)
+ return False
+ return True
+
+ # tag each relevant node with "decl" information, giving type, name,
+ # and location of declaration
+ if not runpass(GatherDecls(builtinUsing, self.errors)):
+ return False
+
+ # now that the nodes have decls, type checking is much easier.
+ if not runpass(CheckTypes(self.errors)):
+ return False
+
+ return True
+
+ def reportErrors(self, errout):
+ for error in self.errors:
+ print(error, file=errout)
+
+
+class TcheckVisitor(Visitor):
+ def __init__(self, errors):
+ self.errors = errors
+
+ def error(self, loc, fmt, *args):
+ self.errors.append(errormsg(loc, fmt, *args))
+
+
+class GatherDecls(TcheckVisitor):
+ def __init__(self, builtinUsing, errors):
+ TcheckVisitor.__init__(self, errors)
+
+ # |self.symtab| is the symbol table for the translation unit
+ # currently being visited
+ self.symtab = None
+ self.builtinUsing = builtinUsing
+
+ def declare(
+ self, loc, type, shortname=None, fullname=None, progname=None, attributes={}
+ ):
+ d = Decl(loc)
+ d.type = type
+ d.progname = progname
+ d.shortname = shortname
+ d.fullname = fullname
+ d.attributes = attributes
+ self.symtab.declare(d)
+ return d
+
+ # Check that only attributes allowed by an attribute spec are present
+ # within the given attribute dictionary. The spec value may be either
+ # `None`, for a valueless attribute, a list of valid attribute values, or a
+ # callable which returns a truthy value if the attribute is valid.
+ def checkAttributes(self, attributes, spec):
+ for attr in attributes.values():
+ if attr.name not in spec:
+ self.error(attr.loc, "unknown attribute `%s'", attr.name)
+ continue
+
+ aspec = spec[attr.name]
+ if aspec is None:
+ if attr.value is not None:
+ self.error(
+ attr.loc,
+ "unexpected value for valueless attribute `%s'",
+ attr.name,
+ )
+ elif isinstance(aspec, (list, tuple)):
+ if not any(
+ isinstance(attr.value, s)
+ if isinstance(s, type)
+ else attr.value == s
+ for s in aspec
+ ):
+ self.error(
+ attr.loc,
+ "invalid value for attribute `%s', expected one of: %s",
+ attr.name,
+ ", ".join(
+ s.__name__ if isinstance(s, type) else str(s) for s in aspec
+ ),
+ )
+ elif callable(aspec):
+ if not aspec(attr.value):
+ self.error(attr.loc, "invalid value for attribute `%s'", attr.name)
+ else:
+ raise Exception("INTERNAL ERROR: Invalid attribute spec")
+
+ def visitTranslationUnit(self, tu):
+ # all TranslationUnits declare symbols in global scope
+ if hasattr(tu, "visited"):
+ return
+ tu.visited = True
+ savedSymtab = self.symtab
+ self.symtab = SymbolTable(self.errors)
+
+ # pretend like the translation unit "using"-ed these for the
+ # sake of type checking and C++ code generation
+ tu.builtinUsing = self.builtinUsing
+
+ # for everyone's sanity, enforce that the filename and tu name
+ # match
+ basefilename = os.path.basename(tu.filename)
+ expectedfilename = "%s.ipdl" % (tu.name)
+ if not tu.protocol:
+ # header
+ expectedfilename += "h"
+ if basefilename != expectedfilename:
+ self.error(
+ tu.loc,
+ "expected file for translation unit `%s' to be named `%s'; instead it's named `%s'", # NOQA: E501
+ tu.name,
+ expectedfilename,
+ basefilename,
+ )
+
+ if tu.protocol:
+ assert tu.name == tu.protocol.name
+
+ p = tu.protocol
+
+ self.checkAttributes(
+ p.attributes,
+ {
+ "ManualDealloc": None,
+ "NestedUpTo": ("not", "inside_sync", "inside_cpow"),
+ "NeedsOtherPid": None,
+ "ChildImpl": ("virtual", StringLiteral),
+ "ParentImpl": ("virtual", StringLiteral),
+ },
+ )
+
+ # FIXME/cjones: it's a little weird and counterintuitive
+ # to put both the namespace and non-namespaced name in the
+ # global scope. try to figure out something better; maybe
+ # a type-neutral |using| that works for C++ and protocol
+ # types?
+ qname = p.qname()
+ fullname = str(qname)
+ p.decl = self.declare(
+ loc=p.loc,
+ type=ProtocolType(
+ qname,
+ p.nestedUpTo(),
+ p.sendSemantics,
+ "ManualDealloc" not in p.attributes,
+ "NeedsOtherPid" in p.attributes,
+ ),
+ shortname=p.name,
+ fullname=fullname,
+ )
+
+ p.parentEndpointDecl = self.declare(
+ loc=p.loc,
+ type=EndpointType(
+ QualifiedId(
+ p.loc, "Endpoint<" + fullname + "Parent>", ["mozilla", "ipc"]
+ ),
+ ActorType(p.decl.type),
+ ),
+ shortname="Endpoint<" + p.name + "Parent>",
+ )
+ p.childEndpointDecl = self.declare(
+ loc=p.loc,
+ type=EndpointType(
+ QualifiedId(
+ p.loc, "Endpoint<" + fullname + "Child>", ["mozilla", "ipc"]
+ ),
+ ActorType(p.decl.type),
+ ),
+ shortname="Endpoint<" + p.name + "Child>",
+ )
+
+ p.parentManagedEndpointDecl = self.declare(
+ loc=p.loc,
+ type=ManagedEndpointType(
+ QualifiedId(
+ p.loc,
+ "ManagedEndpoint<" + fullname + "Parent>",
+ ["mozilla", "ipc"],
+ ),
+ ActorType(p.decl.type),
+ ),
+ shortname="ManagedEndpoint<" + p.name + "Parent>",
+ )
+ p.childManagedEndpointDecl = self.declare(
+ loc=p.loc,
+ type=ManagedEndpointType(
+ QualifiedId(
+ p.loc,
+ "ManagedEndpoint<" + fullname + "Child>",
+ ["mozilla", "ipc"],
+ ),
+ ActorType(p.decl.type),
+ ),
+ shortname="ManagedEndpoint<" + p.name + "Child>",
+ )
+
+ # XXX ugh, this sucks. but we need this information to compute
+ # what friend decls we need in generated C++
+ p.decl.type._ast = p
+
+ # make sure we have decls for all dependent protocols
+ for pinc in tu.includes:
+ pinc.accept(self)
+
+ # declare imported (and builtin) C and C++ types
+ for ctype in builtin.CTypes:
+ self.declare(
+ loc=_builtinloc,
+ type=BuiltinCType(ctype),
+ shortname=ctype,
+ )
+ for using in tu.builtinUsing:
+ using.accept(self)
+ for using in tu.using:
+ using.accept(self)
+
+ # first pass to "forward-declare" all structs and unions in
+ # order to support recursive definitions
+ for su in tu.structsAndUnions:
+ self.declareStructOrUnion(su)
+
+ # second pass to check each definition
+ for su in tu.structsAndUnions:
+ su.accept(self)
+
+ if tu.protocol:
+ # grab symbols in the protocol itself
+ p.accept(self)
+
+ self.symtab = savedSymtab
+
+ def declareStructOrUnion(self, su):
+ if hasattr(su, "decl"):
+ self.symtab.declare(su.decl)
+ return
+
+ qname = su.qname()
+ fullname = str(qname)
+
+ if isinstance(su, StructDecl):
+ sutype = StructType(qname, [])
+ elif isinstance(su, UnionDecl):
+ sutype = UnionType(qname, [])
+ else:
+ assert 0 and "unknown type"
+
+ # XXX more suckage. this time for pickling structs/unions
+ # declared in headers.
+ sutype._ast = su
+
+ su.decl = self.declare(
+ loc=su.loc, type=sutype, shortname=su.name, fullname=fullname
+ )
+
+ def visitInclude(self, inc):
+ if inc.tu is None:
+ self.error(
+ inc.loc,
+ "(type checking here will be unreliable because of an earlier error)",
+ )
+ return
+ inc.tu.accept(self)
+ if inc.tu.protocol:
+ self.symtab.declare(inc.tu.protocol.decl)
+ self.symtab.declare(inc.tu.protocol.parentEndpointDecl)
+ self.symtab.declare(inc.tu.protocol.childEndpointDecl)
+ self.symtab.declare(inc.tu.protocol.parentManagedEndpointDecl)
+ self.symtab.declare(inc.tu.protocol.childManagedEndpointDecl)
+ else:
+ # This is a header. Import its "exported" globals into
+ # our scope.
+ for using in inc.tu.using:
+ using.accept(self)
+ for su in inc.tu.structsAndUnions:
+ self.declareStructOrUnion(su)
+
+ def visitStructDecl(self, sd):
+ # If we've already processed this struct, don't do it again.
+ if hasattr(sd, "visited"):
+ return
+
+ stype = sd.decl.type
+
+ self.symtab.enterScope()
+ sd.visited = True
+
+ self.checkAttributes(sd.attributes, {"Comparable": None})
+
+ for f in sd.fields:
+ ftypedecl = self.symtab.lookup(str(f.typespec))
+ if ftypedecl is None:
+ self.error(
+ f.loc,
+ "field `%s' of struct `%s' has unknown type `%s'",
+ f.name,
+ sd.name,
+ str(f.typespec),
+ )
+ continue
+
+ f.decl = self.declare(
+ loc=f.loc,
+ type=self._canonicalType(ftypedecl.type, f.typespec),
+ shortname=f.name,
+ fullname=None,
+ )
+ stype.fields.append(f.decl.type)
+
+ self.symtab.exitScope()
+
+ def visitUnionDecl(self, ud):
+ utype = ud.decl.type
+
+ # If we've already processed this union, don't do it again.
+ if len(utype.components):
+ return
+
+ self.checkAttributes(ud.attributes, {"Comparable": None})
+
+ for c in ud.components:
+ cdecl = self.symtab.lookup(str(c))
+ if cdecl is None:
+ self.error(
+ c.loc, "unknown component type `%s' of union `%s'", str(c), ud.name
+ )
+ continue
+ utype.components.append(self._canonicalType(cdecl.type, c))
+
+ def visitUsingStmt(self, using):
+ fullname = str(using.type)
+
+ self.checkAttributes(
+ using.attributes,
+ {
+ "MoveOnly": (None, "data", "send"),
+ "RefCounted": None,
+ },
+ )
+
+ if fullname == "::mozilla::ipc::Shmem":
+ ipdltype = ShmemType(using.type)
+ elif fullname == "::mozilla::ipc::ByteBuf":
+ ipdltype = ByteBufType(using.type)
+ elif fullname == "::mozilla::ipc::FileDescriptor":
+ ipdltype = FDType(using.type)
+ else:
+ ipdltype = ImportedCxxType(
+ using.type,
+ using.isRefcounted(),
+ using.isSendMoveOnly(),
+ using.isDataMoveOnly(),
+ )
+ existingType = self.symtab.lookup(ipdltype.fullname())
+ if existingType and existingType.fullname == ipdltype.fullname():
+ if ipdltype.isRefcounted() != existingType.type.isRefcounted():
+ self.error(
+ using.loc,
+ "inconsistent refcounted status of type `%s`",
+ str(using.type),
+ )
+ if (
+ ipdltype.isSendMoveOnly() != existingType.type.isSendMoveOnly()
+ or ipdltype.isDataMoveOnly() != existingType.type.isDataMoveOnly()
+ ):
+ self.error(
+ using.loc,
+ "inconsistent moveonly status of type `%s`",
+ str(using.type),
+ )
+ using.decl = existingType
+ return
+ using.decl = self.declare(
+ loc=using.loc,
+ type=ipdltype,
+ shortname=using.type.baseid,
+ fullname=fullname,
+ )
+
+ def visitProtocol(self, p):
+ # protocol scope
+ self.symtab.enterScope()
+
+ seenmgrs = set()
+ for mgr in p.managers:
+ if mgr.name in seenmgrs:
+ self.error(mgr.loc, "manager `%s' appears multiple times", mgr.name)
+ continue
+
+ seenmgrs.add(mgr.name)
+ mgr.of = p
+ mgr.accept(self)
+
+ for managed in p.managesStmts:
+ managed.manager = p
+ managed.accept(self)
+
+ if not (p.managers or p.messageDecls or p.managesStmts):
+ self.error(p.loc, "top-level protocol `%s' cannot be empty", p.name)
+
+ setattr(self, "currentProtocolDecl", p.decl)
+ for msg in p.messageDecls:
+ msg.accept(self)
+ del self.currentProtocolDecl
+
+ p.decl.type.hasDelete = not not self.symtab.lookup(_DELETE_MSG)
+ if not (p.decl.type.hasDelete or p.decl.type.isToplevel()):
+ self.error(
+ p.loc,
+ "destructor declaration `%s(...)' required for managed protocol `%s'",
+ _DELETE_MSG,
+ p.name,
+ )
+
+ if not p.decl.type.isToplevel() and p.decl.type.needsotherpid:
+ self.error(p.loc, "[NeedsOtherPid] only applies to toplevel protocols")
+
+ if p.decl.type.isToplevel() and not p.decl.type.isRefcounted():
+ self.error(p.loc, "Toplevel protocols cannot be [ManualDealloc]")
+
+ # FIXME/cjones declare all the little C++ thingies that will
+ # be generated. they're not relevant to IPDL itself, but
+ # those ("invisible") symbols can clash with others in the
+ # IPDL spec, and we'd like to catch those before C++ compilers
+ # are allowed to obfuscate the error
+
+ self.symtab.exitScope()
+
+ def visitManager(self, mgr):
+ mgrdecl = self.symtab.lookup(mgr.name)
+ pdecl = mgr.of.decl
+ assert pdecl
+
+ pname, mgrname = pdecl.shortname, mgr.name
+ loc = mgr.loc
+
+ if mgrdecl is None:
+ self.error(
+ loc,
+ "protocol `%s' referenced as |manager| of `%s' has not been declared",
+ mgrname,
+ pname,
+ )
+ elif not isinstance(mgrdecl.type, ProtocolType):
+ self.error(
+ loc,
+ "entity `%s' referenced as |manager| of `%s' is not of `protocol' type; instead it is of type `%s'", # NOQA: E501
+ mgrname,
+ pname,
+ mgrdecl.type.typename(),
+ )
+ else:
+ mgr.decl = mgrdecl
+ pdecl.type.addManager(mgrdecl.type)
+
+ def visitManagesStmt(self, mgs):
+ mgsdecl = self.symtab.lookup(mgs.name)
+ pdecl = mgs.manager.decl
+ assert pdecl
+
+ pname, mgsname = pdecl.shortname, mgs.name
+ loc = mgs.loc
+
+ if mgsdecl is None:
+ self.error(
+ loc,
+ "protocol `%s', managed by `%s', has not been declared",
+ mgsname,
+ pname,
+ )
+ elif not isinstance(mgsdecl.type, ProtocolType):
+ self.error(
+ loc,
+ "%s declares itself managing a non-`protocol' entity `%s' of type `%s'",
+ pname,
+ mgsname,
+ mgsdecl.type.typename(),
+ )
+ else:
+ mgs.decl = mgsdecl
+ pdecl.type.manages.append(mgsdecl.type)
+
+ def visitMessageDecl(self, md):
+ msgname = md.name
+ loc = md.loc
+
+ self.checkAttributes(
+ md.attributes,
+ {
+ "Tainted": None,
+ "Compress": (None, "all"),
+ "Priority": priorityList,
+ "ReplyPriority": priorityList,
+ "Nested": ("not", "inside_sync", "inside_cpow"),
+ "LegacyIntr": None,
+ "VirtualSendImpl": None,
+ "LazySend": None,
+ },
+ )
+
+ if md.sendSemantics is INTR and "LegacyIntr" not in md.attributes:
+ self.error(
+ loc,
+ "intr message `%s' allowed only with [LegacyIntr]; DO NOT USE IN SHIPPING CODE",
+ msgname,
+ )
+
+ if md.sendSemantics is INTR and "Priority" in md.attributes:
+ self.error(loc, "intr message `%s' cannot specify [Priority]", msgname)
+
+ if md.sendSemantics is INTR and "Nested" in md.attributes:
+ self.error(loc, "intr message `%s' cannot specify [Nested]", msgname)
+
+ if md.sendSemantics is not ASYNC and "LazySend" in md.attributes:
+ self.error(loc, "non-async message `%s' cannot specify [LazySend]", msgname)
+
+ if md.sendSemantics is not ASYNC and "ReplyPriority" in md.attributes:
+ self.error(
+ loc, "non-async message `%s' cannot specify [ReplyPriority]", msgname
+ )
+
+ if not md.outParams and "ReplyPriority" in md.attributes:
+ self.error(
+ loc, "non-returns message `%s' cannot specify [ReplyPriority]", msgname
+ )
+
+ isctor = False
+ isdtor = False
+ cdtype = None
+
+ decl = self.symtab.lookup(msgname)
+ if decl is not None and decl.type.isProtocol():
+ # probably a ctor. we'll check validity later.
+ msgname += "Constructor"
+ isctor = True
+ cdtype = decl.type
+ elif decl is not None:
+ self.error(
+ loc,
+ "message name `%s' already declared as `%s'",
+ msgname,
+ decl.type.typename(),
+ )
+ # if we error here, no big deal; move on to find more
+
+ if _DELETE_MSG == msgname:
+ isdtor = True
+ cdtype = self.currentProtocolDecl.type
+
+ # enter message scope
+ self.symtab.enterScope()
+
+ msgtype = MessageType(
+ nested=md.nested(),
+ prio=md.priority(),
+ replyPrio=md.replyPriority(),
+ sendSemantics=md.sendSemantics,
+ direction=md.direction,
+ ctor=isctor,
+ dtor=isdtor,
+ cdtype=cdtype,
+ compress=md.attributes.get("Compress"),
+ tainted="Tainted" in md.attributes,
+ lazySend="LazySend" in md.attributes,
+ )
+
+ # replace inparam Param nodes with proper Decls
+ def paramToDecl(param):
+ self.checkAttributes(
+ param.attributes,
+ {
+ # Passback indicates that the argument is unused by the Parent and is
+ # merely returned to the Child later.
+ # AllValid indicates that the entire span of values representable by
+ # the type are acceptable. e.g. 0-255 in a uint8
+ "NoTaint": ("passback", "allvalid")
+ },
+ )
+
+ ptname = param.typespec.basename()
+ ploc = param.typespec.loc
+
+ if "NoTaint" in param.attributes and "Tainted" not in md.attributes:
+ self.error(
+ ploc,
+ "argument typename `%s' of message `%s' has a NoTaint attribute, but the message lacks the Tainted attribute",
+ ptname,
+ msgname,
+ )
+
+ ptdecl = self.symtab.lookup(ptname)
+ if ptdecl is None:
+ self.error(
+ ploc,
+ "argument typename `%s' of message `%s' has not been declared",
+ ptname,
+ msgname,
+ )
+ ptype = VOID
+ else:
+ ptype = self._canonicalType(ptdecl.type, param.typespec)
+ return self.declare(
+ loc=ploc, type=ptype, progname=param.name, attributes=param.attributes
+ )
+
+ for i, inparam in enumerate(md.inParams):
+ pdecl = paramToDecl(inparam)
+ msgtype.params.append(pdecl.type)
+ md.inParams[i] = pdecl
+ for i, outparam in enumerate(md.outParams):
+ pdecl = paramToDecl(outparam)
+ msgtype.returns.append(pdecl.type)
+ md.outParams[i] = pdecl
+
+ self.symtab.exitScope()
+
+ md.decl = self.declare(loc=loc, type=msgtype, progname=msgname)
+ md.protocolDecl = self.currentProtocolDecl
+ md.decl._md = md
+
+ def _canonicalType(self, itype, typespec):
+ loc = typespec.loc
+ if typespec.uniqueptr:
+ itype = UniquePtrType(itype)
+
+ if itype.isIPDL() and itype.isProtocol():
+ itype = ActorType(itype)
+
+ if itype.supportsNullable():
+ if not typespec.nullable:
+ itype = NotNullType(itype)
+ elif typespec.nullable:
+ self.error(
+ loc, "`nullable' qualifier for type `%s' is unsupported", itype.name()
+ )
+
+ if typespec.array:
+ itype = ArrayType(itype)
+
+ if typespec.maybe:
+ itype = MaybeType(itype)
+
+ return itype
+
+
+# -----------------------------------------------------------------------------
+
+
+def checkcycles(p, stack=None):
+ cycles = []
+
+ if stack is None:
+ stack = []
+
+ for cp in p.manages:
+ # special case for self-managed protocols
+ if cp is p:
+ continue
+
+ if cp in stack:
+ return [stack + [p, cp]]
+ cycles += checkcycles(cp, stack + [p])
+
+ return cycles
+
+
+def formatcycles(cycles):
+ r = []
+ for cycle in cycles:
+ s = " -> ".join([ptype.name() for ptype in cycle])
+ r.append("`%s'" % s)
+ return ", ".join(r)
+
+
+def fullyDefined(t, exploring=None):
+ """The rules for "full definition" of a type are
+ defined(atom) := true
+ defined(array basetype) := defined(basetype)
+ defined(struct f1 f2...) := defined(f1) and defined(f2) and ...
+ defined(union c1 c2 ...) := defined(c1) or defined(c2) or ..."""
+ if exploring is None:
+ exploring = set()
+
+ if t.isAtom():
+ return True
+ elif t.hasBaseType():
+ return fullyDefined(t.basetype, exploring)
+ elif t.defined:
+ return True
+ assert t.isCompound()
+
+ if t in exploring:
+ return False
+
+ exploring.add(t)
+ for c in t.itercomponents():
+ cdefined = fullyDefined(c, exploring)
+ if t.isStruct() and not cdefined:
+ t.defined = False
+ break
+ elif t.isUnion() and cdefined:
+ t.defined = True
+ break
+ else:
+ if t.isStruct():
+ t.defined = True
+ elif t.isUnion():
+ t.defined = False
+ exploring.remove(t)
+
+ return t.defined
+
+
+class CheckTypes(TcheckVisitor):
+ def __init__(self, errors):
+ TcheckVisitor.__init__(self, errors)
+ self.visited = set()
+ self.ptype = None
+
+ def visitInclude(self, inc):
+ if inc.tu.filename in self.visited:
+ return
+ self.visited.add(inc.tu.filename)
+ if inc.tu.protocol:
+ inc.tu.protocol.accept(self)
+
+ def visitStructDecl(self, sd):
+ if not fullyDefined(sd.decl.type):
+ self.error(sd.decl.loc, "struct `%s' is only partially defined", sd.name)
+
+ def visitUnionDecl(self, ud):
+ if not fullyDefined(ud.decl.type):
+ self.error(ud.decl.loc, "union `%s' is only partially defined", ud.name)
+
+ def visitProtocol(self, p):
+ self.ptype = p.decl.type
+
+ # check that we require no more "power" than our manager protocols
+ ptype, pname = p.decl.type, p.decl.shortname
+
+ for mgrtype in ptype.managers:
+ if mgrtype is not None and not ptype.sendSemanticsSatisfiedBy(mgrtype):
+ self.error(
+ p.decl.loc,
+ "protocol `%s' requires more powerful send semantics than its manager `%s' provides", # NOQA: E501
+ pname,
+ mgrtype.name(),
+ )
+
+ if ptype.isInterrupt() and ptype.nestedRange != (NOT_NESTED, NOT_NESTED):
+ self.error(
+ p.decl.loc, "intr protocol `%s' cannot specify [NestedUpTo]", p.name
+ )
+
+ if ptype.isToplevel():
+ cycles = checkcycles(p.decl.type)
+ if cycles:
+ self.error(
+ p.decl.loc,
+ "cycle(s) detected in manager/manages hierarchy: %s",
+ formatcycles(cycles),
+ )
+
+ if 1 == len(ptype.managers) and ptype is ptype.manager():
+ self.error(
+ p.decl.loc, "top-level protocol `%s' cannot manage itself", p.name
+ )
+
+ return Visitor.visitProtocol(self, p)
+
+ def visitManagesStmt(self, mgs):
+ pdecl = mgs.manager.decl
+ ptype, pname = pdecl.type, pdecl.shortname
+
+ mgsdecl = mgs.decl
+ mgstype, mgsname = mgsdecl.type, mgsdecl.shortname
+
+ loc = mgs.loc
+
+ # we added this information; sanity check it
+ assert ptype.isManagerOf(mgstype)
+
+ # check that the "managed" protocol agrees
+ if not mgstype.isManagedBy(ptype):
+ self.error(
+ loc,
+ "|manages| declaration in protocol `%s' does not match any |manager| declaration in protocol `%s'", # NOQA: E501
+ pname,
+ mgsname,
+ )
+
+ def visitManager(self, mgr):
+ pdecl = mgr.of.decl
+ ptype, pname = pdecl.type, pdecl.shortname
+
+ mgrdecl = mgr.decl
+ mgrtype, mgrname = mgrdecl.type, mgrdecl.shortname
+
+ # we added this information; sanity check it
+ assert ptype.isManagedBy(mgrtype)
+
+ loc = mgr.loc
+
+ # check that the "manager" protocol agrees
+ if not mgrtype.isManagerOf(ptype):
+ self.error(
+ loc,
+ "|manager| declaration in protocol `%s' does not match any |manages| declaration in protocol `%s'", # NOQA: E501
+ pname,
+ mgrname,
+ )
+
+ def visitMessageDecl(self, md):
+ mtype, mname = md.decl.type, md.decl.progname
+ ptype, pname = md.protocolDecl.type, md.protocolDecl.shortname
+
+ loc = md.decl.loc
+
+ if mtype.nested == INSIDE_SYNC_NESTED and not mtype.isSync():
+ self.error(
+ loc,
+ "inside_sync nested messages must be sync (here, message `%s' in protocol `%s')",
+ mname,
+ pname,
+ )
+
+ if mtype.nested == INSIDE_CPOW_NESTED and (mtype.isOut() or mtype.isInout()):
+ self.error(
+ loc,
+ "inside_cpow nested parent-to-child messages are verboten (here, message `%s' in protocol `%s')", # NOQA: E501
+ mname,
+ pname,
+ )
+
+ # We allow inside_sync messages that are themselves sync to be sent from the
+ # parent. Normal and inside_cpow nested messages that are sync can only come from
+ # the child.
+ if (
+ mtype.isSync()
+ and mtype.nested == NOT_NESTED
+ and (mtype.isOut() or mtype.isInout())
+ ):
+ self.error(
+ loc,
+ "sync parent-to-child messages are verboten (here, message `%s' in protocol `%s')",
+ mname,
+ pname,
+ )
+
+ if not mtype.sendSemanticsSatisfiedBy(ptype):
+ self.error(
+ loc,
+ "message `%s' requires more powerful send semantics than its protocol `%s' provides", # NOQA: E501
+ mname,
+ pname,
+ )
+
+ if (mtype.isCtor() or mtype.isDtor()) and mtype.isAsync() and mtype.returns:
+ self.error(
+ loc, "asynchronous ctor/dtor message `%s' declares return values", mname
+ )
+
+ if mtype.compress and (not mtype.isAsync() or mtype.isCtor() or mtype.isDtor()):
+
+ if mtype.isCtor() or mtype.isDtor():
+ message_type = "constructor" if mtype.isCtor() else "destructor"
+ error_message = (
+ "%s messages can't use compression (here, in protocol `%s')"
+ % (message_type, pname)
+ )
+ else:
+ error_message = (
+ "message `%s' in protocol `%s' requests compression but is not async"
+ % (mname, pname) # NOQA: E501
+ )
+
+ self.error(loc, error_message)
+
+ if mtype.isCtor() and not ptype.isManagerOf(mtype.constructedType()):
+ self.error(
+ loc,
+ "ctor for protocol `%s', which is not managed by protocol `%s'",
+ mname[: -len("constructor")],
+ pname,
+ )