diff options
Diffstat (limited to 'ipc/ipdl/ipdl/type.py')
-rw-r--r-- | ipc/ipdl/ipdl/type.py | 1748 |
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, + ) |