1# vim: set ts=4 sw=4 tw=99 et:
2# This Source Code Form is subject to the terms of the Mozilla Public
3# License, v. 2.0. If a copy of the MPL was not distributed with this
4# file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
6from __future__ import print_function
7
8import os
9import sys
10
11from ipdl.ast import CxxInclude, Decl, Loc, QualifiedId, StructDecl
12from ipdl.ast import TypeSpec, UnionDecl, UsingStmt, Visitor
13from ipdl.ast import ASYNC, SYNC, INTR
14from ipdl.ast import IN, OUT, INOUT
15from ipdl.ast import NOT_NESTED, INSIDE_SYNC_NESTED, INSIDE_CPOW_NESTED
16import ipdl.builtin as builtin
17from ipdl.util import hash_str
18
19_DELETE_MSG = "__delete__"
20
21
22class TypeVisitor:
23    def __init__(self):
24        self.visited = set()
25
26    def defaultVisit(self, node, *args):
27        raise Exception(
28            "INTERNAL ERROR: no visitor for node type `%s'" % (node.__class__.__name__)
29        )
30
31    def visitVoidType(self, v, *args):
32        pass
33
34    def visitImportedCxxType(self, t, *args):
35        pass
36
37    def visitMessageType(self, m, *args):
38        for param in m.params:
39            param.accept(self, *args)
40        for ret in m.returns:
41            ret.accept(self, *args)
42        if m.cdtype is not None:
43            m.cdtype.accept(self, *args)
44
45    def visitProtocolType(self, p, *args):
46        # NB: don't visit manager and manages. a naive default impl
47        # could result in an infinite loop
48        pass
49
50    def visitActorType(self, a, *args):
51        a.protocol.accept(self, *args)
52
53    def visitStructType(self, s, *args):
54        if s in self.visited:
55            return
56
57        self.visited.add(s)
58        for field in s.fields:
59            field.accept(self, *args)
60
61    def visitUnionType(self, u, *args):
62        if u in self.visited:
63            return
64
65        self.visited.add(u)
66        for component in u.components:
67            component.accept(self, *args)
68
69    def visitArrayType(self, a, *args):
70        a.basetype.accept(self, *args)
71
72    def visitMaybeType(self, m, *args):
73        m.basetype.accept(self, *args)
74
75    def visitUniquePtrType(self, m, *args):
76        m.basetype.accept(self, *args)
77
78    def visitShmemType(self, s, *args):
79        pass
80
81    def visitByteBufType(self, s, *args):
82        pass
83
84    def visitShmemChmodType(self, c, *args):
85        c.shmem.accept(self)
86
87    def visitFDType(self, s, *args):
88        pass
89
90    def visitEndpointType(self, s, *args):
91        pass
92
93    def visitManagedEndpointType(self, s, *args):
94        pass
95
96
97class Type:
98    def __cmp__(self, o):
99        return cmp(self.fullname(), o.fullname())
100
101    def __eq__(self, o):
102        return self.__class__ == o.__class__ and self.fullname() == o.fullname()
103
104    def __hash__(self):
105        return hash_str(self.fullname())
106
107    # Is this a C++ type?
108    def isCxx(self):
109        return False
110
111    # Is this an IPDL type?
112
113    def isIPDL(self):
114        return False
115
116    # Is this type neither compound nor an array?
117
118    def isAtom(self):
119        return False
120
121    def isRefcounted(self):
122        return False
123
124    def isUniquePtr(self):
125        return False
126
127    def typename(self):
128        return self.__class__.__name__
129
130    def name(self):
131        raise NotImplementedError()
132
133    def fullname(self):
134        raise NotImplementedError()
135
136    def accept(self, visitor, *args):
137        visit = getattr(visitor, "visit" + self.__class__.__name__, None)
138        if visit is None:
139            return getattr(visitor, "defaultVisit")(self, *args)
140        return visit(self, *args)
141
142
143class VoidType(Type):
144    def isCxx(self):
145        return True
146
147    def isIPDL(self):
148        return False
149
150    def isAtom(self):
151        return True
152
153    def name(self):
154        return "void"
155
156    def fullname(self):
157        return "void"
158
159
160VOID = VoidType()
161
162# --------------------
163
164
165class ImportedCxxType(Type):
166    def __init__(self, qname, refcounted, moveonly):
167        assert isinstance(qname, QualifiedId)
168        self.loc = qname.loc
169        self.qname = qname
170        self.refcounted = refcounted
171        self.moveonly = moveonly
172
173    def isCxx(self):
174        return True
175
176    def isAtom(self):
177        return True
178
179    def isRefcounted(self):
180        return self.refcounted
181
182    def isMoveonly(self):
183        return self.moveonly
184
185    def name(self):
186        return self.qname.baseid
187
188    def fullname(self):
189        return str(self.qname)
190
191
192# --------------------
193
194
195class IPDLType(Type):
196    def isIPDL(self):
197        return True
198
199    def isMessage(self):
200        return False
201
202    def isProtocol(self):
203        return False
204
205    def isActor(self):
206        return False
207
208    def isStruct(self):
209        return False
210
211    def isUnion(self):
212        return False
213
214    def isArray(self):
215        return False
216
217    def isMaybe(self):
218        return False
219
220    def isAtom(self):
221        return True
222
223    def isCompound(self):
224        return False
225
226    def isShmem(self):
227        return False
228
229    def isByteBuf(self):
230        return False
231
232    def isFD(self):
233        return False
234
235    def isEndpoint(self):
236        return False
237
238    def isManagedEndpoint(self):
239        return False
240
241    def isAsync(self):
242        return self.sendSemantics == ASYNC
243
244    def isSync(self):
245        return self.sendSemantics == SYNC
246
247    def isInterrupt(self):
248        return self.sendSemantics is INTR
249
250    def hasReply(self):
251        return self.isSync() or self.isInterrupt()
252
253    def hasBaseType(self):
254        return False
255
256    @classmethod
257    def convertsTo(cls, lesser, greater):
258        def _unwrap(nr):
259            if isinstance(nr, dict):
260                return _unwrap(nr["nested"])
261            elif isinstance(nr, int):
262                return nr
263            else:
264                raise ValueError("Got unexpected nestedRange value: %s" % nr)
265
266        lnr0, gnr0, lnr1, gnr1 = (
267            _unwrap(lesser.nestedRange[0]),
268            _unwrap(greater.nestedRange[0]),
269            _unwrap(lesser.nestedRange[1]),
270            _unwrap(greater.nestedRange[1]),
271        )
272        if lnr0 < gnr0 or lnr1 > gnr1:
273            return False
274
275        # Protocols that use intr semantics are not allowed to use
276        # message nesting.
277        if greater.isInterrupt() and lesser.nestedRange != (NOT_NESTED, NOT_NESTED):
278            return False
279
280        if lesser.isAsync():
281            return True
282        elif lesser.isSync() and not greater.isAsync():
283            return True
284        elif greater.isInterrupt():
285            return True
286
287        return False
288
289    def needsMoreJuiceThan(self, o):
290        return not IPDLType.convertsTo(self, o)
291
292
293class MessageType(IPDLType):
294    def __init__(
295        self,
296        nested,
297        prio,
298        sendSemantics,
299        direction,
300        ctor=False,
301        dtor=False,
302        cdtype=None,
303        compress=False,
304        tainted=False,
305    ):
306        assert not (ctor and dtor)
307        assert not (ctor or dtor) or cdtype is not None
308
309        self.nested = nested
310        self.prio = prio
311        self.nestedRange = (nested, nested)
312        self.sendSemantics = sendSemantics
313        self.direction = direction
314        self.params = []
315        self.returns = []
316        self.ctor = ctor
317        self.dtor = dtor
318        self.cdtype = cdtype
319        self.compress = compress
320        self.tainted = tainted
321
322    def isMessage(self):
323        return True
324
325    def isCtor(self):
326        return self.ctor
327
328    def isDtor(self):
329        return self.dtor
330
331    def constructedType(self):
332        return self.cdtype
333
334    def isIn(self):
335        return self.direction is IN
336
337    def isOut(self):
338        return self.direction is OUT
339
340    def isInout(self):
341        return self.direction is INOUT
342
343    def hasReply(self):
344        return len(self.returns) or IPDLType.hasReply(self)
345
346    def hasImplicitActorParam(self):
347        return self.isCtor() or self.isDtor()
348
349
350class ProtocolType(IPDLType):
351    def __init__(self, qname, nested, sendSemantics, refcounted):
352        self.qname = qname
353        self.nestedRange = (NOT_NESTED, nested)
354        self.sendSemantics = sendSemantics
355        self.managers = []  # ProtocolType
356        self.manages = []
357        self.hasDelete = False
358        self.refcounted = refcounted
359
360    def isProtocol(self):
361        return True
362
363    def isRefcounted(self):
364        return self.refcounted
365
366    def name(self):
367        return self.qname.baseid
368
369    def fullname(self):
370        return str(self.qname)
371
372    def addManager(self, mgrtype):
373        assert mgrtype.isIPDL() and mgrtype.isProtocol()
374        self.managers.append(mgrtype)
375
376    def managedBy(self, mgr):
377        self.managers = list(mgr)
378
379    def toplevel(self):
380        if self.isToplevel():
381            return self
382        for mgr in self.managers:
383            if mgr is not self:
384                return mgr.toplevel()
385
386    def toplevels(self):
387        if self.isToplevel():
388            return [self]
389        toplevels = list()
390        for mgr in self.managers:
391            if mgr is not self:
392                toplevels.extend(mgr.toplevels())
393        return set(toplevels)
394
395    def isManagerOf(self, pt):
396        for managed in self.manages:
397            if pt is managed:
398                return True
399        return False
400
401    def isManagedBy(self, pt):
402        return pt in self.managers
403
404    def isManager(self):
405        return len(self.manages) > 0
406
407    def isManaged(self):
408        return 0 < len(self.managers)
409
410    def isToplevel(self):
411        return not self.isManaged()
412
413    def manager(self):
414        assert 1 == len(self.managers)
415        for mgr in self.managers:
416            return mgr
417
418
419class ActorType(IPDLType):
420    def __init__(self, protocol, nullable=False):
421        self.protocol = protocol
422        self.nullable = nullable
423
424    def isActor(self):
425        return True
426
427    def isRefcounted(self):
428        return self.protocol.isRefcounted()
429
430    def name(self):
431        return self.protocol.name()
432
433    def fullname(self):
434        return self.protocol.fullname()
435
436
437class _CompoundType(IPDLType):
438    def __init__(self):
439        self.defined = False  # bool
440        self.mutualRec = set()  # set(_CompoundType | ArrayType)
441
442    def isAtom(self):
443        return False
444
445    def isCompound(self):
446        return True
447
448    def itercomponents(self):
449        raise Exception('"pure virtual" method')
450
451    def mutuallyRecursiveWith(self, t, exploring=None):
452        """|self| is mutually recursive with |t| iff |self| and |t|
453        are in a cycle in the type graph rooted at |self|.  This function
454        looks for such a cycle and returns True if found."""
455        if exploring is None:
456            exploring = set()
457
458        if t.isAtom():
459            return False
460        elif t is self or t in self.mutualRec:
461            return True
462        elif t.hasBaseType():
463            isrec = self.mutuallyRecursiveWith(t.basetype, exploring)
464            if isrec:
465                self.mutualRec.add(t)
466            return isrec
467        elif t in exploring:
468            return False
469
470        exploring.add(t)
471        for c in t.itercomponents():
472            if self.mutuallyRecursiveWith(c, exploring):
473                self.mutualRec.add(c)
474                return True
475        exploring.remove(t)
476
477        return False
478
479
480class StructType(_CompoundType):
481    def __init__(self, qname, fields):
482        _CompoundType.__init__(self)
483        self.qname = qname
484        self.fields = fields  # [ Type ]
485
486    def isStruct(self):
487        return True
488
489    def itercomponents(self):
490        for f in self.fields:
491            yield f
492
493    def name(self):
494        return self.qname.baseid
495
496    def fullname(self):
497        return str(self.qname)
498
499
500class UnionType(_CompoundType):
501    def __init__(self, qname, components):
502        _CompoundType.__init__(self)
503        self.qname = qname
504        self.components = components  # [ Type ]
505
506    def isUnion(self):
507        return True
508
509    def itercomponents(self):
510        for c in self.components:
511            yield c
512
513    def name(self):
514        return self.qname.baseid
515
516    def fullname(self):
517        return str(self.qname)
518
519
520class ArrayType(IPDLType):
521    def __init__(self, basetype):
522        self.basetype = basetype
523
524    def isAtom(self):
525        return False
526
527    def isArray(self):
528        return True
529
530    def hasBaseType(self):
531        return True
532
533    def name(self):
534        return self.basetype.name() + "[]"
535
536    def fullname(self):
537        return self.basetype.fullname() + "[]"
538
539
540class MaybeType(IPDLType):
541    def __init__(self, basetype):
542        self.basetype = basetype
543
544    def isAtom(self):
545        return False
546
547    def isMaybe(self):
548        return True
549
550    def hasBaseType(self):
551        return True
552
553    def name(self):
554        return self.basetype.name() + "?"
555
556    def fullname(self):
557        return self.basetype.fullname() + "?"
558
559
560class ShmemType(IPDLType):
561    def __init__(self, qname):
562        self.qname = qname
563
564    def isShmem(self):
565        return True
566
567    def name(self):
568        return self.qname.baseid
569
570    def fullname(self):
571        return str(self.qname)
572
573
574class ByteBufType(IPDLType):
575    def __init__(self, qname):
576        self.qname = qname
577
578    def isByteBuf(self):
579        return True
580
581    def name(self):
582        return self.qname.baseid
583
584    def fullname(self):
585        return str(self.qname)
586
587
588class FDType(IPDLType):
589    def __init__(self, qname):
590        self.qname = qname
591
592    def isFD(self):
593        return True
594
595    def name(self):
596        return self.qname.baseid
597
598    def fullname(self):
599        return str(self.qname)
600
601
602class EndpointType(IPDLType):
603    def __init__(self, qname, actor):
604        self.qname = qname
605        self.actor = actor
606
607    def isEndpoint(self):
608        return True
609
610    def name(self):
611        return self.qname.baseid
612
613    def fullname(self):
614        return str(self.qname)
615
616
617class ManagedEndpointType(IPDLType):
618    def __init__(self, qname, actor):
619        self.qname = qname
620        self.actor = actor
621
622    def isManagedEndpoint(self):
623        return True
624
625    def name(self):
626        return self.qname.baseid
627
628    def fullname(self):
629        return str(self.qname)
630
631
632class UniquePtrType(IPDLType):
633    def __init__(self, basetype):
634        self.basetype = basetype
635
636    def isAtom(self):
637        return False
638
639    def isUniquePtr(self):
640        return True
641
642    def hasBaseType(self):
643        return True
644
645    def name(self):
646        return "UniquePtr<" + self.basetype.name() + ">"
647
648    def fullname(self):
649        return "mozilla::UniquePtr<" + self.basetype.fullname() + ">"
650
651
652def iteractortypes(t, visited=None):
653    """Iterate over any actor(s) buried in |type|."""
654    if visited is None:
655        visited = set()
656
657    # XXX |yield| semantics makes it hard to use TypeVisitor
658    if not t.isIPDL():
659        return
660    elif t.isActor():
661        yield t
662    elif t.hasBaseType():
663        for actor in iteractortypes(t.basetype, visited):
664            yield actor
665    elif t.isCompound() and t not in visited:
666        visited.add(t)
667        for c in t.itercomponents():
668            for actor in iteractortypes(c, visited):
669                yield actor
670
671
672def hasshmem(type):
673    """Return true iff |type| is shmem or has it buried within."""
674
675    class found(BaseException):
676        pass
677
678    class findShmem(TypeVisitor):
679        def visitShmemType(self, s):
680            raise found()
681
682    try:
683        type.accept(findShmem())
684    except found:
685        return True
686    return False
687
688
689# --------------------
690_builtinloc = Loc("<builtin>", 0)
691
692
693def makeBuiltinUsing(tname):
694    quals = tname.split("::")
695    base = quals.pop()
696    quals = quals[0:]
697    return UsingStmt(
698        _builtinloc, TypeSpec(_builtinloc, QualifiedId(_builtinloc, base, quals))
699    )
700
701
702builtinUsing = [makeBuiltinUsing(t) for t in builtin.Types]
703builtinHeaderIncludes = [CxxInclude(_builtinloc, f) for f in builtin.HeaderIncludes]
704
705
706def errormsg(loc, fmt, *args):
707    while not isinstance(loc, Loc):
708        if loc is None:
709            loc = Loc.NONE
710        else:
711            loc = loc.loc
712    return "%s: error: %s" % (str(loc), fmt % args)
713
714
715# --------------------
716
717
718class SymbolTable:
719    def __init__(self, errors):
720        self.errors = errors
721        self.scopes = [{}]  # stack({})
722        self.currentScope = self.scopes[0]
723
724    def enterScope(self):
725        assert isinstance(self.scopes[0], dict)
726        assert isinstance(self.currentScope, dict)
727
728        self.scopes.append({})
729        self.currentScope = self.scopes[-1]
730
731    def exitScope(self):
732        symtab = self.scopes.pop()
733        assert self.currentScope is symtab
734
735        self.currentScope = self.scopes[-1]
736
737        assert isinstance(self.scopes[0], dict)
738        assert isinstance(self.currentScope, dict)
739
740    def lookup(self, sym):
741        # NB: since IPDL doesn't allow any aliased names of different types,
742        # it doesn't matter in which order we walk the scope chain to resolve
743        # |sym|
744        for scope in self.scopes:
745            decl = scope.get(sym, None)
746            if decl is not None:
747                return decl
748        return None
749
750    def declare(self, decl):
751        assert decl.progname or decl.shortname or decl.fullname
752        assert decl.loc
753        assert decl.type
754
755        def tryadd(name):
756            olddecl = self.lookup(name)
757            if olddecl is not None:
758                self.errors.append(
759                    errormsg(
760                        decl.loc,
761                        "redeclaration of symbol `%s', first declared at %s",
762                        name,
763                        olddecl.loc,
764                    )
765                )
766                return
767            self.currentScope[name] = decl
768            decl.scope = self.currentScope
769
770        if decl.progname:
771            tryadd(decl.progname)
772        if decl.shortname:
773            tryadd(decl.shortname)
774        if decl.fullname:
775            tryadd(decl.fullname)
776
777
778class TypeCheck:
779    """This pass sets the .decl attribute of AST nodes for which that is relevant;
780    a decl says where, with what type, and under what name(s) a node was
781    declared.
782
783    With this information, it type checks the AST."""
784
785    def __init__(self):
786        # NB: no IPDL compile will EVER print a warning.  A program has
787        # one of two attributes: it is either well typed, or not well typed.
788        self.errors = []  # [ string ]
789
790    def check(self, tu, errout=sys.stderr):
791        def runpass(tcheckpass):
792            tu.accept(tcheckpass)
793            if len(self.errors):
794                self.reportErrors(errout)
795                return False
796            return True
797
798        # tag each relevant node with "decl" information, giving type, name,
799        # and location of declaration
800        if not runpass(GatherDecls(builtinUsing, self.errors)):
801            return False
802
803        # now that the nodes have decls, type checking is much easier.
804        if not runpass(CheckTypes(self.errors)):
805            return False
806
807        return True
808
809    def reportErrors(self, errout):
810        for error in self.errors:
811            print(error, file=errout)
812
813
814class TcheckVisitor(Visitor):
815    def __init__(self, errors):
816        self.errors = errors
817
818    def error(self, loc, fmt, *args):
819        self.errors.append(errormsg(loc, fmt, *args))
820
821
822class GatherDecls(TcheckVisitor):
823    def __init__(self, builtinUsing, errors):
824        TcheckVisitor.__init__(self, errors)
825
826        # |self.symtab| is the symbol table for the translation unit
827        # currently being visited
828        self.symtab = None
829        self.builtinUsing = builtinUsing
830
831    def declare(
832        self, loc, type, shortname=None, fullname=None, progname=None, attributes={}
833    ):
834        d = Decl(loc)
835        d.type = type
836        d.progname = progname
837        d.shortname = shortname
838        d.fullname = fullname
839        d.attributes = attributes
840        self.symtab.declare(d)
841        return d
842
843    # Check that only attributes allowed by an attribute spec are present
844    # within the given attribute dictionary. The spec value may be either
845    # `None`, for a valueless attribute, a list of valid attribute values, or a
846    # callable which returns a truthy value if the attribute is valid.
847    def checkAttributes(self, attributes, spec):
848        for attr in attributes.values():
849            if attr.name not in spec:
850                self.error(attr.loc, "unknown attribute `%s'", attr.name)
851                continue
852
853            aspec = spec[attr.name]
854            if aspec is None:
855                if attr.value is not None:
856                    self.error(
857                        attr.loc,
858                        "unexpected value for valueless attribute `%s'",
859                        attr.name,
860                    )
861            elif isinstance(aspec, (list, tuple)):
862                if attr.value not in aspec:
863                    self.error(
864                        attr.loc,
865                        "invalid value for attribute `%s', expected one of: %s",
866                        attr.name,
867                        ", ".join(str(s) for s in aspec),
868                    )
869            elif callable(aspec):
870                if not aspec(attr.value):
871                    self.error(attr.loc, "invalid value for attribute `%s'", attr.name)
872            else:
873                raise Exception("INTERNAL ERROR: Invalid attribute spec")
874
875    def visitTranslationUnit(self, tu):
876        # all TranslationUnits declare symbols in global scope
877        if hasattr(tu, "visited"):
878            return
879        tu.visited = True
880        savedSymtab = self.symtab
881        self.symtab = SymbolTable(self.errors)
882
883        # pretend like the translation unit "using"-ed these for the
884        # sake of type checking and C++ code generation
885        tu.builtinUsing = self.builtinUsing
886
887        # for everyone's sanity, enforce that the filename and tu name
888        # match
889        basefilename = os.path.basename(tu.filename)
890        expectedfilename = "%s.ipdl" % (tu.name)
891        if not tu.protocol:
892            # header
893            expectedfilename += "h"
894        if basefilename != expectedfilename:
895            self.error(
896                tu.loc,
897                "expected file for translation unit `%s' to be named `%s'; instead it's named `%s'",  # NOQA: E501
898                tu.name,
899                expectedfilename,
900                basefilename,
901            )
902
903        if tu.protocol:
904            assert tu.name == tu.protocol.name
905
906            p = tu.protocol
907
908            # FIXME/cjones: it's a little weird and counterintuitive
909            # to put both the namespace and non-namespaced name in the
910            # global scope.  try to figure out something better; maybe
911            # a type-neutral |using| that works for C++ and protocol
912            # types?
913            qname = p.qname()
914            fullname = str(qname)
915            p.decl = self.declare(
916                loc=p.loc,
917                type=ProtocolType(qname, p.nested, p.sendSemantics, p.refcounted),
918                shortname=p.name,
919                fullname=None if 0 == len(qname.quals) else fullname,
920            )
921
922            p.parentEndpointDecl = self.declare(
923                loc=p.loc,
924                type=EndpointType(
925                    QualifiedId(
926                        p.loc, "Endpoint<" + fullname + "Parent>", ["mozilla", "ipc"]
927                    ),
928                    ActorType(p.decl.type),
929                ),
930                shortname="Endpoint<" + p.name + "Parent>",
931            )
932            p.childEndpointDecl = self.declare(
933                loc=p.loc,
934                type=EndpointType(
935                    QualifiedId(
936                        p.loc, "Endpoint<" + fullname + "Child>", ["mozilla", "ipc"]
937                    ),
938                    ActorType(p.decl.type),
939                ),
940                shortname="Endpoint<" + p.name + "Child>",
941            )
942
943            p.parentManagedEndpointDecl = self.declare(
944                loc=p.loc,
945                type=ManagedEndpointType(
946                    QualifiedId(
947                        p.loc,
948                        "ManagedEndpoint<" + fullname + "Parent>",
949                        ["mozilla", "ipc"],
950                    ),
951                    ActorType(p.decl.type),
952                ),
953                shortname="ManagedEndpoint<" + p.name + "Parent>",
954            )
955            p.childManagedEndpointDecl = self.declare(
956                loc=p.loc,
957                type=ManagedEndpointType(
958                    QualifiedId(
959                        p.loc,
960                        "ManagedEndpoint<" + fullname + "Child>",
961                        ["mozilla", "ipc"],
962                    ),
963                    ActorType(p.decl.type),
964                ),
965                shortname="ManagedEndpoint<" + p.name + "Child>",
966            )
967
968            # XXX ugh, this sucks.  but we need this information to compute
969            # what friend decls we need in generated C++
970            p.decl.type._ast = p
971
972        # make sure we have decls for all dependent protocols
973        for pinc in tu.includes:
974            pinc.accept(self)
975
976        # declare imported (and builtin) C++ types
977        for using in tu.builtinUsing:
978            using.accept(self)
979        for using in tu.using:
980            using.accept(self)
981
982        # first pass to "forward-declare" all structs and unions in
983        # order to support recursive definitions
984        for su in tu.structsAndUnions:
985            self.declareStructOrUnion(su)
986
987        # second pass to check each definition
988        for su in tu.structsAndUnions:
989            su.accept(self)
990
991        if tu.protocol:
992            # grab symbols in the protocol itself
993            p.accept(self)
994
995        self.symtab = savedSymtab
996
997    def declareStructOrUnion(self, su):
998        if hasattr(su, "decl"):
999            self.symtab.declare(su.decl)
1000            return
1001
1002        qname = su.qname()
1003        if 0 == len(qname.quals):
1004            fullname = None
1005        else:
1006            fullname = str(qname)
1007
1008        if isinstance(su, StructDecl):
1009            sutype = StructType(qname, [])
1010        elif isinstance(su, UnionDecl):
1011            sutype = UnionType(qname, [])
1012        else:
1013            assert 0 and "unknown type"
1014
1015        # XXX more suckage.  this time for pickling structs/unions
1016        # declared in headers.
1017        sutype._ast = su
1018
1019        su.decl = self.declare(
1020            loc=su.loc, type=sutype, shortname=su.name, fullname=fullname
1021        )
1022
1023    def visitInclude(self, inc):
1024        if inc.tu is None:
1025            self.error(
1026                inc.loc,
1027                "(type checking here will be unreliable because of an earlier error)",
1028            )
1029            return
1030        inc.tu.accept(self)
1031        if inc.tu.protocol:
1032            self.symtab.declare(inc.tu.protocol.decl)
1033            self.symtab.declare(inc.tu.protocol.parentEndpointDecl)
1034            self.symtab.declare(inc.tu.protocol.childEndpointDecl)
1035            self.symtab.declare(inc.tu.protocol.parentManagedEndpointDecl)
1036            self.symtab.declare(inc.tu.protocol.childManagedEndpointDecl)
1037        else:
1038            # This is a header.  Import its "exported" globals into
1039            # our scope.
1040            for using in inc.tu.using:
1041                using.accept(self)
1042            for su in inc.tu.structsAndUnions:
1043                self.declareStructOrUnion(su)
1044
1045    def visitStructDecl(self, sd):
1046        # If we've already processed this struct, don't do it again.
1047        if hasattr(sd, "visited"):
1048            return
1049
1050        stype = sd.decl.type
1051
1052        self.symtab.enterScope()
1053        sd.visited = True
1054
1055        self.checkAttributes(sd.attributes, {"Comparable": None})
1056
1057        for f in sd.fields:
1058            ftypedecl = self.symtab.lookup(str(f.typespec))
1059            if ftypedecl is None:
1060                self.error(
1061                    f.loc,
1062                    "field `%s' of struct `%s' has unknown type `%s'",
1063                    f.name,
1064                    sd.name,
1065                    str(f.typespec),
1066                )
1067                continue
1068
1069            f.decl = self.declare(
1070                loc=f.loc,
1071                type=self._canonicalType(ftypedecl.type, f.typespec),
1072                shortname=f.name,
1073                fullname=None,
1074            )
1075            stype.fields.append(f.decl.type)
1076
1077        self.symtab.exitScope()
1078
1079    def visitUnionDecl(self, ud):
1080        utype = ud.decl.type
1081
1082        # If we've already processed this union, don't do it again.
1083        if len(utype.components):
1084            return
1085
1086        self.checkAttributes(ud.attributes, {"Comparable": None})
1087
1088        for c in ud.components:
1089            cdecl = self.symtab.lookup(str(c))
1090            if cdecl is None:
1091                self.error(
1092                    c.loc, "unknown component type `%s' of union `%s'", str(c), ud.name
1093                )
1094                continue
1095            utype.components.append(self._canonicalType(cdecl.type, c))
1096
1097    def visitUsingStmt(self, using):
1098        fullname = str(using.type)
1099        if (using.type.basename() == fullname) or using.type.uniqueptr:
1100            # Prevent generation of typedefs.  If basename == fullname then
1101            # there is nothing to typedef.  With UniquePtrs, basenames
1102            # are generic so typedefs would be illegal.
1103            fullname = None
1104
1105        self.checkAttributes(
1106            using.attributes,
1107            {
1108                "MoveOnly": None,
1109                "RefCounted": None,
1110            },
1111        )
1112
1113        if fullname == "mozilla::ipc::Shmem":
1114            ipdltype = ShmemType(using.type.spec)
1115        elif fullname == "mozilla::ipc::ByteBuf":
1116            ipdltype = ByteBufType(using.type.spec)
1117        elif fullname == "mozilla::ipc::FileDescriptor":
1118            ipdltype = FDType(using.type.spec)
1119        else:
1120            ipdltype = ImportedCxxType(
1121                using.type.spec, using.isRefcounted(), using.isMoveonly()
1122            )
1123            existingType = self.symtab.lookup(ipdltype.fullname())
1124            if existingType and existingType.fullname == ipdltype.fullname():
1125                if ipdltype.isRefcounted() != existingType.type.isRefcounted():
1126                    self.error(
1127                        using.loc,
1128                        "inconsistent refcounted status of type `%s`",
1129                        str(using.type),
1130                    )
1131                if ipdltype.isMoveonly() != existingType.type.isMoveonly():
1132                    self.error(
1133                        using.loc,
1134                        "inconsistent moveonly status of type `%s`",
1135                        str(using.type),
1136                    )
1137                using.decl = existingType
1138                return
1139        using.decl = self.declare(
1140            loc=using.loc,
1141            type=ipdltype,
1142            shortname=using.type.basename(),
1143            fullname=fullname,
1144        )
1145
1146    def visitProtocol(self, p):
1147        # protocol scope
1148        self.symtab.enterScope()
1149
1150        seenmgrs = set()
1151        for mgr in p.managers:
1152            if mgr.name in seenmgrs:
1153                self.error(mgr.loc, "manager `%s' appears multiple times", mgr.name)
1154                continue
1155
1156            seenmgrs.add(mgr.name)
1157            mgr.of = p
1158            mgr.accept(self)
1159
1160        for managed in p.managesStmts:
1161            managed.manager = p
1162            managed.accept(self)
1163
1164        if not (p.managers or p.messageDecls or p.managesStmts):
1165            self.error(p.loc, "top-level protocol `%s' cannot be empty", p.name)
1166
1167        setattr(self, "currentProtocolDecl", p.decl)
1168        for msg in p.messageDecls:
1169            msg.accept(self)
1170        del self.currentProtocolDecl
1171
1172        p.decl.type.hasDelete = not not self.symtab.lookup(_DELETE_MSG)
1173        if not (p.decl.type.hasDelete or p.decl.type.isToplevel()):
1174            self.error(
1175                p.loc,
1176                "destructor declaration `%s(...)' required for managed protocol `%s'",
1177                _DELETE_MSG,
1178                p.name,
1179            )
1180
1181        # FIXME/cjones declare all the little C++ thingies that will
1182        # be generated.  they're not relevant to IPDL itself, but
1183        # those ("invisible") symbols can clash with others in the
1184        # IPDL spec, and we'd like to catch those before C++ compilers
1185        # are allowed to obfuscate the error
1186
1187        self.symtab.exitScope()
1188
1189    def visitManager(self, mgr):
1190        mgrdecl = self.symtab.lookup(mgr.name)
1191        pdecl = mgr.of.decl
1192        assert pdecl
1193
1194        pname, mgrname = pdecl.shortname, mgr.name
1195        loc = mgr.loc
1196
1197        if mgrdecl is None:
1198            self.error(
1199                loc,
1200                "protocol `%s' referenced as |manager| of `%s' has not been declared",
1201                mgrname,
1202                pname,
1203            )
1204        elif not isinstance(mgrdecl.type, ProtocolType):
1205            self.error(
1206                loc,
1207                "entity `%s' referenced as |manager| of `%s' is not of `protocol' type; instead it is of type `%s'",  # NOQA: E501
1208                mgrname,
1209                pname,
1210                mgrdecl.type.typename(),
1211            )
1212        else:
1213            mgr.decl = mgrdecl
1214            pdecl.type.addManager(mgrdecl.type)
1215
1216    def visitManagesStmt(self, mgs):
1217        mgsdecl = self.symtab.lookup(mgs.name)
1218        pdecl = mgs.manager.decl
1219        assert pdecl
1220
1221        pname, mgsname = pdecl.shortname, mgs.name
1222        loc = mgs.loc
1223
1224        if mgsdecl is None:
1225            self.error(
1226                loc,
1227                "protocol `%s', managed by `%s', has not been declared",
1228                mgsname,
1229                pname,
1230            )
1231        elif not isinstance(mgsdecl.type, ProtocolType):
1232            self.error(
1233                loc,
1234                "%s declares itself managing a non-`protocol' entity `%s' of type `%s'",
1235                pname,
1236                mgsname,
1237                mgsdecl.type.typename(),
1238            )
1239        else:
1240            mgs.decl = mgsdecl
1241            pdecl.type.manages.append(mgsdecl.type)
1242
1243    def visitMessageDecl(self, md):
1244        msgname = md.name
1245        loc = md.loc
1246
1247        self.checkAttributes(
1248            md.attributes,
1249            {
1250                "Tainted": None,
1251                "Compress": (None, "all"),
1252                "Priority": ("normal", "input", "vsync", "mediumhigh", "control"),
1253                "Nested": ("not", "inside_sync", "inside_cpow"),
1254            },
1255        )
1256
1257        if md.sendSemantics is INTR and "Priority" in md.attributes:
1258            self.error(loc, "intr message `%s' cannot specify [Priority]", msgname)
1259
1260        if md.sendSemantics is INTR and "Nested" in md.attributes:
1261            self.error(loc, "intr message `%s' cannot specify [Nested]", msgname)
1262
1263        isctor = False
1264        isdtor = False
1265        cdtype = None
1266
1267        decl = self.symtab.lookup(msgname)
1268        if decl is not None and decl.type.isProtocol():
1269            # probably a ctor.  we'll check validity later.
1270            msgname += "Constructor"
1271            isctor = True
1272            cdtype = decl.type
1273        elif decl is not None:
1274            self.error(
1275                loc,
1276                "message name `%s' already declared as `%s'",
1277                msgname,
1278                decl.type.typename(),
1279            )
1280            # if we error here, no big deal; move on to find more
1281
1282        if _DELETE_MSG == msgname:
1283            isdtor = True
1284            cdtype = self.currentProtocolDecl.type
1285
1286        # enter message scope
1287        self.symtab.enterScope()
1288
1289        msgtype = MessageType(
1290            md.nested(),
1291            md.priority(),
1292            md.sendSemantics,
1293            md.direction,
1294            ctor=isctor,
1295            dtor=isdtor,
1296            cdtype=cdtype,
1297            compress=md.attributes.get("Compress"),
1298            tainted="Tainted" in md.attributes,
1299        )
1300
1301        # replace inparam Param nodes with proper Decls
1302        def paramToDecl(param):
1303            self.checkAttributes(
1304                param.attributes,
1305                {
1306                    # Passback indicates that the argument is unused by the Parent and is
1307                    #    merely returned to the Child later.
1308                    # AllValid indicates that the entire span of values representable by
1309                    #    the type are acceptable.  e.g. 0-255 in a uint8
1310                    "NoTaint": ("passback", "allvalid")
1311                },
1312            )
1313
1314            ptname = param.typespec.basename()
1315            ploc = param.typespec.loc
1316
1317            if "NoTaint" in param.attributes and "Tainted" not in md.attributes:
1318                self.error(
1319                    ploc,
1320                    "argument typename `%s' of message `%s' has a NoTaint attribute, but the message lacks the Tainted attribute",
1321                    ptname,
1322                    msgname,
1323                )
1324
1325            ptdecl = self.symtab.lookup(ptname)
1326            if ptdecl is None:
1327                self.error(
1328                    ploc,
1329                    "argument typename `%s' of message `%s' has not been declared",
1330                    ptname,
1331                    msgname,
1332                )
1333                ptype = VOID
1334            else:
1335                ptype = self._canonicalType(ptdecl.type, param.typespec)
1336            return self.declare(
1337                loc=ploc, type=ptype, progname=param.name, attributes=param.attributes
1338            )
1339
1340        for i, inparam in enumerate(md.inParams):
1341            pdecl = paramToDecl(inparam)
1342            msgtype.params.append(pdecl.type)
1343            md.inParams[i] = pdecl
1344        for i, outparam in enumerate(md.outParams):
1345            pdecl = paramToDecl(outparam)
1346            msgtype.returns.append(pdecl.type)
1347            md.outParams[i] = pdecl
1348
1349        self.symtab.exitScope()
1350
1351        md.decl = self.declare(loc=loc, type=msgtype, progname=msgname)
1352        md.protocolDecl = self.currentProtocolDecl
1353        md.decl._md = md
1354
1355    def _canonicalType(self, itype, typespec):
1356        loc = typespec.loc
1357        if itype.isIPDL():
1358            if itype.isProtocol():
1359                itype = ActorType(itype, nullable=typespec.nullable)
1360
1361        if typespec.nullable and not (itype.isIPDL() and itype.isActor()):
1362            self.error(
1363                loc, "`nullable' qualifier for type `%s' makes no sense", itype.name()
1364            )
1365
1366        if typespec.array:
1367            itype = ArrayType(itype)
1368
1369        if typespec.maybe:
1370            itype = MaybeType(itype)
1371
1372        if typespec.uniqueptr:
1373            itype = UniquePtrType(itype)
1374
1375        return itype
1376
1377
1378# -----------------------------------------------------------------------------
1379
1380
1381def checkcycles(p, stack=None):
1382    cycles = []
1383
1384    if stack is None:
1385        stack = []
1386
1387    for cp in p.manages:
1388        # special case for self-managed protocols
1389        if cp is p:
1390            continue
1391
1392        if cp in stack:
1393            return [stack + [p, cp]]
1394        cycles += checkcycles(cp, stack + [p])
1395
1396    return cycles
1397
1398
1399def formatcycles(cycles):
1400    r = []
1401    for cycle in cycles:
1402        s = " -> ".join([ptype.name() for ptype in cycle])
1403        r.append("`%s'" % s)
1404    return ", ".join(r)
1405
1406
1407def fullyDefined(t, exploring=None):
1408    """The rules for "full definition" of a type are
1409    defined(atom)             := true
1410    defined(array basetype)   := defined(basetype)
1411    defined(struct f1 f2...)  := defined(f1) and defined(f2) and ...
1412    defined(union c1 c2 ...)  := defined(c1) or defined(c2) or ..."""
1413    if exploring is None:
1414        exploring = set()
1415
1416    if t.isAtom():
1417        return True
1418    elif t.hasBaseType():
1419        return fullyDefined(t.basetype, exploring)
1420    elif t.defined:
1421        return True
1422    assert t.isCompound()
1423
1424    if t in exploring:
1425        return False
1426
1427    exploring.add(t)
1428    for c in t.itercomponents():
1429        cdefined = fullyDefined(c, exploring)
1430        if t.isStruct() and not cdefined:
1431            t.defined = False
1432            break
1433        elif t.isUnion() and cdefined:
1434            t.defined = True
1435            break
1436    else:
1437        if t.isStruct():
1438            t.defined = True
1439        elif t.isUnion():
1440            t.defined = False
1441    exploring.remove(t)
1442
1443    return t.defined
1444
1445
1446class CheckTypes(TcheckVisitor):
1447    def __init__(self, errors):
1448        TcheckVisitor.__init__(self, errors)
1449        self.visited = set()
1450        self.ptype = None
1451
1452    def visitInclude(self, inc):
1453        if inc.tu.filename in self.visited:
1454            return
1455        self.visited.add(inc.tu.filename)
1456        if inc.tu.protocol:
1457            inc.tu.protocol.accept(self)
1458
1459    def visitStructDecl(self, sd):
1460        if not fullyDefined(sd.decl.type):
1461            self.error(sd.decl.loc, "struct `%s' is only partially defined", sd.name)
1462
1463    def visitUnionDecl(self, ud):
1464        if not fullyDefined(ud.decl.type):
1465            self.error(ud.decl.loc, "union `%s' is only partially defined", ud.name)
1466
1467    def visitProtocol(self, p):
1468        self.ptype = p.decl.type
1469
1470        # check that we require no more "power" than our manager protocols
1471        ptype, pname = p.decl.type, p.decl.shortname
1472
1473        for mgrtype in ptype.managers:
1474            if mgrtype is not None and ptype.needsMoreJuiceThan(mgrtype):
1475                self.error(
1476                    p.decl.loc,
1477                    "protocol `%s' requires more powerful send semantics than its manager `%s' provides",  # NOQA: E501
1478                    pname,
1479                    mgrtype.name(),
1480                )
1481
1482        if ptype.isToplevel():
1483            cycles = checkcycles(p.decl.type)
1484            if cycles:
1485                self.error(
1486                    p.decl.loc,
1487                    "cycle(s) detected in manager/manages hierarchy: %s",
1488                    formatcycles(cycles),
1489                )
1490
1491        if 1 == len(ptype.managers) and ptype is ptype.manager():
1492            self.error(
1493                p.decl.loc, "top-level protocol `%s' cannot manage itself", p.name
1494            )
1495
1496        return Visitor.visitProtocol(self, p)
1497
1498    def visitManagesStmt(self, mgs):
1499        pdecl = mgs.manager.decl
1500        ptype, pname = pdecl.type, pdecl.shortname
1501
1502        mgsdecl = mgs.decl
1503        mgstype, mgsname = mgsdecl.type, mgsdecl.shortname
1504
1505        loc = mgs.loc
1506
1507        # we added this information; sanity check it
1508        assert ptype.isManagerOf(mgstype)
1509
1510        # check that the "managed" protocol agrees
1511        if not mgstype.isManagedBy(ptype):
1512            self.error(
1513                loc,
1514                "|manages| declaration in protocol `%s' does not match any |manager| declaration in protocol `%s'",  # NOQA: E501
1515                pname,
1516                mgsname,
1517            )
1518
1519    def visitManager(self, mgr):
1520        pdecl = mgr.of.decl
1521        ptype, pname = pdecl.type, pdecl.shortname
1522
1523        mgrdecl = mgr.decl
1524        mgrtype, mgrname = mgrdecl.type, mgrdecl.shortname
1525
1526        # we added this information; sanity check it
1527        assert ptype.isManagedBy(mgrtype)
1528
1529        loc = mgr.loc
1530
1531        # check that the "manager" protocol agrees
1532        if not mgrtype.isManagerOf(ptype):
1533            self.error(
1534                loc,
1535                "|manager| declaration in protocol `%s' does not match any |manages| declaration in protocol `%s'",  # NOQA: E501
1536                pname,
1537                mgrname,
1538            )
1539
1540    def visitMessageDecl(self, md):
1541        mtype, mname = md.decl.type, md.decl.progname
1542        ptype, pname = md.protocolDecl.type, md.protocolDecl.shortname
1543
1544        loc = md.decl.loc
1545
1546        if mtype.nested == INSIDE_SYNC_NESTED and not mtype.isSync():
1547            self.error(
1548                loc,
1549                "inside_sync nested messages must be sync (here, message `%s' in protocol `%s')",
1550                mname,
1551                pname,
1552            )
1553
1554        if mtype.nested == INSIDE_CPOW_NESTED and (mtype.isOut() or mtype.isInout()):
1555            self.error(
1556                loc,
1557                "inside_cpow nested parent-to-child messages are verboten (here, message `%s' in protocol `%s')",  # NOQA: E501
1558                mname,
1559                pname,
1560            )
1561
1562        # We allow inside_sync messages that are themselves sync to be sent from the
1563        # parent. Normal and inside_cpow nested messages that are sync can only come from
1564        # the child.
1565        if (
1566            mtype.isSync()
1567            and mtype.nested == NOT_NESTED
1568            and (mtype.isOut() or mtype.isInout())
1569        ):
1570            self.error(
1571                loc,
1572                "sync parent-to-child messages are verboten (here, message `%s' in protocol `%s')",
1573                mname,
1574                pname,
1575            )
1576
1577        if mtype.needsMoreJuiceThan(ptype):
1578            self.error(
1579                loc,
1580                "message `%s' requires more powerful send semantics than its protocol `%s' provides",  # NOQA: E501
1581                mname,
1582                pname,
1583            )
1584
1585        if (mtype.isCtor() or mtype.isDtor()) and mtype.isAsync() and mtype.returns:
1586            self.error(
1587                loc, "asynchronous ctor/dtor message `%s' declares return values", mname
1588            )
1589
1590        if mtype.compress and (not mtype.isAsync() or mtype.isCtor() or mtype.isDtor()):
1591
1592            if mtype.isCtor() or mtype.isDtor():
1593                message_type = "constructor" if mtype.isCtor() else "destructor"
1594                error_message = (
1595                    "%s messages can't use compression (here, in protocol `%s')"
1596                    % (message_type, pname)
1597                )
1598            else:
1599                error_message = (
1600                    "message `%s' in protocol `%s' requests compression but is not async"
1601                    % (mname, pname)  # NOQA: E501
1602                )
1603
1604            self.error(loc, error_message)
1605
1606        if mtype.isCtor() and not ptype.isManagerOf(mtype.constructedType()):
1607            self.error(
1608                loc,
1609                "ctor for protocol `%s', which is not managed by protocol `%s'",
1610                mname[: -len("constructor")],
1611                pname,
1612            )
1613