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.py2200
1 files changed, 2200 insertions, 0 deletions
diff --git a/ipc/ipdl/ipdl/type.py b/ipc/ipdl/ipdl/type.py
new file mode 100644
index 000000000..68a10cd01
--- /dev/null
+++ b/ipc/ipdl/ipdl/type.py
@@ -0,0 +1,2200 @@
+# 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, sys
+
+from ipdl.ast import CxxInclude, Decl, Loc, QualifiedId, State, StructDecl, TransitionStmt
+from ipdl.ast import TypeSpec, UnionDecl, UsingStmt, Visitor
+from ipdl.ast import ASYNC, SYNC, INTR
+from ipdl.ast import IN, OUT, INOUT, ANSWER, CALL, RECV, SEND
+from ipdl.ast import NOT_NESTED, INSIDE_SYNC_NESTED, INSIDE_CPOW_NESTED
+import ipdl.builtin as builtin
+
+_DELETE_MSG = '__delete__'
+
+
+def _otherside(side):
+ if side == 'parent': return 'child'
+ elif side == 'child': return 'parent'
+ else: assert 0 and 'unknown side "%s"'% (side)
+
+def unique_pairs(s):
+ n = len(s)
+ for i, e1 in enumerate(s):
+ for j in xrange(i+1, n):
+ yield (e1, s[j])
+
+def cartesian_product(s1, s2):
+ for e1 in s1:
+ for e2 in s2:
+ yield (e1, e2)
+
+
+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 visitBuiltinCxxType(self, t, *args):
+ pass
+
+ def visitImportedCxxType(self, t, *args):
+ pass
+
+ def visitStateType(self, s, *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)
+ a.state.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 visitShmemType(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
+
+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(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
+ # Can this type appear in IPDL programs?
+ def isVisible(self):
+ return False
+ def isVoid(self):
+ return False
+ def typename(self):
+ return self.__class__.__name__
+
+ def name(self): raise Exception, 'NYI'
+ def fullname(self): raise Exception, 'NYI'
+
+ 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 isVisible(self):
+ return False
+ def isVoid(self):
+ return True
+
+ def name(self): return 'void'
+ def fullname(self): return 'void'
+
+VOID = VoidType()
+
+##--------------------
+class CxxType(Type):
+ def isCxx(self):
+ return True
+ def isAtom(self):
+ return True
+ def isBuiltin(self):
+ return False
+ def isImported(self):
+ return False
+ def isGenerated(self):
+ return False
+ def isVisible(self):
+ return True
+
+class BuiltinCxxType(CxxType):
+ def __init__(self, qname):
+ assert isinstance(qname, QualifiedId)
+ self.loc = qname.loc
+ self.qname = qname
+ def isBuiltin(self): return True
+
+ def name(self):
+ return self.qname.baseid
+ def fullname(self):
+ return str(self.qname)
+
+class ImportedCxxType(CxxType):
+ def __init__(self, qname):
+ assert isinstance(qname, QualifiedId)
+ self.loc = qname.loc
+ self.qname = qname
+ def isImported(self): return True
+
+ def name(self):
+ return self.qname.baseid
+ def fullname(self):
+ return str(self.qname)
+
+##--------------------
+class IPDLType(Type):
+ def isIPDL(self): return True
+ def isVisible(self): return True
+ def isState(self): return False
+ 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 isAtom(self): return True
+ def isCompound(self): return False
+ def isShmem(self): return False
+ def isChmod(self): return False
+ def isFD(self): return False
+ def isEndpoint(self): return False
+
+ def isAsync(self): return self.sendSemantics == ASYNC
+ def isSync(self): return self.sendSemantics == SYNC
+ def isInterrupt(self): return self.sendSemantics is INTR
+
+ def hasReply(self): return (self.isSync() or self.isInterrupt())
+
+ @classmethod
+ def convertsTo(cls, lesser, greater):
+ if (lesser.nestedRange[0] < greater.nestedRange[0] or
+ lesser.nestedRange[1] > greater.nestedRange[1]):
+ 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
+
+ def needsMoreJuiceThan(self, o):
+ return not IPDLType.convertsTo(self, o)
+
+class StateType(IPDLType):
+ def __init__(self, protocol, name, start=False):
+ self.protocol = protocol
+ self.name = name
+ self.start = start
+ def isState(self): return True
+ def name(self):
+ return self.name
+ def fullname(self):
+ return self.name()
+
+class MessageType(IPDLType):
+ def __init__(self, nested, prio, sendSemantics, direction,
+ ctor=False, dtor=False, cdtype=None, compress=False,
+ verify=False):
+ assert not (ctor and dtor)
+ assert not (ctor or dtor) or type is not None
+
+ self.nested = nested
+ self.prio = prio
+ self.nestedRange = (nested, nested)
+ self.sendSemantics = sendSemantics
+ self.direction = direction
+ self.params = [ ]
+ self.returns = [ ]
+ self.ctor = ctor
+ self.dtor = dtor
+ self.cdtype = cdtype
+ self.compress = compress
+ self.verify = verify
+ 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 hasImplicitActorParam(self):
+ return self.isCtor() or self.isDtor()
+
+class Bridge:
+ def __init__(self, parentPtype, childPtype):
+ assert parentPtype.isToplevel() and childPtype.isToplevel()
+ self.parent = parentPtype
+ self.child = childPtype
+
+ def __cmp__(self, o):
+ return cmp(self.parent, o.parent) or cmp(self.child, o.child)
+ def __eq__(self, o):
+ return self.parent == o.parent and self.child == o.child
+ def __hash__(self):
+ return hash(self.parent) + hash(self.child)
+
+class ProtocolType(IPDLType):
+ def __init__(self, qname, nestedRange, sendSemantics, stateless=False):
+ self.qname = qname
+ self.nestedRange = nestedRange
+ self.sendSemantics = sendSemantics
+ self.spawns = set() # ProtocolType
+ self.opens = set() # ProtocolType
+ self.managers = [] # ProtocolType
+ self.manages = [ ]
+ self.stateless = stateless
+ self.hasDelete = False
+ self.hasReentrantDelete = False
+ def isProtocol(self): return True
+
+ 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 addSpawn(self, ptype):
+ assert self.isToplevel() and ptype.isToplevel()
+ self.spawns.add(ptype)
+
+ def addOpen(self, ptype):
+ assert self.isToplevel() and ptype.isToplevel()
+ self.opens.add(ptype)
+
+ 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, state=None, nullable=0):
+ self.protocol = protocol
+ self.state = state
+ self.nullable = nullable
+ def isActor(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.isArray():
+ 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 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 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):
+ self.qname = qname
+ def isEndpoint(self): return True
+
+ def name(self):
+ return self.qname.baseid
+ def fullname(self):
+ return str(self.qname)
+
+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.isArray():
+ 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 hasactor(type):
+ """Return true iff |type| is an actor or has one buried within."""
+ for _ in iteractortypes(type): return True
+ return False
+
+def hasshmem(type):
+ """Return true iff |type| is shmem or has it buried within."""
+ class found: pass
+ class findShmem(TypeVisitor):
+ def visitShmemType(self, s): raise found()
+ try:
+ type.accept(findShmem())
+ except found:
+ return True
+ return False
+
+def hasfd(type):
+ """Return true iff |type| is fd or has it buried within."""
+ class found: pass
+ class findFD(TypeVisitor):
+ def visitFDType(self, s): raise found()
+ try:
+ type.accept(findFD())
+ 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,
+ TypeSpec(_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.globalScope = self.scopes[0]
+ self.currentScope = self.globalScope
+
+ def enterScope(self, node):
+ assert (isinstance(self.scopes[0], dict)
+ and self.globalScope is self.scopes[0])
+ assert (isinstance(self.currentScope, dict))
+
+ if not hasattr(node, 'symtab'):
+ node.symtab = { }
+
+ self.scopes.append(node.symtab)
+ self.currentScope = self.scopes[-1]
+
+ def exitScope(self, node):
+ symtab = self.scopes.pop()
+ assert self.currentScope is symtab
+
+ self.currentScope = self.scopes[-1]
+
+ assert (isinstance(self.scopes[0], dict)
+ and self.globalScope is self.scopes[0])
+ 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 .type attribute of every AST node. For some
+nodes, the type is meaningless and it is set to "VOID." This pass
+also 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 finally 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
+
+ if not (runpass(BuildProcessGraph(self.errors))
+ and runpass(CheckProcessGraph(self.errors))):
+ return False
+
+ if (tu.protocol
+ and len(tu.protocol.startStates)
+ and not runpass(CheckStateMachine(self.errors))):
+ return False
+ return True
+
+ def reportErrors(self, errout):
+ for error in self.errors:
+ print >>errout, error
+
+
+class TcheckVisitor(Visitor):
+ def __init__(self, symtab, errors):
+ self.symtab = symtab
+ self.errors = errors
+
+ def error(self, loc, fmt, *args):
+ self.errors.append(errormsg(loc, fmt, *args))
+
+ def declare(self, loc, type, shortname=None, fullname=None, progname=None):
+ d = Decl(loc)
+ d.type = type
+ d.progname = progname
+ d.shortname = shortname
+ d.fullname = fullname
+ self.symtab.declare(d)
+ return d
+
+class GatherDecls(TcheckVisitor):
+ def __init__(self, builtinUsing, errors):
+ # |self.symtab| is the symbol table for the translation unit
+ # currently being visited
+ TcheckVisitor.__init__(self, None, errors)
+ self.builtinUsing = builtinUsing
+
+ def visitTranslationUnit(self, tu):
+ # all TranslationUnits declare symbols in global scope
+ if hasattr(tu, 'symtab'):
+ return
+ tu.symtab = SymbolTable(self.errors)
+ savedSymtab = self.symtab
+ self.symtab = tu.symtab
+
+ # 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'",
+ tu.name, expectedfilename, basefilename)
+
+ if tu.protocol:
+ assert tu.name == tu.protocol.name
+
+ p = tu.protocol
+
+ # 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()
+ if 0 == len(qname.quals):
+ fullname = None
+ else:
+ fullname = str(qname)
+ p.decl = self.declare(
+ loc=p.loc,
+ type=ProtocolType(qname, p.nestedRange, p.sendSemantics,
+ stateless=(0 == len(p.transitionStmts))),
+ shortname=p.name,
+ fullname=fullname)
+
+ p.parentEndpointDecl = self.declare(
+ loc=p.loc,
+ type=EndpointType(QualifiedId(p.loc, 'Endpoint<' + fullname + 'Parent>', ['mozilla', 'ipc'])),
+ shortname='Endpoint<' + p.name + 'Parent>')
+ p.childEndpointDecl = self.declare(
+ loc=p.loc,
+ type=EndpointType(QualifiedId(p.loc, 'Endpoint<' + fullname + 'Child>', ['mozilla', 'ipc'])),
+ shortname='Endpoint<' + 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++ types
+ 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)
+ for inc in tu.includes:
+ if inc.tu.filetype == 'header':
+ for su in inc.tu.structsAndUnions:
+ su.accept(self)
+
+ if tu.protocol:
+ # grab symbols in the protocol itself
+ p.accept(self)
+
+
+ tu.type = VOID
+
+ self.symtab = savedSymtab
+
+ def declareStructOrUnion(self, su):
+ if hasattr(su, 'decl'):
+ self.symtab.declare(su.decl)
+ return
+
+ qname = su.qname()
+ if 0 == len(qname.quals):
+ fullname = None
+ else:
+ 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)
+ 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, 'symtab'):
+ return
+
+ stype = sd.decl.type
+
+ self.symtab.enterScope(sd)
+
+ 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(sd)
+
+ 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
+
+ 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)
+ if using.type.basename() == fullname:
+ fullname = None
+ if fullname == 'mozilla::ipc::Shmem':
+ ipdltype = ShmemType(using.type.spec)
+ elif fullname == 'mozilla::ipc::FileDescriptor':
+ ipdltype = FDType(using.type.spec)
+ else:
+ ipdltype = ImportedCxxType(using.type.spec)
+ existingType = self.symtab.lookup(ipdltype.fullname())
+ if existingType and existingType.fullname == ipdltype.fullname():
+ using.decl = existingType
+ return
+ using.decl = self.declare(
+ loc=using.loc,
+ type=ipdltype,
+ shortname=using.type.basename(),
+ fullname=fullname)
+
+ def visitProtocol(self, p):
+ # protocol scope
+ self.symtab.enterScope(p)
+
+ for spawns in p.spawnsStmts:
+ spawns.accept(self)
+
+ for bridges in p.bridgesStmts:
+ bridges.accept(self)
+
+ for opens in p.opensStmts:
+ opens.accept(self)
+
+ 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 0 == len(p.managers) and 0 == len(p.messageDecls):
+ 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)
+
+ p.decl.type.hasReentrantDelete = p.decl.type.hasDelete and self.symtab.lookup(_DELETE_MSG).type.isInterrupt()
+
+ for managed in p.managesStmts:
+ mgdname = managed.name
+ ctordecl = self.symtab.lookup(mgdname +'Constructor')
+
+ if not (ctordecl and ctordecl.type.isCtor()):
+ self.error(
+ managed.loc,
+ "constructor declaration required for managed protocol `%s' (managed by protocol `%s')",
+ mgdname, p.name)
+
+ p.states = { }
+
+ if len(p.transitionStmts):
+ p.startStates = [ ts for ts in p.transitionStmts
+ if ts.state.start ]
+ if 0 == len(p.startStates):
+ p.startStates = [ p.transitionStmts[0] ]
+
+ # declare implicit "any", "dead", and "dying" states
+ self.declare(loc=State.ANY.loc,
+ type=StateType(p.decl.type, State.ANY.name, start=False),
+ progname=State.ANY.name)
+ self.declare(loc=State.DEAD.loc,
+ type=StateType(p.decl.type, State.DEAD.name, start=False),
+ progname=State.DEAD.name)
+ if p.decl.type.hasReentrantDelete:
+ self.declare(loc=State.DYING.loc,
+ type=StateType(p.decl.type, State.DYING.name, start=False),
+ progname=State.DYING.name)
+
+ # declare each state before decorating their mention
+ for trans in p.transitionStmts:
+ p.states[trans.state] = trans
+ trans.state.decl = self.declare(
+ loc=trans.state.loc,
+ type=StateType(p.decl.type, trans.state, trans.state.start),
+ progname=trans.state.name)
+
+ for trans in p.transitionStmts:
+ self.seentriggers = set()
+ trans.accept(self)
+
+ if not (p.decl.type.stateless
+ or (p.decl.type.isToplevel()
+ and None is self.symtab.lookup(_DELETE_MSG))):
+ # add a special state |state DEAD: null goto DEAD;|
+ deadtrans = TransitionStmt.makeNullStmt(State.DEAD)
+ p.states[State.DEAD] = deadtrans
+ if p.decl.type.hasReentrantDelete:
+ dyingtrans = TransitionStmt.makeNullStmt(State.DYING)
+ p.states[State.DYING] = dyingtrans
+
+ # visit the message decls once more and resolve the state names
+ # attached to actor params and returns
+ def resolvestate(loc, actortype):
+ assert actortype.isIPDL() and actortype.isActor()
+
+ # already resolved this guy's state
+ if isinstance(actortype.state, Decl):
+ return
+
+ if actortype.state is None:
+ # we thought this was a C++ type until type checking,
+ # when we realized it was an IPDL actor type. But
+ # that means that the actor wasn't specified to be in
+ # any particular state
+ actortype.state = State.ANY
+
+ statename = actortype.state.name
+ # FIXME/cjones: this is just wrong. we need the symbol table
+ # of the protocol this actor refers to. low priority bug
+ # since nobody's using this feature yet
+ statedecl = self.symtab.lookup(statename)
+ if statedecl is None:
+ self.error(
+ loc,
+ "protocol `%s' does not have the state `%s'",
+ actortype.protocol.name(),
+ statename)
+ elif not statedecl.type.isState():
+ self.error(
+ loc,
+ "tag `%s' is supposed to be of state type, but is instead of type `%s'",
+ statename,
+ statedecl.type.typename())
+ else:
+ actortype.state = statedecl.type
+
+ for msg in p.messageDecls:
+ for iparam in msg.inParams:
+ loc = iparam.loc
+ for actortype in iteractortypes(iparam.type):
+ resolvestate(loc, actortype)
+ for oparam in msg.outParams:
+ loc = oparam.loc
+ for actortype in iteractortypes(oparam.type):
+ resolvestate(loc, actortype)
+
+ # 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(p)
+
+
+ def visitSpawnsStmt(self, spawns):
+ pname = spawns.proto
+ spawns.proto = self.symtab.lookup(pname)
+ if spawns.proto is None:
+ self.error(spawns.loc,
+ "spawned protocol `%s' has not been declared",
+ pname)
+
+ def visitBridgesStmt(self, bridges):
+ def lookup(p):
+ decl = self.symtab.lookup(p)
+ if decl is None:
+ self.error(bridges.loc,
+ "bridged protocol `%s' has not been declared", p)
+ return decl
+ bridges.parentSide = lookup(bridges.parentSide)
+ bridges.childSide = lookup(bridges.childSide)
+
+ def visitOpensStmt(self, opens):
+ pname = opens.proto
+ opens.proto = self.symtab.lookup(pname)
+ if opens.proto is None:
+ self.error(opens.loc,
+ "opened protocol `%s' has not been declared",
+ pname)
+
+
+ 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'",
+ 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
+
+ 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(md)
+
+ msgtype = MessageType(md.nested, md.prio, md.sendSemantics, md.direction,
+ ctor=isctor, dtor=isdtor, cdtype=cdtype,
+ compress=md.compress, verify=md.verify)
+
+ # replace inparam Param nodes with proper Decls
+ def paramToDecl(param):
+ ptname = param.typespec.basename()
+ ploc = param.typespec.loc
+
+ 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,
+ chmodallowed=1)
+ return self.declare(loc=ploc,
+ type=ptype,
+ progname=param.name)
+
+ 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)
+
+ md.decl = self.declare(
+ loc=loc,
+ type=msgtype,
+ progname=msgname)
+ md.protocolDecl = self.currentProtocolDecl
+ md.decl._md = md
+
+
+ def visitTransitionStmt(self, ts):
+ self.seentriggers = set()
+ TcheckVisitor.visitTransitionStmt(self, ts)
+
+ def visitTransition(self, t):
+ loc = t.loc
+
+ # check the trigger message
+ mname = t.msg
+ if t in self.seentriggers:
+ self.error(loc, "trigger `%s' appears multiple times", t.msg)
+ self.seentriggers.add(t)
+
+ mdecl = self.symtab.lookup(mname)
+ if mdecl is not None and mdecl.type.isIPDL() and mdecl.type.isProtocol():
+ mdecl = self.symtab.lookup(mname +'Constructor')
+
+ if mdecl is None:
+ self.error(loc, "message `%s' has not been declared", mname)
+ elif not mdecl.type.isMessage():
+ self.error(
+ loc,
+ "`%s' should have message type, but instead has type `%s'",
+ mname, mdecl.type.typename())
+ else:
+ t.msg = mdecl
+
+ # check the to-states
+ seenstates = set()
+ for toState in t.toStates:
+ sname = toState.name
+ sdecl = self.symtab.lookup(sname)
+
+ if sname in seenstates:
+ self.error(loc, "to-state `%s' appears multiple times", sname)
+ seenstates.add(sname)
+
+ if sdecl is None:
+ self.error(loc, "state `%s' has not been declared", sname)
+ elif not sdecl.type.isState():
+ self.error(
+ loc, "`%s' should have state type, but instead has type `%s'",
+ sname, sdecl.type.typename())
+ else:
+ toState.decl = sdecl
+ toState.start = sdecl.type.start
+
+ t.toStates = set(t.toStates)
+
+
+ def _canonicalType(self, itype, typespec, chmodallowed=0):
+ loc = typespec.loc
+
+ if itype.isIPDL():
+ if itype.isProtocol():
+ itype = ActorType(itype,
+ state=typespec.state,
+ nullable=typespec.nullable)
+ # FIXME/cjones: ShmemChmod is disabled until bug 524193
+ if 0 and chmodallowed and itype.isShmem():
+ itype = ShmemChmodType(
+ itype,
+ myChmod=typespec.myChmod,
+ otherChmod=typespec.otherChmod)
+
+ if ((typespec.myChmod or typespec.otherChmod)
+ and not (itype.isIPDL() and (itype.isShmem() or itype.isChmod()))):
+ self.error(
+ loc,
+ "fine-grained access controls make no sense for type `%s'",
+ itype.name())
+
+ if not chmodallowed and (typespec.myChmod or typespec.otherChmod):
+ self.error(loc, "fine-grained access controls not allowed here")
+
+ if typespec.nullable and not (itype.isIPDL() and itype.isActor()):
+ self.error(
+ loc,
+ "`nullable' qualifier for type `%s' makes no sense",
+ itype.name())
+
+ if typespec.array:
+ itype = ArrayType(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.isArray():
+ 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):
+ # don't need the symbol table, we just want the error reporting
+ TcheckVisitor.__init__(self, None, 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
+
+ if len(p.spawnsStmts) and not ptype.isToplevel():
+ self.error(p.decl.loc,
+ "protocol `%s' is not top-level and so cannot declare |spawns|",
+ pname)
+
+ if len(p.bridgesStmts) and not ptype.isToplevel():
+ self.error(p.decl.loc,
+ "protocol `%s' is not top-level and so cannot declare |bridges|",
+ pname)
+
+ if len(p.opensStmts) and not ptype.isToplevel():
+ self.error(p.decl.loc,
+ "protocol `%s' is not top-level and so cannot declare |opens|",
+ pname)
+
+ for mgrtype in ptype.managers:
+ if mgrtype is not None and ptype.needsMoreJuiceThan(mgrtype):
+ self.error(
+ p.decl.loc,
+ "protocol `%s' requires more powerful send semantics than its manager `%s' provides",
+ pname, mgrtype.name())
+
+ # XXX currently we don't require a delete() message of top-level
+ # actors. need to let experience guide this decision
+ if not ptype.isToplevel():
+ for md in p.messageDecls:
+ if _DELETE_MSG == md.name: break
+ else:
+ self.error(
+ p.decl.loc,
+ "managed protocol `%s' requires a `delete()' message to be declared",
+ p.name)
+ else:
+ cycles = checkcycles(p.decl.type)
+ if cycles:
+ self.error(
+ p.decl.loc,
+ "cycle(s) detected in manager/manages heirarchy: %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 visitSpawnsStmt(self, spawns):
+ if not self.ptype.isToplevel():
+ self.error(spawns.loc,
+ "only top-level protocols can have |spawns| statements; `%s' cannot",
+ self.ptype.name())
+ return
+
+ spawnedType = spawns.proto.type
+ if not (spawnedType.isIPDL() and spawnedType.isProtocol()
+ and spawnedType.isToplevel()):
+ self.error(spawns.loc,
+ "cannot spawn non-top-level-protocol `%s'",
+ spawnedType.name())
+ else:
+ self.ptype.addSpawn(spawnedType)
+
+
+ def visitBridgesStmt(self, bridges):
+ if not self.ptype.isToplevel():
+ self.error(bridges.loc,
+ "only top-level protocols can have |bridges| statements; `%s' cannot",
+ self.ptype.name())
+ return
+
+ parentType = bridges.parentSide.type
+ childType = bridges.childSide.type
+ if not (parentType.isIPDL() and parentType.isProtocol()
+ and childType.isIPDL() and childType.isProtocol()
+ and parentType.isToplevel() and childType.isToplevel()):
+ self.error(bridges.loc,
+ "cannot bridge non-top-level-protocol(s) `%s' and `%s'",
+ parentType.name(), childType.name())
+
+
+ def visitOpensStmt(self, opens):
+ if not self.ptype.isToplevel():
+ self.error(opens.loc,
+ "only top-level protocols can have |opens| statements; `%s' cannot",
+ self.ptype.name())
+ return
+
+ openedType = opens.proto.type
+ if not (openedType.isIPDL() and openedType.isProtocol()
+ and openedType.isToplevel()):
+ self.error(opens.loc,
+ "cannot open non-top-level-protocol `%s'",
+ openedType.name())
+ else:
+ self.ptype.addOpen(openedType)
+
+
+ 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'",
+ pname, mgsname)
+
+
+ def visitManager(self, mgr):
+ # FIXME/bug 541126: check that the protocol graph is acyclic
+
+ 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'",
+ 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')",
+ 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 mtype.needsMoreJuiceThan(ptype):
+ self.error(
+ loc,
+ "message `%s' requires more powerful send semantics than its protocol `%s' provides",
+ mname, pname)
+
+ if mtype.isAsync() and len(mtype.returns):
+ # XXX/cjones could modify grammar to disallow this ...
+ self.error(loc,
+ "asynchronous message `%s' declares return values",
+ mname)
+
+ if (mtype.compress and
+ (not mtype.isAsync() or mtype.isCtor() or mtype.isDtor())):
+ self.error(
+ loc,
+ "message `%s' in protocol `%s' requests compression but is not async or is special (ctor or dtor)",
+ mname[:-len('constructor')], pname)
+
+ 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)
+
+
+ def visitTransition(self, t):
+ _YNC = [ ASYNC, SYNC ]
+
+ loc = t.loc
+ impliedDirection, impliedSems = {
+ SEND: [ OUT, _YNC ], RECV: [ IN, _YNC ],
+ CALL: [ OUT, INTR ], ANSWER: [ IN, INTR ],
+ } [t.trigger]
+
+ if (OUT is impliedDirection and t.msg.type.isIn()
+ or IN is impliedDirection and t.msg.type.isOut()
+ or _YNC is impliedSems and t.msg.type.isInterrupt()
+ or INTR is impliedSems and (not t.msg.type.isInterrupt())):
+ mtype = t.msg.type
+
+ self.error(
+ loc, "%s %s message `%s' is not `%s'd",
+ mtype.sendSemantics.pretty, mtype.direction.pretty,
+ t.msg.progname,
+ t.trigger.pretty)
+
+##-----------------------------------------------------------------------------
+
+class Process:
+ def __init__(self):
+ self.actors = set() # set(Actor)
+ self.edges = { } # Actor -> [ SpawnsEdge ]
+ self.spawn = set() # set(Actor)
+
+ def edge(self, spawner, spawn):
+ if spawner not in self.edges: self.edges[spawner] = [ ]
+ self.edges[spawner].append(SpawnsEdge(spawner, spawn))
+ self.spawn.add(spawn)
+
+ def iteredges(self):
+ for edgelist in self.edges.itervalues():
+ for edge in edgelist:
+ yield edge
+
+ def merge(self, o):
+ 'Merge the Process |o| into this Process'
+ if self == o:
+ return
+ for actor in o.actors:
+ ProcessGraph.actorToProcess[actor] = self
+ self.actors.update(o.actors)
+ self.edges.update(o.edges)
+ self.spawn.update(o.spawn)
+ ProcessGraph.processes.remove(o)
+
+ def spawns(self, actor):
+ return actor in self.spawn
+
+ def __cmp__(self, o): return cmp(self.actors, o.actors)
+ def __eq__(self, o): return self.actors == o.actors
+ def __hash__(self): return hash(id(self))
+ def __repr__(self):
+ return reduce(lambda a, x: str(a) + str(x) +'|', self.actors, '|')
+ def __str__(self): return repr(self)
+
+class Actor:
+ def __init__(self, ptype, side):
+ self.ptype = ptype
+ self.side = side
+
+ def asType(self):
+ return ActorType(self.ptype)
+ def other(self):
+ return Actor(self.ptype, _otherside(self.side))
+
+ def __cmp__(self, o):
+ return cmp(self.ptype, o.ptype) or cmp(self.side, o.side)
+ def __eq__(self, o):
+ return self.ptype == o.ptype and self.side == o.side
+ def __hash__(self): return hash(repr(self))
+ def __repr__(self): return '%s%s'% (self.ptype.name(), self.side.title())
+ def __str__(self): return repr(self)
+
+class SpawnsEdge:
+ def __init__(self, spawner, spawn):
+ self.spawner = spawner # Actor
+ self.spawn = spawn # Actor
+ def __repr__(self):
+ return '(%r)--spawns-->(%r)'% (self.spawner, self.spawn)
+ def __str__(self): return repr(self)
+
+class BridgeEdge:
+ def __init__(self, bridgeProto, parent, child):
+ self.bridgeProto = bridgeProto # ProtocolType
+ self.parent = parent # Actor
+ self.child = child # Actor
+ def __repr__(self):
+ return '(%r)--%s bridge-->(%r)'% (
+ self.parent, self.bridgeProto.name(), self.child)
+ def __str__(self): return repr(self)
+
+class OpensEdge:
+ def __init__(self, opener, openedProto):
+ self.opener = opener # Actor
+ self.openedProto = openedProto # ProtocolType
+ def __repr__(self):
+ return '(%r)--opens-->(%s)'% (self.opener, self.openedProto.name())
+ def __str__(self): return repr(self)
+
+# "singleton" class with state that persists across type checking of
+# all protocols
+class ProcessGraph:
+ processes = set() # set(Process)
+ bridges = { } # ProtocolType -> [ BridgeEdge ]
+ opens = { } # ProtocolType -> [ OpensEdge ]
+ actorToProcess = { } # Actor -> Process
+ visitedSpawns = set() # set(ActorType)
+ visitedBridges = set() # set(ActorType)
+
+ @classmethod
+ def findProcess(cls, actor):
+ return cls.actorToProcess.get(actor, None)
+
+ @classmethod
+ def getProcess(cls, actor):
+ if actor not in cls.actorToProcess:
+ p = Process()
+ p.actors.add(actor)
+ cls.processes.add(p)
+ cls.actorToProcess[actor] = p
+ return cls.actorToProcess[actor]
+
+ @classmethod
+ def bridgesOf(cls, bridgeP):
+ return cls.bridges.get(bridgeP, [])
+
+ @classmethod
+ def bridgeEndpointsOf(cls, ptype, side):
+ actor = Actor(ptype, side)
+ endpoints = []
+ for b in cls.iterbridges():
+ if b.parent == actor:
+ endpoints.append(Actor(b.bridgeProto, 'parent'))
+ if b.child == actor:
+ endpoints.append(Actor(b.bridgeProto, 'child'))
+ return endpoints
+
+ @classmethod
+ def iterbridges(cls):
+ for edges in cls.bridges.itervalues():
+ for bridge in edges:
+ yield bridge
+
+ @classmethod
+ def opensOf(cls, openedP):
+ return cls.opens.get(openedP, [])
+
+ @classmethod
+ def opensEndpointsOf(cls, ptype, side):
+ actor = Actor(ptype, side)
+ endpoints = []
+ for o in cls.iteropens():
+ if actor == o.opener:
+ endpoints.append(Actor(o.openedProto, o.opener.side))
+ elif actor == o.opener.other():
+ endpoints.append(Actor(o.openedProto, o.opener.other().side))
+ return endpoints
+
+ @classmethod
+ def iteropens(cls):
+ for edges in cls.opens.itervalues():
+ for opens in edges:
+ yield opens
+
+ @classmethod
+ def spawn(cls, spawner, remoteSpawn):
+ localSpawn = remoteSpawn.other()
+ spawnerProcess = ProcessGraph.getProcess(spawner)
+ spawnerProcess.merge(ProcessGraph.getProcess(localSpawn))
+ spawnerProcess.edge(spawner, remoteSpawn)
+
+ @classmethod
+ def bridge(cls, parent, child, bridgeP):
+ bridgeParent = Actor(bridgeP, 'parent')
+ parentProcess = ProcessGraph.getProcess(parent)
+ parentProcess.merge(ProcessGraph.getProcess(bridgeParent))
+ bridgeChild = Actor(bridgeP, 'child')
+ childProcess = ProcessGraph.getProcess(child)
+ childProcess.merge(ProcessGraph.getProcess(bridgeChild))
+ if bridgeP not in cls.bridges:
+ cls.bridges[bridgeP] = [ ]
+ cls.bridges[bridgeP].append(BridgeEdge(bridgeP, parent, child))
+
+ @classmethod
+ def open(cls, opener, opened, openedP):
+ remoteOpener, remoteOpened, = opener.other(), opened.other()
+ openerProcess = ProcessGraph.getProcess(opener)
+ openerProcess.merge(ProcessGraph.getProcess(opened))
+ remoteOpenerProcess = ProcessGraph.getProcess(remoteOpener)
+ remoteOpenerProcess.merge(ProcessGraph.getProcess(remoteOpened))
+ if openedP not in cls.opens:
+ cls.opens[openedP] = [ ]
+ cls.opens[openedP].append(OpensEdge(opener, openedP))
+
+
+class BuildProcessGraph(TcheckVisitor):
+ class findSpawns(TcheckVisitor):
+ def __init__(self, errors):
+ TcheckVisitor.__init__(self, None, errors)
+
+ def visitTranslationUnit(self, tu):
+ TcheckVisitor.visitTranslationUnit(self, tu)
+
+ def visitInclude(self, inc):
+ if inc.tu.protocol:
+ inc.tu.protocol.accept(self)
+
+ def visitProtocol(self, p):
+ ptype = p.decl.type
+ # non-top-level protocols don't add any information
+ if not ptype.isToplevel() or ptype in ProcessGraph.visitedSpawns:
+ return
+
+ ProcessGraph.visitedSpawns.add(ptype)
+ self.visiting = ptype
+ ProcessGraph.getProcess(Actor(ptype, 'parent'))
+ ProcessGraph.getProcess(Actor(ptype, 'child'))
+ return TcheckVisitor.visitProtocol(self, p)
+
+ def visitSpawnsStmt(self, spawns):
+ # The picture here is:
+ # [ spawner | localSpawn | ??? ] (process 1)
+ # |
+ # |
+ # [ remoteSpawn | ???] (process 2)
+ #
+ # A spawns stmt tells us that |spawner| and |localSpawn|
+ # are in the same process.
+ spawner = Actor(self.visiting, spawns.side)
+ remoteSpawn = Actor(spawns.proto.type, spawns.spawnedAs)
+ ProcessGraph.spawn(spawner, remoteSpawn)
+
+ def __init__(self, errors):
+ TcheckVisitor.__init__(self, None, errors)
+ self.visiting = None # ActorType
+ self.visited = set() # set(ActorType)
+
+ def visitTranslationUnit(self, tu):
+ tu.accept(self.findSpawns(self.errors))
+ TcheckVisitor.visitTranslationUnit(self, tu)
+
+ def visitInclude(self, inc):
+ if inc.tu.protocol:
+ inc.tu.protocol.accept(self)
+
+ def visitProtocol(self, p):
+ ptype = p.decl.type
+ # non-top-level protocols don't add any information
+ if not ptype.isToplevel() or ptype in ProcessGraph.visitedBridges:
+ return
+
+ ProcessGraph.visitedBridges.add(ptype)
+ self.visiting = ptype
+ return TcheckVisitor.visitProtocol(self, p)
+
+ def visitBridgesStmt(self, bridges):
+ bridgeProto = self.visiting
+ parentSideProto = bridges.parentSide.type
+ childSideProto = bridges.childSide.type
+
+ # the picture here is:
+ # (process 1|
+ # [ parentSide(Parent|Child) | childSide(Parent|Child) | ... ]
+ # | |
+ # | (process 2| |
+ # [ parentSide(Child|Parent) | bridgeParent ] |
+ # | |
+ # | | (process 3|
+ # [ bridgeChild | childSide(Child|Parent) ]
+ #
+ # First we have to figure out which parentSide/childSide
+ # actors live in the same process. The possibilities are {
+ # parent, child } x { parent, child }. (Multiple matches
+ # aren't allowed yet.) Then we make ProcessGraph aware of the
+ # new bridge.
+ parentSideActor, childSideActor = None, None
+ pc = ( 'parent', 'child' )
+ for parentSide, childSide in cartesian_product(pc, pc):
+ pactor = Actor(parentSideProto, parentSide)
+ pproc = ProcessGraph.findProcess(pactor)
+ cactor = Actor(childSideProto, childSide)
+ cproc = ProcessGraph.findProcess(cactor)
+ assert pproc and cproc
+
+ if pproc == cproc:
+ if parentSideActor is not None:
+ if parentSideProto != childSideProto:
+ self.error(bridges.loc,
+ "ambiguous bridge `%s' between `%s' and `%s'",
+ bridgeProto.name(),
+ parentSideProto.name(),
+ childSideProto.name())
+ else:
+ parentSideActor, childSideActor = pactor.other(), cactor.other()
+
+ if parentSideActor is None:
+ self.error(bridges.loc,
+ "`%s' and `%s' cannot be bridged by `%s' ",
+ parentSideProto.name(), childSideProto.name(),
+ bridgeProto.name())
+
+ ProcessGraph.bridge(parentSideActor, childSideActor, bridgeProto)
+
+ def visitOpensStmt(self, opens):
+ openedP = opens.proto.type
+ opener = Actor(self.visiting, opens.side)
+ opened = Actor(openedP, opens.side)
+
+ # The picture here is:
+ # [ opener | opened ] (process 1)
+ # | |
+ # | |
+ # [ remoteOpener | remoteOpened ] (process 2)
+ #
+ # An opens stmt tells us that the pairs |opener|/|opened|
+ # and |remoteOpener|/|remoteOpened| are each in the same
+ # process.
+ ProcessGraph.open(opener, opened, openedP)
+
+
+class CheckProcessGraph(TcheckVisitor):
+ def __init__(self, errors):
+ TcheckVisitor.__init__(self, None, errors)
+
+ # TODO: verify spawns-per-process assumption and check that graph
+ # is a dag
+ def visitTranslationUnit(self, tu):
+ if 0:
+ print 'Processes'
+ for process in ProcessGraph.processes:
+ print ' ', process
+ for edge in process.iteredges():
+ print ' ', edge
+ print 'Bridges'
+ for bridgeList in ProcessGraph.bridges.itervalues():
+ for bridge in bridgeList:
+ print ' ', bridge
+ print 'Opens'
+ for opensList in ProcessGraph.opens.itervalues():
+ for opens in opensList:
+ print ' ', opens
+
+##-----------------------------------------------------------------------------
+
+class CheckStateMachine(TcheckVisitor):
+ def __init__(self, errors):
+ # don't need the symbol table, we just want the error reporting
+ TcheckVisitor.__init__(self, None, errors)
+ self.p = None
+
+ def visitProtocol(self, p):
+ self.p = p
+ self.checkReachability(p)
+ for ts in p.transitionStmts:
+ ts.accept(self)
+
+ def visitTransitionStmt(self, ts):
+ # We want to disallow "race conditions" in protocols. These
+ # can occur when a protocol state machine has a state that
+ # allows triggers of opposite direction. That declaration
+ # allows the parent to send the child a message at the
+ # exact instance the child sends the parent a message. One of
+ # those messages would (probably) violate the state machine
+ # and cause the child to be terminated. It's obviously very
+ # nice if we can forbid this at the level of IPDL state
+ # machines, rather than resorting to static or dynamic
+ # checking of C++ implementation code.
+ #
+ # An easy way to avoid this problem in IPDL is to only allow
+ # "unidirectional" protocol states; that is, from each state,
+ # only send or only recv triggers are allowed. This approach
+ # is taken by the Singularity project's "contract-based
+ # message channels." However, this can be something of a
+ # notational burden for stateful protocols.
+ #
+ # If two messages race, the effect is that the parent's and
+ # child's states get temporarily out of sync. Informally,
+ # IPDL allows this *only if* the state machines get out of
+ # sync for only *one* step (state machine transition), then
+ # sync back up. This is a design decision: the states could
+ # be allowd to get out of sync for any constant k number of
+ # steps. (If k is unbounded, there's no point in presenting
+ # the abstraction of parent and child actor states being
+ # "entangled".) The working hypothesis is that the more steps
+ # the states are allowed to be out of sync, the harder it is
+ # to reason about the protocol.
+ #
+ # Slightly less informally, two messages are allowed to race
+ # only if processing them in either order leaves the protocol
+ # in the same state. That is, messages A and B are allowed to
+ # race only if processing A then B leaves the protocol in
+ # state S, *and* processing B then A also leaves the protocol
+ # in state S. Technically, if this holds, then messages A and
+ # B could be called "commutative" wrt to actor state.
+ #
+ # "Formally", state machine definitions must adhere to two
+ # rules.
+ #
+ # *Rule 1*: from a state S, all sync triggers must be of the same
+ # "direction," i.e. only |send| or only |recv|
+ #
+ # (Pairs of sync messages can't commute, because otherwise
+ # deadlock can occur from simultaneously in-flight sync
+ # requests.)
+ #
+ # *Rule 2*: the "Diamond Rule".
+ # from a state S,
+ # for any pair of triggers t1 and t2,
+ # where t1 and t2 have opposite direction,
+ # and t1 transitions to state T1 and t2 to T2,
+ # then the following must be true:
+ # (T2 allows the trigger t1, transitioning to state U)
+ # and
+ # (T1 allows the trigger t2, transitioning to state U)
+ # and
+ # (
+ # (
+ # (all of T1's triggers have the same direction as t2)
+ # and
+ # (all of T2's triggers have the same direction as t1)
+ # )
+ # or
+ # (T1, T2, and U are the same "terminal state")
+ # )
+ #
+ # A "terminal state" S is one from which all triggers
+ # transition back to S itself.
+ #
+ # The presence of triggers with multiple out states complicates
+ # this check slightly, but doesn't fundamentally change it.
+ #
+ # from a state S,
+ # for any pair of triggers t1 and t2,
+ # where t1 and t2 have opposite direction,
+ # for each pair of states (T1, T2) \in t1_out x t2_out,
+ # where t1_out is the set of outstates from t1
+ # t2_out is the set of outstates from t2
+ # t1_out x t2_out is their Cartesian product
+ # and t1 transitions to state T1 and t2 to T2,
+ # then the following must be true:
+ # (T2 allows the trigger t1, with out-state set { U })
+ # and
+ # (T1 allows the trigger t2, with out-state set { U })
+ # and
+ # (
+ # (
+ # (all of T1's triggers have the same direction as t2)
+ # and
+ # (all of T2's triggers have the same direction as t1)
+ # )
+ # or
+ # (T1, T2, and U are the same "terminal state")
+ # )
+
+ # check Rule 1
+ syncdirection = None
+ syncok = True
+ for trans in ts.transitions:
+ if not trans.msg.type.isSync(): continue
+ if syncdirection is None:
+ syncdirection = trans.trigger.direction()
+ elif syncdirection is not trans.trigger.direction():
+ self.error(
+ trans.loc,
+ "sync trigger at state `%s' in protocol `%s' has different direction from earlier sync trigger at same state",
+ ts.state.name, self.p.name)
+ syncok = False
+ # don't check the Diamond Rule if Rule 1 doesn't hold
+ if not syncok:
+ return
+
+ # helper functions
+ def triggerTargets(S, t):
+ '''Return the set of states transitioned to from state |S|
+upon trigger |t|, or { } if |t| is not a trigger in |S|.'''
+ for trans in self.p.states[S].transitions:
+ if t.trigger is trans.trigger and t.msg is trans.msg:
+ return trans.toStates
+ return set()
+
+ def allTriggersSameDirectionAs(S, t):
+ '''Return true iff all the triggers from state |S| have the same
+direction as trigger |t|'''
+ direction = t.direction()
+ for trans in self.p.states[S].transitions:
+ if direction != trans.trigger.direction():
+ return False
+ return True
+
+ def terminalState(S):
+ '''Return true iff |S| is a "terminal state".'''
+ for trans in self.p.states[S].transitions:
+ for S_ in trans.toStates:
+ if S_ != S: return False
+ return True
+
+ def sameTerminalState(S1, S2, S3):
+ '''Return true iff states |S1|, |S2|, and |S3| are all the same
+"terminal state".'''
+ if isinstance(S3, set):
+ assert len(S3) == 1
+ for S3_ in S3: pass
+ S3 = S3_
+
+ return (S1 == S2 == S3) and terminalState(S1)
+
+ S = ts.state.name
+
+ # check the Diamond Rule
+ for (t1, t2) in unique_pairs(ts.transitions):
+ # if the triggers have the same direction, they can't race,
+ # since only one endpoint can initiate either (and delivery
+ # is in-order)
+ if t1.trigger.direction() == t2.trigger.direction():
+ continue
+
+ loc = t1.loc
+ t1_out = t1.toStates
+ t2_out = t2.toStates
+
+ for (T1, T2) in cartesian_product(t1_out, t2_out):
+ # U1 <- { u | T1 --t2--> u }
+ U1 = triggerTargets(T1, t2)
+ # U2 <- { u | T2 --t1--> u }
+ U2 = triggerTargets(T2, t1)
+
+ # don't report more than one Diamond Rule violation
+ # per state. there may be O(n^4) total, way too many
+ # for a human to parse
+ #
+ # XXX/cjones: could set a limit on #printed and stop
+ # after that limit ...
+ raceError = False
+ errT1 = None
+ errT2 = None
+
+ if 0 == len(U1) or 0 == len(U2):
+ print "******* case 1"
+ raceError = True
+ elif 1 < len(U1) or 1 < len(U2):
+ raceError = True
+ # there are potentially many unpaired states; just
+ # pick two
+ print "******* case 2"
+ for u1, u2 in cartesian_product(U1, U2):
+ if u1 != u2:
+ errT1, errT2 = u1, u2
+ break
+ elif U1 != U2:
+ print "******* case 3"
+ raceError = True
+ for errT1 in U1: pass
+ for errT2 in U2: pass
+
+ if raceError:
+ self.reportRaceError(loc, S,
+ [ T1, t1, errT1 ],
+ [ T2, t2, errT2 ])
+ return
+
+ if not ((allTriggersSameDirectionAs(T1, t2.trigger)
+ and allTriggersSameDirectionAs(T2, t1.trigger))
+ or sameTerminalState(T1, T2, U1)):
+ self.reportRunawayError(loc, S, [ T1, t1, None ], [ T2, t2, None ])
+ return
+
+ def checkReachability(self, p):
+ def explore(ts, visited):
+ if ts.state in visited:
+ return
+ visited.add(ts.state)
+ for outedge in ts.transitions:
+ for toState in outedge.toStates:
+ explore(p.states[toState], visited)
+
+ checkfordelete = (State.DEAD in p.states)
+
+ allvisited = set() # set(State)
+ for root in p.startStates:
+ visited = set()
+
+ explore(root, visited)
+ allvisited.update(visited)
+
+ if checkfordelete and State.DEAD not in visited:
+ self.error(
+ root.loc,
+ "when starting from state `%s', actors of protocol `%s' cannot be deleted", root.state.name, p.name)
+
+ for ts in p.states.itervalues():
+ if ts.state is not State.DEAD and ts.state not in allvisited:
+ self.error(ts.loc,
+ "unreachable state `%s' in protocol `%s'",
+ ts.state.name, p.name)
+
+
+ def _normalizeTransitionSequences(self, t1Seq, t2Seq):
+ T1, M1, U1 = t1Seq
+ T2, M2, U2 = t2Seq
+ assert M1 is not None and M2 is not None
+
+ # make sure that T1/M1/U1 is the parent side of the race
+ if M1.trigger is RECV or M1.trigger is ANSWER:
+ T1, M1, U1, T2, M2, U2 = T2, M2, U2, T1, M1, U1
+
+ def stateName(S):
+ if S: return S.name
+ return '[error]'
+
+ T1 = stateName(T1)
+ T2 = stateName(T2)
+ U1 = stateName(U1)
+ U2 = stateName(U2)
+
+ return T1, M1.msg.progname, U1, T2, M2.msg.progname, U2
+
+
+ def reportRaceError(self, loc, S, t1Seq, t2Seq):
+ T1, M1, U1, T2, M2, U2 = self._normalizeTransitionSequences(t1Seq, t2Seq)
+ self.error(
+ loc,
+"""in protocol `%(P)s', the sequence of events
+ parent: +--`send %(M1)s'-->( state `%(T1)s' )--`recv %(M2)s'-->( state %(U1)s )
+ /
+ ( state `%(S)s' )
+ \\
+ child: +--`send %(M2)s'-->( state `%(T2)s' )--`recv %(M1)s'-->( state %(U2)s )
+results in error(s) or leaves parent/child state out of sync for more than one step and is thus a race hazard; i.e., triggers `%(M1)s' and `%(M2)s' fail to commute in state `%(S)s'"""% {
+ 'P': self.p.name, 'S': S, 'M1': M1, 'M2': M2,
+ 'T1': T1, 'T2': T2, 'U1': U1, 'U2': U2
+ })
+
+
+ def reportRunawayError(self, loc, S, t1Seq, t2Seq):
+ T1, M1, _, T2, M2, __ = self._normalizeTransitionSequences(t1Seq, t2Seq)
+ self.error(
+ loc,
+ """in protocol `%(P)s', the sequence of events
+ parent: +--`send %(M1)s'-->( state `%(T1)s' )
+ /
+ ( state `%(S)s' )
+ \\
+ child: +--`send %(M2)s'-->( state `%(T2)s' )
+lead to parent/child states in which parent/child state can become more than one step out of sync (though this divergence might not lead to error conditions)"""% {
+ 'P': self.p.name, 'S': S, 'M1': M1, 'M2': M2, 'T1': T1, 'T2': T2
+ })