diff options
Diffstat (limited to '')
366 files changed, 25354 insertions, 0 deletions
diff --git a/ipc/ipdl/Makefile.in b/ipc/ipdl/Makefile.in new file mode 100644 index 0000000000..7a4d2acfba --- /dev/null +++ b/ipc/ipdl/Makefile.in @@ -0,0 +1,33 @@ +# 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/. + +ifdef COMPILE_ENVIRONMENT + +# This file is generated by the moz.build backend. +include ipdlsrcs.mk + +include $(topsrcdir)/config/rules.mk + +ipdl_py_deps := \ + $(wildcard $(srcdir)/*.py) \ + $(wildcard $(srcdir)/ipdl/*.py) \ + $(wildcard $(srcdir)/ipdl/cxx/*.py) \ + $(wildcard $(topsrcdir)/other-licenses/ply/ply/*.py) \ + $(NULL) + +# NB: the IPDL compiler manages .ipdl-->.h/.cpp dependencies itself, +# which is why we don't have explicit .h/.cpp targets here +ipdl.track: $(ALL_IPDLSRCS) $(srcdir)/sync-messages.ini $(srcdir)/message-metadata.ini $(ipdl_py_deps) + $(PYTHON3) $(srcdir)/ipdl.py \ + --sync-msg-list=$(srcdir)/sync-messages.ini \ + --msg-metadata=$(srcdir)/message-metadata.ini \ + --outheaders-dir=_ipdlheaders \ + --outcpp-dir=. \ + $(IPDLDIRS:%=-I%) \ + $(ALL_IPDLSRCS) + touch $@ + +export:: ipdl.track +endif + diff --git a/ipc/ipdl/ipdl.py b/ipc/ipdl/ipdl.py new file mode 100644 index 0000000000..1770416aaa --- /dev/null +++ b/ipc/ipdl/ipdl.py @@ -0,0 +1,336 @@ +# 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 optparse +import os +import sys +from configparser import RawConfigParser +from io import StringIO + +import ipdl + + +def log(minv, fmt, *args): + if _verbosity >= minv: + print(fmt % args) + + +# process command line + + +op = optparse.OptionParser(usage="ipdl.py [options] IPDLfiles...") +op.add_option( + "-I", + "--include", + dest="includedirs", + default=[], + action="append", + help="Additional directory to search for included protocol specifications", +) +op.add_option( + "-s", + "--sync-msg-list", + dest="syncMsgList", + default="sync-messages.ini", + help="Config file listing allowed sync messages", +) +op.add_option( + "-m", + "--msg-metadata", + dest="msgMetadata", + default="message-metadata.ini", + help="Predicted message sizes for reducing serialization malloc overhead.", +) +op.add_option( + "-v", + "--verbose", + dest="verbosity", + default=1, + action="count", + help="Verbose logging (specify -vv or -vvv for very verbose logging)", +) +op.add_option( + "-q", + "--quiet", + dest="verbosity", + action="store_const", + const=0, + help="Suppress logging output", +) +op.add_option( + "-d", + "--outheaders-dir", + dest="headersdir", + default=".", + help="""Directory into which C++ headers will be generated. +A protocol Foo in the namespace bar will cause the headers + dir/bar/Foo.h, dir/bar/FooParent.h, and dir/bar/FooParent.h +to be generated""", +) +op.add_option( + "-o", + "--outcpp-dir", + dest="cppdir", + default=".", + help="""Directory into which C++ sources will be generated +A protocol Foo in the namespace bar will cause the sources + cppdir/FooParent.cpp, cppdir/FooChild.cpp +to be generated""", +) + +options, files = op.parse_args() +_verbosity = options.verbosity +syncMsgList = options.syncMsgList +msgMetadata = options.msgMetadata +headersdir = options.headersdir +cppdir = options.cppdir +includedirs = [os.path.abspath(incdir) for incdir in options.includedirs] + +if not len(files): + op.error("No IPDL files specified") + +ipcmessagestartpath = os.path.join(headersdir, "IPCMessageStart.h") +ipc_msgtype_name_path = os.path.join(cppdir, "IPCMessageTypeName.cpp") + +log(2, 'Generated C++ headers will be generated relative to "%s"', headersdir) +log(2, 'Generated C++ sources will be generated in "%s"', cppdir) + +allmessages = {} +allmessageprognames = [] +allprotocols = [] + + +def normalizedFilename(f): + if f == "-": + return "<stdin>" + return f + + +log(2, "Reading sync message list") +parser = RawConfigParser() +parser.read_file(open(options.syncMsgList)) +syncMsgList = parser.sections() + +for section in syncMsgList: + if not parser.get(section, "description"): + print("Error: Sync message %s lacks a description" % section, file=sys.stderr) + sys.exit(1) + +# Read message metadata. Right now we only have 'segment_capacity' +# for the standard segment size used for serialization. +log(2, "Reading message metadata...") +msgMetadataConfig = RawConfigParser() +msgMetadataConfig.read_file(open(options.msgMetadata)) + +segmentCapacityDict = {} +for msgName in msgMetadataConfig.sections(): + if msgMetadataConfig.has_option(msgName, "segment_capacity"): + capacity = msgMetadataConfig.get(msgName, "segment_capacity") + segmentCapacityDict[msgName] = capacity + +# First pass: parse and type-check all protocols +for f in files: + log(2, os.path.basename(f)) + filename = normalizedFilename(f) + if f == "-": + fd = sys.stdin + else: + fd = open(f) + + specstring = fd.read() + fd.close() + + ast = ipdl.parse(specstring, filename, includedirs=includedirs) + if ast is None: + print("Specification could not be parsed.", file=sys.stderr) + sys.exit(1) + + log(2, "checking types") + if not ipdl.typecheck(ast): + print("Specification is not well typed.", file=sys.stderr) + sys.exit(1) + + if not ipdl.checkSyncMessage(ast, syncMsgList): + print( + "Error: New sync IPC messages must be reviewed by an IPC peer and recorded in %s" + % options.syncMsgList, + file=sys.stderr, + ) # NOQA: E501 + sys.exit(1) + +if not ipdl.checkFixedSyncMessages(parser): + # Errors have alraedy been printed to stderr, just exit + sys.exit(1) + +# Second pass: generate code +for f in files: + # Read from parser cache + filename = normalizedFilename(f) + ast = ipdl.parse(None, filename, includedirs=includedirs) + ipdl.gencxx(filename, ast, headersdir, cppdir, segmentCapacityDict) + + if ast.protocol: + allmessages[ast.protocol.name] = ipdl.genmsgenum(ast) + allprotocols.append(ast.protocol.name) + # e.g. PContent::RequestMemoryReport (not prefixed or suffixed.) + for md in ast.protocol.messageDecls: + allmessageprognames.append("%s::%s" % (md.namespace, md.decl.progname)) + +allprotocols.sort() + +# Check if we have undefined message names in segmentCapacityDict. +# This is a fool-proof of the 'message-metadata.ini' file. +undefinedMessages = set(segmentCapacityDict.keys()) - set(allmessageprognames) +if len(undefinedMessages) > 0: + print("Error: Undefined message names in message-metadata.ini:", file=sys.stderr) + print(undefinedMessages, file=sys.stderr) + sys.exit(1) + +ipcmsgstart = StringIO() + +print( + """ +// CODE GENERATED by ipdl.py. Do not edit. + +#ifndef IPCMessageStart_h +#define IPCMessageStart_h + +enum IPCMessageStart { +""", + file=ipcmsgstart, +) + +for name in allprotocols: + print(" %sMsgStart," % name, file=ipcmsgstart) + +print( + """ + LastMsgIndex +}; + +static_assert(LastMsgIndex <= 65536, "need to update IPC_MESSAGE_MACRO"); + +#endif // ifndef IPCMessageStart_h +""", + file=ipcmsgstart, +) + +ipc_msgtype_name = StringIO() +print( + """ +// CODE GENERATED by ipdl.py. Do not edit. +#include <cstdint> + +#include "mozilla/ipc/ProtocolUtils.h" +#include "IPCMessageStart.h" + +using std::uint32_t; + +namespace { + +enum IPCMessages { +""", + file=ipc_msgtype_name, +) + +for protocol in sorted(allmessages.keys()): + for (msg, num) in allmessages[protocol].idnums: + if num: + print(" %s = %s," % (msg, num), file=ipc_msgtype_name) + elif not msg.endswith("End"): + print(" %s__%s," % (protocol, msg), file=ipc_msgtype_name) + +print( + """ +}; + +} // anonymous namespace + +namespace IPC { + +const char* StringFromIPCMessageType(uint32_t aMessageType) +{ + switch (aMessageType) { +""", + file=ipc_msgtype_name, +) + +for protocol in sorted(allmessages.keys()): + for (msg, num) in allmessages[protocol].idnums: + if num or msg.endswith("End"): + continue + print( + """ + case %s__%s: + return "%s::%s";""" + % (protocol, msg, protocol, msg), + file=ipc_msgtype_name, + ) + +print( + """ + case DATA_PIPE_CLOSED_MESSAGE_TYPE: + return "DATA_PIPE_CLOSED_MESSAGE"; + case DATA_PIPE_BYTES_CONSUMED_MESSAGE_TYPE: + return "DATA_PIPE_BYTES_CONSUMED_MESSAGE"; + case ACCEPT_INVITE_MESSAGE_TYPE: + return "ACCEPT_INVITE_MESSAGE"; + case REQUEST_INTRODUCTION_MESSAGE_TYPE: + return "REQUEST_INTRODUCTION_MESSAGE"; + case INTRODUCE_MESSAGE_TYPE: + return "INTRODUCE_MESSAGE"; + case BROADCAST_MESSAGE_TYPE: + return "BROADCAST_MESSAGE"; + case EVENT_MESSAGE_TYPE: + return "EVENT_MESSAGE"; + case IMPENDING_SHUTDOWN_MESSAGE_TYPE: + return "IMPENDING_SHUTDOWN"; + case BUILD_IDS_MATCH_MESSAGE_TYPE: + return "BUILD_IDS_MATCH_MESSAGE"; + case BUILD_ID_MESSAGE_TYPE: + return "BUILD_ID_MESSAGE"; + case CHANNEL_OPENED_MESSAGE_TYPE: + return "CHANNEL_OPENED_MESSAGE"; + case SHMEM_DESTROYED_MESSAGE_TYPE: + return "SHMEM_DESTROYED_MESSAGE"; + case SHMEM_CREATED_MESSAGE_TYPE: + return "SHMEM_CREATED_MESSAGE"; + case GOODBYE_MESSAGE_TYPE: + return "GOODBYE_MESSAGE"; + case CANCEL_MESSAGE_TYPE: + return "CANCEL_MESSAGE"; + default: + return "<unknown IPC msg name>"; + } +} + +} // namespace IPC + +namespace mozilla { +namespace ipc { + +const char* ProtocolIdToName(IPCMessageStart aId) { + switch (aId) { +""", + file=ipc_msgtype_name, +) + +for name in allprotocols: + print(" case %sMsgStart:" % name, file=ipc_msgtype_name) + print(' return "%s";' % name, file=ipc_msgtype_name) + +print( + """ + default: + return "<unknown protocol id>"; + } +} + +} // namespace ipc +} // namespace mozilla +""", + file=ipc_msgtype_name, +) + +ipdl.writeifmodified(ipcmsgstart.getvalue(), ipcmessagestartpath) +ipdl.writeifmodified(ipc_msgtype_name.getvalue(), ipc_msgtype_name_path) diff --git a/ipc/ipdl/ipdl/__init__.py b/ipc/ipdl/ipdl/__init__.py new file mode 100644 index 0000000000..50ceb4f953 --- /dev/null +++ b/ipc/ipdl/ipdl/__init__.py @@ -0,0 +1,98 @@ +# 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/. + +__all__ = [ + "gencxx", + "genipdl", + "parse", + "typecheck", + "writeifmodified", + "checkSyncMessage", + "checkFixedSyncMessages", +] + +import os +import sys +from io import StringIO + +from ipdl.cgen import IPDLCodeGen +from ipdl.lower import LowerToCxx, msgenums +from ipdl.parser import Parser, ParseError +from ipdl.type import TypeCheck +from ipdl.checker import checkSyncMessage, checkFixedSyncMessages + +from ipdl.cxx.cgen import CxxCodeGen + + +def parse(specstring, filename="/stdin", includedirs=[], errout=sys.stderr): + """Return an IPDL AST if parsing was successful. Print errors to |errout| + if it is not.""" + # The file type and name are later enforced by the type checker. + # This is just a hint to the parser. + prefix, ext = os.path.splitext(filename) + name = os.path.basename(prefix) + if ext == ".ipdlh": + type = "header" + else: + type = "protocol" + + try: + return Parser(type, name).parse( + specstring, os.path.abspath(filename), includedirs + ) + except ParseError as p: + print(p, file=errout) + return None + + +def typecheck(ast, errout=sys.stderr): + """Return True iff |ast| is well typed. Print errors to |errout| if + it is not.""" + return TypeCheck().check(ast, errout) + + +def gencxx(ipdlfilename, ast, outheadersdir, outcppdir, segmentcapacitydict): + headers, cpps = LowerToCxx().lower(ast, segmentcapacitydict) + + def resolveHeader(hdr): + return [ + hdr, + os.path.join( + outheadersdir, *([ns.name for ns in ast.namespaces] + [hdr.name]) + ), + ] + + def resolveCpp(cpp): + return [cpp, os.path.join(outcppdir, cpp.name)] + + for ast, filename in [resolveHeader(hdr) for hdr in headers] + [ + resolveCpp(cpp) for cpp in cpps + ]: + tempfile = StringIO() + CxxCodeGen(tempfile).cgen(ast) + writeifmodified(tempfile.getvalue(), filename) + + +def genipdl(ast, outdir): + return IPDLCodeGen().cgen(ast) + + +def genmsgenum(ast): + return msgenums(ast.protocol, pretty=True) + + +def writeifmodified(contents, file): + contents = contents.encode("utf-8") + dir = os.path.dirname(file) + os.path.exists(dir) or os.makedirs(dir) + + oldcontents = None + if os.path.exists(file): + fd = open(file, "rb") + oldcontents = fd.read() + fd.close() + if oldcontents != contents: + fd = open(file, "wb") + fd.write(contents) + fd.close() diff --git a/ipc/ipdl/ipdl/ast.py b/ipc/ipdl/ipdl/ast.py new file mode 100644 index 0000000000..e50cbb5c65 --- /dev/null +++ b/ipc/ipdl/ipdl/ast.py @@ -0,0 +1,468 @@ +# 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/. + +from .util import hash_str + + +NOT_NESTED = 1 +INSIDE_SYNC_NESTED = 2 +INSIDE_CPOW_NESTED = 3 + +NESTED_ATTR_MAP = { + "not": NOT_NESTED, + "inside_sync": INSIDE_SYNC_NESTED, + "inside_cpow": INSIDE_CPOW_NESTED, +} + +# Each element of this list is the IPDL source representation of a priority. +priorityList = ["normal", "input", "vsync", "mediumhigh", "control"] + +priorityAttrMap = {src: idx for idx, src in enumerate(priorityList)} + +NORMAL_PRIORITY = priorityAttrMap["normal"] + + +class Visitor: + def defaultVisit(self, node): + raise Exception( + "INTERNAL ERROR: no visitor for node type `%s'" % (node.__class__.__name__) + ) + + def visitTranslationUnit(self, tu): + for cxxInc in tu.cxxIncludes: + cxxInc.accept(self) + for inc in tu.includes: + inc.accept(self) + for su in tu.structsAndUnions: + su.accept(self) + for using in tu.builtinUsing: + using.accept(self) + for using in tu.using: + using.accept(self) + if tu.protocol: + tu.protocol.accept(self) + + def visitCxxInclude(self, inc): + pass + + def visitInclude(self, inc): + # Note: we don't visit the child AST here, because that needs delicate + # and pass-specific handling + pass + + def visitStructDecl(self, struct): + for f in struct.fields: + f.accept(self) + for a in struct.attributes.values(): + a.accept(self) + + def visitStructField(self, field): + field.typespec.accept(self) + + def visitUnionDecl(self, union): + for t in union.components: + t.accept(self) + for a in union.attributes.values(): + a.accept(self) + + def visitUsingStmt(self, using): + for a in using.attributes.values(): + a.accept(self) + + def visitProtocol(self, p): + for namespace in p.namespaces: + namespace.accept(self) + for mgr in p.managers: + mgr.accept(self) + for managed in p.managesStmts: + managed.accept(self) + for msgDecl in p.messageDecls: + msgDecl.accept(self) + for a in p.attributes.values(): + a.accept(self) + + def visitNamespace(self, ns): + pass + + def visitManager(self, mgr): + pass + + def visitManagesStmt(self, mgs): + pass + + def visitMessageDecl(self, md): + for inParam in md.inParams: + inParam.accept(self) + for outParam in md.outParams: + outParam.accept(self) + for a in md.attributes.values(): + a.accept(self) + + def visitParam(self, decl): + for a in decl.attributes.values(): + a.accept(self) + + def visitTypeSpec(self, ts): + pass + + def visitAttribute(self, a): + if isinstance(a.value, Node): + a.value.accept(self) + + def visitStringLiteral(self, sl): + pass + + def visitDecl(self, d): + for a in d.attributes.values(): + a.accept(self) + + +class Loc: + def __init__(self, filename="<??>", lineno=0): + assert filename + self.filename = filename + self.lineno = lineno + + def __repr__(self): + return "%r:%r" % (self.filename, self.lineno) + + def __str__(self): + return "%s:%s" % (self.filename, self.lineno) + + +Loc.NONE = Loc(filename="<??>", lineno=0) + + +class _struct: + pass + + +class Node: + def __init__(self, loc=Loc.NONE): + self.loc = loc + + def accept(self, visitor): + visit = getattr(visitor, "visit" + self.__class__.__name__, None) + if visit is None: + return getattr(visitor, "defaultVisit")(self) + return visit(self) + + def addAttrs(self, attrsName): + if not hasattr(self, attrsName): + setattr(self, attrsName, _struct()) + + +class NamespacedNode(Node): + def __init__(self, loc=Loc.NONE, name=None): + Node.__init__(self, loc) + self.name = name + self.namespaces = [] + + def addOuterNamespace(self, namespace): + self.namespaces.insert(0, namespace) + + def qname(self): + return QualifiedId(self.loc, self.name, [ns.name for ns in self.namespaces]) + + +class TranslationUnit(NamespacedNode): + def __init__(self, type, name): + NamespacedNode.__init__(self, name=name) + self.filetype = type + self.filename = None + self.cxxIncludes = [] + self.includes = [] + self.builtinUsing = [] + self.using = [] + self.structsAndUnions = [] + self.protocol = None + + def addCxxInclude(self, cxxInclude): + self.cxxIncludes.append(cxxInclude) + + def addInclude(self, inc): + self.includes.append(inc) + + def addStructDecl(self, struct): + self.structsAndUnions.append(struct) + + def addUnionDecl(self, union): + self.structsAndUnions.append(union) + + def addUsingStmt(self, using): + self.using.append(using) + + def setProtocol(self, protocol): + self.protocol = protocol + + +class CxxInclude(Node): + def __init__(self, loc, cxxFile): + Node.__init__(self, loc) + self.file = cxxFile + + +class Include(Node): + def __init__(self, loc, type, name): + Node.__init__(self, loc) + suffix = "ipdl" + if type == "header": + suffix += "h" + self.file = "%s.%s" % (name, suffix) + + +class UsingStmt(Node): + def __init__( + self, + loc, + cxxTypeSpec, + cxxHeader=None, + kind=None, + attributes={}, + ): + Node.__init__(self, loc) + assert isinstance(cxxTypeSpec, QualifiedId) + assert cxxHeader is None or isinstance(cxxHeader, str) + assert kind is None or kind == "class" or kind == "struct" + self.type = cxxTypeSpec + self.header = cxxHeader + self.kind = kind + self.attributes = attributes + + def canBeForwardDeclared(self): + return self.isClass() or self.isStruct() + + def isClass(self): + return self.kind == "class" + + def isStruct(self): + return self.kind == "struct" + + def isRefcounted(self): + return "RefCounted" in self.attributes + + def isSendMoveOnly(self): + moveonly = self.attributes.get("MoveOnly") + return moveonly and moveonly.value in (None, "send") + + def isDataMoveOnly(self): + moveonly = self.attributes.get("MoveOnly") + return moveonly and moveonly.value in (None, "data") + + +# "singletons" + + +class PrettyPrinted: + @classmethod + def __hash__(cls): + return hash_str(cls.pretty) + + @classmethod + def __str__(cls): + return cls.pretty + + +class ASYNC(PrettyPrinted): + pretty = "async" + + +class INTR(PrettyPrinted): + pretty = "intr" + + +class SYNC(PrettyPrinted): + pretty = "sync" + + +class INOUT(PrettyPrinted): + pretty = "inout" + + +class IN(PrettyPrinted): + pretty = "in" + + +class OUT(PrettyPrinted): + pretty = "out" + + +class Namespace(Node): + def __init__(self, loc, namespace): + Node.__init__(self, loc) + self.name = namespace + + +class Protocol(NamespacedNode): + def __init__(self, loc): + NamespacedNode.__init__(self, loc) + self.attributes = {} + self.sendSemantics = ASYNC + self.managers = [] + self.managesStmts = [] + self.messageDecls = [] + + def nestedUpTo(self): + if "NestedUpTo" not in self.attributes: + return NOT_NESTED + + return NESTED_ATTR_MAP.get(self.attributes["NestedUpTo"].value, NOT_NESTED) + + def implAttribute(self, side): + assert side in ("parent", "child") + attr = self.attributes.get(side.capitalize() + "Impl") + if attr is not None: + return attr.value + return None + + +class StructField(Node): + def __init__(self, loc, type, name): + Node.__init__(self, loc) + self.typespec = type + self.name = name + + +class StructDecl(NamespacedNode): + def __init__(self, loc, name, fields, attributes): + NamespacedNode.__init__(self, loc, name) + self.fields = fields + self.attributes = attributes + # A list of indices into `fields` for determining the order in + # which fields are laid out in memory. We don't just reorder + # `fields` itself so as to keep the ordering reasonably stable + # for e.g. C++ constructors when new fields are added. + self.packed_field_ordering = [] + + +class UnionDecl(NamespacedNode): + def __init__(self, loc, name, components, attributes): + NamespacedNode.__init__(self, loc, name) + self.components = components + self.attributes = attributes + + +class Manager(Node): + def __init__(self, loc, managerName): + Node.__init__(self, loc) + self.name = managerName + + +class ManagesStmt(Node): + def __init__(self, loc, managedName): + Node.__init__(self, loc) + self.name = managedName + + +class MessageDecl(Node): + def __init__(self, loc): + Node.__init__(self, loc) + self.name = None + self.attributes = {} + self.sendSemantics = ASYNC + self.direction = None + self.inParams = [] + self.outParams = [] + + def addInParams(self, inParamsList): + self.inParams += inParamsList + + def addOutParams(self, outParamsList): + self.outParams += outParamsList + + def nested(self): + if "Nested" not in self.attributes: + return NOT_NESTED + + return NESTED_ATTR_MAP.get(self.attributes["Nested"].value, NOT_NESTED) + + def priority(self): + if "Priority" in self.attributes: + sourcePriority = self.attributes["Priority"].value + else: + sourcePriority = "normal" + return priorityAttrMap.get(sourcePriority, NORMAL_PRIORITY) + + def replyPriority(self): + if "ReplyPriority" in self.attributes: + sourcePriority = self.attributes["ReplyPriority"].value + if sourcePriority in priorityAttrMap: + return priorityAttrMap[sourcePriority] + return self.priority() + + +class Param(Node): + def __init__(self, loc, typespec, name, attributes={}): + Node.__init__(self, loc) + self.name = name + self.typespec = typespec + self.attributes = attributes + + +class TypeSpec(Node): + def __init__(self, loc, spec): + Node.__init__(self, loc) + assert isinstance(spec, str) + self.spec = spec # str + self.array = False # bool + self.maybe = False # bool + self.nullable = False # bool + self.uniqueptr = False # bool + + def basename(self): + return self.spec + + def __str__(self): + return self.spec + + +class Attribute(Node): + def __init__(self, loc, name, value): + Node.__init__(self, loc) + self.name = name + self.value = value + + +class StringLiteral(Node): + def __init__(self, loc, value): + Node.__init__(self, loc) + self.value = value + + def __str__(self): + return '"%s"' % self.value + + +class QualifiedId: # FIXME inherit from node? + def __init__(self, loc, baseid, quals=[]): + assert isinstance(baseid, str) + for qual in quals: + assert isinstance(qual, str) + + self.loc = loc + self.baseid = baseid + self.quals = quals + + def qualify(self, id): + self.quals.append(self.baseid) + self.baseid = id + + def __str__(self): + # NOTE: include a leading "::" in order to force all QualifiedIds to be + # fully qualified types in C++ + return "::" + "::".join(self.quals + [self.baseid]) + + +# added by type checking passes + + +class Decl(Node): + def __init__(self, loc): + Node.__init__(self, loc) + self.progname = None # what the programmer typed, if relevant + self.shortname = None # shortest way to refer to this decl + self.fullname = None # full way to refer to this decl + self.loc = loc + self.type = None + self.scope = None + self.attributes = {} diff --git a/ipc/ipdl/ipdl/builtin.py b/ipc/ipdl/ipdl/builtin.py new file mode 100644 index 0000000000..b1bab64af8 --- /dev/null +++ b/ipc/ipdl/ipdl/builtin.py @@ -0,0 +1,76 @@ +# 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/. + +# WARNING: the syntax of the builtin types is not checked, so please +# don't add something syntactically invalid. It will not be fun to +# track down the bug. + +# C types +# These types don't live in any namespace, so can't be imported with `using` +# statements like normal C++ types. +CTypes = ( + "bool", + "char", + "short", + "int", + "long", + "float", + "double", +) + +# C++ types +# These types must be fully qualified, and will be `typedef`-ed into IPDL +# structs to make them readily available when used. +Types = ( + # stdint types + "int8_t", + "uint8_t", + "int16_t", + "uint16_t", + "int32_t", + "uint32_t", + "int64_t", + "uint64_t", + "intptr_t", + "uintptr_t", + # You may be tempted to add size_t. Do not! See bug 1525199. + # Mozilla types: "less" standard things we know how serialize/deserialize + "nsresult", + "nsString", + "nsCString", + "mozilla::ipc::Shmem", + "mozilla::ipc::ByteBuf", + "mozilla::UniquePtr", + "mozilla::ipc::FileDescriptor", +) + + +# XXX(Bug 1677487) Can we restrict including ByteBuf.h, FileDescriptor.h, +# MozPromise.h and Shmem.h to those protocols that really use them? +HeaderIncludes = ( + "mozilla/Attributes.h", + "IPCMessageStart.h", + "mozilla/RefPtr.h", + "nsString.h", + "nsTArray.h", + "nsTHashtable.h", + "mozilla/MozPromise.h", + "mozilla/OperatorNewExtensions.h", + "mozilla/UniquePtr.h", + "mozilla/ipc/ByteBuf.h", + "mozilla/ipc/FileDescriptor.h", + "mozilla/ipc/IPCForwards.h", + "mozilla/ipc/Shmem.h", +) + +CppIncludes = ( + "ipc/IPCMessageUtils.h", + "ipc/IPCMessageUtilsSpecializations.h", + "nsIFile.h", + "mozilla/ipc/Endpoint.h", + "mozilla/ipc/ProtocolMessageUtils.h", + "mozilla/ipc/ProtocolUtils.h", + "mozilla/ipc/ShmemMessageUtils.h", + "mozilla/ipc/TaintingIPCUtils.h", +) diff --git a/ipc/ipdl/ipdl/cgen.py b/ipc/ipdl/ipdl/cgen.py new file mode 100644 index 0000000000..8ed8da4d81 --- /dev/null +++ b/ipc/ipdl/ipdl/cgen.py @@ -0,0 +1,108 @@ +# 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 sys + +from ipdl.ast import Visitor + + +class CodePrinter: + def __init__(self, outf=sys.stdout, indentCols=4): + self.outf = outf + self.col = 0 + self.indentCols = indentCols + + def write(self, str): + self.outf.write(str) + + def printdent(self, str=""): + self.write((" " * self.col) + str) + + def println(self, str=""): + self.write(str + "\n") + + def printdentln(self, str): + self.write((" " * self.col) + str + "\n") + + def indent(self): + self.col += self.indentCols + + def dedent(self): + self.col -= self.indentCols + + +# ----------------------------------------------------------------------------- +class IPDLCodeGen(CodePrinter, Visitor): + """Spits back out equivalent IPDL to the code that generated this. + Also known as pretty-printing.""" + + def __init__(self, outf=sys.stdout, indentCols=4, printed=set()): + CodePrinter.__init__(self, outf, indentCols) + self.printed = printed + + def visitTranslationUnit(self, tu): + self.printed.add(tu.filename) + self.println("//\n// Automatically generated by ipdlc\n//") + CodeGen.visitTranslationUnit(self, tu) # NOQA: F821 + + def visitCxxInclude(self, inc): + self.println('include "' + inc.file + '";') + + def visitProtocolInclude(self, inc): + self.println('include protocol "' + inc.file + '";') + if inc.tu.filename not in self.printed: + self.println("/* Included file:") + IPDLCodeGen( + outf=self.outf, indentCols=self.indentCols, printed=self.printed + ).visitTranslationUnit(inc.tu) + + self.println("*/") + + def visitProtocol(self, p): + self.println() + for namespace in p.namespaces: + namespace.accept(self) + + self.println("%s protocol %s\n{" % (p.sendSemantics[0], p.name)) + self.indent() + + for mgs in p.managesStmts: + mgs.accept(self) + if len(p.managesStmts): + self.println() + + for msgDecl in p.messageDecls: + msgDecl.accept(self) + self.println() + + self.dedent() + self.println("}") + self.write("}\n" * len(p.namespaces)) + + def visitManagerStmt(self, mgr): + self.printdentln("manager " + mgr.name + ";") + + def visitManagesStmt(self, mgs): + self.printdentln("manages " + mgs.name + ";") + + def visitMessageDecl(self, msg): + self.printdent("%s %s %s(" % (msg.sendSemantics[0], msg.direction[0], msg.name)) + for i, inp in enumerate(msg.inParams): + inp.accept(self) + if i != (len(msg.inParams) - 1): + self.write(", ") + self.write(")") + if 0 == len(msg.outParams): + self.println(";") + return + + self.println() + self.indent() + self.printdent("returns (") + for i, outp in enumerate(msg.outParams): + outp.accept(self) + if i != (len(msg.outParams) - 1): + self.write(", ") + self.println(");") + self.dedent() diff --git a/ipc/ipdl/ipdl/checker.py b/ipc/ipdl/ipdl/checker.py new file mode 100644 index 0000000000..c93dc3e0e8 --- /dev/null +++ b/ipc/ipdl/ipdl/checker.py @@ -0,0 +1,79 @@ +# 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 sys +from ipdl.ast import Visitor, ASYNC + + +class SyncMessageChecker(Visitor): + syncMsgList = [] + seenProtocols = [] + seenSyncMessages = [] + + def __init__(self, syncMsgList): + SyncMessageChecker.syncMsgList = syncMsgList + self.errors = [] + + def prettyMsgName(self, msg): + return "%s::%s" % (self.currentProtocol, msg) + + def errorUnknownSyncMessage(self, loc, msg): + self.errors.append("%s: error: Unknown sync IPC message %s" % (str(loc), msg)) + + def errorAsyncMessageCanRemove(self, loc, msg): + self.errors.append( + "%s: error: IPC message %s is async, can be delisted" % (str(loc), msg) + ) + + def visitProtocol(self, p): + self.errors = [] + self.currentProtocol = p.name + SyncMessageChecker.seenProtocols.append(p.name) + Visitor.visitProtocol(self, p) + + def visitMessageDecl(self, md): + pn = self.prettyMsgName(md.name) + if md.sendSemantics is not ASYNC: + if pn not in SyncMessageChecker.syncMsgList: + self.errorUnknownSyncMessage(md.loc, pn) + SyncMessageChecker.seenSyncMessages.append(pn) + elif pn in SyncMessageChecker.syncMsgList: + self.errorAsyncMessageCanRemove(md.loc, pn) + + @staticmethod + def getFixedSyncMessages(): + return set(SyncMessageChecker.syncMsgList) - set( + SyncMessageChecker.seenSyncMessages + ) + + +def checkSyncMessage(tu, syncMsgList, errout=sys.stderr): + checker = SyncMessageChecker(syncMsgList) + tu.accept(checker) + if len(checker.errors): + for error in checker.errors: + print(error, file=errout) + return False + return True + + +def checkFixedSyncMessages(config, errout=sys.stderr): + fixed = SyncMessageChecker.getFixedSyncMessages() + error_free = True + for item in fixed: + protocol = item.split("::")[0] + # Ignore things like sync messages in test protocols we didn't compile. + # Also, ignore platform-specific IPC messages. + if ( + protocol in SyncMessageChecker.seenProtocols + and "platform" not in config.options(item) + ): + print( + "Error: Sync IPC message %s not found, it appears to be fixed.\n" + "Please remove it from sync-messages.ini." % item, + file=errout, + ) + error_free = False + return error_free diff --git a/ipc/ipdl/ipdl/cxx/__init__.py b/ipc/ipdl/ipdl/cxx/__init__.py new file mode 100644 index 0000000000..6fbe8159b2 --- /dev/null +++ b/ipc/ipdl/ipdl/cxx/__init__.py @@ -0,0 +1,3 @@ +# 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/. diff --git a/ipc/ipdl/ipdl/cxx/ast.py b/ipc/ipdl/ipdl/cxx/ast.py new file mode 100644 index 0000000000..3b9448859b --- /dev/null +++ b/ipc/ipdl/ipdl/cxx/ast.py @@ -0,0 +1,1033 @@ +# 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 copy +import functools + +from ipdl.util import hash_str + + +class Visitor: + def defaultVisit(self, node): + raise Exception( + "INTERNAL ERROR: no visitor for node type `%s'" % (node.__class__.__name__) + ) + + def visitWhitespace(self, ws): + pass + + def visitVerbatimNode(self, verb): + pass + + def visitGroupNode(self, group): + for node in group.nodes: + node.accept(self) + + def visitFile(self, f): + for thing in f.stuff: + thing.accept(self) + + def visitCppDirective(self, ppd): + pass + + def visitBlock(self, block): + for stmt in block.stmts: + stmt.accept(self) + + def visitNamespace(self, ns): + self.visitBlock(ns) + + def visitType(self, type): + pass + + def visitTypeArray(self, ta): + ta.basetype.accept(self) + ta.nmemb.accept(self) + + def visitTypeEnum(self, enum): + pass + + def visitTypeFunction(self, fn): + pass + + def visitTypeUnion(self, union): + for t, name in union.components: + t.accept(self) + + def visitTypedef(self, tdef): + tdef.fromtype.accept(self) + + def visitUsing(self, us): + us.type.accept(self) + + def visitForwardDecl(self, fd): + pass + + def visitDecl(self, decl): + decl.type.accept(self) + + def visitParam(self, param): + self.visitDecl(param) + if param.default is not None: + param.default.accept(self) + + def visitClass(self, cls): + for inherit in cls.inherits: + inherit.accept(self) + self.visitBlock(cls) + + def visitInherit(self, inh): + pass + + def visitFriendClassDecl(self, fcd): + pass + + def visitMethodDecl(self, meth): + for param in meth.params: + param.accept(self) + if meth.ret is not None: + meth.ret.accept(self) + if meth.typeop is not None: + meth.typeop.accept(self) + if meth.T is not None: + meth.T.accept(self) + + def visitMethodDefn(self, meth): + meth.decl.accept(self) + self.visitBlock(meth) + + def visitFunctionDecl(self, fun): + self.visitMethodDecl(fun) + + def visitFunctionDefn(self, fd): + self.visitMethodDefn(fd) + + def visitConstructorDecl(self, ctor): + self.visitMethodDecl(ctor) + + def visitConstructorDefn(self, cd): + cd.decl.accept(self) + for init in cd.memberinits: + init.accept(self) + self.visitBlock(cd) + + def visitDestructorDecl(self, dtor): + self.visitMethodDecl(dtor) + + def visitDestructorDefn(self, dd): + dd.decl.accept(self) + self.visitBlock(dd) + + def visitExprLiteral(self, l): + pass + + def visitExprVar(self, v): + pass + + def visitExprPrefixUnop(self, e): + e.expr.accept(self) + + def visitExprBinary(self, e): + e.left.accept(self) + e.right.accept(self) + + def visitExprConditional(self, c): + c.cond.accept(self) + c.ife.accept(self) + c.elsee.accept(self) + + def visitExprAddrOf(self, eao): + self.visitExprPrefixUnop(eao) + + def visitExprDeref(self, ed): + self.visitExprPrefixUnop(ed) + + def visitExprNot(self, en): + self.visitExprPrefixUnop(en) + + def visitExprCast(self, ec): + ec.expr.accept(self) + + def visitExprSelect(self, es): + es.obj.accept(self) + + def visitExprAssn(self, ea): + ea.lhs.accept(self) + ea.rhs.accept(self) + + def visitExprCall(self, ec): + ec.func.accept(self) + for arg in ec.args: + arg.accept(self) + + def visitExprMove(self, ec): + self.visitExprCall(ec) + + def visitExprNothing(self, ec): + self.visitExprCall(ec) + + def visitExprSome(self, ec): + self.visitExprCall(ec) + + def visitExprNew(self, en): + en.ctype.accept(self) + if en.newargs is not None: + for arg in en.newargs: + arg.accept(self) + if en.args is not None: + for arg in en.args: + arg.accept(self) + + def visitExprDelete(self, ed): + ed.obj.accept(self) + + def visitExprMemberInit(self, minit): + self.visitExprCall(minit) + + def visitExprLambda(self, l): + self.visitBlock(l) + + def visitStmtBlock(self, sb): + self.visitBlock(sb) + + def visitStmtDecl(self, sd): + sd.decl.accept(self) + if sd.init is not None: + sd.init.accept(self) + + def visitLabel(self, label): + pass + + def visitCaseLabel(self, case): + pass + + def visitDefaultLabel(self, dl): + pass + + def visitStmtIf(self, si): + si.cond.accept(self) + si.ifb.accept(self) + if si.elseb is not None: + si.elseb.accept(self) + + def visitStmtFor(self, sf): + if sf.init is not None: + sf.init.accept(self) + if sf.cond is not None: + sf.cond.accept(self) + if sf.update is not None: + sf.update.accept(self) + + def visitStmtSwitch(self, ss): + ss.expr.accept(self) + self.visitBlock(ss) + + def visitStmtBreak(self, sb): + pass + + def visitStmtExpr(self, se): + se.expr.accept(self) + + def visitStmtReturn(self, sr): + if sr.expr is not None: + sr.expr.accept(self) + + +# ------------------------------ + + +class Node: + def __init__(self): + pass + + def accept(self, visitor): + visit = getattr(visitor, "visit" + self.__class__.__name__, None) + if visit is None: + return getattr(visitor, "defaultVisit")(self) + return visit(self) + + +class Whitespace(Node): + # yes, this is silly. but we need to stick comments in the + # generated code without resorting to more serious hacks + def __init__(self, ws, indent=False): + Node.__init__(self) + self.ws = ws + self.indent = indent + + +Whitespace.NL = Whitespace("\n") + + +class VerbatimNode(Node): + # A block of text to be written verbatim to the output file. + # + # NOTE: This node is usually created by `code`. See `code.py` for details. + # FIXME: Merge Whitespace and VerbatimNode? They're identical. + def __init__(self, text, indent=0): + Node.__init__(self) + self.text = text + self.indent = indent + + +class GroupNode(Node): + # A group of nodes to be treated as a single node. These nodes have an + # optional indentation level which should be applied when generating them. + # + # NOTE: This node is usually created by `code`. See `code.py` for details. + def __init__(self, nodes, offset=0): + Node.__init__(self) + self.nodes = nodes + self.offset = offset + + +class File(Node): + def __init__(self, filename): + Node.__init__(self) + self.name = filename + # array of stuff in the file --- stmts and preprocessor thingies + self.stuff = [] + + def addthing(self, thing): + assert thing is not None + assert not isinstance(thing, list) + self.stuff.append(thing) + + def addthings(self, things): + for t in things: + self.addthing(t) + + # "look like" a Block so code doesn't have to care whether they're + # in global scope or not + def addstmt(self, stmt): + assert stmt is not None + assert not isinstance(stmt, list) + self.stuff.append(stmt) + + def addstmts(self, stmts): + for s in stmts: + self.addstmt(s) + + def addcode(self, tmpl, **context): + from ipdl.cxx.code import StmtCode + + self.addstmt(StmtCode(tmpl, **context)) + + +class CppDirective(Node): + """represents |#[directive] [rest]|, where |rest| is any string""" + + def __init__(self, directive, rest=None): + Node.__init__(self) + self.directive = directive + self.rest = rest + + +class Block(Node): + def __init__(self): + Node.__init__(self) + self.stmts = [] + + def addstmt(self, stmt): + assert stmt is not None + assert not isinstance(stmt, tuple) + self.stmts.append(stmt) + + def addstmts(self, stmts): + for s in stmts: + self.addstmt(s) + + def addcode(self, tmpl, **context): + from ipdl.cxx.code import StmtCode + + self.addstmt(StmtCode(tmpl, **context)) + + +# ------------------------------ +# type and decl thingies + + +class Namespace(Block): + def __init__(self, name): + assert isinstance(name, str) + + Block.__init__(self) + self.name = name + + +class Type(Node): + def __init__( + self, + name, + const=False, + ptr=False, + ptrptr=False, + ptrconstptr=False, + ref=False, + rvalref=False, + rightconst=False, + hasimplicitcopyctor=True, + T=None, + inner=None, + ): + """ + Represents the type |name<T>::inner| with the ptr and const + modifiers as specified. + + To avoid getting fancy with recursive types, we limit the kinds + of pointer types that can be be constructed. + + ptr => T* + ptrptr => T** + ptrconstptr => T* const* + ref => T& + rvalref => T&& + + Any type, naked or pointer, can be const (const T) or ref (T&).""" + # XXX(nika): This type is complex enough at this point, perhaps we + # should get "fancy with recursive types" to simplify it. + assert isinstance(name, str) + assert isinstance(const, bool) + assert isinstance(ptr, bool) + assert isinstance(ptrptr, bool) + assert isinstance(ptrconstptr, bool) + assert isinstance(ref, bool) + assert isinstance(rvalref, bool) + assert isinstance(rightconst, bool) + assert not isinstance(T, str) + + Node.__init__(self) + self.name = name + self.const = const + self.ptr = ptr + self.ptrptr = ptrptr + self.ptrconstptr = ptrconstptr + self.ref = ref + self.rvalref = rvalref + self.rightconst = rightconst + self.hasimplicitcopyctor = hasimplicitcopyctor + self.T = T + self.inner = inner + # XXX could get serious here with recursive types, but shouldn't + # need that for this codegen + + def __deepcopy__(self, memo): + return Type( + self.name, + const=self.const, + ptr=self.ptr, + ptrptr=self.ptrptr, + ptrconstptr=self.ptrconstptr, + ref=self.ref, + rvalref=self.rvalref, + rightconst=self.rightconst, + T=copy.deepcopy(self.T, memo), + inner=copy.deepcopy(self.inner, memo), + ) + + +Type.BOOL = Type("bool") +Type.INT = Type("int") +Type.INT32 = Type("int32_t") +Type.INTPTR = Type("intptr_t") +Type.NSRESULT = Type("nsresult") +Type.UINT32 = Type("uint32_t") +Type.UINT32PTR = Type("uint32_t", ptr=True) +Type.SIZE = Type("size_t") +Type.VOID = Type("void") +Type.VOIDPTR = Type("void", ptr=True) +Type.AUTO = Type("auto") +Type.AUTORVAL = Type("auto", rvalref=True) + + +class TypeArray(Node): + def __init__(self, basetype, nmemb): + """the type |basetype DECLNAME[nmemb]|. |nmemb| is an Expr""" + self.basetype = basetype + self.nmemb = nmemb + + +class TypeEnum(Node): + def __init__(self, name=None): + """name can be None""" + Node.__init__(self) + self.name = name + self.idnums = [] # pairs of ('Foo', [num]) or ('Foo', None) + + def addId(self, id, num=None): + self.idnums.append((id, num)) + + +class TypeUnion(Node): + def __init__(self, name=None): + Node.__init__(self) + self.name = name + self.components = [] # [ Decl ] + + def addComponent(self, type, name): + self.components.append(Decl(type, name)) + + +class TypeFunction(Node): + def __init__(self, params=[], ret=Type("void")): + """Anonymous function type std::function<>""" + self.params = params + self.ret = ret + + +@functools.total_ordering +class Typedef(Node): + def __init__(self, fromtype, totypename, templateargs=[]): + assert isinstance(totypename, str) + + Node.__init__(self) + self.fromtype = fromtype + self.totypename = totypename + self.templateargs = templateargs + + def __lt__(self, other): + return self.totypename < other.totypename + + def __eq__(self, other): + return self.__class__ == other.__class__ and self.totypename == other.totypename + + def __hash__(self): + return hash_str(self.totypename) + + +class Using(Node): + def __init__(self, type): + Node.__init__(self) + self.type = type + + +class ForwardDecl(Node): + def __init__(self, pqname, cls=False, struct=False): + # Exactly one of cls and struct must be set + assert cls ^ struct + + self.pqname = pqname + self.cls = cls + self.struct = struct + + +class Decl(Node): + """represents |Foo bar|, e.g. in a function signature""" + + def __init__(self, type, name): + assert type is not None + assert not isinstance(type, str) + assert isinstance(name, str) + + Node.__init__(self) + self.type = type + self.name = name + + def __deepcopy__(self, memo): + return Decl(copy.deepcopy(self.type, memo), self.name) + + +class Param(Decl): + def __init__(self, type, name, default=None): + Decl.__init__(self, type, name) + self.default = default + + def __deepcopy__(self, memo): + return Param( + copy.deepcopy(self.type, memo), self.name, copy.deepcopy(self.default, memo) + ) + + +# ------------------------------ +# class stuff + + +class Class(Block): + def __init__( + self, + name, + inherits=[], + interface=False, + abstract=False, + final=False, + specializes=None, + struct=False, + ): + assert not (interface and abstract) + assert not (abstract and final) + assert not (interface and final) + assert not (inherits and specializes) + + Block.__init__(self) + self.name = name + self.inherits = inherits # [ Type ] + self.interface = interface # bool + self.abstract = abstract # bool + self.final = final # bool + self.specializes = specializes # Type or None + self.struct = struct # bool + + +class Inherit(Node): + def __init__(self, type, viz="public"): + assert isinstance(viz, str) + Node.__init__(self) + self.type = type + self.viz = viz + + +class FriendClassDecl(Node): + def __init__(self, friend): + Node.__init__(self) + self.friend = friend + + +# Python2 polyfill for Python3's Enum() functional API. + + +def make_enum(name, members_str): + members_list = members_str.split() + members_dict = {} + for member_value, member in enumerate(members_list, start=1): + members_dict[member] = member_value + return type(name, (), members_dict) + + +MethodSpec = make_enum("MethodSpec", "NONE VIRTUAL PURE OVERRIDE STATIC") + + +class MethodDecl(Node): + def __init__( + self, + name, + params=[], + ret=Type("void"), + methodspec=MethodSpec.NONE, + const=False, + warn_unused=False, + force_inline=False, + typeop=None, + T=None, + cls=None, + ): + assert not (name and typeop) + assert name is None or isinstance(name, str) + assert not isinstance(ret, list) + for decl in params: + assert not isinstance(decl, str) + assert not isinstance(T, int) + assert isinstance(const, bool) + assert isinstance(warn_unused, bool) + assert isinstance(force_inline, bool) + + if typeop is not None: + assert methodspec == MethodSpec.NONE + ret = None + + Node.__init__(self) + self.name = name + self.params = params # [ Param ] + self.ret = ret # Type or None + self.methodspec = methodspec # enum + self.const = const # bool + self.warn_unused = warn_unused # bool + self.force_inline = force_inline or bool(T) # bool + self.typeop = typeop # Type or None + self.T = T # Type or None + self.cls = cls # Class or None + self.only_for_definition = False + + def __deepcopy__(self, memo): + return MethodDecl( + self.name, + params=copy.deepcopy(self.params, memo), + ret=copy.deepcopy(self.ret, memo), + methodspec=self.methodspec, + const=self.const, + warn_unused=self.warn_unused, + force_inline=self.force_inline, + typeop=copy.deepcopy(self.typeop, memo), + T=copy.deepcopy(self.T, memo), + ) + + +class MethodDefn(Block): + def __init__(self, decl): + Block.__init__(self) + self.decl = decl + + +class FunctionDecl(MethodDecl): + def __init__( + self, + name, + params=[], + ret=Type("void"), + methodspec=MethodSpec.NONE, + warn_unused=False, + force_inline=False, + T=None, + ): + assert methodspec == MethodSpec.NONE or methodspec == MethodSpec.STATIC + MethodDecl.__init__( + self, + name, + params=params, + ret=ret, + methodspec=methodspec, + warn_unused=warn_unused, + force_inline=force_inline, + T=T, + ) + + +class FunctionDefn(MethodDefn): + def __init__(self, decl): + MethodDefn.__init__(self, decl) + + +class ConstructorDecl(MethodDecl): + def __init__(self, name, params=[], explicit=False, force_inline=False): + MethodDecl.__init__( + self, name, params=params, ret=None, force_inline=force_inline + ) + self.explicit = explicit + + def __deepcopy__(self, memo): + return ConstructorDecl( + self.name, copy.deepcopy(self.params, memo), self.explicit + ) + + +class ConstructorDefn(MethodDefn): + def __init__(self, decl, memberinits=[]): + MethodDefn.__init__(self, decl) + self.memberinits = memberinits + + +class DestructorDecl(MethodDecl): + def __init__(self, name, methodspec=MethodSpec.NONE, force_inline=False): + # C++ allows pure or override destructors, but ipdl cgen does not. + assert methodspec == MethodSpec.NONE or methodspec == MethodSpec.VIRTUAL + MethodDecl.__init__( + self, + name, + params=[], + ret=None, + methodspec=methodspec, + force_inline=force_inline, + ) + + def __deepcopy__(self, memo): + return DestructorDecl( + self.name, methodspec=self.methodspec, force_inline=self.force_inline + ) + + +class DestructorDefn(MethodDefn): + def __init__(self, decl): + MethodDefn.__init__(self, decl) + + +# ------------------------------ +# expressions + + +class ExprVar(Node): + def __init__(self, name): + assert isinstance(name, str) + + Node.__init__(self) + self.name = name + + +ExprVar.THIS = ExprVar("this") + + +class ExprLiteral(Node): + def __init__(self, value, type): + """|type| is a Python format specifier; 'd' for example""" + Node.__init__(self) + self.value = value + self.type = type + + @staticmethod + def Int(i): + return ExprLiteral(i, "d") + + @staticmethod + def String(s): + return ExprLiteral('"' + s + '"', "s") + + def __str__(self): + return ("%" + self.type) % (self.value) + + +ExprLiteral.ZERO = ExprLiteral.Int(0) +ExprLiteral.ONE = ExprLiteral.Int(1) +ExprLiteral.NULL = ExprVar("nullptr") +ExprLiteral.TRUE = ExprVar("true") +ExprLiteral.FALSE = ExprVar("false") + + +class ExprPrefixUnop(Node): + def __init__(self, expr, op): + assert not isinstance(expr, tuple) + self.expr = expr + self.op = op + + +class ExprNot(ExprPrefixUnop): + def __init__(self, expr): + ExprPrefixUnop.__init__(self, expr, "!") + + +class ExprAddrOf(ExprPrefixUnop): + def __init__(self, expr): + ExprPrefixUnop.__init__(self, expr, "&") + + +class ExprDeref(ExprPrefixUnop): + def __init__(self, expr): + ExprPrefixUnop.__init__(self, expr, "*") + + +class ExprCast(Node): + def __init__(self, expr, type, static=False, const=False): + # Exactly one of these should be set + assert static ^ const + + Node.__init__(self) + self.expr = expr + self.type = type + self.static = static + self.const = const + + +class ExprBinary(Node): + def __init__(self, left, op, right): + Node.__init__(self) + self.left = left + self.op = op + self.right = right + + +class ExprConditional(Node): + def __init__(self, cond, ife, elsee): + Node.__init__(self) + self.cond = cond + self.ife = ife + self.elsee = elsee + + +class ExprSelect(Node): + def __init__(self, obj, op, field): + assert obj and op and field + assert not isinstance(obj, str) + assert isinstance(op, str) + + Node.__init__(self) + self.obj = obj + self.op = op + if isinstance(field, str): + self.field = ExprVar(field) + else: + self.field = field + + +class ExprAssn(Node): + def __init__(self, lhs, rhs, op="="): + Node.__init__(self) + self.lhs = lhs + self.op = op + self.rhs = rhs + + +class ExprCall(Node): + def __init__(self, func, args=[]): + assert hasattr(func, "accept") + assert isinstance(args, list) + for arg in args: + assert arg and not isinstance(arg, str) + + Node.__init__(self) + self.func = func + self.args = args + + +class ExprMove(ExprCall): + def __init__(self, arg): + ExprCall.__init__(self, ExprVar("std::move"), args=[arg]) + + +class ExprNew(Node): + # XXX taking some poetic license ... + def __init__(self, ctype, args=[], newargs=None): + assert not (ctype.const or ctype.ref or ctype.rvalref) + + Node.__init__(self) + self.ctype = ctype + self.args = args + self.newargs = newargs + + +class ExprDelete(Node): + def __init__(self, obj): + Node.__init__(self) + self.obj = obj + + +class ExprMemberInit(ExprCall): + def __init__(self, member, args=[]): + ExprCall.__init__(self, member, args) + + +class ExprLambda(Block): + def __init__(self, captures=[], params=[], ret=None): + Block.__init__(self) + assert isinstance(captures, list) + assert isinstance(params, list) + self.captures = captures + self.params = params + self.ret = ret + + +# ------------------------------ +# statements etc. + + +class StmtBlock(Block): + def __init__(self, stmts=[]): + Block.__init__(self) + self.addstmts(stmts) + + +class StmtDecl(Node): + def __init__(self, decl, init=None, initargs=None): + assert not (init and initargs) + assert not isinstance(init, str) # easy to confuse with Decl + assert not isinstance(init, list) + assert not isinstance(decl, tuple) + + Node.__init__(self) + self.decl = decl + self.init = init + self.initargs = initargs + + +class Label(Node): + def __init__(self, name): + Node.__init__(self) + self.name = name + + +Label.PUBLIC = Label("public") +Label.PROTECTED = Label("protected") +Label.PRIVATE = Label("private") + + +class CaseLabel(Node): + def __init__(self, name): + Node.__init__(self) + self.name = name + + +class DefaultLabel(Node): + def __init__(self): + Node.__init__(self) + + +class StmtIf(Node): + def __init__(self, cond): + Node.__init__(self) + self.cond = cond + self.ifb = Block() + self.elseb = None + + def addifstmt(self, stmt): + self.ifb.addstmt(stmt) + + def addifstmts(self, stmts): + self.ifb.addstmts(stmts) + + def addelsestmt(self, stmt): + if self.elseb is None: + self.elseb = Block() + self.elseb.addstmt(stmt) + + def addelsestmts(self, stmts): + if self.elseb is None: + self.elseb = Block() + self.elseb.addstmts(stmts) + + +class StmtFor(Block): + def __init__(self, init=None, cond=None, update=None): + Block.__init__(self) + self.init = init + self.cond = cond + self.update = update + + +class StmtRangedFor(Block): + def __init__(self, var, iteree): + assert isinstance(var, ExprVar) + assert iteree + + Block.__init__(self) + self.var = var + self.iteree = iteree + + +class StmtSwitch(Block): + def __init__(self, expr): + Block.__init__(self) + self.expr = expr + self.nr_cases = 0 + + def addcase(self, case, block): + """NOTE: |case| is not checked for uniqueness""" + assert not isinstance(case, str) + assert ( + isinstance(block, StmtBreak) + or isinstance(block, StmtReturn) + or isinstance(block, StmtSwitch) + or isinstance(block, GroupNode) + or isinstance(block, VerbatimNode) + or ( + hasattr(block, "stmts") + and ( + isinstance(block.stmts[-1], StmtBreak) + or isinstance(block.stmts[-1], StmtReturn) + or isinstance(block.stmts[-1], GroupNode) + or isinstance(block.stmts[-1], VerbatimNode) + ) + ) + ) + self.addstmt(case) + self.addstmt(block) + self.nr_cases += 1 + + +class StmtBreak(Node): + def __init__(self): + Node.__init__(self) + + +class StmtExpr(Node): + def __init__(self, expr): + assert expr is not None + + Node.__init__(self) + self.expr = expr + + +class StmtReturn(Node): + def __init__(self, expr=None): + Node.__init__(self) + self.expr = expr + + +StmtReturn.TRUE = StmtReturn(ExprLiteral.TRUE) +StmtReturn.FALSE = StmtReturn(ExprLiteral.FALSE) diff --git a/ipc/ipdl/ipdl/cxx/cgen.py b/ipc/ipdl/ipdl/cxx/cgen.py new file mode 100644 index 0000000000..fecb90e97b --- /dev/null +++ b/ipc/ipdl/ipdl/cxx/cgen.py @@ -0,0 +1,557 @@ +# 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 sys + +from ipdl.cgen import CodePrinter +from ipdl.cxx.ast import MethodSpec, TypeArray, Visitor, DestructorDecl + + +class CxxCodeGen(CodePrinter, Visitor): + def __init__(self, outf=sys.stdout, indentCols=4): + CodePrinter.__init__(self, outf, indentCols) + + def cgen(self, cxxfile): + cxxfile.accept(self) + + def visitWhitespace(self, ws): + if ws.indent: + self.printdent("") + self.write(ws.ws) + + def visitVerbatimNode(self, verb): + if verb.indent: + self.printdent("") + self.write(verb.text) + + def visitGroupNode(self, group): + offsetCols = self.indentCols * group.offset + self.col += offsetCols + for node in group.nodes: + node.accept(self) + self.col -= offsetCols + + def visitCppDirective(self, cd): + if cd.rest: + self.println("#%s %s" % (cd.directive, cd.rest)) + else: + self.println("#%s" % (cd.directive)) + + def visitNamespace(self, ns): + self.println("namespace " + ns.name + " {") + self.visitBlock(ns) + self.println("} // namespace " + ns.name) + + def visitType(self, t): + if t.const: + self.write("const ") + + self.write(t.name) + + if t.T is not None: + self.write("<") + if type(t.T) is list: + t.T[0].accept(self) + for tt in t.T[1:]: + self.write(", ") + tt.accept(self) + else: + t.T.accept(self) + self.write(">") + + if t.inner is not None: + self.write("::") + t.inner.accept(self) + + ts = "" + if t.ptr: + ts += "*" + elif t.ptrptr: + ts += "**" + elif t.ptrconstptr: + ts += "* const*" + + if t.ref: + ts += "&" + elif t.rvalref: + ts += "&&" + elif t.rightconst: + ts += " const" + + self.write(ts) + + def visitTypeEnum(self, te): + self.write("enum") + if te.name: + self.write(" " + te.name) + self.println(" {") + + self.indent() + nids = len(te.idnums) + for i, (id, num) in enumerate(te.idnums): + self.printdent(id) + if num: + self.write(" = " + str(num)) + if i != (nids - 1): + self.write(",") + self.println() + self.dedent() + self.printdent("}") + + def visitTypeUnion(self, u): + self.write("union") + if u.name: + self.write(" " + u.name) + self.println(" {") + + self.indent() + for decl in u.components: + self.printdent() + decl.accept(self) + self.println(";") + self.dedent() + + self.printdent("}") + + def visitTypeFunction(self, fn): + self.write("std::function<") + fn.ret.accept(self) + self.write("(") + self.writeDeclList(fn.params) + self.write(")>") + + def visitTypedef(self, td): + if td.templateargs: + formals = ", ".join(["class " + T for T in td.templateargs]) + args = ", ".join(td.templateargs) + self.printdent("template<" + formals + "> using " + td.totypename + " = ") + td.fromtype.accept(self) + self.println("<" + args + ">;") + else: + self.printdent("typedef ") + td.fromtype.accept(self) + self.println(" " + td.totypename + ";") + + def visitUsing(self, us): + self.printdent("using ") + us.type.accept(self) + self.println(";") + + def visitForwardDecl(self, fd): + if fd.cls: + self.printdent("class ") + elif fd.struct: + self.printdent("struct ") + self.write(str(fd.pqname)) + self.println(";") + + def visitDecl(self, d): + # C-syntax arrays make code generation much more annoying + if isinstance(d.type, TypeArray): + d.type.basetype.accept(self) + else: + d.type.accept(self) + + if d.name: + self.write(" " + d.name) + + if isinstance(d.type, TypeArray): + self.write("[") + d.type.nmemb.accept(self) + self.write("]") + + def visitParam(self, p): + self.visitDecl(p) + if p.default is not None: + self.write(" = ") + p.default.accept(self) + + def visitClass(self, c): + if c.specializes is not None: + self.printdentln("template<>") + + if c.struct: + self.printdent("struct") + else: + self.printdent("class") + self.write(" " + c.name) + if c.final: + self.write(" final") + + if c.specializes is not None: + self.write(" <") + c.specializes.accept(self) + self.write(">") + + ninh = len(c.inherits) + if 0 < ninh: + self.println(" :") + self.indent() + for i, inherit in enumerate(c.inherits): + self.printdent() + inherit.accept(self) + if i != (ninh - 1): + self.println(",") + self.dedent() + self.println() + + self.printdentln("{") + self.indent() + + self.visitBlock(c) + + self.dedent() + self.printdentln("};") + + def visitInherit(self, inh): + self.write(inh.viz + " ") + inh.type.accept(self) + + def visitFriendClassDecl(self, fcd): + self.printdentln("friend class " + fcd.friend + ";") + + def visitMethodDecl(self, md): + if md.T: + self.write("template<") + self.write("typename ") + md.T.accept(self) + self.println(">") + self.printdent() + + if md.warn_unused: + self.write("[[nodiscard]] ") + + if md.methodspec == MethodSpec.STATIC: + self.write("static ") + elif md.methodspec == MethodSpec.VIRTUAL or md.methodspec == MethodSpec.PURE: + self.write("virtual ") + + if md.ret: + if md.only_for_definition: + self.write("auto ") + else: + md.ret.accept(self) + self.println() + self.printdent() + + if md.cls is not None: + assert md.only_for_definition + + self.write(md.cls.name) + if md.cls.specializes is not None: + self.write("<") + md.cls.specializes.accept(self) + self.write(">") + self.write("::") + + if md.typeop is not None: + self.write("operator ") + md.typeop.accept(self) + else: + if isinstance(md, DestructorDecl): + self.write("~") + self.write(md.name) + + self.write("(") + self.writeDeclList(md.params) + self.write(")") + + if md.const: + self.write(" const") + if md.ret and md.only_for_definition: + self.write(" -> ") + md.ret.accept(self) + + if md.methodspec == MethodSpec.OVERRIDE: + self.write(" override") + elif md.methodspec == MethodSpec.PURE: + self.write(" = 0") + + def visitMethodDefn(self, md): + # Method specifiers are for decls, not defns. + assert md.decl.methodspec == MethodSpec.NONE + + self.printdent() + md.decl.accept(self) + self.println() + + self.printdentln("{") + self.indent() + self.visitBlock(md) + self.dedent() + self.printdentln("}") + + def visitConstructorDecl(self, cd): + if cd.explicit: + self.write("explicit ") + else: + self.write("MOZ_IMPLICIT ") + self.visitMethodDecl(cd) + + def visitConstructorDefn(self, cd): + self.printdent() + cd.decl.accept(self) + if len(cd.memberinits): + self.println(" :") + self.indent() + ninits = len(cd.memberinits) + for i, init in enumerate(cd.memberinits): + self.printdent() + init.accept(self) + if i != (ninits - 1): + self.println(",") + self.dedent() + self.println() + + self.printdentln("{") + self.indent() + + self.visitBlock(cd) + + self.dedent() + self.printdentln("}") + + def visitDestructorDecl(self, dd): + self.visitMethodDecl(dd) + + def visitDestructorDefn(self, dd): + self.printdent() + dd.decl.accept(self) + self.println() + + self.printdentln("{") + self.indent() + + self.visitBlock(dd) + + self.dedent() + self.printdentln("}") + + def visitExprLiteral(self, el): + self.write(str(el)) + + def visitExprVar(self, ev): + self.write(ev.name) + + def visitExprPrefixUnop(self, e): + self.write("(") + self.write(e.op) + self.write("(") + e.expr.accept(self) + self.write(")") + self.write(")") + + def visitExprCast(self, c): + if c.static: + pfx, sfx = "static_cast<", ">" + else: + assert c.const + pfx, sfx = "const_cast<", ">" + self.write(pfx) + c.type.accept(self) + self.write(sfx + "(") + c.expr.accept(self) + self.write(")") + + def visitExprBinary(self, e): + self.write("(") + e.left.accept(self) + self.write(") " + e.op + " (") + e.right.accept(self) + self.write(")") + + def visitExprConditional(self, c): + self.write("(") + c.cond.accept(self) + self.write(" ? ") + c.ife.accept(self) + self.write(" : ") + c.elsee.accept(self) + self.write(")") + + def visitExprSelect(self, es): + self.write("(") + es.obj.accept(self) + self.write(")") + self.write(es.op) + es.field.accept(self) + + def visitExprAssn(self, ea): + ea.lhs.accept(self) + self.write(" " + ea.op + " ") + ea.rhs.accept(self) + + def visitExprCall(self, ec): + ec.func.accept(self) + self.write("(") + self.writeExprList(ec.args) + self.write(")") + + def visitExprNew(self, en): + self.write("new ") + if en.newargs is not None: + self.write("(") + self.writeExprList(en.newargs) + self.write(") ") + en.ctype.accept(self) + if en.args is not None: + self.write("(") + self.writeExprList(en.args) + self.write(")") + + def visitExprDelete(self, ed): + self.write("delete ") + ed.obj.accept(self) + + def visitExprLambda(self, l): + self.write("[") + ncaptures = len(l.captures) + for i, c in enumerate(l.captures): + c.accept(self) + if i != (ncaptures - 1): + self.write(", ") + self.write("](") + self.writeDeclList(l.params) + self.write(")") + if l.ret: + self.write(" -> ") + l.ret.accept(self) + self.println(" {") + self.indent() + self.visitBlock(l) + self.dedent() + self.printdent("}") + + def visitStmtBlock(self, b): + self.printdentln("{") + self.indent() + self.visitBlock(b) + self.dedent() + self.printdentln("}") + + def visitLabel(self, label): + self.dedent() # better not be at global scope ... + self.printdentln(label.name + ":") + self.indent() + + def visitCaseLabel(self, cl): + self.dedent() + self.printdentln("case " + cl.name + ":") + self.indent() + + def visitDefaultLabel(self, dl): + self.dedent() + self.printdentln("default:") + self.indent() + + def visitStmtIf(self, si): + self.printdent("if (") + si.cond.accept(self) + self.println(") {") + self.indent() + si.ifb.accept(self) + self.dedent() + self.printdentln("}") + + if si.elseb is not None: + self.printdentln("else {") + self.indent() + si.elseb.accept(self) + self.dedent() + self.printdentln("}") + + def visitStmtFor(self, sf): + self.printdent("for (") + if sf.init is not None: + sf.init.accept(self) + self.write("; ") + if sf.cond is not None: + sf.cond.accept(self) + self.write("; ") + if sf.update is not None: + sf.update.accept(self) + self.println(") {") + + self.indent() + self.visitBlock(sf) + self.dedent() + self.printdentln("}") + + def visitStmtRangedFor(self, rf): + self.printdent("for (auto& ") + rf.var.accept(self) + self.write(" : ") + rf.iteree.accept(self) + self.println(") {") + + self.indent() + self.visitBlock(rf) + self.dedent() + self.printdentln("}") + + def visitStmtSwitch(self, sw): + self.printdent("switch (") + sw.expr.accept(self) + self.println(") {") + self.indent() + self.visitBlock(sw) + self.dedent() + self.printdentln("}") + + def visitStmtBreak(self, sb): + self.printdentln("break;") + + def visitStmtDecl(self, sd): + self.printdent() + sd.decl.accept(self) + if sd.initargs is not None: + self.write("{") + self.writeDeclList(sd.initargs) + self.write("}") + if sd.init is not None: + self.write(" = ") + sd.init.accept(self) + self.println(";") + + def visitStmtExpr(self, se): + self.printdent() + se.expr.accept(self) + self.println(";") + + def visitStmtReturn(self, sr): + self.printdent("return") + if sr.expr: + self.write(" ") + sr.expr.accept(self) + self.println(";") + + def writeDeclList(self, decls): + # FIXME/cjones: try to do nice formatting of these guys + + ndecls = len(decls) + if 0 == ndecls: + return + elif 1 == ndecls: + decls[0].accept(self) + return + + self.indent() + self.indent() + for i, decl in enumerate(decls): + self.println() + self.printdent() + decl.accept(self) + if i != (ndecls - 1): + self.write(",") + self.dedent() + self.dedent() + + def writeExprList(self, exprs): + # FIXME/cjones: try to do nice formatting and share code with + # writeDeclList() + nexprs = len(exprs) + for i, expr in enumerate(exprs): + expr.accept(self) + if i != (nexprs - 1): + self.write(", ") diff --git a/ipc/ipdl/ipdl/cxx/code.py b/ipc/ipdl/ipdl/cxx/code.py new file mode 100644 index 0000000000..0b5019b623 --- /dev/null +++ b/ipc/ipdl/ipdl/cxx/code.py @@ -0,0 +1,187 @@ +# 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/. + +# This module contains functionality for adding formatted, opaque "code" blocks +# into the IPDL ast. These code objects follow IPDL C++ ast patterns, and +# perform lowering in much the same way. + +# In general it is recommended to use these for blocks of code which would +# otherwise be specified by building a hardcoded IPDL-C++ AST, as users of this +# API are often easier to read than users of the AST APIs in these cases. + +import re +import math +import textwrap + +from ipdl.cxx.ast import Node, Whitespace, GroupNode, VerbatimNode + + +# ----------------------------------------------------------------------------- +# Public API. + + +def StmtCode(tmpl, **kwargs): + """Perform template substitution to build opaque C++ AST nodes. See the + module documentation for more information on the templating syntax. + + StmtCode nodes should be used where Stmt* nodes are used. They are placed + on their own line and indented.""" + return _code(tmpl, False, kwargs) + + +def ExprCode(tmpl, **kwargs): + """Perform template substitution to build opaque C++ AST nodes. See the + module documentation for more information on the templating syntax. + + ExprCode nodes should be used where Expr* nodes are used. They are placed + inline, and no trailing newline is added.""" + return _code(tmpl, True, kwargs) + + +def StmtVerbatim(text): + """Build an opaque C++ AST node which emits input text verbatim. + + StmtVerbatim nodes should be used where Stmt* nodes are used. They are placed + on their own line and indented.""" + return _verbatim(text, False) + + +def ExprVerbatim(text): + """Build an opaque C++ AST node which emits input text verbatim. + + ExprVerbatim nodes should be used where Expr* nodes are used. They are + placed inline, and no trailing newline is added.""" + return _verbatim(text, True) + + +# ----------------------------------------------------------------------------- +# Implementation + + +def _code(tmpl, inline, context): + # Remove common indentation, and strip the preceding newline from + # '''-quoting, because we usually don't want it. + if tmpl.startswith("\n"): + tmpl = tmpl[1:] + tmpl = textwrap.dedent(tmpl) + + # Process each line in turn, building up a list of nodes. + nodes = [] + for idx, line in enumerate(tmpl.splitlines()): + # Place newline tokens between lines in the input. + if idx > 0: + nodes.append(Whitespace.NL) + + # Don't indent the first line if `inline` is set. + skip_indent = inline and idx == 0 + nodes.append(_line(line.rstrip(), skip_indent, idx + 1, context)) + + # If we're inline, don't add the final trailing newline. + if not inline: + nodes.append(Whitespace.NL) + return GroupNode(nodes) + + +def _verbatim(text, inline): + # For simplicitly, _verbatim is implemented using the same logic as _code, + # but with '$' characters escaped. This ensures we only need to worry about + # a single, albeit complex, codepath. + return _code(text.replace("$", "$$"), inline, {}) + + +# Pattern used to identify substitutions. +_substPat = re.compile( + r""" + \$(?: + (?P<escaped>\$) | # '$$' is an escaped '$' + (?P<list>[*,])?{(?P<expr>[^}]+)} | # ${expr}, $*{expr}, or $,{expr} + (?P<invalid>) # For error reporting + ) + """, + re.IGNORECASE | re.VERBOSE, +) + + +def _line(raw, skip_indent, lineno, context): + assert "\n" not in raw + + # Determine the level of indentation used for this line + line = raw.lstrip() + offset = int(math.ceil((len(raw) - len(line)) / 4)) + + # If line starts with a directive, don't indent it. + if line.startswith("#"): + skip_indent = True + + column = 0 + children = [] + for match in _substPat.finditer(line): + if match.group("invalid") is not None: + raise ValueError("Invalid substitution on line %d" % lineno) + + # Any text from before the current entry should be written, and column + # advanced. + if match.start() > column: + before = line[column : match.start()] + children.append(VerbatimNode(before)) + column = match.end() + + # If we have an escaped group, emit a '$' node. + if match.group("escaped") is not None: + children.append(VerbatimNode("$")) + continue + + # At this point we should have an expression. + list_chr = match.group("list") + expr = match.group("expr") + assert expr is not None + + # Evaluate our expression in the context to get the values. + try: + values = eval(expr, context, {}) + except Exception as e: + msg = "%s in substitution on line %d" % (repr(e), lineno) + raise ValueError(msg) from e + + # If we aren't dealing with lists, wrap the result into a + # single-element list. + if list_chr is None: + values = [values] + + # Check if this substitution is inline, or the entire line. + inline = match.span() != (0, len(line)) + + for idx, value in enumerate(values): + # If we're using ',' as list mode, put a comma between each node. + if idx > 0 and list_chr == ",": + children.append(VerbatimNode(", ")) + + # If our value isn't a node, turn it into one. Verbatim should be + # inline unless indent isn't being skipped, and the match isn't + # inline. + if not isinstance(value, Node): + value = _verbatim(str(value), skip_indent or inline) + children.append(value) + + # If we were the entire line, indentation is handled by the added child + # nodes. Do this after the above loop such that created verbatims have + # the correct inline-ness. + if not inline: + skip_indent = True + + # Add any remaining text in the line. + if len(line) > column: + children.append(VerbatimNode(line[column:])) + + # If we have no children, just emit the empty string. This will become a + # blank line. + if len(children) == 0: + return VerbatimNode("") + + # Add the initial indent if we aren't skipping it. + if not skip_indent: + children.insert(0, VerbatimNode("", indent=True)) + + # Wrap ourselves into a group node with the correct indent offset + return GroupNode(children, offset=offset) diff --git a/ipc/ipdl/ipdl/lower.py b/ipc/ipdl/ipdl/lower.py new file mode 100644 index 0000000000..b8ef219b9b --- /dev/null +++ b/ipc/ipdl/ipdl/lower.py @@ -0,0 +1,5688 @@ +# 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 re +from copy import deepcopy +from collections import OrderedDict +import itertools + +import ipdl.ast +import ipdl.builtin +from ipdl.cxx.ast import * +from ipdl.cxx.code import * +from ipdl.type import ActorType, UnionType, TypeVisitor, builtinHeaderIncludes +from ipdl.util import hash_str + + +# ----------------------------------------------------------------------------- +# "Public" interface to lowering +## + + +class LowerToCxx: + def lower(self, tu, segmentcapacitydict): + """returns |[ header: File ], [ cpp : File ]| representing the + lowered form of |tu|""" + # annotate the AST with IPDL/C++ IR-type stuff used later + tu.accept(_DecorateWithCxxStuff()) + + # Any modifications to the filename scheme here need corresponding + # modifications in the ipdl.py driver script. + name = tu.name + pheader, pcpp = File(name + ".h"), File(name + ".cpp") + + _GenerateProtocolCode().lower(tu, pheader, pcpp, segmentcapacitydict) + headers = [pheader] + cpps = [pcpp] + + if tu.protocol: + pname = tu.protocol.name + + parentheader, parentcpp = ( + File(pname + "Parent.h"), + File(pname + "Parent.cpp"), + ) + _GenerateProtocolParentCode().lower( + tu, pname + "Parent", parentheader, parentcpp + ) + + childheader, childcpp = File(pname + "Child.h"), File(pname + "Child.cpp") + _GenerateProtocolChildCode().lower( + tu, pname + "Child", childheader, childcpp + ) + + headers += [parentheader, childheader] + cpps += [parentcpp, childcpp] + + return headers, cpps + + +# ----------------------------------------------------------------------------- +# Helper code +## + + +def hashfunc(value): + h = hash_str(value) % 2 ** 32 + if h < 0: + h += 2 ** 32 + return h + + +_NULL_ACTOR_ID = ExprLiteral.ZERO +_FREED_ACTOR_ID = ExprLiteral.ONE + +_DISCLAIMER = Whitespace( + """// +// Automatically generated by ipdlc. +// Edit at your own risk +// + +""" +) + + +class _struct: + pass + + +def _namespacedHeaderName(name, namespaces): + pfx = "/".join([ns.name for ns in namespaces]) + if pfx: + return pfx + "/" + name + else: + return name + + +def _ipdlhHeaderName(tu): + assert tu.filetype == "header" + return _namespacedHeaderName(tu.name, tu.namespaces) + + +def _protocolHeaderName(p, side=""): + if side: + side = side.title() + base = p.name + side + return _namespacedHeaderName(base, p.namespaces) + + +def _includeGuardMacroName(headerfile): + return re.sub(r"[./]", "_", headerfile.name) + + +def _includeGuardStart(headerfile): + guard = _includeGuardMacroName(headerfile) + return [CppDirective("ifndef", guard), CppDirective("define", guard)] + + +def _includeGuardEnd(headerfile): + guard = _includeGuardMacroName(headerfile) + return [CppDirective("endif", "// ifndef " + guard)] + + +def _messageStartName(ptype): + return ptype.name() + "MsgStart" + + +def _protocolId(ptype): + return ExprVar(_messageStartName(ptype)) + + +def _protocolIdType(): + return Type.INT32 + + +def _actorName(pname, side): + """|pname| is the protocol name. |side| is 'Parent' or 'Child'.""" + tag = side + if not tag[0].isupper(): + tag = side.title() + return pname + tag + + +def _actorIdType(): + return Type.INT32 + + +def _actorTypeTagType(): + return Type.INT32 + + +def _actorId(actor=None): + if actor is not None: + return ExprCall(ExprSelect(actor, "->", "Id")) + return ExprCall(ExprVar("Id")) + + +def _actorHId(actorhandle): + return ExprSelect(actorhandle, ".", "mId") + + +def _backstagePass(): + return ExprCall(ExprVar("mozilla::ipc::PrivateIPDLInterface")) + + +def _deleteId(): + return ExprVar("Msg___delete____ID") + + +def _deleteReplyId(): + return ExprVar("Reply___delete____ID") + + +def _lookupListener(idexpr): + return ExprCall(ExprVar("Lookup"), args=[idexpr]) + + +def _makeForwardDeclForQClass(clsname, quals, cls=True, struct=False): + fd = ForwardDecl(clsname, cls=cls, struct=struct) + if 0 == len(quals): + return fd + + outerns = Namespace(quals[0]) + innerns = outerns + for ns in quals[1:]: + tmpns = Namespace(ns) + innerns.addstmt(tmpns) + innerns = tmpns + + innerns.addstmt(fd) + return outerns + + +def _makeForwardDeclForActor(ptype, side): + return _makeForwardDeclForQClass( + _actorName(ptype.qname.baseid, side), ptype.qname.quals + ) + + +def _makeForwardDecl(type): + return _makeForwardDeclForQClass(type.name(), type.qname.quals) + + +def _putInNamespaces(cxxthing, namespaces): + """|namespaces| is in order [ outer, ..., inner ]""" + if 0 == len(namespaces): + return cxxthing + + outerns = Namespace(namespaces[0].name) + innerns = outerns + for ns in namespaces[1:]: + newns = Namespace(ns.name) + innerns.addstmt(newns) + innerns = newns + innerns.addstmt(cxxthing) + return outerns + + +def _sendPrefix(msgtype): + """Prefix of the name of the C++ method that sends |msgtype|.""" + if msgtype.isInterrupt(): + return "Call" + return "Send" + + +def _recvPrefix(msgtype): + """Prefix of the name of the C++ method that handles |msgtype|.""" + if msgtype.isInterrupt(): + return "Answer" + return "Recv" + + +def _flatTypeName(ipdltype): + """Return a 'flattened' IPDL type name that can be used as an + identifier. + E.g., |Foo[]| --> |ArrayOfFoo|.""" + # NB: this logic depends heavily on what IPDL types are allowed to + # be constructed; e.g., Foo[][] is disallowed. needs to be kept in + # sync with grammar. + if ipdltype.isIPDL() and ipdltype.isArray(): + return "ArrayOf" + _flatTypeName(ipdltype.basetype) + if ipdltype.isIPDL() and ipdltype.isMaybe(): + return "Maybe" + _flatTypeName(ipdltype.basetype) + # NotNull types just assume the underlying variant name to avoid unnecessary + # noise, as a NotNull<T> and T should never exist in the same union. + if ipdltype.isIPDL() and ipdltype.isNotNull(): + return _flatTypeName(ipdltype.basetype) + return ipdltype.name() + + +def _hasVisibleActor(ipdltype): + """Return true iff a C++ decl of |ipdltype| would have an Actor* type. + For example: |Actor[]| would turn into |Array<ActorParent*>|, so this + function would return true for |Actor[]|.""" + return ipdltype.isIPDL() and ( + ipdltype.isActor() + or (ipdltype.hasBaseType() and _hasVisibleActor(ipdltype.basetype)) + ) + + +def _abortIfFalse(cond, msg): + return StmtExpr( + ExprCall(ExprVar("MOZ_RELEASE_ASSERT"), [cond, ExprLiteral.String(msg)]) + ) + + +def _refptr(T): + return Type("RefPtr", T=T) + + +def _alreadyaddrefed(T): + return Type("already_AddRefed", T=T) + + +def _tuple(types, const=False, ref=False): + return Type("std::tuple", T=types, const=const, ref=ref) + + +def _promise(resolvetype, rejecttype, tail, resolver=False): + inner = Type("Private") if resolver else None + return Type("MozPromise", T=[resolvetype, rejecttype, tail], inner=inner) + + +def _makePromise(returns, side, resolver=False): + if len(returns) > 1: + resolvetype = _tuple([d.bareType(side) for d in returns]) + else: + resolvetype = returns[0].bareType(side) + + # MozPromise is purposefully made to be exclusive only. Really, we mean it. + return _promise( + resolvetype, _ResponseRejectReason.Type(), ExprLiteral.TRUE, resolver=resolver + ) + + +def _resolveType(returns, side): + if len(returns) > 1: + return _tuple([d.inType(side, "send") for d in returns]) + return returns[0].inType(side, "send") + + +def _makeResolver(returns, side): + return TypeFunction([Decl(_resolveType(returns, side), "")]) + + +def _cxxArrayType(basetype, const=False, ref=False): + return Type("nsTArray", T=basetype, const=const, ref=ref, hasimplicitcopyctor=False) + + +def _cxxSpanType(basetype, const=False, ref=False): + basetype = deepcopy(basetype) + basetype.rightconst = True + return Type( + "mozilla::Span", T=basetype, const=const, ref=ref, hasimplicitcopyctor=True + ) + + +def _cxxMaybeType(basetype, const=False, ref=False): + return Type( + "mozilla::Maybe", + T=basetype, + const=const, + ref=ref, + hasimplicitcopyctor=basetype.hasimplicitcopyctor, + ) + + +def _cxxReadResultType(basetype, const=False, ref=False): + return Type( + "IPC::ReadResult", + T=basetype, + const=const, + ref=ref, + hasimplicitcopyctor=basetype.hasimplicitcopyctor, + ) + + +def _cxxNotNullType(basetype, const=False, ref=False): + return Type( + "mozilla::NotNull", + T=basetype, + const=const, + ref=ref, + hasimplicitcopyctor=basetype.hasimplicitcopyctor, + ) + + +def _cxxManagedContainerType(basetype, const=False, ref=False): + return Type( + "ManagedContainer", T=basetype, const=const, ref=ref, hasimplicitcopyctor=False + ) + + +def _cxxLifecycleProxyType(ptr=False): + return Type("mozilla::ipc::ActorLifecycleProxy", ptr=ptr) + + +def _otherSide(side): + if side == "child": + return "parent" + if side == "parent": + return "child" + assert 0 + + +def _ifLogging(topLevelProtocol, stmts): + return StmtCode( + """ + if (mozilla::ipc::LoggingEnabledFor(${proto})) { + $*{stmts} + } + """, + proto=topLevelProtocol, + stmts=stmts, + ) + + +# XXX we need to remove these and install proper error handling + + +def _printErrorMessage(msg): + if isinstance(msg, str): + msg = ExprLiteral.String(msg) + return StmtExpr(ExprCall(ExprVar("NS_ERROR"), args=[msg])) + + +def _protocolErrorBreakpoint(msg): + if isinstance(msg, str): + msg = ExprLiteral.String(msg) + return StmtExpr( + ExprCall(ExprVar("mozilla::ipc::ProtocolErrorBreakpoint"), args=[msg]) + ) + + +def _printWarningMessage(msg): + if isinstance(msg, str): + msg = ExprLiteral.String(msg) + return StmtExpr(ExprCall(ExprVar("NS_WARNING"), args=[msg])) + + +def _fatalError(msg): + return StmtExpr(ExprCall(ExprVar("FatalError"), args=[ExprLiteral.String(msg)])) + + +def _logicError(msg): + return StmtExpr( + ExprCall(ExprVar("mozilla::ipc::LogicError"), args=[ExprLiteral.String(msg)]) + ) + + +def _sentinelReadError(classname): + return StmtExpr( + ExprCall( + ExprVar("mozilla::ipc::SentinelReadError"), + args=[ExprLiteral.String(classname)], + ) + ) + + +# Results that IPDL-generated code returns back to *Channel code. +# Users never see these + + +class _Result: + @staticmethod + def Type(): + return Type("Result") + + Processed = ExprVar("MsgProcessed") + NotKnown = ExprVar("MsgNotKnown") + NotAllowed = ExprVar("MsgNotAllowed") + PayloadError = ExprVar("MsgPayloadError") + ProcessingError = ExprVar("MsgProcessingError") + RouteError = ExprVar("MsgRouteError") + ValuError = ExprVar("MsgValueError") # [sic] + + +# these |errfn*| are functions that generate code to be executed on an +# error, such as "bad actor ID". each is given a Python string +# containing a description of the error + +# used in user-facing Send*() methods + + +def errfnSend(msg, errcode=ExprLiteral.FALSE): + return [_fatalError(msg), StmtReturn(errcode)] + + +def errfnSendCtor(msg): + return errfnSend(msg, errcode=ExprLiteral.NULL) + + +# TODO should this error handling be strengthened for dtors? + + +def errfnSendDtor(msg): + return [_printErrorMessage(msg), StmtReturn.FALSE] + + +# used in |OnMessage*()| handlers that hand in-messages off to Recv*() +# interface methods + + +def errfnRecv(msg, errcode=_Result.ValuError): + return [_fatalError(msg), StmtReturn(errcode)] + + +def errfnSentinel(rvalue=ExprLiteral.FALSE): + def inner(msg): + return [_sentinelReadError(msg), StmtReturn(rvalue)] + + return inner + + +def _destroyMethod(): + return ExprVar("ActorDestroy") + + +def errfnUnreachable(msg): + return [_logicError(msg)] + + +def readResultError(): + return ExprCode("{}") + + +class _DestroyReason: + @staticmethod + def Type(): + return Type("ActorDestroyReason") + + Deletion = ExprVar("Deletion") + AncestorDeletion = ExprVar("AncestorDeletion") + NormalShutdown = ExprVar("NormalShutdown") + AbnormalShutdown = ExprVar("AbnormalShutdown") + FailedConstructor = ExprVar("FailedConstructor") + ManagedEndpointDropped = ExprVar("ManagedEndpointDropped") + + +class _ResponseRejectReason: + @staticmethod + def Type(): + return Type("ResponseRejectReason") + + SendError = ExprVar("ResponseRejectReason::SendError") + ChannelClosed = ExprVar("ResponseRejectReason::ChannelClosed") + HandlerRejected = ExprVar("ResponseRejectReason::HandlerRejected") + ActorDestroyed = ExprVar("ResponseRejectReason::ActorDestroyed") + + +# ----------------------------------------------------------------------------- +# Intermediate representation (IR) nodes used during lowering + + +class _ConvertToCxxType(TypeVisitor): + def __init__(self, side, fq): + self.side = side + self.fq = fq + + def typename(self, thing): + if self.fq: + return thing.fullname() + return thing.name() + + def visitImportedCxxType(self, t): + cxxtype = Type(self.typename(t)) + if t.isRefcounted(): + cxxtype = _refptr(cxxtype) + return cxxtype + + def visitBuiltinCType(self, b): + return Type(self.typename(b)) + + def visitActorType(self, a): + if self.side is None: + return Type( + "::mozilla::ipc::SideVariant", + T=[ + _cxxBareType(a, "parent", self.fq), + _cxxBareType(a, "child", self.fq), + ], + ) + return Type(_actorName(self.typename(a.protocol), self.side), ptr=True) + + def visitStructType(self, s): + return Type(self.typename(s)) + + def visitUnionType(self, u): + return Type(self.typename(u)) + + def visitArrayType(self, a): + basecxxtype = a.basetype.accept(self) + return _cxxArrayType(basecxxtype) + + def visitMaybeType(self, m): + basecxxtype = m.basetype.accept(self) + return _cxxMaybeType(basecxxtype) + + def visitShmemType(self, s): + return Type(self.typename(s)) + + def visitByteBufType(self, s): + return Type(self.typename(s)) + + def visitFDType(self, s): + return Type(self.typename(s)) + + def visitEndpointType(self, s): + return Type(self.typename(s)) + + def visitManagedEndpointType(self, s): + return Type(self.typename(s)) + + def visitUniquePtrType(self, s): + return Type(self.typename(s)) + + def visitNotNullType(self, n): + basecxxtype = n.basetype.accept(self) + return _cxxNotNullType(basecxxtype) + + def visitProtocolType(self, p): + assert 0 + + def visitMessageType(self, m): + assert 0 + + def visitVoidType(self, v): + assert 0 + + +def _cxxBareType(ipdltype, side, fq=False): + return ipdltype.accept(_ConvertToCxxType(side, fq)) + + +def _cxxRefType(ipdltype, side): + t = _cxxBareType(ipdltype, side) + t.ref = True + return t + + +def _cxxConstRefType(ipdltype, side): + t = _cxxBareType(ipdltype, side) + if ipdltype.isIPDL() and ipdltype.isActor(): + return t + if ipdltype.isIPDL() and ipdltype.isShmem(): + t.ref = True + return t + if ipdltype.isIPDL() and ipdltype.isNotNull(): + # If the inner type chooses to use a raw pointer, wrap that instead. + inner = _cxxConstRefType(ipdltype.basetype, side) + if inner.ptr: + t = _cxxNotNullType(inner) + return t + if ipdltype.isIPDL() and ipdltype.hasBaseType(): + # Keep same constness as inner type. + inner = _cxxConstRefType(ipdltype.basetype, side) + t.const = inner.const or not inner.ref + t.ref = True + return t + if ipdltype.isCxx() and (ipdltype.isSendMoveOnly() or ipdltype.isDataMoveOnly()): + t.const = True + t.ref = True + return t + if ipdltype.isCxx() and ipdltype.isRefcounted(): + # Use T* instead of const RefPtr<T>& + t = t.T + t.ptr = True + return t + t.const = True + t.ref = True + return t + + +def _cxxTypeNeedsMoveForSend(ipdltype, context="root", visited=None): + """Returns `True` if serializing ipdltype requires a mutable reference, e.g. + because the underlying resource represented by the value is being + transferred to another process. This is occasionally distinct from whether + the C++ type exposes a copy constructor, such as for types which are not + cheaply copiable, but are not mutated when serialized.""" + + if visited is None: + visited = set() + + visited.add(ipdltype) + + if ipdltype.isCxx(): + return ipdltype.isSendMoveOnly() + + if ipdltype.isIPDL(): + if ipdltype.hasBaseType(): + return _cxxTypeNeedsMoveForSend(ipdltype.basetype, "wrapper", visited) + if ipdltype.isStruct() or ipdltype.isUnion(): + return any( + _cxxTypeNeedsMoveForSend(t, "compound", visited) + for t in ipdltype.itercomponents() + if t not in visited + ) + + # For historical reasons, shmem is `const_cast` to a mutable reference + # when being stored in a struct or union (see + # `_StructField.constRefExpr` and `_UnionMember.getConstValue`), meaning + # that they do not cause the containing struct to require move for + # sending. + if ipdltype.isShmem(): + return context != "compound" + + return ( + ipdltype.isByteBuf() + or ipdltype.isEndpoint() + or ipdltype.isManagedEndpoint() + ) + + return False + + +def _cxxTypeNeedsMoveForData(ipdltype, context="root", visited=None): + """Returns `True` if the bare C++ type corresponding to ipdltype does not + satisfy std::is_copy_constructible_v<T>. All C++ types supported by IPDL + must support std::is_move_constructible_v<T>, so non-movable types must be + passed behind a `UniquePtr`.""" + + if visited is None: + visited = set() + + visited.add(ipdltype) + + if ipdltype.isCxx(): + return ipdltype.isDataMoveOnly() + + if ipdltype.isIPDL(): + if ipdltype.isUniquePtr(): + return True + + # When nested within a maybe or array, arrays are no longer copyable. + if context == "wrapper" and ipdltype.isArray(): + return True + if ipdltype.hasBaseType(): + return _cxxTypeNeedsMoveForData(ipdltype.basetype, "wrapper", visited) + if ipdltype.isStruct() or ipdltype.isUnion(): + return any( + _cxxTypeNeedsMoveForData(t, "compound", visited) + for t in ipdltype.itercomponents() + if t not in visited + ) + return ( + ipdltype.isByteBuf() + or ipdltype.isEndpoint() + or ipdltype.isManagedEndpoint() + ) + + return False + + +def _cxxTypeCanMove(ipdltype): + return not (ipdltype.isIPDL() and ipdltype.isActor()) + + +def _cxxForceMoveRefType(ipdltype, side): + assert _cxxTypeCanMove(ipdltype) + t = _cxxBareType(ipdltype, side) + t.rvalref = True + return t + + +def _cxxPtrToType(ipdltype, side): + t = _cxxBareType(ipdltype, side) + if ipdltype.isIPDL() and ipdltype.isActor() and side is not None: + t.ptr = False + t.ptrptr = True + return t + t.ptr = True + return t + + +def _cxxConstPtrToType(ipdltype, side): + t = _cxxBareType(ipdltype, side) + if ipdltype.isIPDL() and ipdltype.isActor() and side is not None: + t.ptr = False + t.ptrconstptr = True + return t + t.const = True + t.ptr = True + return t + + +def _cxxInType(ipdltype, side, direction): + t = _cxxBareType(ipdltype, side) + if ipdltype.isIPDL() and ipdltype.isActor(): + return t + if ipdltype.isIPDL() and ipdltype.isNotNull(): + # If the inner type chooses to use a raw pointer, wrap that instead. + inner = _cxxInType(ipdltype.basetype, side, direction) + if inner.ptr: + t = _cxxNotNullType(inner) + return t + if _cxxTypeNeedsMoveForSend(ipdltype): + t.rvalref = True + return t + if ipdltype.isCxx(): + if ipdltype.isRefcounted(): + # Use T* instead of const RefPtr<T>& + t = t.T + t.ptr = True + return t + if ipdltype.name() == "nsCString": + t = Type("nsACString") + if ipdltype.name() == "nsString": + t = Type("nsAString") + # Use Span<T const> rather than nsTArray<T> for array types which aren't + # `_cxxTypeNeedsMoveForSend`. This is only done for the "send" side, and not + # for recv signatures. + if direction == "send" and ipdltype.isIPDL() and ipdltype.isArray(): + inner = _cxxBareType(ipdltype.basetype, side) + return _cxxSpanType(inner) + + t.const = True + t.ref = True + return t + + +def _allocMethod(ptype, side): + return "Alloc" + ptype.name() + side.title() + + +def _deallocMethod(ptype, side): + return "Dealloc" + ptype.name() + side.title() + + +## +# A _HybridDecl straddles IPDL and C++ decls. It knows which C++ +# types correspond to which IPDL types, and it also knows how +# serialize and deserialize "special" IPDL C++ types. +## + + +class _HybridDecl: + """A hybrid decl stores both an IPDL type and all the C++ type + info needed by later passes, along with a basic name for the decl.""" + + def __init__(self, ipdltype, name, attributes={}): + self.ipdltype = ipdltype + self.name = name + self.attributes = attributes + + def var(self): + return ExprVar(self.name) + + def bareType(self, side, fq=False): + """Return this decl's unqualified C++ type.""" + return _cxxBareType(self.ipdltype, side, fq=fq) + + def refType(self, side): + """Return this decl's C++ type as a 'reference' type, which is not + necessarily a C++ reference.""" + return _cxxRefType(self.ipdltype, side) + + def constRefType(self, side): + """Return this decl's C++ type as a const, 'reference' type.""" + return _cxxConstRefType(self.ipdltype, side) + + def ptrToType(self, side): + return _cxxPtrToType(self.ipdltype, side) + + def constPtrToType(self, side): + return _cxxConstPtrToType(self.ipdltype, side) + + def inType(self, side, direction): + """Return this decl's C++ Type with sending inparam semantics.""" + return _cxxInType(self.ipdltype, side, direction) + + def outType(self, side): + """Return this decl's C++ Type with outparam semantics.""" + t = self.bareType(side) + if self.ipdltype.isIPDL() and self.ipdltype.isActor(): + t.ptr = False + t.ptrptr = True + return t + t.ptr = True + return t + + def forceMoveType(self, side): + """Return this decl's C++ Type with forced move semantics.""" + assert _cxxTypeCanMove(self.ipdltype) + return _cxxForceMoveRefType(self.ipdltype, side) + + +# -------------------------------------------------- + + +class HasFQName: + def fqClassName(self): + return self.decl.type.fullname() + + +class _CompoundTypeComponent(_HybridDecl): + # @override the following methods to make the side argument optional. + def bareType(self, side=None, fq=False): + return _HybridDecl.bareType(self, side, fq=fq) + + def refType(self, side=None): + return _HybridDecl.refType(self, side) + + def constRefType(self, side=None): + return _HybridDecl.constRefType(self, side) + + def ptrToType(self, side=None): + return _HybridDecl.ptrToType(self, side) + + def constPtrToType(self, side=None): + return _HybridDecl.constPtrToType(self, side) + + def forceMoveType(self, side=None): + return _HybridDecl.forceMoveType(self, side) + + +class StructDecl(ipdl.ast.StructDecl, HasFQName): + def fields_ipdl_order(self): + for f in self.fields: + yield f + + def fields_member_order(self): + assert len(self.packed_field_order) == len(self.fields) + + for i in self.packed_field_order: + yield self.fields[i] + + @staticmethod + def upgrade(structDecl): + assert isinstance(structDecl, ipdl.ast.StructDecl) + structDecl.__class__ = StructDecl + + +class _StructField(_CompoundTypeComponent): + def __init__(self, ipdltype, name, sd): + self.basename = name + + _CompoundTypeComponent.__init__(self, ipdltype, name) + + def getMethod(self, thisexpr=None, sel="."): + meth = self.var() + if thisexpr is not None: + return ExprSelect(thisexpr, sel, meth.name) + return meth + + def refExpr(self, thisexpr=None): + ref = self.memberVar() + if thisexpr is not None: + ref = ExprSelect(thisexpr, ".", ref.name) + return ref + + def constRefExpr(self, thisexpr=None): + # sigh, gross hack + refexpr = self.refExpr(thisexpr) + if "Shmem" == self.ipdltype.name(): + refexpr = ExprCast(refexpr, Type("Shmem", ref=True), const=True) + return refexpr + + def argVar(self): + return ExprVar("_" + self.name) + + def memberVar(self): + return ExprVar(self.name + "_") + + +class UnionDecl(ipdl.ast.UnionDecl, HasFQName): + def callType(self, var=None): + func = ExprVar("type") + if var is not None: + func = ExprSelect(var, ".", func.name) + return ExprCall(func) + + @staticmethod + def upgrade(unionDecl): + assert isinstance(unionDecl, ipdl.ast.UnionDecl) + unionDecl.__class__ = UnionDecl + + +class _UnionMember(_CompoundTypeComponent): + """Not in the AFL sense, but rather a member (e.g. |int;|) of an + IPDL union type.""" + + def __init__(self, ipdltype, ud): + flatname = _flatTypeName(ipdltype) + + _CompoundTypeComponent.__init__(self, ipdltype, "V" + flatname) + self.flattypename = flatname + + # To create a finite object with a mutually recursive type, a union must + # be present somewhere in the recursive loop. Because of that we only + # need to care about introducing indirections inside unions. + self.recursive = ud.decl.type.mutuallyRecursiveWith(ipdltype) + + def enum(self): + return "T" + self.flattypename + + def enumvar(self): + return ExprVar(self.enum()) + + def internalType(self): + if self.recursive: + return self.ptrToType() + else: + return self.bareType() + + def unionType(self): + """Type used for storage in generated C union decl.""" + if self.recursive: + return self.ptrToType() + else: + return Type("mozilla::AlignedStorage2", T=self.internalType()) + + def unionValue(self): + # NB: knows that Union's storage C union is named |mValue| + return ExprSelect(ExprVar("mValue"), ".", self.name) + + def typedef(self): + return self.flattypename + "__tdef" + + def callGetConstPtr(self): + """Return an expression of type self.constptrToSelfType()""" + return ExprCall(ExprVar(self.getConstPtrName())) + + def callGetPtr(self): + """Return an expression of type self.ptrToSelfType()""" + return ExprCall(ExprVar(self.getPtrName())) + + def callCtor(self, expr=None): + assert not isinstance(expr, list) + + if expr is None: + args = None + elif ( + self.ipdltype.isIPDL() + and self.ipdltype.isArray() + and not isinstance(expr, ExprMove) + ): + args = [ExprCall(ExprSelect(expr, ".", "Clone"), args=[])] + else: + args = [expr] + + if self.recursive: + return ExprAssn(self.callGetPtr(), ExprNew(self.bareType(), args=args)) + else: + return ExprNew( + self.bareType(), + args=args, + newargs=[ExprVar("mozilla::KnownNotNull"), self.callGetPtr()], + ) + + def callDtor(self): + if self.recursive: + return ExprDelete(self.callGetPtr()) + else: + return ExprCall(ExprSelect(self.callGetPtr(), "->", "~" + self.typedef())) + + def getTypeName(self): + return "get_" + self.flattypename + + def getConstTypeName(self): + return "get_" + self.flattypename + + def getOtherTypeName(self): + return "get_" + self.otherflattypename + + def getPtrName(self): + return "ptr_" + self.flattypename + + def getConstPtrName(self): + return "constptr_" + self.flattypename + + def ptrToSelfExpr(self): + """|*ptrToSelfExpr()| has type |self.bareType()|""" + v = self.unionValue() + if self.recursive: + return v + else: + return ExprCall(ExprSelect(v, ".", "addr")) + + def constptrToSelfExpr(self): + """|*constptrToSelfExpr()| has type |self.constType()|""" + v = self.unionValue() + if self.recursive: + return v + return ExprCall(ExprSelect(v, ".", "addr")) + + def ptrToInternalType(self): + t = self.ptrToType() + if self.recursive: + t.ref = True + return t + + def defaultValue(self, fq=False): + # Use the default constructor for any class that does not have an + # implicit copy constructor. + if not self.bareType().hasimplicitcopyctor: + return None + + if self.ipdltype.isIPDL() and self.ipdltype.isActor(): + return ExprLiteral.NULL + # XXX sneaky here, maybe need ExprCtor()? + return ExprCall(self.bareType(fq=fq)) + + def getConstValue(self): + v = ExprDeref(self.callGetConstPtr()) + # sigh + if "Shmem" == self.ipdltype.name(): + v = ExprCast(v, Type("Shmem", ref=True), const=True) + return v + + +# -------------------------------------------------- + + +class MessageDecl(ipdl.ast.MessageDecl): + def baseName(self): + return self.name + + def recvMethod(self): + name = _recvPrefix(self.decl.type) + self.baseName() + if self.decl.type.isCtor(): + name += "Constructor" + return name + + def sendMethod(self): + name = _sendPrefix(self.decl.type) + self.baseName() + if self.decl.type.isCtor(): + name += "Constructor" + return name + + def hasReply(self): + return ( + self.decl.type.hasReply() + or self.decl.type.isCtor() + or self.decl.type.isDtor() + ) + + def hasAsyncReturns(self): + return self.decl.type.isAsync() and self.returns + + def msgCtorFunc(self): + return "Msg_%s" % (self.decl.progname) + + def prettyMsgName(self, pfx=""): + return pfx + self.msgCtorFunc() + + def pqMsgCtorFunc(self): + return "%s::%s" % (self.namespace, self.msgCtorFunc()) + + def msgId(self): + return self.msgCtorFunc() + "__ID" + + def pqMsgId(self): + return "%s::%s" % (self.namespace, self.msgId()) + + def replyCtorFunc(self): + return "Reply_%s" % (self.decl.progname) + + def pqReplyCtorFunc(self): + return "%s::%s" % (self.namespace, self.replyCtorFunc()) + + def replyId(self): + return self.replyCtorFunc() + "__ID" + + def pqReplyId(self): + return "%s::%s" % (self.namespace, self.replyId()) + + def prettyReplyName(self, pfx=""): + return pfx + self.replyCtorFunc() + + def promiseName(self): + name = self.baseName() + if self.decl.type.isCtor(): + name += "Constructor" + name += "Promise" + return name + + def resolverName(self): + return self.baseName() + "Resolver" + + def actorDecl(self): + return self.params[0] + + def makeCxxParams( + self, paramsems="in", returnsems="out", side=None, implicit=True, direction=None + ): + """Return a list of C++ decls per the spec'd configuration. + |params| and |returns| is the C++ semantics of those: 'in', 'out', or None.""" + + def makeDecl(d, sems): + if ( + self.decl.type.tainted + and "NoTaint" not in d.attributes + and direction == "recv" + ): + # Tainted types are passed by-value, allowing the receiver to move them if desired. + assert sems != "out" + return Decl(Type("Tainted", T=d.bareType(side)), d.name) + + if sems == "in": + t = d.inType(side, direction) + # If this is the `recv` side, and we're not using "move" + # semantics, that means we're an alloc method, and cannot accept + # values by rvalue reference. Downgrade to an lvalue reference. + if direction == "recv" and t.rvalref: + t.rvalref = False + t.ref = True + return Decl(t, d.name) + elif sems == "move": + assert direction == "recv" + # For legacy reasons, use an rvalue reference when generating + # parameters for recv methods which accept arrays. + if d.ipdltype.isIPDL() and d.ipdltype.isArray(): + t = d.bareType(side) + t.rvalref = True + return Decl(t, d.name) + return Decl(d.inType(side, direction), d.name) + elif sems == "out": + return Decl(d.outType(side), d.name) + else: + assert 0 + + def makeResolverDecl(returns): + return Decl(Type(self.resolverName(), rvalref=True), "aResolve") + + def makeCallbackResolveDecl(returns): + if len(returns) > 1: + resolvetype = _tuple([d.bareType(side) for d in returns]) + else: + resolvetype = returns[0].bareType(side) + + return Decl( + Type("mozilla::ipc::ResolveCallback", T=resolvetype, rvalref=True), + "aResolve", + ) + + def makeCallbackRejectDecl(returns): + return Decl(Type("mozilla::ipc::RejectCallback", rvalref=True), "aReject") + + cxxparams = [] + if paramsems is not None: + cxxparams.extend([makeDecl(d, paramsems) for d in self.params]) + + if returnsems == "promise" and self.returns: + pass + elif returnsems == "callback" and self.returns: + cxxparams.extend( + [ + makeCallbackResolveDecl(self.returns), + makeCallbackRejectDecl(self.returns), + ] + ) + elif returnsems == "resolver" and self.returns: + cxxparams.extend([makeResolverDecl(self.returns)]) + elif returnsems is not None: + cxxparams.extend([makeDecl(r, returnsems) for r in self.returns]) + + if not implicit and self.decl.type.hasImplicitActorParam(): + cxxparams = cxxparams[1:] + + return cxxparams + + def makeCxxArgs( + self, paramsems="in", retsems="out", retcallsems="out", implicit=True + ): + assert not retcallsems or retsems # retcallsems => returnsems + cxxargs = [] + + if paramsems == "move": + # We don't std::move() RefPtr<T> types because current Recv*() + # implementors take these parameters as T*, and + # std::move(RefPtr<T>) doesn't coerce to T*. + # We also don't move NotNull, as it has no move constructor. + cxxargs.extend( + [ + p.var() + if p.ipdltype.isRefcounted() + or (p.ipdltype.isIPDL() and p.ipdltype.isNotNull()) + else ExprMove(p.var()) + for p in self.params + ] + ) + elif paramsems == "in": + cxxargs.extend([p.var() for p in self.params]) + else: + assert False + + for ret in self.returns: + if retsems == "in": + if retcallsems == "in": + cxxargs.append(ret.var()) + elif retcallsems == "out": + cxxargs.append(ExprAddrOf(ret.var())) + else: + assert 0 + elif retsems == "out": + if retcallsems == "in": + cxxargs.append(ExprDeref(ret.var())) + elif retcallsems == "out": + cxxargs.append(ret.var()) + else: + assert 0 + elif retsems == "resolver": + pass + if retsems == "resolver": + cxxargs.append(ExprMove(ExprVar("resolver"))) + + if not implicit: + assert self.decl.type.hasImplicitActorParam() + cxxargs = cxxargs[1:] + + return cxxargs + + @staticmethod + def upgrade(messageDecl): + assert isinstance(messageDecl, ipdl.ast.MessageDecl) + if messageDecl.decl.type.hasImplicitActorParam(): + messageDecl.params.insert( + 0, + _HybridDecl( + ipdl.type.ActorType(messageDecl.decl.type.constructedType()), + "actor", + ), + ) + messageDecl.__class__ = MessageDecl + + +# -------------------------------------------------- +def _usesShmem(p): + for md in p.messageDecls: + for param in md.inParams: + if ipdl.type.hasshmem(param.type): + return True + for ret in md.outParams: + if ipdl.type.hasshmem(ret.type): + return True + return False + + +def _subtreeUsesShmem(p): + if _usesShmem(p): + return True + + ptype = p.decl.type + for mgd in ptype.manages: + if ptype is not mgd: + if _subtreeUsesShmem(mgd._ast): + return True + return False + + +class Protocol(ipdl.ast.Protocol): + def managerInterfaceType(self, ptr=False): + return Type("mozilla::ipc::IProtocol", ptr=ptr) + + def openedProtocolInterfaceType(self, ptr=False): + return Type("mozilla::ipc::IToplevelProtocol", ptr=ptr) + + def _ipdlmgrtype(self): + assert 1 == len(self.decl.type.managers) + for mgr in self.decl.type.managers: + return mgr + + def managerActorType(self, side, ptr=False): + return Type(_actorName(self._ipdlmgrtype().name(), side), ptr=ptr) + + def unregisterMethod(self, actorThis=None): + if actorThis is not None: + return ExprSelect(actorThis, "->", "Unregister") + return ExprVar("Unregister") + + def removeManageeMethod(self): + return ExprVar("RemoveManagee") + + def deallocManageeMethod(self): + return ExprVar("DeallocManagee") + + def getChannelMethod(self): + return ExprVar("GetIPCChannel") + + def callGetChannel(self, actorThis=None): + fn = self.getChannelMethod() + if actorThis is not None: + fn = ExprSelect(actorThis, "->", fn.name) + return ExprCall(fn) + + def processingErrorVar(self): + assert self.decl.type.isToplevel() + return ExprVar("ProcessingError") + + def shouldContinueFromTimeoutVar(self): + assert self.decl.type.isToplevel() + return ExprVar("ShouldContinueFromReplyTimeout") + + def routingId(self, actorThis=None): + if self.decl.type.isToplevel(): + return ExprVar("MSG_ROUTING_CONTROL") + if actorThis is not None: + return ExprCall(ExprSelect(actorThis, "->", "Id")) + return ExprCall(ExprVar("Id")) + + def managerVar(self, thisexpr=None): + assert thisexpr is not None or not self.decl.type.isToplevel() + mvar = ExprCall(ExprVar("Manager"), args=[]) + if thisexpr is not None: + mvar = ExprCall(ExprSelect(thisexpr, "->", "Manager"), args=[]) + return mvar + + def managedCxxType(self, actortype, side): + assert self.decl.type.isManagerOf(actortype) + return Type(_actorName(actortype.name(), side), ptr=True) + + def managedMethod(self, actortype, side): + assert self.decl.type.isManagerOf(actortype) + return ExprVar("Managed" + _actorName(actortype.name(), side)) + + def managedVar(self, actortype, side): + assert self.decl.type.isManagerOf(actortype) + return ExprVar("mManaged" + _actorName(actortype.name(), side)) + + def managedVarType(self, actortype, side, const=False, ref=False): + assert self.decl.type.isManagerOf(actortype) + return _cxxManagedContainerType( + Type(_actorName(actortype.name(), side)), const=const, ref=ref + ) + + def subtreeUsesShmem(self): + return _subtreeUsesShmem(self) + + @staticmethod + def upgrade(protocol): + assert isinstance(protocol, ipdl.ast.Protocol) + protocol.__class__ = Protocol + + +class TranslationUnit(ipdl.ast.TranslationUnit): + @staticmethod + def upgrade(tu): + assert isinstance(tu, ipdl.ast.TranslationUnit) + tu.__class__ = TranslationUnit + + +# ----------------------------------------------------------------------------- + +pod_types = { + "::int8_t": 1, + "::uint8_t": 1, + "::int16_t": 2, + "::uint16_t": 2, + "::int32_t": 4, + "::uint32_t": 4, + "::int64_t": 8, + "::uint64_t": 8, + "float": 4, + "double": 8, +} +max_pod_size = max(pod_types.values()) +# We claim that all types we don't recognize are automatically "bigger" +# than pod types for ease of sorting. +pod_size_sentinel = max_pod_size * 2 + + +def pod_size(ipdltype): + if not ipdltype.isCxx(): + return pod_size_sentinel + + return pod_types.get(ipdltype.fullname(), pod_size_sentinel) + + +class _DecorateWithCxxStuff(ipdl.ast.Visitor): + """Phase 1 of lowering: decorate the IPDL AST with information + relevant to C++ code generation. + + This pass results in an AST that is a poor man's "IR"; in reality, a + "hybrid" AST mainly consisting of IPDL nodes with new C++ info along + with some new IPDL/C++ nodes that are tuned for C++ codegen.""" + + def __init__(self): + self.visitedTus = set() + self.protocolName = None + + def visitTranslationUnit(self, tu): + if tu not in self.visitedTus: + self.visitedTus.add(tu) + ipdl.ast.Visitor.visitTranslationUnit(self, tu) + if not isinstance(tu, TranslationUnit): + TranslationUnit.upgrade(tu) + + def visitInclude(self, inc): + if inc.tu.filetype == "header": + inc.tu.accept(self) + + def visitProtocol(self, pro): + self.protocolName = pro.name + Protocol.upgrade(pro) + return ipdl.ast.Visitor.visitProtocol(self, pro) + + def visitStructDecl(self, sd): + if not isinstance(sd, StructDecl): + newfields = [_StructField(f.decl.type, f.name, sd) for f in sd.fields] + + # Compute a permutation of the fields for in-memory storage such + # that the memory layout of the structure will be well-packed. + permutation = list(range(len(newfields))) + + # Note that the results of `pod_size` ensure that non-POD fields + # sort before POD ones. + def size(idx): + return pod_size(newfields[idx].ipdltype) + + permutation.sort(key=size, reverse=True) + + sd.fields = newfields + sd.packed_field_order = permutation + StructDecl.upgrade(sd) + + def visitUnionDecl(self, ud): + ud.components = [_UnionMember(ctype, ud) for ctype in ud.decl.type.components] + UnionDecl.upgrade(ud) + + def visitDecl(self, decl): + return _HybridDecl(decl.type, decl.progname, decl.attributes) + + def visitMessageDecl(self, md): + md.namespace = self.protocolName + md.params = [param.accept(self) for param in md.inParams] + md.returns = [ret.accept(self) for ret in md.outParams] + MessageDecl.upgrade(md) + + +# ----------------------------------------------------------------------------- + + +def msgenums(protocol, pretty=False): + msgenum = TypeEnum("MessageType") + msgstart = _messageStartName(protocol.decl.type) + " << 16" + msgenum.addId(protocol.name + "Start", msgstart) + + for md in protocol.messageDecls: + msgenum.addId(md.prettyMsgName() if pretty else md.msgId()) + if md.hasReply(): + msgenum.addId(md.prettyReplyName() if pretty else md.replyId()) + + msgenum.addId(protocol.name + "End") + return msgenum + + +class _GenerateProtocolCode(ipdl.ast.Visitor): + """Creates code common to both the parent and child actors.""" + + def __init__(self): + self.protocol = None # protocol we're generating a class for + self.hdrfile = None # what will become Protocol.h + self.cppfile = None # what will become Protocol.cpp + self.cppIncludeHeaders = [] + self.structUnionDefns = [] + self.funcDefns = [] + + def lower(self, tu, cxxHeaderFile, cxxFile, segmentcapacitydict): + self.protocol = tu.protocol + self.hdrfile = cxxHeaderFile + self.cppfile = cxxFile + self.segmentcapacitydict = segmentcapacitydict + tu.accept(self) + + def visitTranslationUnit(self, tu): + hf = self.hdrfile + + hf.addthing(_DISCLAIMER) + hf.addthings(_includeGuardStart(hf)) + hf.addthing(Whitespace.NL) + + for inc in builtinHeaderIncludes: + self.visitBuiltinCxxInclude(inc) + + # Compute the set of includes we need for declared structure/union + # classes for this protocol. + typesToIncludes = {} + for using in tu.using: + typestr = str(using.type) + if typestr not in typesToIncludes: + typesToIncludes[typestr] = using.header + else: + assert typesToIncludes[typestr] == using.header + + aggregateTypeIncludes = set() + for su in tu.structsAndUnions: + typedeps = _ComputeTypeDeps(su.decl.type, typesToIncludes) + if isinstance(su, ipdl.ast.StructDecl): + aggregateTypeIncludes.add("mozilla/ipc/IPDLStructMember.h") + for f in su.fields: + f.ipdltype.accept(typedeps) + elif isinstance(su, ipdl.ast.UnionDecl): + for c in su.components: + c.ipdltype.accept(typedeps) + + aggregateTypeIncludes.update(typedeps.includeHeaders) + + if len(aggregateTypeIncludes) != 0: + hf.addthing(Whitespace.NL) + hf.addthings([Whitespace("// Headers for typedefs"), Whitespace.NL]) + + for headername in sorted(iter(aggregateTypeIncludes)): + hf.addthing(CppDirective("include", '"' + headername + '"')) + + # Manually run Visitor.visitTranslationUnit. For dependency resolution + # we need to handle structs and unions separately. + for cxxInc in tu.cxxIncludes: + cxxInc.accept(self) + for inc in tu.includes: + inc.accept(self) + self.generateStructsAndUnions(tu) + for using in tu.builtinUsing: + using.accept(self) + for using in tu.using: + using.accept(self) + if tu.protocol: + tu.protocol.accept(self) + + if tu.filetype == "header": + self.cppIncludeHeaders.append(_ipdlhHeaderName(tu) + ".h") + + hf.addthing(Whitespace.NL) + hf.addthings(_includeGuardEnd(hf)) + + cf = self.cppfile + cf.addthings( + ( + [_DISCLAIMER, Whitespace.NL] + + [ + CppDirective("include", '"' + h + '"') + for h in self.cppIncludeHeaders + ] + + [Whitespace.NL] + + [ + CppDirective("include", '"%s"' % filename) + for filename in ipdl.builtin.CppIncludes + ] + + [Whitespace.NL] + ) + ) + + if self.protocol: + # construct the namespace into which we'll stick all our defns + ns = Namespace(self.protocol.name) + cf.addthing(_putInNamespaces(ns, self.protocol.namespaces)) + ns.addstmts(([Whitespace.NL] + self.funcDefns + [Whitespace.NL])) + + cf.addthings(self.structUnionDefns) + + def visitBuiltinCxxInclude(self, inc): + self.hdrfile.addthing(CppDirective("include", '"' + inc.file + '"')) + + def visitCxxInclude(self, inc): + self.cppIncludeHeaders.append(inc.file) + + def visitInclude(self, inc): + if inc.tu.filetype == "header": + self.hdrfile.addthing( + CppDirective("include", '"' + _ipdlhHeaderName(inc.tu) + '.h"') + ) + # Inherit cpp includes defined by imported header files, as they may + # be required to serialize an imported `using` type. + for cxxinc in inc.tu.cxxIncludes: + cxxinc.accept(self) + else: + self.cppIncludeHeaders += [ + _protocolHeaderName(inc.tu.protocol, "parent") + ".h", + _protocolHeaderName(inc.tu.protocol, "child") + ".h", + ] + + def generateStructsAndUnions(self, tu): + """Generate the definitions for all structs and unions. This will + re-order the declarations if needed in the C++ code such that + dependencies have already been defined.""" + decls = OrderedDict() + for su in tu.structsAndUnions: + if isinstance(su, StructDecl): + which = "struct" + forwarddecls, fulldecltypes, cls = _generateCxxStruct(su) + traitsdecl, traitsdefns = _ParamTraits.structPickling(su.decl.type) + else: + assert isinstance(su, UnionDecl) + which = "union" + forwarddecls, fulldecltypes, cls = _generateCxxUnion(su) + traitsdecl, traitsdefns = _ParamTraits.unionPickling(su.decl.type) + + clsdecl, methoddefns = _splitClassDeclDefn(cls) + + # Store the declarations in the decls map so we can emit in + # dependency order. + decls[su.decl.type] = ( + fulldecltypes, + [Whitespace.NL] + + forwarddecls + + [ + Whitespace( + """ +//----------------------------------------------------------------------------- +// Declaration of the IPDL type |%s %s| +// +""" + % (which, su.name) + ), + _putInNamespaces(clsdecl, su.namespaces), + ] + + [Whitespace.NL, traitsdecl], + ) + + self.structUnionDefns.extend( + [ + Whitespace( + """ +//----------------------------------------------------------------------------- +// Method definitions for the IPDL type |%s %s| +// +""" + % (which, su.name) + ), + _putInNamespaces(methoddefns, su.namespaces), + Whitespace.NL, + traitsdefns, + ] + ) + + # Generate the declarations structs in dependency order. + def gen_struct(deps, defn): + for dep in deps: + if dep in decls: + d, t = decls[dep] + del decls[dep] + gen_struct(d, t) + self.hdrfile.addthings(defn) + + while len(decls) > 0: + _, (d, t) = decls.popitem(False) + gen_struct(d, t) + + def visitProtocol(self, p): + self.cppIncludeHeaders.append(_protocolHeaderName(self.protocol, "") + ".h") + self.cppIncludeHeaders.append( + _protocolHeaderName(self.protocol, "Parent") + ".h" + ) + self.cppIncludeHeaders.append( + _protocolHeaderName(self.protocol, "Child") + ".h" + ) + + # Forward declare our own actors. + self.hdrfile.addthings( + [ + Whitespace.NL, + _makeForwardDeclForActor(p.decl.type, "Parent"), + _makeForwardDeclForActor(p.decl.type, "Child"), + ] + ) + + self.hdrfile.addthing( + Whitespace( + """ +//----------------------------------------------------------------------------- +// Code common to %sChild and %sParent +// +""" + % (p.name, p.name) + ) + ) + + # construct the namespace into which we'll stick all our decls + ns = Namespace(self.protocol.name) + self.hdrfile.addthing(_putInNamespaces(ns, p.namespaces)) + ns.addstmt(Whitespace.NL) + + for func in self.genEndpointFuncs(): + edecl, edefn = _splitFuncDeclDefn(func) + ns.addstmts([edecl, Whitespace.NL]) + self.funcDefns.append(edefn) + + # spit out message type enum and classes + msgenum = msgenums(self.protocol) + ns.addstmts([StmtDecl(Decl(msgenum, "")), Whitespace.NL]) + + for md in p.messageDecls: + decls = [] + + # Look up the segment capacity used for serializing this + # message. If the capacity is not specified, use '0' for + # the default capacity (defined in ipc_message.cc) + name = "%s::%s" % (md.namespace, md.decl.progname) + segmentcapacity = self.segmentcapacitydict.get(name, 0) + + mfDecl, mfDefn = _splitFuncDeclDefn( + _generateMessageConstructor(md, segmentcapacity, p, forReply=False) + ) + decls.append(mfDecl) + self.funcDefns.append(mfDefn) + + if md.hasReply(): + rfDecl, rfDefn = _splitFuncDeclDefn( + _generateMessageConstructor(md, 0, p, forReply=True) + ) + decls.append(rfDecl) + self.funcDefns.append(rfDefn) + + decls.append(Whitespace.NL) + ns.addstmts(decls) + + ns.addstmts([Whitespace.NL, Whitespace.NL]) + + # Generate code for PFoo::CreateEndpoints. + def genEndpointFuncs(self): + p = self.protocol.decl.type + tparent = _cxxBareType(ActorType(p), "Parent", fq=True) + tchild = _cxxBareType(ActorType(p), "Child", fq=True) + + def mkOverload(includepids): + params = [] + if includepids: + params = [ + Decl(Type("base::ProcessId"), "aParentDestPid"), + Decl(Type("base::ProcessId"), "aChildDestPid"), + ] + params += [ + Decl( + Type("mozilla::ipc::Endpoint<" + tparent.name + ">", ptr=True), + "aParent", + ), + Decl( + Type("mozilla::ipc::Endpoint<" + tchild.name + ">", ptr=True), + "aChild", + ), + ] + openfunc = MethodDefn( + MethodDecl("CreateEndpoints", params=params, ret=Type.NSRESULT) + ) + openfunc.addcode( + """ + return mozilla::ipc::CreateEndpoints( + mozilla::ipc::PrivateIPDLInterface(), + $,{args}); + """, + args=[ExprVar(d.name) for d in params], + ) + return openfunc + + funcs = [mkOverload(True)] + if not p.hasOtherPid(): + funcs.append(mkOverload(False)) + return funcs + + +# -------------------------------------------------- + +cppPriorityList = list( + map(lambda src: src.upper() + "_PRIORITY", ipdl.ast.priorityList) +) + + +def _generateMessageConstructor(md, segmentSize, protocol, forReply=False): + if forReply: + clsname = md.replyCtorFunc() + msgid = md.replyId() + replyEnum = "REPLY" + prioEnum = cppPriorityList[md.decl.type.replyPrio] + else: + clsname = md.msgCtorFunc() + msgid = md.msgId() + replyEnum = "NOT_REPLY" + prioEnum = cppPriorityList[md.decl.type.prio] + + nested = md.decl.type.nested + compress = md.decl.type.compress + lazySend = md.decl.type.lazySend + + routingId = ExprVar("routingId") + + func = FunctionDefn( + FunctionDecl( + clsname, + params=[Decl(Type("int32_t"), routingId.name)], + ret=Type("mozilla::UniquePtr<IPC::Message>"), + ) + ) + + if not compress: + compression = "COMPRESSION_NONE" + elif compress.value == "all": + compression = "COMPRESSION_ALL" + else: + assert compress.value is None + compression = "COMPRESSION_ENABLED" + + if lazySend: + lazySendEnum = "LAZY_SEND" + else: + lazySendEnum = "EAGER_SEND" + + if nested == ipdl.ast.NOT_NESTED: + nestedEnum = "NOT_NESTED" + elif nested == ipdl.ast.INSIDE_SYNC_NESTED: + nestedEnum = "NESTED_INSIDE_SYNC" + else: + assert nested == ipdl.ast.INSIDE_CPOW_NESTED + nestedEnum = "NESTED_INSIDE_CPOW" + + if md.decl.type.isSync(): + syncEnum = "SYNC" + else: + syncEnum = "ASYNC" + + # FIXME(bug ???) - remove support for interrupt messages from the IPDL compiler. + if md.decl.type.isInterrupt(): + func.addcode( + """ + static_assert( + false, + "runtime support for intr messages has been removed from IPDL"); + """ + ) + + if md.decl.type.isCtor(): + ctorEnum = "CONSTRUCTOR" + else: + ctorEnum = "NOT_CONSTRUCTOR" + + def messageEnum(valname): + return ExprVar("IPC::Message::" + valname) + + flags = ExprCall( + ExprVar("IPC::Message::HeaderFlags"), + args=[ + messageEnum(nestedEnum), + messageEnum(prioEnum), + messageEnum(compression), + messageEnum(lazySendEnum), + messageEnum(ctorEnum), + messageEnum(syncEnum), + messageEnum(replyEnum), + ], + ) + + segmentSize = int(segmentSize) + if not segmentSize: + segmentSize = 0 + func.addstmt( + StmtReturn( + ExprCall( + ExprVar("IPC::Message::IPDLMessage"), + args=[ + routingId, + ExprVar(msgid), + ExprLiteral.Int(int(segmentSize)), + flags, + ], + ) + ) + ) + + return func + + +# -------------------------------------------------- + + +class _ParamTraits: + var = ExprVar("aVar") + writervar = ExprVar("aWriter") + readervar = ExprVar("aReader") + + @classmethod + def ifsideis(cls, rdrwtr, side, then, els=None): + cxxside = ExprVar("mozilla::ipc::ChildSide") + if side == "parent": + cxxside = ExprVar("mozilla::ipc::ParentSide") + + ifstmt = StmtIf( + ExprBinary( + cxxside, + "==", + ExprCode("${rdrwtr}->GetActor()->GetSide()", rdrwtr=rdrwtr), + ) + ) + ifstmt.addifstmt(then) + if els is not None: + ifstmt.addelsestmt(els) + return ifstmt + + @classmethod + def fatalError(cls, rdrwtr, reason): + return StmtCode( + "${rdrwtr}->FatalError(${reason});", + rdrwtr=rdrwtr, + reason=ExprLiteral.String(reason), + ) + + @classmethod + def writeSentinel(cls, writervar, sentinelKey): + return [ + Whitespace("// Sentinel = " + repr(sentinelKey) + "\n", indent=True), + StmtExpr( + ExprCall( + ExprSelect(writervar, "->", "WriteSentinel"), + args=[ExprLiteral.Int(hashfunc(sentinelKey))], + ) + ), + ] + + @classmethod + def readSentinel(cls, readervar, sentinelKey, sentinelFail): + # Read the sentinel + read = ExprCall( + ExprSelect(readervar, "->", "ReadSentinel"), + args=[ExprLiteral.Int(hashfunc(sentinelKey))], + ) + ifsentinel = StmtIf(ExprNot(read)) + ifsentinel.addifstmts(sentinelFail) + + return [ + Whitespace("// Sentinel = " + repr(sentinelKey) + "\n", indent=True), + ifsentinel, + ] + + @classmethod + def write(cls, var, writervar, ipdltype=None): + if ipdltype and _cxxTypeNeedsMoveForSend(ipdltype): + var = ExprMove(var) + return ExprCall(ExprVar("IPC::WriteParam"), args=[writervar, var]) + + @classmethod + def checkedWrite(cls, ipdltype, var, writervar, sentinelKey): + assert sentinelKey + block = Block() + + block.addstmts( + [ + StmtExpr(cls.write(var, writervar, ipdltype)), + ] + ) + block.addstmts(cls.writeSentinel(writervar, sentinelKey)) + return block + + @classmethod + def bulkSentinelKey(cls, fields): + return " | ".join(f.basename for f in fields) + + @classmethod + def checkedBulkWrite(cls, var, size, fields): + block = Block() + first = fields[0] + + block.addstmts( + [ + StmtExpr( + ExprCall( + ExprSelect(cls.writervar, "->", "WriteBytes"), + args=[ + ExprAddrOf( + ExprCall(first.getMethod(thisexpr=var, sel=".")) + ), + ExprLiteral.Int(size * len(fields)), + ], + ) + ) + ] + ) + block.addstmts(cls.writeSentinel(cls.writervar, cls.bulkSentinelKey(fields))) + + return block + + @classmethod + def checkedBulkRead(cls, var, size, fields): + block = Block() + first = fields[0] + + readbytes = ExprCall( + ExprSelect(cls.readervar, "->", "ReadBytesInto"), + args=[ + ExprAddrOf(ExprCall(first.getMethod(thisexpr=var, sel="->"))), + ExprLiteral.Int(size * len(fields)), + ], + ) + ifbad = StmtIf(ExprNot(readbytes)) + errmsg = "Error bulk reading fields from %s" % first.ipdltype.name() + ifbad.addifstmts( + [cls.fatalError(cls.readervar, errmsg), StmtReturn(readResultError())] + ) + block.addstmt(ifbad) + block.addstmts( + cls.readSentinel( + cls.readervar, + cls.bulkSentinelKey(fields), + errfnSentinel(readResultError())(errmsg), + ) + ) + + return block + + @classmethod + def checkedRead( + cls, + ipdltype, + cxxtype, + var, + readervar, + errfn, + paramtype, + sentinelKey, + errfnSentinel, + ): + assert isinstance(var, ExprVar) + + if not isinstance(paramtype, list): + paramtype = ["Error deserializing " + paramtype] + + block = Block() + + # Read the data + block.addcode( + """ + auto ${maybevar} = IPC::ReadParam<${ty}>(${reader}); + if (!${maybevar}) { + $*{errfn} + } + auto& ${var} = *${maybevar}; + """, + maybevar=ExprVar("maybe__" + var.name), + ty=cxxtype, + reader=readervar, + errfn=errfn(*paramtype), + var=var, + ) + + block.addstmts( + cls.readSentinel(readervar, sentinelKey, errfnSentinel(*paramtype)) + ) + + return block + + # Helper wrapper for checkedRead for use within _ParamTraits + @classmethod + def _checkedRead(cls, ipdltype, cxxtype, var, sentinelKey, what): + def errfn(msg): + return [cls.fatalError(cls.readervar, msg), StmtReturn(readResultError())] + + return cls.checkedRead( + ipdltype, + cxxtype, + var, + cls.readervar, + errfn=errfn, + paramtype=what, + sentinelKey=sentinelKey, + errfnSentinel=errfnSentinel(readResultError()), + ) + + @classmethod + def generateDecl(cls, fortype, write, read, needsmove=False): + # ParamTraits impls are selected ignoring constness, and references. + pt = Class( + "ParamTraits", + specializes=Type( + fortype.name, T=fortype.T, inner=fortype.inner, ptr=fortype.ptr + ), + struct=True, + ) + + # typedef T paramType; + pt.addstmt(Typedef(fortype, "paramType")) + + # static void Write(Message*, const T&); + if needsmove: + intype = Type("paramType", rvalref=True) + else: + intype = Type("paramType", ref=True, const=True) + writemthd = MethodDefn( + MethodDecl( + "Write", + params=[ + Decl(Type("IPC::MessageWriter", ptr=True), cls.writervar.name), + Decl(intype, cls.var.name), + ], + methodspec=MethodSpec.STATIC, + ) + ) + writemthd.addstmts(write) + pt.addstmt(writemthd) + + # static ReadResult<T> Read(MessageReader*); + readmthd = MethodDefn( + MethodDecl( + "Read", + params=[ + Decl(Type("IPC::MessageReader", ptr=True), cls.readervar.name), + ], + ret=Type("IPC::ReadResult<paramType>"), + methodspec=MethodSpec.STATIC, + ) + ) + readmthd.addstmts(read) + pt.addstmt(readmthd) + + # Split the class into declaration and definition + clsdecl, methoddefns = _splitClassDeclDefn(pt) + + namespaces = [Namespace("IPC")] + clsns = _putInNamespaces(clsdecl, namespaces) + defns = _putInNamespaces(methoddefns, namespaces) + return clsns, defns + + @classmethod + def actorPickling(cls, actortype, side): + """Generates pickling for IPDL actors. This is a |nullable| deserializer. + Write and read callers will perform nullability validation.""" + + cxxtype = _cxxBareType(actortype, side, fq=True) + + write = StmtCode( + """ + MOZ_RELEASE_ASSERT( + ${writervar}->GetActor(), + "Cannot serialize managed actors without an actor"); + + int32_t id; + if (!${var}) { + id = 0; // kNullActorId + } else { + id = ${var}->Id(); + if (id == 1) { // kFreedActorId + ${var}->FatalError("Actor has been |delete|d"); + } + MOZ_RELEASE_ASSERT( + ${writervar}->GetActor()->GetIPCChannel() == ${var}->GetIPCChannel(), + "Actor must be from the same channel as the" + " actor it's being sent over"); + MOZ_RELEASE_ASSERT( + ${var}->CanSend(), + "Actor must still be open when sending"); + } + + ${write}; + """, + var=cls.var, + writervar=cls.writervar, + write=cls.write(ExprVar("id"), cls.writervar), + ) + + # bool Read(..) impl + read = StmtCode( + """ + MOZ_RELEASE_ASSERT( + ${readervar}->GetActor(), + "Cannot deserialize managed actors without an actor"); + mozilla::Maybe<mozilla::ipc::IProtocol*> actor = ${readervar}->GetActor() + ->ReadActor(${readervar}, true, ${actortype}, ${protocolid}); + if (actor.isSome()) { + return static_cast<${cxxtype}>(actor.ref()); + } + return {}; + """, + readervar=cls.readervar, + actortype=ExprLiteral.String(actortype.name()), + protocolid=_protocolId(actortype), + cxxtype=cxxtype, + ) + + return cls.generateDecl(cxxtype, [write], [read]) + + @classmethod + def structPickling(cls, structtype): + sd = structtype._ast + # NOTE: Not using _cxxBareType here as we don't have a side + cxxtype = Type(structtype.fullname()) + + write = [] + read = [] + + # First serialize/deserialize all non-pod data in IPDL order. These need + # to be read/written first because they'll be used to invoke the IPDL + # struct's constructor. + ctorargs = [] + for f in sd.fields_ipdl_order(): + if pod_size(f.ipdltype) == pod_size_sentinel: + write.append( + cls.checkedWrite( + f.ipdltype, + ExprCall(f.getMethod(thisexpr=cls.var, sel=".")), + cls.writervar, + sentinelKey=f.basename, + ) + ) + read.append( + cls._checkedRead( + f.ipdltype, + f.bareType(fq=True), + f.argVar(), + f.basename, + "'" + + f.getMethod().name + + "' " + + "(" + + f.ipdltype.name() + + ") member of " + + "'" + + structtype.name() + + "'", + ) + ) + if _cxxTypeCanMove(f.ipdltype): + ctorargs.append(ExprMove(f.argVar())) + else: + ctorargs.append(f.argVar()) + else: + # We're going to bulk-read in this value later, so we'll just + # zero-initialize it for now. + ctorargs.append(ExprCode("${type}{0}", type=f.bareType(fq=True))) + + resultvar = ExprVar("result__") + read.append( + StmtDecl( + Decl(_cxxReadResultType(Type("paramType")), resultvar.name), + initargs=[ExprVar("std::in_place")] + ctorargs, + ) + ) + + # After non-pod data, bulk read/write pod data in member order. This has + # to be done after the result has been constructed, so that we have + # somewhere to read into. + for (size, fields) in itertools.groupby( + sd.fields_member_order(), lambda f: pod_size(f.ipdltype) + ): + if size != pod_size_sentinel: + fields = list(fields) + write.append(cls.checkedBulkWrite(cls.var, size, fields)) + read.append(cls.checkedBulkRead(resultvar, size, fields)) + + read.append(StmtReturn(resultvar)) + + return cls.generateDecl( + cxxtype, write, read, needsmove=_cxxTypeNeedsMoveForSend(structtype) + ) + + @classmethod + def unionPickling(cls, uniontype): + # NOTE: Not using _cxxBareType here as we don't have a side + cxxtype = Type(uniontype.fullname()) + ud = uniontype._ast + + # Use typedef to set up an alias so it's easier to reference the struct type. + alias = "union__" + typevar = ExprVar("type") + + prelude = [ + Typedef(cxxtype, alias), + ] + + writeswitch = StmtSwitch(typevar) + write = prelude + [ + StmtDecl(Decl(Type.INT, typevar.name), init=ud.callType(cls.var)), + cls.checkedWrite( + None, typevar, cls.writervar, sentinelKey=uniontype.name() + ), + Whitespace.NL, + writeswitch, + ] + + readswitch = StmtSwitch(typevar) + read = prelude + [ + cls._checkedRead( + None, + Type.INT, + typevar, + uniontype.name(), + "type of union " + uniontype.name(), + ), + Whitespace.NL, + readswitch, + ] + + for c in ud.components: + caselabel = CaseLabel(alias + "::" + c.enum()) + origenum = c.enum() + + writecase = StmtBlock() + wstmt = cls.checkedWrite( + c.ipdltype, + ExprCall(ExprSelect(cls.var, ".", c.getTypeName())), + cls.writervar, + sentinelKey=c.enum(), + ) + writecase.addstmts([wstmt, StmtReturn()]) + writeswitch.addcase(caselabel, writecase) + + readcase = StmtBlock() + tmpvar = ExprVar("tmp") + readcase.addstmts( + [ + cls._checkedRead( + c.ipdltype, + c.bareType(fq=True), + tmpvar, + origenum, + "variant " + origenum + " of union " + uniontype.name(), + ), + StmtReturn(ExprMove(tmpvar)), + ] + ) + readswitch.addcase(caselabel, readcase) + + # Add the error default case + writeswitch.addcase( + DefaultLabel(), + StmtBlock( + [ + cls.fatalError( + cls.writervar, "unknown variant of union " + uniontype.name() + ), + StmtReturn(), + ] + ), + ) + readswitch.addcase( + DefaultLabel(), + StmtBlock( + [ + cls.fatalError( + cls.readervar, "unknown variant of union " + uniontype.name() + ), + StmtReturn(readResultError()), + ] + ), + ) + + return cls.generateDecl( + cxxtype, write, read, needsmove=_cxxTypeNeedsMoveForSend(uniontype) + ) + + +# -------------------------------------------------- + + +class _ComputeTypeDeps(TypeVisitor): + """Pass that gathers the C++ types that a particular IPDL type + (recursively) depends on. There are three kinds of dependencies: (i) + types that need forward declaration; (ii) types that need a |using| + stmt; (iii) IPDL structs or unions which must be fully declared + before this struct. Some types generate multiple kinds.""" + + def __init__(self, fortype, typesToIncludes=None): + ipdl.type.TypeVisitor.__init__(self) + self.usingTypedefs = [] + self.forwardDeclStmts = [] + self.fullDeclTypes = [] + self.includeHeaders = set() + self.fortype = fortype + self.typesToIncludes = typesToIncludes + + def maybeTypedef(self, fqname, name, templateargs=[]): + assert fqname.startswith("::") + if fqname != name: + self.usingTypedefs.append(Typedef(Type(fqname), name, templateargs)) + if self.typesToIncludes is not None and fqname in self.typesToIncludes: + self.includeHeaders.add(self.typesToIncludes[fqname]) + + def visitImportedCxxType(self, t): + if t in self.visited: + return + self.visited.add(t) + self.maybeTypedef(t.fullname(), t.name()) + + def visitActorType(self, t): + if t in self.visited: + return + self.visited.add(t) + + fqname, name = t.fullname(), t.name() + + self.includeHeaders.add("mozilla/ipc/SideVariant.h") + self.maybeTypedef(_actorName(fqname, "Parent"), _actorName(name, "Parent")) + self.maybeTypedef(_actorName(fqname, "Child"), _actorName(name, "Child")) + + self.forwardDeclStmts.extend( + [ + _makeForwardDeclForActor(t.protocol, "parent"), + Whitespace.NL, + _makeForwardDeclForActor(t.protocol, "child"), + Whitespace.NL, + ] + ) + + def visitStructOrUnionType(self, su, defaultVisit): + if su in self.visited or su == self.fortype: + return + self.visited.add(su) + self.maybeTypedef(su.fullname(), su.name()) + + # Mutually recursive fields in unions are behind indirection, so we only + # need a forward decl, and don't need a full type declaration. + if isinstance(self.fortype, UnionType) and self.fortype.mutuallyRecursiveWith( + su + ): + self.forwardDeclStmts.append(_makeForwardDecl(su)) + else: + self.fullDeclTypes.append(su) + + return defaultVisit(self, su) + + def visitStructType(self, t): + return self.visitStructOrUnionType(t, TypeVisitor.visitStructType) + + def visitUnionType(self, t): + return self.visitStructOrUnionType(t, TypeVisitor.visitUnionType) + + def visitArrayType(self, t): + return TypeVisitor.visitArrayType(self, t) + + def visitMaybeType(self, m): + return TypeVisitor.visitMaybeType(self, m) + + def visitShmemType(self, s): + if s in self.visited: + return + self.visited.add(s) + self.maybeTypedef("::mozilla::ipc::Shmem", "Shmem") + + def visitByteBufType(self, s): + if s in self.visited: + return + self.visited.add(s) + self.maybeTypedef("::mozilla::ipc::ByteBuf", "ByteBuf") + + def visitFDType(self, s): + if s in self.visited: + return + self.visited.add(s) + self.maybeTypedef("::mozilla::ipc::FileDescriptor", "FileDescriptor") + + def visitEndpointType(self, s): + if s in self.visited: + return + self.visited.add(s) + self.maybeTypedef("::mozilla::ipc::Endpoint", "Endpoint", ["FooSide"]) + self.visitActorType(s.actor) + + def visitManagedEndpointType(self, s): + if s in self.visited: + return + self.visited.add(s) + self.maybeTypedef( + "::mozilla::ipc::ManagedEndpoint", "ManagedEndpoint", ["FooSide"] + ) + self.visitActorType(s.actor) + + def visitUniquePtrType(self, s): + if s in self.visited: + return + self.visited.add(s) + + def visitVoidType(self, v): + assert 0 + + def visitMessageType(self, v): + assert 0 + + def visitProtocolType(self, v): + assert 0 + + +def _fieldStaticAssertions(sd): + staticasserts = [] + for (size, fields) in itertools.groupby( + sd.fields_member_order(), lambda f: pod_size(f.ipdltype) + ): + if size == pod_size_sentinel: + continue + + fields = list(fields) + if len(fields) == 1: + continue + + staticasserts.append( + StmtCode( + """ + static_assert( + (offsetof(${struct}, ${last}) - offsetof(${struct}, ${first})) == ${expected}, + "Bad assumptions about field layout!"); + """, + struct=sd.name, + first=fields[0].memberVar(), + last=fields[-1].memberVar(), + expected=ExprLiteral.Int(size * (len(fields) - 1)), + ) + ) + + return staticasserts + + +def _generateCxxStruct(sd): + """ """ + # compute all the typedefs and forward decls we need to make + gettypedeps = _ComputeTypeDeps(sd.decl.type) + for f in sd.fields: + f.ipdltype.accept(gettypedeps) + + usingTypedefs = gettypedeps.usingTypedefs + forwarddeclstmts = gettypedeps.forwardDeclStmts + fulldecltypes = gettypedeps.fullDeclTypes + + struct = Class(sd.name, final=True) + struct.addstmts([Label.PRIVATE] + usingTypedefs + [Whitespace.NL, Label.PUBLIC]) + + constreftype = Type(sd.name, const=True, ref=True) + + # Struct() + # We want the default constructor to be declared if it is available, but + # some of our members may not be default-constructible. Silence the + # warning which clang generates in that case. + # + # Members which need value initialization will be handled by wrapping + # the member in a template type when declaring them. + struct.addcode( + """ + #ifdef __clang__ + # pragma clang diagnostic push + # if __has_warning("-Wdefaulted-function-deleted") + # pragma clang diagnostic ignored "-Wdefaulted-function-deleted" + # endif + #endif + ${name}() = default; + #ifdef __clang__ + # pragma clang diagnostic pop + #endif + + """, + name=sd.name, + ) + + # If this is an empty struct (no fields), then the default ctor + # and "create-with-fields" ctors are equivalent. + if len(sd.fields): + assert len(sd.fields) == len(sd.packed_field_order) + + # Struct(const field1& _f1, ...) + valctor = ConstructorDefn( + ConstructorDecl( + sd.name, + params=[ + Decl( + f.forceMoveType() + if _cxxTypeNeedsMoveForData(f.ipdltype) + else f.constRefType(), + f.argVar().name, + ) + for f in sd.fields_ipdl_order() + ], + force_inline=True, + ) + ) + valctor.memberinits = [] + for f in sd.fields_member_order(): + arg = f.argVar() + if _cxxTypeNeedsMoveForData(f.ipdltype): + arg = ExprMove(arg) + valctor.memberinits.append(ExprMemberInit(f.memberVar(), args=[arg])) + + struct.addstmts([valctor, Whitespace.NL]) + + # If a constructor which moves each argument would be different from the + # `const T&` version, also generate that constructor. + if not all( + _cxxTypeNeedsMoveForData(f.ipdltype) or not _cxxTypeCanMove(f.ipdltype) + for f in sd.fields_ipdl_order() + ): + # Struct(field1&& _f1, ...) + valmovector = ConstructorDefn( + ConstructorDecl( + sd.name, + params=[ + Decl( + f.forceMoveType() + if _cxxTypeCanMove(f.ipdltype) + else f.constRefType(), + f.argVar().name, + ) + for f in sd.fields_ipdl_order() + ], + force_inline=True, + ) + ) + + valmovector.memberinits = [] + for f in sd.fields_member_order(): + arg = f.argVar() + if _cxxTypeCanMove(f.ipdltype): + arg = ExprMove(arg) + valmovector.memberinits.append( + ExprMemberInit(f.memberVar(), args=[arg]) + ) + + struct.addstmts([valmovector, Whitespace.NL]) + + # The default copy, move, and assignment constructors, and the default + # destructor, will do the right thing. + + if "Comparable" in sd.attributes: + # bool operator==(const Struct& _o) + ovar = ExprVar("_o") + opeqeq = MethodDefn( + MethodDecl( + "operator==", + params=[Decl(constreftype, ovar.name)], + ret=Type.BOOL, + const=True, + ) + ) + for f in sd.fields_ipdl_order(): + ifneq = StmtIf( + ExprNot( + ExprBinary( + ExprCall(f.getMethod()), "==", ExprCall(f.getMethod(ovar)) + ) + ) + ) + ifneq.addifstmt(StmtReturn.FALSE) + opeqeq.addstmt(ifneq) + opeqeq.addstmt(StmtReturn.TRUE) + struct.addstmts([opeqeq, Whitespace.NL]) + + # bool operator!=(const Struct& _o) + opneq = MethodDefn( + MethodDecl( + "operator!=", + params=[Decl(constreftype, ovar.name)], + ret=Type.BOOL, + const=True, + ) + ) + opneq.addstmt(StmtReturn(ExprNot(ExprCall(ExprVar("operator=="), args=[ovar])))) + struct.addstmts([opneq, Whitespace.NL]) + + # field1& f1() + # const field1& f1() const + for f in sd.fields_ipdl_order(): + get = MethodDefn( + MethodDecl( + f.getMethod().name, params=[], ret=f.refType(), force_inline=True + ) + ) + get.addstmt(StmtReturn(f.refExpr())) + + getconstdecl = deepcopy(get.decl) + getconstdecl.ret = f.constRefType() + getconstdecl.const = True + getconst = MethodDefn(getconstdecl) + getconst.addstmt(StmtReturn(f.constRefExpr())) + + struct.addstmts([get, getconst, Whitespace.NL]) + + # private: + struct.addstmt(Label.PRIVATE) + + # Static assertions to ensure our assumptions about field layout match + # what the compiler is actually producing. We define this as a member + # function, rather than throwing the assertions in the constructor or + # similar, because we don't want to evaluate the static assertions every + # time the header file containing the structure is included. + staticasserts = _fieldStaticAssertions(sd) + if staticasserts: + method = MethodDefn( + MethodDecl("StaticAssertions", params=[], ret=Type.VOID, const=True) + ) + method.addstmts(staticasserts) + struct.addstmts([method]) + + # members + struct.addstmts( + [ + StmtDecl(Decl(_effectiveMemberType(f), f.memberVar().name)) + for f in sd.fields_member_order() + ] + ) + + return forwarddeclstmts, fulldecltypes, struct + + +def _effectiveMemberType(f): + effective_type = f.bareType() + # Structs must be copyable for backwards compatibility reasons, so we use + # CopyableTArray<T> as their member type for arrays. This is not exposed + # in the method signatures, these keep using nsTArray<T>, which is a base + # class of CopyableTArray<T>. + if effective_type.name == "nsTArray": + effective_type.name = "CopyableTArray" + return Type("::mozilla::ipc::IPDLStructMember", T=[effective_type]) + + +# -------------------------------------------------- + + +def _generateCxxUnion(ud): + # This Union class basically consists of a type (enum) and a + # union for storage. The union can contain POD and non-POD + # types. Each type needs a copy/move ctor, assignment operators, + # and dtor. + # + # Rather than templating this class and only providing + # specializations for the types we support, which is slightly + # "unsafe" in that C++ code can add additional specializations + # without the IPDL compiler's knowledge, we instead explicitly + # implement non-templated methods for each supported type. + # + # The one complication that arises is that C++, for arcane + # reasons, does not allow the placement destructor of a + # builtin type, like int, to be directly invoked. So we need + # to hack around this by internally typedef'ing all + # constituent types. Sigh. + # + # So, for each type, this "Union" class needs: + # (private) + # - entry in the type enum + # - entry in the storage union + # - [type]ptr() method to get a type* from the underlying union + # - same as above to get a const type* + # - typedef to hack around placement delete limitations + # (public) + # - placement delete case for dtor + # - copy ctor + # - move ctor + # - case in generic copy ctor + # - copy operator= impl + # - move operator= impl + # - case in generic operator= + # - operator [type&] + # - operator [const type&] const + # - [type&] get_[type]() + # - [const type&] get_[type]() const + # + cls = Class(ud.name, final=True) + # const Union&, i.e., Union type with inparam semantics + inClsType = Type(ud.name, const=True, ref=True) + refClsType = Type(ud.name, ref=True) + rvalueRefClsType = Type(ud.name, rvalref=True) + typetype = Type("Type") + valuetype = Type("Value") + mtypevar = ExprVar("mType") + mvaluevar = ExprVar("mValue") + maybedtorvar = ExprVar("MaybeDestroy") + assertsanityvar = ExprVar("AssertSanity") + tnonevar = ExprVar("T__None") + tlastvar = ExprVar("T__Last") + + def callAssertSanity(uvar=None, expectTypeVar=None): + func = assertsanityvar + args = [] + if uvar is not None: + func = ExprSelect(uvar, ".", assertsanityvar.name) + if expectTypeVar is not None: + args.append(expectTypeVar) + return ExprCall(func, args=args) + + def maybeDestroy(): + return StmtExpr(ExprCall(maybedtorvar)) + + # compute all the typedefs and forward decls we need to make + gettypedeps = _ComputeTypeDeps(ud.decl.type) + for c in ud.components: + c.ipdltype.accept(gettypedeps) + + usingTypedefs = gettypedeps.usingTypedefs + forwarddeclstmts = gettypedeps.forwardDeclStmts + fulldecltypes = gettypedeps.fullDeclTypes + + # the |Type| enum, used to switch on the discunion's real type + cls.addstmt(Label.PUBLIC) + typeenum = TypeEnum(typetype.name) + typeenum.addId(tnonevar.name, 0) + firstid = ud.components[0].enum() + typeenum.addId(firstid, 1) + for c in ud.components[1:]: + typeenum.addId(c.enum()) + typeenum.addId(tlastvar.name, ud.components[-1].enum()) + cls.addstmts([StmtDecl(Decl(typeenum, "")), Whitespace.NL]) + + cls.addstmt(Label.PRIVATE) + cls.addstmts( + usingTypedefs + # hacky typedef's that allow placement dtors of builtins + + [Typedef(c.internalType(), c.typedef()) for c in ud.components] + ) + cls.addstmt(Whitespace.NL) + + # the C++ union the discunion use for storage + valueunion = TypeUnion(valuetype.name) + for c in ud.components: + valueunion.addComponent(c.unionType(), c.name) + cls.addstmts([StmtDecl(Decl(valueunion, "")), Whitespace.NL]) + + # for each constituent type T, add private accessors that + # return a pointer to the Value union storage casted to |T*| + # and |const T*| + for c in ud.components: + getptr = MethodDefn( + MethodDecl( + c.getPtrName(), params=[], ret=c.ptrToInternalType(), force_inline=True + ) + ) + getptr.addstmt(StmtReturn(c.ptrToSelfExpr())) + + getptrconst = MethodDefn( + MethodDecl( + c.getConstPtrName(), + params=[], + ret=c.constPtrToType(), + const=True, + force_inline=True, + ) + ) + getptrconst.addstmt(StmtReturn(c.constptrToSelfExpr())) + + cls.addstmts([getptr, getptrconst]) + cls.addstmt(Whitespace.NL) + + # add a helper method that invokes the placement dtor on the + # current underlying value, only if |aNewType| is different + # than the current type, and returns true if the underlying + # value needs to be re-constructed + maybedtor = MethodDefn(MethodDecl(maybedtorvar.name, ret=Type.VOID)) + # wasn't /actually/ dtor'd, but it needs to be re-constructed + ifnone = StmtIf(ExprBinary(mtypevar, "==", tnonevar)) + ifnone.addifstmt(StmtReturn()) + # need to destroy. switch on underlying type + dtorswitch = StmtSwitch(mtypevar) + for c in ud.components: + dtorswitch.addcase( + CaseLabel(c.enum()), StmtBlock([StmtExpr(c.callDtor()), StmtBreak()]) + ) + dtorswitch.addcase( + DefaultLabel(), StmtBlock([_logicError("not reached"), StmtBreak()]) + ) + maybedtor.addstmts([ifnone, dtorswitch]) + cls.addstmts([maybedtor, Whitespace.NL]) + + # add helper methods that ensure the discunion has a + # valid type + sanity = MethodDefn( + MethodDecl(assertsanityvar.name, ret=Type.VOID, const=True, force_inline=True) + ) + sanity.addstmts( + [ + _abortIfFalse(ExprBinary(tnonevar, "<=", mtypevar), "invalid type tag"), + _abortIfFalse(ExprBinary(mtypevar, "<=", tlastvar), "invalid type tag"), + ] + ) + cls.addstmt(sanity) + + atypevar = ExprVar("aType") + sanity2 = MethodDefn( + MethodDecl( + assertsanityvar.name, + params=[Decl(typetype, atypevar.name)], + ret=Type.VOID, + const=True, + force_inline=True, + ) + ) + sanity2.addstmts( + [ + StmtExpr(ExprCall(assertsanityvar)), + _abortIfFalse(ExprBinary(mtypevar, "==", atypevar), "unexpected type tag"), + ] + ) + cls.addstmts([sanity2, Whitespace.NL]) + + # ---- begin public methods ----- + + # Union() default ctor + cls.addstmts( + [ + Label.PUBLIC, + ConstructorDefn( + ConstructorDecl(ud.name, force_inline=True), + memberinits=[ExprMemberInit(mtypevar, [tnonevar])], + ), + Whitespace.NL, + ] + ) + + # Union(const T&) copy & Union(T&&) move ctors + othervar = ExprVar("aOther") + for c in ud.components: + if not _cxxTypeNeedsMoveForData(c.ipdltype): + copyctor = ConstructorDefn( + ConstructorDecl(ud.name, params=[Decl(c.constRefType(), othervar.name)]) + ) + copyctor.addstmts( + [ + StmtExpr(c.callCtor(othervar)), + StmtExpr(ExprAssn(mtypevar, c.enumvar())), + ] + ) + cls.addstmts([copyctor, Whitespace.NL]) + + if not _cxxTypeCanMove(c.ipdltype): + continue + movector = ConstructorDefn( + ConstructorDecl(ud.name, params=[Decl(c.forceMoveType(), othervar.name)]) + ) + movector.addstmts( + [ + StmtExpr(c.callCtor(ExprMove(othervar))), + StmtExpr(ExprAssn(mtypevar, c.enumvar())), + ] + ) + cls.addstmts([movector, Whitespace.NL]) + + unionNeedsMove = any(_cxxTypeNeedsMoveForData(c.ipdltype) for c in ud.components) + + # Union(const Union&) copy ctor + if not unionNeedsMove: + copyctor = ConstructorDefn( + ConstructorDecl(ud.name, params=[Decl(inClsType, othervar.name)]) + ) + othertype = ud.callType(othervar) + copyswitch = StmtSwitch(othertype) + for c in ud.components: + copyswitch.addcase( + CaseLabel(c.enum()), + StmtBlock( + [ + StmtExpr( + c.callCtor( + ExprCall( + ExprSelect(othervar, ".", c.getConstTypeName()) + ) + ) + ), + StmtBreak(), + ] + ), + ) + copyswitch.addcase(CaseLabel(tnonevar.name), StmtBlock([StmtBreak()])) + copyswitch.addcase( + DefaultLabel(), StmtBlock([_logicError("unreached"), StmtReturn()]) + ) + copyctor.addstmts( + [ + StmtExpr(callAssertSanity(uvar=othervar)), + copyswitch, + StmtExpr(ExprAssn(mtypevar, othertype)), + ] + ) + cls.addstmts([copyctor, Whitespace.NL]) + + # Union(Union&&) move ctor + movector = ConstructorDefn( + ConstructorDecl(ud.name, params=[Decl(rvalueRefClsType, othervar.name)]) + ) + othertypevar = ExprVar("t") + moveswitch = StmtSwitch(othertypevar) + for c in ud.components: + case = StmtBlock() + if c.recursive: + # This is sound as we set othervar.mTypeVar to T__None after the + # switch. The pointer in the union will be left dangling. + case.addstmts( + [ + # ptr_C() = other.ptr_C() + StmtExpr( + ExprAssn( + c.callGetPtr(), + ExprCall( + ExprSelect(othervar, ".", ExprVar(c.getPtrName())) + ), + ) + ) + ] + ) + else: + case.addstmts( + [ + # new ... (Move(other.get_C())) + StmtExpr( + c.callCtor( + ExprMove( + ExprCall(ExprSelect(othervar, ".", c.getTypeName())) + ) + ) + ), + # other.MaybeDestroy(T__None) + StmtExpr(ExprCall(ExprSelect(othervar, ".", maybedtorvar))), + ] + ) + case.addstmts([StmtBreak()]) + moveswitch.addcase(CaseLabel(c.enum()), case) + moveswitch.addcase(CaseLabel(tnonevar.name), StmtBlock([StmtBreak()])) + moveswitch.addcase( + DefaultLabel(), StmtBlock([_logicError("unreached"), StmtReturn()]) + ) + movector.addstmts( + [ + StmtExpr(callAssertSanity(uvar=othervar)), + StmtDecl(Decl(typetype, othertypevar.name), init=ud.callType(othervar)), + moveswitch, + StmtExpr(ExprAssn(ExprSelect(othervar, ".", mtypevar), tnonevar)), + StmtExpr(ExprAssn(mtypevar, othertypevar)), + ] + ) + cls.addstmts([movector, Whitespace.NL]) + + # ~Union() + dtor = DestructorDefn(DestructorDecl(ud.name)) + dtor.addstmt(maybeDestroy()) + cls.addstmts([dtor, Whitespace.NL]) + + # type() + typemeth = MethodDefn( + MethodDecl("type", ret=typetype, const=True, force_inline=True) + ) + typemeth.addstmt(StmtReturn(mtypevar)) + cls.addstmts([typemeth, Whitespace.NL]) + + # Union& operator= methods + rhsvar = ExprVar("aRhs") + for c in ud.components: + + def opeqBody(rhs): + return [ + # might need to placement-delete old value first + maybeDestroy(), + StmtExpr(c.callCtor(rhs)), + StmtExpr(ExprAssn(mtypevar, c.enumvar())), + StmtReturn(ExprDeref(ExprVar.THIS)), + ] + + if not _cxxTypeNeedsMoveForData(c.ipdltype): + # Union& operator=(const T&) + opeq = MethodDefn( + MethodDecl( + "operator=", + params=[Decl(c.constRefType(), rhsvar.name)], + ret=refClsType, + ) + ) + opeq.addstmts(opeqBody(rhsvar)) + cls.addstmts([opeq, Whitespace.NL]) + + # Union& operator=(T&&) + if not _cxxTypeCanMove(c.ipdltype): + continue + + opeq = MethodDefn( + MethodDecl( + "operator=", + params=[Decl(c.forceMoveType(), rhsvar.name)], + ret=refClsType, + ) + ) + opeq.addstmts(opeqBody(ExprMove(rhsvar))) + cls.addstmts([opeq, Whitespace.NL]) + + # Union& operator=(const Union&) + if not unionNeedsMove: + opeq = MethodDefn( + MethodDecl( + "operator=", params=[Decl(inClsType, rhsvar.name)], ret=refClsType + ) + ) + rhstypevar = ExprVar("t") + opeqswitch = StmtSwitch(rhstypevar) + for c in ud.components: + case = StmtBlock() + case.addstmts( + [ + maybeDestroy(), + StmtExpr( + c.callCtor( + ExprCall(ExprSelect(rhsvar, ".", c.getConstTypeName())) + ) + ), + StmtBreak(), + ] + ) + opeqswitch.addcase(CaseLabel(c.enum()), case) + opeqswitch.addcase( + CaseLabel(tnonevar.name), + StmtBlock([maybeDestroy(), StmtBreak()]), + ) + opeqswitch.addcase( + DefaultLabel(), StmtBlock([_logicError("unreached"), StmtBreak()]) + ) + opeq.addstmts( + [ + StmtExpr(callAssertSanity(uvar=rhsvar)), + StmtDecl(Decl(typetype, rhstypevar.name), init=ud.callType(rhsvar)), + opeqswitch, + StmtExpr(ExprAssn(mtypevar, rhstypevar)), + StmtReturn(ExprDeref(ExprVar.THIS)), + ] + ) + cls.addstmts([opeq, Whitespace.NL]) + + # Union& operator=(Union&&) + opeq = MethodDefn( + MethodDecl( + "operator=", params=[Decl(rvalueRefClsType, rhsvar.name)], ret=refClsType + ) + ) + rhstypevar = ExprVar("t") + opeqswitch = StmtSwitch(rhstypevar) + for c in ud.components: + case = StmtBlock() + if c.recursive: + case.addstmts( + [ + maybeDestroy(), + StmtExpr( + ExprAssn( + c.callGetPtr(), + ExprCall(ExprSelect(rhsvar, ".", ExprVar(c.getPtrName()))), + ) + ), + ] + ) + else: + case.addstmts( + [ + maybeDestroy(), + StmtExpr( + c.callCtor( + ExprMove(ExprCall(ExprSelect(rhsvar, ".", c.getTypeName()))) + ) + ), + # other.MaybeDestroy() + StmtExpr(ExprCall(ExprSelect(rhsvar, ".", maybedtorvar))), + ] + ) + case.addstmts([StmtBreak()]) + opeqswitch.addcase(CaseLabel(c.enum()), case) + opeqswitch.addcase( + CaseLabel(tnonevar.name), + StmtBlock([maybeDestroy(), StmtBreak()]), + ) + opeqswitch.addcase( + DefaultLabel(), StmtBlock([_logicError("unreached"), StmtBreak()]) + ) + opeq.addstmts( + [ + StmtExpr(callAssertSanity(uvar=rhsvar)), + StmtDecl(Decl(typetype, rhstypevar.name), init=ud.callType(rhsvar)), + opeqswitch, + StmtExpr(ExprAssn(ExprSelect(rhsvar, ".", mtypevar), tnonevar)), + StmtExpr(ExprAssn(mtypevar, rhstypevar)), + StmtReturn(ExprDeref(ExprVar.THIS)), + ] + ) + cls.addstmts([opeq, Whitespace.NL]) + + if "Comparable" in ud.attributes: + # bool operator==(const T&) + for c in ud.components: + opeqeq = MethodDefn( + MethodDecl( + "operator==", + params=[Decl(c.constRefType(), rhsvar.name)], + ret=Type.BOOL, + const=True, + ) + ) + opeqeq.addstmt( + StmtReturn(ExprBinary(ExprCall(ExprVar(c.getTypeName())), "==", rhsvar)) + ) + cls.addstmts([opeqeq, Whitespace.NL]) + + # bool operator==(const Union&) + opeqeq = MethodDefn( + MethodDecl( + "operator==", + params=[Decl(inClsType, rhsvar.name)], + ret=Type.BOOL, + const=True, + ) + ) + iftypesmismatch = StmtIf(ExprBinary(ud.callType(), "!=", ud.callType(rhsvar))) + iftypesmismatch.addifstmt(StmtReturn.FALSE) + opeqeq.addstmts([iftypesmismatch, Whitespace.NL]) + + opeqeqswitch = StmtSwitch(ud.callType()) + for c in ud.components: + case = StmtBlock() + case.addstmt( + StmtReturn( + ExprBinary( + ExprCall(ExprVar(c.getTypeName())), + "==", + ExprCall(ExprSelect(rhsvar, ".", c.getTypeName())), + ) + ) + ) + opeqeqswitch.addcase(CaseLabel(c.enum()), case) + opeqeqswitch.addcase( + DefaultLabel(), StmtBlock([_logicError("unreached"), StmtReturn.FALSE]) + ) + opeqeq.addstmt(opeqeqswitch) + + cls.addstmts([opeqeq, Whitespace.NL]) + + # accessors for each type: operator T&, operator const T&, + # T& get(), const T& get() + for c in ud.components: + getValueVar = ExprVar(c.getTypeName()) + getConstValueVar = ExprVar(c.getConstTypeName()) + + getvalue = MethodDefn( + MethodDecl(getValueVar.name, ret=c.refType(), force_inline=True) + ) + getvalue.addstmts( + [ + StmtExpr(callAssertSanity(expectTypeVar=c.enumvar())), + StmtReturn(ExprDeref(c.callGetPtr())), + ] + ) + + getconstvalue = MethodDefn( + MethodDecl( + getConstValueVar.name, + ret=c.constRefType(), + const=True, + force_inline=True, + ) + ) + getconstvalue.addstmts( + [ + StmtExpr(callAssertSanity(expectTypeVar=c.enumvar())), + StmtReturn(c.getConstValue()), + ] + ) + + cls.addstmts([getvalue, getconstvalue]) + + optype = MethodDefn(MethodDecl("", typeop=c.refType(), force_inline=True)) + optype.addstmt(StmtReturn(ExprCall(getValueVar))) + opconsttype = MethodDefn( + MethodDecl("", const=True, typeop=c.constRefType(), force_inline=True) + ) + opconsttype.addstmt(StmtReturn(ExprCall(getConstValueVar))) + + cls.addstmts([optype, opconsttype, Whitespace.NL]) + # private vars + cls.addstmts( + [ + Label.PRIVATE, + StmtDecl(Decl(valuetype, mvaluevar.name)), + StmtDecl(Decl(typetype, mtypevar.name)), + ] + ) + + return forwarddeclstmts, fulldecltypes, cls + + +# ----------------------------------------------------------------------------- + + +class _FindFriends(ipdl.ast.Visitor): + def __init__(self): + self.mytype = None # ProtocolType + self.vtype = None # ProtocolType + self.friends = set() # set<ProtocolType> + + def findFriends(self, ptype): + self.mytype = ptype + for toplvl in ptype.toplevels(): + self.walkDownTheProtocolTree(toplvl) + return self.friends + + # TODO could make this into a _iterProtocolTreeHelper ... + def walkDownTheProtocolTree(self, ptype): + if ptype != self.mytype: + # don't want to |friend| ourself! + self.visit(ptype) + for mtype in ptype.manages: + if mtype is not ptype: + self.walkDownTheProtocolTree(mtype) + + def visit(self, ptype): + # |vtype| is the type currently being visited + savedptype = self.vtype + self.vtype = ptype + ptype._ast.accept(self) + self.vtype = savedptype + + def visitMessageDecl(self, md): + for it in self.iterActorParams(md): + if it.protocol == self.mytype: + self.friends.add(self.vtype) + + def iterActorParams(self, md): + for param in md.inParams: + for actor in ipdl.type.iteractortypes(param.type): + yield actor + for ret in md.outParams: + for actor in ipdl.type.iteractortypes(ret.type): + yield actor + + +class _GenerateProtocolActorCode(ipdl.ast.Visitor): + def __init__(self, myside): + self.side = myside # "parent" or "child" + self.prettyside = myside.title() + self.clsname = None + self.protocol = None + self.hdrfile = None + self.cppfile = None + self.ns = None + self.cls = None + self.protocolCxxIncludes = [] + self.actorForwardDecls = [] + self.usingDecls = [] + self.externalIncludes = set() + self.nonForwardDeclaredHeaders = set() + self.typedefSet = set( + [ + Typedef(Type("mozilla::ipc::ActorHandle"), "ActorHandle"), + Typedef(Type("base::ProcessId"), "ProcessId"), + Typedef(Type("mozilla::ipc::ProtocolId"), "ProtocolId"), + Typedef(Type("mozilla::ipc::Endpoint"), "Endpoint", ["FooSide"]), + Typedef( + Type("mozilla::ipc::ManagedEndpoint"), + "ManagedEndpoint", + ["FooSide"], + ), + Typedef(Type("mozilla::UniquePtr"), "UniquePtr", ["T"]), + Typedef( + Type("mozilla::ipc::ResponseRejectReason"), "ResponseRejectReason" + ), + ] + ) + + def lower(self, tu, clsname, cxxHeaderFile, cxxFile): + self.clsname = clsname + self.hdrfile = cxxHeaderFile + self.cppfile = cxxFile + tu.accept(self) + + def standardTypedefs(self): + return [ + Typedef(Type("mozilla::ipc::IProtocol"), "IProtocol"), + Typedef(Type("IPC::Message"), "Message"), + Typedef(Type("base::ProcessHandle"), "ProcessHandle"), + Typedef(Type("mozilla::ipc::MessageChannel"), "MessageChannel"), + Typedef(Type("mozilla::ipc::SharedMemory"), "SharedMemory"), + ] + + def visitTranslationUnit(self, tu): + self.protocol = tu.protocol + + hf = self.hdrfile + cf = self.cppfile + + # make the C++ header + hf.addthings( + [_DISCLAIMER] + + _includeGuardStart(hf) + + [ + Whitespace.NL, + CppDirective("include", '"' + _protocolHeaderName(tu.protocol) + '.h"'), + ] + ) + + for inc in tu.includes: + inc.accept(self) + for inc in tu.cxxIncludes: + inc.accept(self) + + for using in tu.builtinUsing: + using.accept(self) + for using in tu.using: + using.accept(self) + for su in tu.structsAndUnions: + su.accept(self) + + # this generates the actor's full impl in self.cls + tu.protocol.accept(self) + + clsdecl, clsdefn = _splitClassDeclDefn(self.cls) + + # XXX damn C++ ... return types in the method defn aren't in + # class scope + for stmt in clsdefn.stmts: + if isinstance(stmt, MethodDefn): + if stmt.decl.ret and stmt.decl.ret.name == "Result": + stmt.decl.ret.name = clsdecl.name + "::" + stmt.decl.ret.name + + def setToIncludes(s): + return [CppDirective("include", '"%s"' % i) for i in sorted(iter(s))] + + def makeNamespace(p, file): + if 0 == len(p.namespaces): + return file + ns = Namespace(p.namespaces[-1].name) + outerns = _putInNamespaces(ns, p.namespaces[:-1]) + file.addthing(outerns) + return ns + + if len(self.nonForwardDeclaredHeaders) != 0: + self.hdrfile.addthings( + [ + Whitespace("// Headers for things that cannot be forward declared"), + Whitespace.NL, + ] + + setToIncludes(self.nonForwardDeclaredHeaders) + + [Whitespace.NL] + ) + self.hdrfile.addthings(self.actorForwardDecls) + self.hdrfile.addthings(self.usingDecls) + + hdrns = makeNamespace(self.protocol, self.hdrfile) + hdrns.addstmts( + [Whitespace.NL, Whitespace.NL, clsdecl, Whitespace.NL, Whitespace.NL] + ) + + actortype = ActorType(tu.protocol.decl.type) + traitsdecl, traitsdefn = _ParamTraits.actorPickling(actortype, self.side) + + self.hdrfile.addthings([traitsdecl, Whitespace.NL] + _includeGuardEnd(hf)) + + # If the implementation type is not overridden, add an implicit import + # for the default implementation header file. Explicit implementation + # types will specify their headers manually with `include`. + if self.protocol.implAttribute(self.side) is None: + assert self.protocol.name.startswith("P") + self.externalIncludes.add( + "".join(n.name + "/" for n in self.protocol.namespaces) + + self.protocol.name[1:] + + self.side.capitalize() + + ".h" + ) + + # make the .cpp file + cf.addthings( + [ + _DISCLAIMER, + Whitespace.NL, + CppDirective( + "include", + '"' + _protocolHeaderName(self.protocol, self.side) + '.h"', + ), + ] + + setToIncludes(self.externalIncludes) + ) + + cf.addthings( + ( + [Whitespace.NL] + + [ + CppDirective("include", '"%s.h"' % (inc)) + for inc in self.protocolCxxIncludes + ] + + [Whitespace.NL] + + [ + CppDirective("include", '"%s"' % filename) + for filename in ipdl.builtin.CppIncludes + ] + + [Whitespace.NL] + ) + ) + + cppns = makeNamespace(self.protocol, cf) + cppns.addstmts( + [Whitespace.NL, Whitespace.NL, clsdefn, Whitespace.NL, Whitespace.NL] + ) + + cf.addthing(traitsdefn) + + def visitUsingStmt(self, using): + if using.decl.fullname is not None: + self.typedefSet.add( + Typedef(Type(using.decl.fullname), using.decl.shortname) + ) + + if using.header is None: + return + + if using.canBeForwardDeclared(): + spec = using.type + + self.usingDecls.extend( + [ + _makeForwardDeclForQClass( + spec.baseid, + spec.quals, + cls=using.isClass(), + struct=using.isStruct(), + ), + Whitespace.NL, + ] + ) + self.externalIncludes.add(using.header) + else: + self.nonForwardDeclaredHeaders.add(using.header) + + def visitCxxInclude(self, inc): + self.externalIncludes.add(inc.file) + + def visitInclude(self, inc): + if inc.tu.filetype == "header": + # Including a header will declare any globals defined by "using" + # statements into our scope. To serialize these, we also may need + # cxx include statements, so visit them as well. + for cxxinc in inc.tu.cxxIncludes: + cxxinc.accept(self) + for using in inc.tu.using: + using.accept(self) + for su in inc.tu.structsAndUnions: + su.accept(self) + else: + # Includes for protocols only include types explicitly exported by + # those protocols. + ip = inc.tu.protocol + if ip == self.protocol: + return + + self.actorForwardDecls.extend( + [ + _makeForwardDeclForActor(ip.decl.type, self.side), + _makeForwardDeclForActor(ip.decl.type, _otherSide(self.side)), + Whitespace.NL, + ] + ) + self.protocolCxxIncludes.append(_protocolHeaderName(ip, self.side)) + + if ip.decl.fullname is not None: + self.typedefSet.add( + Typedef( + Type(_actorName(ip.decl.fullname, self.side.title())), + _actorName(ip.decl.shortname, self.side.title()), + ) + ) + + self.typedefSet.add( + Typedef( + Type( + _actorName(ip.decl.fullname, _otherSide(self.side).title()) + ), + _actorName(ip.decl.shortname, _otherSide(self.side).title()), + ) + ) + + def visitStructDecl(self, sd): + if sd.decl.fullname is not None: + self.typedefSet.add(Typedef(Type(sd.fqClassName()), sd.name)) + + def visitUnionDecl(self, ud): + if ud.decl.fullname is not None: + self.typedefSet.add(Typedef(Type(ud.fqClassName()), ud.name)) + + def visitProtocol(self, p): + self.hdrfile.addcode( + """ + #ifdef DEBUG + #include "prenv.h" + #endif // DEBUG + + #include "mozilla/Tainting.h" + #include "mozilla/ipc/MessageChannel.h" + #include "mozilla/ipc/ProtocolUtils.h" + """ + ) + + self.protocol = p + ptype = p.decl.type + toplevel = p.decl.type.toplevel() + + hasAsyncReturns = False + for md in p.messageDecls: + if md.hasAsyncReturns(): + hasAsyncReturns = True + break + + inherits = [] + if ptype.isToplevel(): + inherits.append(Inherit(p.openedProtocolInterfaceType(), viz="public")) + else: + inherits.append(Inherit(p.managerInterfaceType(), viz="public")) + + if ptype.isToplevel() and self.side == "parent": + self.hdrfile.addthings( + [_makeForwardDeclForQClass("nsIFile", []), Whitespace.NL] + ) + + self.cls = Class(self.clsname, inherits=inherits, abstract=True) + + self.cls.addstmt(Label.PRIVATE) + friends = _FindFriends().findFriends(ptype) + if ptype.isManaged(): + friends.update(ptype.managers) + + # |friend| managed actors so that they can call our Dealloc*() + friends.update(ptype.manages) + + # don't friend ourself if we're a self-managed protocol + friends.discard(ptype) + + for friend in sorted(friends, key=lambda f: f.fullname()): + self.actorForwardDecls.extend( + [_makeForwardDeclForActor(friend, self.prettyside), Whitespace.NL] + ) + self.cls.addstmt( + FriendClassDecl(_actorName(friend.fullname(), self.prettyside)) + ) + + self.cls.addstmt(Label.PROTECTED) + for typedef in sorted(self.typedefSet): + self.cls.addstmt(typedef) + + self.cls.addstmt(Whitespace.NL) + + if hasAsyncReturns: + self.cls.addstmt(Label.PUBLIC) + for md in p.messageDecls: + if self.sendsMessage(md) and md.hasAsyncReturns(): + self.cls.addstmt( + Typedef(_makePromise(md.returns, self.side), md.promiseName()) + ) + if self.receivesMessage(md) and md.hasAsyncReturns(): + self.cls.addstmt( + Typedef(_makeResolver(md.returns, self.side), md.resolverName()) + ) + self.cls.addstmt(Whitespace.NL) + + self.cls.addstmt(Label.PROTECTED) + # interface methods that the concrete subclass has to impl + for md in p.messageDecls: + isctor, isdtor = md.decl.type.isCtor(), md.decl.type.isDtor() + + if self.receivesMessage(md): + # generate Recv/Answer* interface + implicit = not isdtor + returnsems = "resolver" if md.decl.type.isAsync() else "out" + recvDecl = MethodDecl( + md.recvMethod(), + params=md.makeCxxParams( + paramsems="move", + returnsems=returnsems, + side=self.side, + implicit=implicit, + direction="recv", + ), + ret=Type("mozilla::ipc::IPCResult"), + methodspec=MethodSpec.VIRTUAL, + ) + + # These method implementations cause problems when trying to + # override them with different types in a direct call class. + # + # For the `isdtor` case there's a simple solution: it doesn't + # make much sense to specify arguments and then completely + # ignore them, and the no-arg case isn't a problem for + # overriding. + if isctor or (isdtor and not md.inParams): + defaultRecv = MethodDefn(recvDecl) + defaultRecv.addcode("return IPC_OK();\n") + self.cls.addstmt(defaultRecv) + elif self.protocol.implAttribute(self.side) == "virtual": + # If we're using virtual calls, we need the methods to be + # declared on the base class. + recvDecl.methodspec = MethodSpec.PURE + self.cls.addstmt(StmtDecl(recvDecl)) + + # If we're using virtual calls, we need the methods to be declared on + # the base class. + if self.protocol.implAttribute(self.side) == "virtual": + for md in p.messageDecls: + managed = md.decl.type.constructedType() + if not ptype.isManagerOf(managed) or md.decl.type.isDtor(): + continue + + # add the Alloc interface for managed actors + actortype = md.actorDecl().bareType(self.side) + + if managed.isRefcounted(): + if not self.receivesMessage(md): + continue + + actortype.ptr = False + actortype = _alreadyaddrefed(actortype) + + self.cls.addstmt( + StmtDecl( + MethodDecl( + _allocMethod(managed, self.side), + params=md.makeCxxParams( + side=self.side, implicit=False, direction="recv" + ), + ret=actortype, + methodspec=MethodSpec.PURE, + ) + ) + ) + + # add the Dealloc interface for all managed non-refcounted actors, + # even without ctors. This is useful for protocols which use + # ManagedEndpoint for construction. + for managed in ptype.manages: + if managed.isRefcounted(): + continue + + self.cls.addstmt( + StmtDecl( + MethodDecl( + _deallocMethod(managed, self.side), + params=[ + Decl(p.managedCxxType(managed, self.side), "aActor") + ], + ret=Type.BOOL, + methodspec=MethodSpec.PURE, + ) + ) + ) + + if ptype.isToplevel(): + # void ProcessingError(code); default to no-op + processingerror = MethodDefn( + MethodDecl( + p.processingErrorVar().name, + params=[ + Param(_Result.Type(), "aCode"), + Param(Type("char", const=True, ptr=True), "aReason"), + ], + methodspec=MethodSpec.OVERRIDE, + ) + ) + + # bool ShouldContinueFromReplyTimeout(); default to |true| + shouldcontinue = MethodDefn( + MethodDecl( + p.shouldContinueFromTimeoutVar().name, + ret=Type.BOOL, + methodspec=MethodSpec.OVERRIDE, + ) + ) + shouldcontinue.addcode("return true;\n") + + self.cls.addstmts( + [ + processingerror, + shouldcontinue, + Whitespace.NL, + ] + ) + + self.cls.addstmts(([Label.PUBLIC] + self.standardTypedefs() + [Whitespace.NL])) + + self.cls.addstmt(Label.PUBLIC) + # Actor() + ctor = ConstructorDefn(ConstructorDecl(self.clsname)) + side = ExprVar("mozilla::ipc::" + self.side.title() + "Side") + if ptype.isToplevel(): + name = ExprLiteral.String(_actorName(p.name, self.side)) + ctor.memberinits = [ + ExprMemberInit( + ExprVar("mozilla::ipc::IToplevelProtocol"), + [name, _protocolId(ptype), side], + ) + ] + else: + ctor.memberinits = [ + ExprMemberInit( + ExprVar("mozilla::ipc::IProtocol"), [_protocolId(ptype), side] + ) + ] + + ctor.addcode("MOZ_COUNT_CTOR(${clsname});\n", clsname=self.clsname) + self.cls.addstmts([ctor, Whitespace.NL]) + + # ~Actor() + dtor = DestructorDefn( + DestructorDecl(self.clsname, methodspec=MethodSpec.VIRTUAL) + ) + dtor.addcode("MOZ_COUNT_DTOR(${clsname});\n", clsname=self.clsname) + + self.cls.addstmts([dtor, Whitespace.NL]) + + if ptype.isRefcounted(): + if not ptype.isToplevel(): + self.cls.addcode( + """ + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + """ + ) + self.cls.addstmt(Label.PROTECTED) + self.cls.addcode( + """ + void ActorAlloc() final { AddRef(); } + void ActorDealloc() final { Release(); } + """ + ) + + self.cls.addstmt(Label.PUBLIC) + if ptype.hasOtherPid(): + otherpidmeth = MethodDefn( + MethodDecl("OtherPid", ret=Type("::base::ProcessId"), const=True) + ) + otherpidmeth.addcode( + """ + ::base::ProcessId pid = + ::mozilla::ipc::IProtocol::ToplevelProtocol()->OtherPidMaybeInvalid(); + MOZ_RELEASE_ASSERT(pid != ::base::kInvalidProcessId); + return pid; + """ + ) + self.cls.addstmts([otherpidmeth, Whitespace.NL]) + + if not ptype.isToplevel(): + if 1 == len(p.managers): + # manager() const + managertype = p.managerActorType(self.side, ptr=True) + managermeth = MethodDefn( + MethodDecl("Manager", ret=managertype, const=True) + ) + managermeth.addcode( + """ + return static_cast<${type}>(IProtocol::Manager()); + """, + type=managertype, + ) + + self.cls.addstmts([managermeth, Whitespace.NL]) + + def actorFromIter(itervar): + return ExprCode("${iter}.Get()->GetKey()", iter=itervar) + + def forLoopOverHashtable(hashtable, itervar, const=False): + itermeth = "ConstIter" if const else "Iter" + return StmtFor( + init=ExprCode( + "auto ${itervar} = ${hashtable}.${itermeth}()", + itervar=itervar, + hashtable=hashtable, + itermeth=itermeth, + ), + cond=ExprCode("!${itervar}.Done()", itervar=itervar), + update=ExprCode("${itervar}.Next()", itervar=itervar), + ) + + # Managed[T](Array& inout) const + # const Array<T>& Managed() const + for managed in ptype.manages: + container = p.managedVar(managed, self.side) + + meth = MethodDefn( + MethodDecl( + p.managedMethod(managed, self.side).name, + params=[ + Decl( + _cxxArrayType( + p.managedCxxType(managed, self.side), ref=True + ), + "aArr", + ) + ], + const=True, + ) + ) + meth.addcode("${container}.ToArray(aArr);\n", container=container) + + refmeth = MethodDefn( + MethodDecl( + p.managedMethod(managed, self.side).name, + params=[], + ret=p.managedVarType(managed, self.side, const=True, ref=True), + const=True, + ) + ) + refmeth.addcode("return ${container};\n", container=container) + + self.cls.addstmts([meth, refmeth, Whitespace.NL]) + + # AllManagedActors(Array& inout) const + arrvar = ExprVar("arr__") + managedmeth = MethodDefn( + MethodDecl( + "AllManagedActors", + params=[ + Decl( + _cxxArrayType(_refptr(_cxxLifecycleProxyType()), ref=True), + arrvar.name, + ) + ], + methodspec=MethodSpec.OVERRIDE, + const=True, + ) + ) + + # Count the number of managed actors, and allocate space in the output array. + managedmeth.addcode( + """ + uint32_t total = 0; + """ + ) + for managed in ptype.manages: + managedmeth.addcode( + """ + total += ${container}.Count(); + """, + container=p.managedVar(managed, self.side), + ) + managedmeth.addcode( + """ + arr__.SetCapacity(total); + + """ + ) + + for managed in ptype.manages: + managedmeth.addcode( + """ + for (auto* key : ${container}) { + arr__.AppendElement(key->GetLifecycleProxy()); + } + + """, + container=p.managedVar(managed, self.side), + ) + + self.cls.addstmts([managedmeth, Whitespace.NL]) + + # OpenPEndpoint(...)/BindPEndpoint(...) + for managed in ptype.manages: + self.genManagedEndpoint(managed) + + # OnMessageReceived()/OnCallReceived() + + # save these away for use in message handler case stmts + msgvar = ExprVar("msg__") + self.msgvar = msgvar + replyvar = ExprVar("reply__") + self.replyvar = replyvar + var = ExprVar("v__") + self.var = var + # for ctor recv cases, we can't read the actor ID into a PFoo* + # because it doesn't exist on this side yet. Use a "special" + # actor handle instead + handlevar = ExprVar("handle__") + self.handlevar = handlevar + + msgtype = ExprCode("msg__.type()") + self.asyncSwitch = StmtSwitch(msgtype) + self.syncSwitch = None + self.interruptSwitch = None + if toplevel.isSync() or toplevel.isInterrupt(): + self.syncSwitch = StmtSwitch(msgtype) + if toplevel.isInterrupt(): + self.interruptSwitch = StmtSwitch(msgtype) + + # Add a handler for the MANAGED_ENDPOINT_BOUND and + # MANAGED_ENDPOINT_DROPPED message types for managed actors. + if not ptype.isToplevel(): + clearawaitingmanagedendpointbind = """ + if (!mAwaitingManagedEndpointBind) { + NS_WARNING("Unexpected managed endpoint lifecycle message after actor bound!"); + return MsgNotAllowed; + } + mAwaitingManagedEndpointBind = false; + """ + self.asyncSwitch.addcase( + CaseLabel("MANAGED_ENDPOINT_BOUND_MESSAGE_TYPE"), + StmtBlock( + [ + StmtCode(clearawaitingmanagedendpointbind), + StmtReturn(_Result.Processed), + ] + ), + ) + self.asyncSwitch.addcase( + CaseLabel("MANAGED_ENDPOINT_DROPPED_MESSAGE_TYPE"), + StmtBlock( + [ + StmtCode(clearawaitingmanagedendpointbind), + *self.destroyActor( + None, + ExprVar.THIS, + why=_DestroyReason.ManagedEndpointDropped, + ), + StmtReturn(_Result.Processed), + ] + ), + ) + + # implement Send*() methods and add dispatcher cases to + # message switch()es + for md in p.messageDecls: + self.visitMessageDecl(md) + + # add default cases + default = StmtCode( + """ + return MsgNotKnown; + """ + ) + self.asyncSwitch.addcase(DefaultLabel(), default) + if toplevel.isSync() or toplevel.isInterrupt(): + self.syncSwitch.addcase(DefaultLabel(), default) + if toplevel.isInterrupt(): + self.interruptSwitch.addcase(DefaultLabel(), default) + + self.cls.addstmts(self.implementManagerIface()) + + def makeHandlerMethod(name, switch, hasReply, dispatches=False): + params = [Decl(Type("Message", const=True, ref=True), msgvar.name)] + if hasReply: + params.append(Decl(Type("UniquePtr<Message>", ref=True), replyvar.name)) + + method = MethodDefn( + MethodDecl( + name, + methodspec=MethodSpec.OVERRIDE, + params=params, + ret=_Result.Type(), + ) + ) + + if not switch: + method.addcode( + """ + MOZ_ASSERT_UNREACHABLE("message protocol not supported"); + return MsgNotKnown; + """ + ) + return method + + if dispatches: + if hasReply: + ondeadactor = [StmtReturn(_Result.RouteError)] + else: + ondeadactor = [ + self.logMessage( + None, ExprAddrOf(msgvar), "Ignored message for dead actor" + ), + StmtReturn(_Result.Processed), + ] + + method.addcode( + """ + int32_t route__ = ${msgvar}.routing_id(); + if (MSG_ROUTING_CONTROL != route__) { + IProtocol* routed__ = Lookup(route__); + if (!routed__ || !routed__->GetLifecycleProxy()) { + $*{ondeadactor} + } + + RefPtr<mozilla::ipc::ActorLifecycleProxy> proxy__ = + routed__->GetLifecycleProxy(); + return proxy__->Get()->${name}($,{args}); + } + + """, + msgvar=msgvar, + ondeadactor=ondeadactor, + name=name, + args=[p.name for p in params], + ) + + # bug 509581: don't generate the switch stmt if there + # is only the default case; MSVC doesn't like that + if switch.nr_cases > 1: + method.addstmt(switch) + else: + method.addstmt(StmtReturn(_Result.NotKnown)) + + return method + + dispatches = ptype.isToplevel() and ptype.isManager() + self.cls.addstmts( + [ + makeHandlerMethod( + "OnMessageReceived", + self.asyncSwitch, + hasReply=False, + dispatches=dispatches, + ), + Whitespace.NL, + ] + ) + self.cls.addstmts( + [ + makeHandlerMethod( + "OnMessageReceived", + self.syncSwitch, + hasReply=True, + dispatches=dispatches, + ), + Whitespace.NL, + ] + ) + self.cls.addstmts( + [ + makeHandlerMethod( + "OnCallReceived", + self.interruptSwitch, + hasReply=True, + dispatches=dispatches, + ), + Whitespace.NL, + ] + ) + + clearsubtreevar = ExprVar("ClearSubtree") + + if ptype.isToplevel(): + # OnChannelClose() + onclose = MethodDefn( + MethodDecl("OnChannelClose", methodspec=MethodSpec.OVERRIDE) + ) + onclose.addcode( + """ + DestroySubtree(NormalShutdown); + ClearSubtree(); + DeallocShmems(); + if (GetLifecycleProxy()) { + GetLifecycleProxy()->Release(); + } + """ + ) + self.cls.addstmts([onclose, Whitespace.NL]) + + # OnChannelError() + onerror = MethodDefn( + MethodDecl("OnChannelError", methodspec=MethodSpec.OVERRIDE) + ) + onerror.addcode( + """ + DestroySubtree(AbnormalShutdown); + ClearSubtree(); + DeallocShmems(); + if (GetLifecycleProxy()) { + GetLifecycleProxy()->Release(); + } + """ + ) + self.cls.addstmts([onerror, Whitespace.NL]) + + if ptype.isToplevel() and ptype.isInterrupt(): + processnative = MethodDefn( + MethodDecl("ProcessNativeEventsInInterruptCall", ret=Type.VOID) + ) + processnative.addcode( + """ + #ifdef OS_WIN + GetIPCChannel()->ProcessNativeEventsInInterruptCall(); + #else + FatalError("This method is Windows-only"); + #endif + """ + ) + + self.cls.addstmts([processnative, Whitespace.NL]) + + # private methods + self.cls.addstmt(Label.PRIVATE) + + # ClearSubtree() + clearsubtree = MethodDefn(MethodDecl(clearsubtreevar.name)) + for managed in ptype.manages: + clearsubtree.addcode( + """ + for (auto* key : ${container}) { + key->ClearSubtree(); + } + for (auto* key : ${container}) { + // Recursively releasing ${container} kids. + auto* proxy = key->GetLifecycleProxy(); + NS_IF_RELEASE(proxy); + } + ${container}.Clear(); + + """, + container=p.managedVar(managed, self.side), + ) + + # don't release our own IPC reference: either the manager will do it, + # or we're toplevel + self.cls.addstmts([clearsubtree, Whitespace.NL]) + + if not ptype.isToplevel(): + self.cls.addstmts( + [ + StmtDecl( + Decl(Type.BOOL, "mAwaitingManagedEndpointBind"), + init=ExprLiteral.FALSE, + ), + Whitespace.NL, + ] + ) + + for managed in ptype.manages: + self.cls.addstmts( + [ + StmtDecl( + Decl( + p.managedVarType(managed, self.side), + p.managedVar(managed, self.side).name, + ) + ) + ] + ) + + def genManagedEndpoint(self, managed): + hereEp = "ManagedEndpoint<%s>" % _actorName(managed.name(), self.side) + thereEp = "ManagedEndpoint<%s>" % _actorName( + managed.name(), _otherSide(self.side) + ) + + actor = _HybridDecl(ipdl.type.ActorType(managed), "aActor") + + # ManagedEndpoint<PThere> OpenPEndpoint(PHere* aActor) + openmeth = MethodDefn( + MethodDecl( + "Open%sEndpoint" % managed.name(), + params=[ + Decl(self.protocol.managedCxxType(managed, self.side), actor.name) + ], + ret=Type(thereEp), + ) + ) + openmeth.addcode( + """ + $*{bind} + // Mark our actor as awaiting the other side to be bound. This will + // be cleared when a `MANAGED_ENDPOINT_{DROPPED,BOUND}` message is + // received. + aActor->mAwaitingManagedEndpointBind = true; + return ${thereEp}(mozilla::ipc::PrivateIPDLInterface(), aActor); + """, + bind=self.bindManagedActor(actor, errfn=ExprCall(ExprVar(thereEp))), + thereEp=thereEp, + ) + + # void BindPEndpoint(ManagedEndpoint<PHere>&& aEndpoint, PHere* aActor) + bindmeth = MethodDefn( + MethodDecl( + "Bind%sEndpoint" % managed.name(), + params=[ + Decl(Type(hereEp), "aEndpoint"), + Decl(self.protocol.managedCxxType(managed, self.side), actor.name), + ], + ret=Type.BOOL, + ) + ) + bindmeth.addcode( + """ + return aEndpoint.Bind(mozilla::ipc::PrivateIPDLInterface(), aActor, this, ${container}); + """, + container=self.protocol.managedVar(managed, self.side), + ) + + self.cls.addstmts([openmeth, bindmeth, Whitespace.NL]) + + def implementManagerIface(self): + p = self.protocol + protocolbase = Type("IProtocol", ptr=True) + + methods = [] + + if p.decl.type.isToplevel(): + # FIXME: This used to be declared conditionally based on whether + # shmem appeared somewhere in the protocol hierarchy, however that + # caused issues due to Shmem instances hidden within custom C++ + # types. + self.asyncSwitch.addcase( + CaseLabel("SHMEM_CREATED_MESSAGE_TYPE"), + self.genShmemCreatedHandler(), + ) + self.asyncSwitch.addcase( + CaseLabel("SHMEM_DESTROYED_MESSAGE_TYPE"), + self.genShmemDestroyedHandler(), + ) + + # Keep track of types created with an INOUT ctor. We need to call + # Register() or RegisterID() for them depending on the side the managee + # is created. + inoutCtorTypes = [] + for msg in p.messageDecls: + msgtype = msg.decl.type + if msgtype.isCtor() and msgtype.isInout(): + inoutCtorTypes.append(msgtype.constructedType()) + + # all protocols share the "same" RemoveManagee() implementation + pvar = ExprVar("aProtocolId") + listenervar = ExprVar("aListener") + removemanagee = MethodDefn( + MethodDecl( + p.removeManageeMethod().name, + params=[ + Decl(_protocolIdType(), pvar.name), + Decl(protocolbase, listenervar.name), + ], + methodspec=MethodSpec.OVERRIDE, + ) + ) + + if not len(p.managesStmts): + removemanagee.addcode( + """ + FatalError("unreached"); + return; + """ + ) + else: + switchontype = StmtSwitch(pvar) + for managee in p.managesStmts: + manageeipdltype = managee.decl.type + manageecxxtype = _cxxBareType( + ipdl.type.ActorType(manageeipdltype), self.side + ) + case = ExprCode( + """ + { + ${manageecxxtype} actor = static_cast<${manageecxxtype}>(aListener); + + const bool removed = ${container}.EnsureRemoved(actor); + MOZ_RELEASE_ASSERT(removed, "actor not managed by this!"); + + auto* proxy = actor->GetLifecycleProxy(); + NS_IF_RELEASE(proxy); + return; + } + """, + manageecxxtype=manageecxxtype, + container=p.managedVar(manageeipdltype, self.side), + ) + switchontype.addcase(CaseLabel(_protocolId(manageeipdltype).name), case) + switchontype.addcase( + DefaultLabel(), + ExprCode( + """ + FatalError("unreached"); + return; + """ + ), + ) + removemanagee.addstmt(switchontype) + + # The `DeallocManagee` method is called for managed actors to trigger + # deallocation when ActorLifecycleProxy is freed. + deallocmanagee = MethodDefn( + MethodDecl( + p.deallocManageeMethod().name, + params=[ + Decl(_protocolIdType(), pvar.name), + Decl(protocolbase, listenervar.name), + ], + methodspec=MethodSpec.OVERRIDE, + ) + ) + + if not len(p.managesStmts): + deallocmanagee.addcode( + """ + FatalError("unreached"); + return; + """ + ) + else: + switchontype = StmtSwitch(pvar) + for managee in p.managesStmts: + manageeipdltype = managee.decl.type + # Reference counted actor types don't have corresponding + # `Dealloc` methods, as they are deallocated by releasing the + # IPDL-held reference. + if manageeipdltype.isRefcounted(): + continue + + case = StmtCode( + """ + ${concrete}->${dealloc}(static_cast<${type}>(aListener)); + return; + """, + concrete=self.concreteThis(), + dealloc=_deallocMethod(manageeipdltype, self.side), + type=_cxxBareType(ipdl.type.ActorType(manageeipdltype), self.side), + ) + switchontype.addcase(CaseLabel(_protocolId(manageeipdltype).name), case) + switchontype.addcase( + DefaultLabel(), + StmtCode( + """ + FatalError("unreached"); + return; + """ + ), + ) + deallocmanagee.addstmt(switchontype) + + return methods + [removemanagee, deallocmanagee, Whitespace.NL] + + def genShmemCreatedHandler(self): + assert self.protocol.decl.type.isToplevel() + + return StmtCode( + """ + { + if (!ShmemCreated(${msgvar})) { + return MsgPayloadError; + } + return MsgProcessed; + } + """, + msgvar=self.msgvar, + ) + + def genShmemDestroyedHandler(self): + assert self.protocol.decl.type.isToplevel() + + return StmtCode( + """ + { + if (!ShmemDestroyed(${msgvar})) { + return MsgPayloadError; + } + return MsgProcessed; + } + """, + msgvar=self.msgvar, + ) + + # ------------------------------------------------------------------------- + # The next few functions are the crux of the IPDL code generator. + # They generate code for all the nasty work of message + # serialization/deserialization and dispatching handlers for + # received messages. + ## + + def concreteThis(self): + implAttr = self.protocol.implAttribute(self.side) + if implAttr == "virtual": + return ExprVar.THIS + + if implAttr is None: + assert self.protocol.name.startswith("P") + className = self.protocol.name[1:] + self.side.capitalize() + else: + assert isinstance(implAttr, ipdl.ast.StringLiteral) + className = implAttr.value + + return ExprCode("static_cast<${className}*>(this)", className=className) + + def thisCall(self, function, args): + return ExprCall(ExprSelect(self.concreteThis(), "->", function), args=args) + + def visitMessageDecl(self, md): + isctor = md.decl.type.isCtor() + isdtor = md.decl.type.isDtor() + decltype = md.decl.type + sendmethod = None + movesendmethod = None + promisesendmethod = None + recvlbl, recvcase = None, None + + def addRecvCase(lbl, case): + if decltype.isAsync(): + self.asyncSwitch.addcase(lbl, case) + elif decltype.isSync(): + self.syncSwitch.addcase(lbl, case) + elif decltype.isInterrupt(): + self.interruptSwitch.addcase(lbl, case) + else: + assert 0 + + if self.sendsMessage(md): + isasync = decltype.isAsync() + + # NOTE: Don't generate helper ctors for refcounted types. + # + # Safety concerns around providing your own actor to a ctor (namely + # that the return value won't be checked, and the argument will be + # `delete`-ed) are less critical with refcounted actors, due to the + # actor being held alive by the callsite. + # + # This allows refcounted actors to not implement crashing AllocPFoo + # methods on the sending side. + if isctor and not md.decl.type.constructedType().isRefcounted(): + self.cls.addstmts([self.genHelperCtor(md), Whitespace.NL]) + + if isctor and isasync: + sendmethod, (recvlbl, recvcase) = self.genAsyncCtor(md) + elif isctor: + sendmethod = self.genBlockingCtorMethod(md) + elif isdtor and isasync: + sendmethod, (recvlbl, recvcase) = self.genAsyncDtor(md) + elif isdtor: + sendmethod = self.genBlockingDtorMethod(md) + elif isasync: + ( + sendmethod, + movesendmethod, + promisesendmethod, + (recvlbl, recvcase), + ) = self.genAsyncSendMethod(md) + else: + sendmethod, movesendmethod = self.genBlockingSendMethod(md) + + # XXX figure out what to do here + if isdtor and md.decl.type.constructedType().isToplevel(): + sendmethod = None + + if sendmethod is not None: + self.cls.addstmts([sendmethod, Whitespace.NL]) + if movesendmethod is not None: + self.cls.addstmts([movesendmethod, Whitespace.NL]) + if promisesendmethod is not None: + self.cls.addstmts([promisesendmethod, Whitespace.NL]) + if recvcase is not None: + addRecvCase(recvlbl, recvcase) + recvlbl, recvcase = None, None + + if self.receivesMessage(md): + if isctor: + recvlbl, recvcase = self.genCtorRecvCase(md) + elif isdtor: + recvlbl, recvcase = self.genDtorRecvCase(md) + else: + recvlbl, recvcase = self.genRecvCase(md) + + # XXX figure out what to do here + if isdtor and md.decl.type.constructedType().isToplevel(): + return + + addRecvCase(recvlbl, recvcase) + + def genAsyncCtor(self, md): + actor = md.actorDecl() + method = MethodDefn(self.makeSendMethodDecl(md)) + + msgvar, stmts = self.makeMessage(md, errfnSendCtor) + sendok, sendstmts = self.sendAsync(md, msgvar) + + method.addcode( + """ + $*{bind} + + // Build our constructor message. + $*{stmts} + + // Notify the other side about the newly created actor. This can + // fail if our manager has already been destroyed. + // + // NOTE: If the send call fails due to toplevel channel teardown, + // the `IProtocol::ChannelSend` wrapper absorbs the error for us, + // so we don't tear down actors unexpectedly. + $*{sendstmts} + + // Warn, destroy the actor, and return null if the message failed to + // send. Otherwise, return the successfully created actor reference. + if (!${sendok}) { + NS_WARNING("Error sending ${actorname} constructor"); + $*{destroy} + return nullptr; + } + return ${actor}; + """, + bind=self.bindManagedActor(actor), + stmts=stmts, + sendstmts=sendstmts, + sendok=sendok, + destroy=self.destroyActor( + md, actor.var(), why=_DestroyReason.FailedConstructor + ), + actor=actor.var(), + actorname=actor.ipdltype.protocol.name() + self.side.capitalize(), + ) + + lbl = CaseLabel(md.pqReplyId()) + case = StmtBlock() + case.addstmt(StmtReturn(_Result.Processed)) + # TODO not really sure what to do with async ctor "replies" yet. + # destroy actor if there was an error? tricky ... + + return method, (lbl, case) + + def genBlockingCtorMethod(self, md): + actor = md.actorDecl() + method = MethodDefn(self.makeSendMethodDecl(md)) + + msgvar, stmts = self.makeMessage(md, errfnSendCtor) + + replyvar = self.replyvar + sendok, sendstmts = self.sendBlocking(md, msgvar, replyvar) + replystmts = self.deserializeReply( + md, + replyvar, + self.side, + errfnSendCtor, + errfnSentinel(ExprLiteral.NULL), + ) + + method.addcode( + """ + $*{bind} + + // Build our constructor message. + $*{stmts} + + // Synchronously send the constructor message to the other side. If + // the send fails, e.g. due to the remote side shutting down, the + // actor will be destroyed and potentially freed. + UniquePtr<Message> ${replyvar}; + $*{sendstmts} + + if (!(${sendok})) { + // Warn, destroy the actor and return null if the message + // failed to send. + NS_WARNING("Error sending constructor"); + $*{destroy} + return nullptr; + } + + $*{replystmts} + return ${actor}; + """, + bind=self.bindManagedActor(actor), + stmts=stmts, + replyvar=replyvar, + sendstmts=sendstmts, + sendok=sendok, + destroy=self.destroyActor( + md, actor.var(), why=_DestroyReason.FailedConstructor + ), + replystmts=replystmts, + actor=actor.var(), + actorname=actor.ipdltype.protocol.name() + self.side.capitalize(), + ) + + return method + + def bindManagedActor(self, actordecl, errfn=ExprLiteral.NULL, idexpr=None): + actorproto = actordecl.ipdltype.protocol + + if idexpr is None: + setManagerArgs = [ExprVar.THIS] + else: + setManagerArgs = [ExprVar.THIS, idexpr] + + return [ + StmtCode( + """ + if (!${actor}) { + NS_WARNING("Cannot bind null ${actorname} actor"); + return ${errfn}; + } + + ${actor}->SetManagerAndRegister($,{setManagerArgs}); + ${container}.Insert(${actor}); + """, + actor=actordecl.var(), + actorname=actorproto.name() + self.side.capitalize(), + errfn=errfn, + setManagerArgs=setManagerArgs, + container=self.protocol.managedVar(actorproto, self.side), + ) + ] + + def genHelperCtor(self, md): + helperdecl = self.makeSendMethodDecl(md) + helperdecl.params = helperdecl.params[1:] + helper = MethodDefn(helperdecl) + + helper.addstmts( + [ + self.callAllocActor(md, retsems="out", side=self.side), + StmtReturn( + ExprCall( + ExprVar(helperdecl.name), args=md.makeCxxArgs(paramsems="move") + ) + ), + ] + ) + return helper + + def genAsyncDtor(self, md): + actorvar = ExprVar("actor") + method = MethodDefn(self.makeDtorMethodDecl(md, actorvar)) + + method.addstmt(self.dtorPrologue(actorvar)) + + msgvar, stmts = self.makeMessage(md, errfnSendDtor, actorvar) + sendok, sendstmts = self.sendAsync(md, msgvar, actorvar) + method.addstmts( + stmts + + sendstmts + + [Whitespace.NL] + + self.dtorEpilogue(md, actorvar) + + [StmtReturn(sendok)] + ) + + lbl = CaseLabel(md.pqReplyId()) + case = StmtBlock() + case.addstmt(StmtReturn(_Result.Processed)) + # TODO if the dtor is "inherently racy", keep the actor alive + # until the other side acks + + return method, (lbl, case) + + def genBlockingDtorMethod(self, md): + actorvar = ExprVar("actor") + method = MethodDefn(self.makeDtorMethodDecl(md, actorvar)) + + method.addstmt(self.dtorPrologue(actorvar)) + + msgvar, stmts = self.makeMessage(md, errfnSendDtor, actorvar) + + replyvar = self.replyvar + sendok, sendstmts = self.sendBlocking(md, msgvar, replyvar, actorvar) + method.addstmts( + stmts + + [Whitespace.NL, StmtDecl(Decl(Type("UniquePtr<Message>"), replyvar.name))] + + sendstmts + ) + + destmts = self.deserializeReply( + md, replyvar, self.side, errfnSend, errfnSentinel(), actorvar + ) + ifsendok = StmtIf(ExprLiteral.FALSE) + ifsendok.addifstmts(destmts) + ifsendok.addifstmts( + [Whitespace.NL, StmtExpr(ExprAssn(sendok, ExprLiteral.FALSE, "&="))] + ) + + method.addstmt(ifsendok) + + method.addstmts( + self.dtorEpilogue(md, actorvar) + [Whitespace.NL, StmtReturn(sendok)] + ) + + return method + + def destroyActor(self, md, actorexpr, why=_DestroyReason.Deletion): + if md and md.decl.type.isCtor(): + destroyedType = md.decl.type.constructedType() + else: + destroyedType = self.protocol.decl.type + + return [ + StmtCode( + """ + IProtocol* mgr = ${actor}->Manager(); + ${actor}->DestroySubtree(${why}); + ${actor}->ClearSubtree(); + mgr->RemoveManagee(${protoId}, ${actor}); + """, + actor=actorexpr, + why=why, + protoId=_protocolId(destroyedType), + ) + ] + + def dtorPrologue(self, actorexpr): + return StmtCode( + """ + if (!${actor} || !${actor}->CanSend()) { + NS_WARNING("Attempt to __delete__ missing or closed actor"); + return false; + } + """, + actor=actorexpr, + ) + + def dtorEpilogue(self, md, actorexpr): + return self.destroyActor(md, actorexpr) + + def genRecvAsyncReplyCase(self, md): + lbl = CaseLabel(md.pqReplyId()) + case = StmtBlock() + resolve, reason, prologue, desrej, desstmts = self.deserializeAsyncReply( + md, self.side, errfnRecv, errfnSentinel(_Result.ValuError) + ) + + if len(md.returns) > 1: + resolvetype = _tuple([d.bareType(self.side) for d in md.returns]) + resolvearg = ExprCall( + ExprVar("std::make_tuple"), args=[ExprMove(p.var()) for p in md.returns] + ) + else: + resolvetype = md.returns[0].bareType(self.side) + resolvearg = ExprMove(md.returns[0].var()) + + case.addcode( + """ + $*{prologue} + + UniquePtr<MessageChannel::UntypedCallbackHolder> untypedCallback = + GetIPCChannel()->PopCallback(${msgvar}, Id()); + + typedef MessageChannel::CallbackHolder<${resolvetype}> CallbackHolder; + auto* callback = static_cast<CallbackHolder*>(untypedCallback.get()); + if (!callback) { + FatalError("Error unknown callback"); + return MsgProcessingError; + } + + if (${resolve}) { + $*{desstmts} + callback->Resolve(${resolvearg}); + } else { + $*{desrej} + callback->Reject(std::move(${reason})); + } + return MsgProcessed; + """, + prologue=prologue, + msgvar=self.msgvar, + resolve=resolve, + resolvetype=resolvetype, + desstmts=desstmts, + resolvearg=resolvearg, + desrej=desrej, + reason=reason, + ) + + return (lbl, case) + + def genAsyncSendMethod(self, md): + decl = self.makeSendMethodDecl(md) + if "VirtualSendImpl" in md.attributes: + decl.methodspec = MethodSpec.VIRTUAL + method = MethodDefn(decl) + msgvar, stmts = self.makeMessage(md, errfnSend) + retvar, sendstmts = self.sendAsync(md, msgvar) + + method.addstmts(stmts + [Whitespace.NL] + sendstmts + [StmtReturn(retvar)]) + + movemethod = None + + # Add the promise overload if we need one. + if md.returns: + decl = self.makeSendMethodDecl(md, promise=True) + if "VirtualSendImpl" in md.attributes: + decl.methodspec = MethodSpec.VIRTUAL + promisemethod = MethodDefn(decl) + stmts = self.sendAsyncWithPromise(md) + promisemethod.addstmts(stmts) + + (lbl, case) = self.genRecvAsyncReplyCase(md) + else: + (promisemethod, lbl, case) = (None, None, None) + + return method, movemethod, promisemethod, (lbl, case) + + def genBlockingSendMethod(self, md): + method = MethodDefn(self.makeSendMethodDecl(md)) + + msgvar, serstmts = self.makeMessage(md, errfnSend) + replyvar = self.replyvar + + sendok, sendstmts = self.sendBlocking(md, msgvar, replyvar) + failif = StmtIf(ExprNot(sendok)) + failif.addifstmt(StmtReturn.FALSE) + + desstmts = self.deserializeReply( + md, replyvar, self.side, errfnSend, errfnSentinel() + ) + + method.addstmts( + serstmts + + [Whitespace.NL, StmtDecl(Decl(Type("UniquePtr<Message>"), replyvar.name))] + + sendstmts + + [failif] + + desstmts + + [Whitespace.NL, StmtReturn.TRUE] + ) + + movemethod = None + + return method, movemethod + + def genCtorRecvCase(self, md): + lbl = CaseLabel(md.pqMsgId()) + case = StmtBlock() + actorhandle = self.handlevar + + stmts = self.deserializeMessage( + md, self.side, errfnRecv, errfnSent=errfnSentinel(_Result.ValuError) + ) + + idvar, saveIdStmts = self.saveActorId(md) + case.addstmts( + stmts + + [ + StmtDecl(Decl(r.bareType(self.side), r.var().name), initargs=[]) + for r in md.returns + ] + # alloc the actor, register it under the foreign ID + + [self.callAllocActor(md, retsems="in", side=self.side)] + + self.bindManagedActor( + md.actorDecl(), errfn=_Result.ValuError, idexpr=_actorHId(actorhandle) + ) + + [Whitespace.NL] + + saveIdStmts + + self.invokeRecvHandler(md) + + self.makeReply(md, errfnRecv, idvar) + + [Whitespace.NL, StmtReturn(_Result.Processed)] + ) + + return lbl, case + + def genDtorRecvCase(self, md): + lbl = CaseLabel(md.pqMsgId()) + case = StmtBlock() + + stmts = self.deserializeMessage( + md, self.side, errfnRecv, errfnSent=errfnSentinel(_Result.ValuError) + ) + + idvar, saveIdStmts = self.saveActorId(md) + case.addstmts( + stmts + + [ + StmtDecl(Decl(r.bareType(self.side), r.var().name), initargs=[]) + for r in md.returns + ] + + self.invokeRecvHandler(md) + + [Whitespace.NL] + + saveIdStmts + + self.makeReply(md, errfnRecv, routingId=idvar) + + [Whitespace.NL] + + self.dtorEpilogue(md, ExprVar.THIS) + + [Whitespace.NL, StmtReturn(_Result.Processed)] + ) + + return lbl, case + + def genRecvCase(self, md): + lbl = CaseLabel(md.pqMsgId()) + case = StmtBlock() + + stmts = self.deserializeMessage( + md, self.side, errfn=errfnRecv, errfnSent=errfnSentinel(_Result.ValuError) + ) + + idvar, saveIdStmts = self.saveActorId(md) + declstmts = [ + StmtDecl(Decl(r.bareType(self.side), r.var().name), initargs=[]) + for r in md.returns + ] + if md.decl.type.isAsync() and md.returns: + declstmts = self.makeResolver(md, errfnRecv, routingId=idvar) + case.addstmts( + stmts + + saveIdStmts + + declstmts + + self.invokeRecvHandler(md) + + [Whitespace.NL] + + self.makeReply(md, errfnRecv, routingId=idvar) + + [StmtReturn(_Result.Processed)] + ) + + return lbl, case + + # helper methods + + def makeMessage(self, md, errfn, fromActor=None): + msgvar = self.msgvar + writervar = ExprVar("writer__") + routingId = self.protocol.routingId(fromActor) + this = fromActor or ExprVar.THIS + + stmts = ( + [ + StmtDecl( + Decl(Type("UniquePtr<IPC::Message>"), msgvar.name), + init=ExprCall(ExprVar(md.pqMsgCtorFunc()), args=[routingId]), + ), + StmtDecl( + Decl(Type("IPC::MessageWriter"), writervar.name), + initargs=[ExprDeref(msgvar), this], + ), + ] + + [Whitespace.NL] + + [ + _ParamTraits.checkedWrite( + p.ipdltype, + p.var(), + ExprAddrOf(writervar), + sentinelKey=p.name, + ) + for p in md.params + ] + + [Whitespace.NL] + + self.setMessageFlags(md, msgvar) + ) + return msgvar, stmts + + def makeResolver(self, md, errfn, routingId): + if routingId is None: + routingId = self.protocol.routingId() + if not md.decl.type.isAsync() or not md.hasReply(): + return [] + + def paramValue(idx): + assert idx < len(md.returns) + if len(md.returns) > 1: + return ExprCode("std::get<${idx}>(aParam)", idx=idx) + return ExprVar("aParam") + + serializeParams = [ + _ParamTraits.checkedWrite( + p.ipdltype, + paramValue(idx), + ExprAddrOf(ExprVar("writer__")), + sentinelKey=p.name, + ) + for idx, p in enumerate(md.returns) + ] + + return [ + StmtCode( + """ + UniquePtr<IPC::Message> ${replyvar}(${replyCtor}(${routingId})); + ${replyvar}->set_seqno(${msgvar}.seqno()); + + RefPtr<mozilla::ipc::IPDLResolverInner> resolver__ = + new mozilla::ipc::IPDLResolverInner(std::move(${replyvar}), this); + + ${resolvertype} resolver = [resolver__ = std::move(resolver__)](${resolveType} aParam) { + resolver__->Resolve([&] (IPC::Message* ${replyvar}, IProtocol* self__) { + IPC::MessageWriter writer__(*${replyvar}, self__); + $*{serializeParams} + ${logSendingReply} + }); + }; + """, + msgvar=self.msgvar, + resolvertype=Type(md.resolverName()), + routingId=routingId, + resolveType=_resolveType(md.returns, self.side), + replyvar=self.replyvar, + replyCtor=ExprVar(md.pqReplyCtorFunc()), + serializeParams=serializeParams, + logSendingReply=self.logMessage( + md, + self.replyvar, + "Sending reply ", + actor=ExprVar("self__"), + ), + ) + ] + + def makeReply(self, md, errfn, routingId): + if routingId is None: + routingId = self.protocol.routingId() + # TODO special cases for async ctor/dtor replies + if not md.decl.type.hasReply(): + return [] + if md.decl.type.isAsync() and md.decl.type.hasReply(): + return [] + + replyvar = self.replyvar + return ( + [ + StmtExpr( + ExprAssn( + replyvar, + ExprCall(ExprVar(md.pqReplyCtorFunc()), args=[routingId]), + ) + ), + StmtDecl( + Decl(Type("IPC::MessageWriter"), "writer__"), + initargs=[ExprDeref(replyvar), ExprVar.THIS], + ), + Whitespace.NL, + ] + + [ + _ParamTraits.checkedWrite( + r.ipdltype, + r.var(), + ExprAddrOf(ExprVar("writer__")), + sentinelKey=r.name, + ) + for r in md.returns + ] + + self.setMessageFlags(md, replyvar) + + [self.logMessage(md, replyvar, "Sending reply ")] + ) + + def setMessageFlags(self, md, var, seqno=None): + stmts = [] + + if seqno: + stmts.append( + StmtExpr(ExprCall(ExprSelect(var, "->", "set_seqno"), args=[seqno])) + ) + + return stmts + [Whitespace.NL] + + def deserializeMessage(self, md, side, errfn, errfnSent): + msgvar = self.msgvar + msgexpr = ExprAddrOf(msgvar) + readervar = ExprVar("reader__") + isctor = md.decl.type.isCtor() + stmts = [ + self.logMessage(md, msgexpr, "Received ", receiving=True), + self.profilerLabel(md), + Whitespace.NL, + ] + + if 0 == len(md.params): + return stmts + + start, reads = 0, [] + if isctor: + # return the raw actor handle so that its ID can be used + # to construct the "real" actor + handlevar = self.handlevar + handletype = Type("ActorHandle") + reads = [ + _ParamTraits.checkedRead( + None, + handletype, + handlevar, + ExprAddrOf(readervar), + errfn, + "'%s'" % handletype.name, + sentinelKey="actor", + errfnSentinel=errfnSent, + ) + ] + start = 1 + + def maybeTainted(p, side): + if md.decl.type.tainted and "NoTaint" not in p.attributes: + return Type("Tainted", T=p.bareType(side)) + return p.bareType(side) + + reads.extend( + [ + _ParamTraits.checkedRead( + p.ipdltype, + maybeTainted(p, side), + p.var(), + ExprAddrOf(readervar), + errfn, + "'%s'" % p.ipdltype.name(), + sentinelKey=p.name, + errfnSentinel=errfnSent, + ) + for p in md.params[start:] + ] + ) + + stmts.extend( + ( + [ + StmtDecl( + Decl(Type("IPC::MessageReader"), readervar.name), + initargs=[msgvar, ExprVar.THIS], + ) + ] + + [Whitespace.NL] + + reads + + [StmtCode("${reader}.EndRead();\n", reader=readervar)] + ) + ) + + return stmts + + def deserializeAsyncReply(self, md, side, errfn, errfnSent): + msgvar = self.msgvar + readervar = ExprVar("reader__") + msgexpr = ExprAddrOf(msgvar) + isctor = md.decl.type.isCtor() + resolve = ExprVar("resolve__") + reason = ExprVar("reason__") + + # NOTE: The `resolve__` and `reason__` parameters don't have sentinels, + # as they are serialized by the IPDLResolverInner type in + # ProtocolUtils.cpp rather than by generated code. + desresolve = [ + StmtCode( + """ + bool resolve__ = false; + if (!IPC::ReadParam(&${readervar}, &resolve__)) { + FatalError("Error deserializing bool"); + return MsgValueError; + } + """, + readervar=readervar, + ), + ] + desrej = [ + StmtCode( + """ + ResponseRejectReason reason__{}; + if (!IPC::ReadParam(&${readervar}, &reason__)) { + FatalError("Error deserializing ResponseRejectReason"); + return MsgValueError; + } + ${readervar}.EndRead(); + """, + readervar=readervar, + ), + ] + prologue = [ + self.logMessage(md, msgexpr, "Received ", receiving=True), + self.profilerLabel(md), + Whitespace.NL, + ] + + if not md.returns: + return prologue + + prologue.extend( + [ + StmtDecl( + Decl(Type("IPC::MessageReader"), readervar.name), + initargs=[msgvar, ExprVar.THIS], + ) + ] + + desresolve + ) + + start, reads = 0, [] + if isctor: + # return the raw actor handle so that its ID can be used + # to construct the "real" actor + handlevar = self.handlevar + handletype = Type("ActorHandle") + reads = [ + _ParamTraits.checkedRead( + None, + handletype, + handlevar, + ExprAddrOf(readervar), + errfn, + "'%s'" % handletype.name, + sentinelKey="actor", + errfnSentinel=errfnSent, + ) + ] + start = 1 + + stmts = ( + reads + + [ + _ParamTraits.checkedRead( + p.ipdltype, + p.bareType(side), + p.var(), + ExprAddrOf(readervar), + errfn, + "'%s'" % p.ipdltype.name(), + sentinelKey=p.name, + errfnSentinel=errfnSent, + ) + for p in md.returns[start:] + ] + + [StmtCode("${reader}.EndRead();", reader=readervar)] + ) + + return resolve, reason, prologue, desrej, stmts + + def deserializeReply(self, md, replyexpr, side, errfn, errfnSentinel, actor=None): + stmts = [ + Whitespace.NL, + self.logMessage(md, replyexpr, "Received reply ", actor, receiving=True), + ] + if 0 == len(md.returns): + return stmts + + def tempvar(r): + return ExprVar(r.var().name + "__reply") + + readervar = ExprVar("reader__") + stmts.extend( + [ + Whitespace.NL, + StmtDecl( + Decl(Type("IPC::MessageReader"), readervar.name), + initargs=[ExprDeref(self.replyvar), ExprVar.THIS], + ), + ] + + [Whitespace.NL] + + [ + _ParamTraits.checkedRead( + r.ipdltype, + r.bareType(side), + tempvar(r), + ExprAddrOf(readervar), + errfn, + "'%s'" % r.ipdltype.name(), + sentinelKey=r.name, + errfnSentinel=errfnSentinel, + ) + for r in md.returns + ] + # Move-assign the values out of the variables created with + # checkedRead into outparams. + + [ + StmtExpr(ExprAssn(ExprDeref(r.var()), ExprMove(tempvar(r)))) + for r in md.returns + ] + + [StmtCode("${reader}.EndRead();", reader=readervar)] + ) + + return stmts + + def sendAsync(self, md, msgexpr, actor=None): + sendok = ExprVar("sendok__") + resolvefn = ExprVar("aResolve") + rejectfn = ExprVar("aReject") + + stmts = [ + Whitespace.NL, + self.logMessage(md, msgexpr, "Sending ", actor), + self.profilerLabel(md), + ] + stmts.append(Whitespace.NL) + + # Generate the actual call expression. + send = ExprVar("ChannelSend") + if actor is not None: + send = ExprSelect(actor, "->", send.name) + if md.returns: + stmts.append( + StmtExpr( + ExprCall( + send, + args=[ + ExprMove(msgexpr), + ExprVar(md.pqReplyId()), + ExprMove(resolvefn), + ExprMove(rejectfn), + ], + ) + ) + ) + retvar = None + else: + stmts.append( + StmtDecl( + Decl(Type.BOOL, sendok.name), + init=ExprCall(send, args=[ExprMove(msgexpr)]), + ) + ) + retvar = sendok + + return (retvar, stmts) + + def sendBlocking(self, md, msgexpr, replyexpr, actor=None): + send = ExprVar("ChannelSend") + if md.decl.type.isInterrupt(): + send = ExprVar("ChannelCall") + if actor is not None: + send = ExprSelect(actor, "->", send.name) + + sendok = ExprVar("sendok__") + self.externalIncludes.add("mozilla/ProfilerMarkers.h") + return ( + sendok, + ( + [ + Whitespace.NL, + self.logMessage(md, msgexpr, "Sending ", actor), + self.profilerLabel(md), + ] + + [ + Whitespace.NL, + StmtDecl(Decl(Type.BOOL, sendok.name), init=ExprLiteral.FALSE), + StmtBlock( + [ + StmtExpr( + ExprCall( + ExprVar("AUTO_PROFILER_TRACING_MARKER"), + [ + ExprLiteral.String("Sync IPC"), + ExprLiteral.String( + self.protocol.name + + "::" + + md.prettyMsgName() + ), + ExprVar("IPC"), + ], + ) + ), + StmtExpr( + ExprAssn( + sendok, + ExprCall( + send, + args=[ExprMove(msgexpr), ExprAddrOf(replyexpr)], + ), + ) + ), + ] + ), + ] + ), + ) + + def sendAsyncWithPromise(self, md): + # Create a new promise, and forward to the callback send overload. + promise = _makePromise(md.returns, self.side, resolver=True) + + if len(md.returns) > 1: + resolvetype = _tuple([d.bareType(self.side) for d in md.returns]) + else: + resolvetype = md.returns[0].bareType(self.side) + + resolve = ExprCode( + """ + [promise__](${resolvetype}&& aValue) { + promise__->Resolve(std::move(aValue), __func__); + } + """, + resolvetype=resolvetype, + ) + reject = ExprCode( + """ + [promise__](ResponseRejectReason&& aReason) { + promise__->Reject(std::move(aReason), __func__); + } + """, + resolvetype=resolvetype, + ) + + args = [ExprMove(p.var()) for p in md.params] + [resolve, reject] + stmt = StmtCode( + """ + RefPtr<${promise}> promise__ = new ${promise}(__func__); + promise__->UseDirectTaskDispatch(__func__); + ${send}($,{args}); + return promise__; + """, + promise=promise, + send=md.sendMethod(), + args=args, + ) + return [stmt] + + def callAllocActor(self, md, retsems, side): + actortype = md.actorDecl().bareType(self.side) + if md.decl.type.constructedType().isRefcounted(): + actortype.ptr = False + actortype = _refptr(actortype) + + callalloc = self.thisCall( + _allocMethod(md.decl.type.constructedType(), side), + args=md.makeCxxArgs(retsems=retsems, retcallsems="out", implicit=False), + ) + + return StmtDecl(Decl(actortype, md.actorDecl().var().name), init=callalloc) + + def invokeRecvHandler(self, md): + retsems = "in" + if md.decl.type.isAsync() and md.returns: + retsems = "resolver" + okdecl = StmtDecl( + Decl(Type("mozilla::ipc::IPCResult"), "__ok"), + init=self.thisCall( + md.recvMethod(), + md.makeCxxArgs( + paramsems="move", + retsems=retsems, + retcallsems="out", + ), + ), + ) + failif = StmtIf(ExprNot(ExprVar("__ok"))) + failif.addifstmts( + [ + _protocolErrorBreakpoint("Handler returned error code!"), + Whitespace( + "// Error handled in mozilla::ipc::IPCResult\n", indent=True + ), + StmtReturn(_Result.ProcessingError), + ] + ) + return [okdecl, failif] + + def makeDtorMethodDecl(self, md, actorvar): + decl = self.makeSendMethodDecl(md) + decl.params.insert( + 0, + Decl( + _cxxInType( + ipdl.type.ActorType(md.decl.type.constructedType()), + side=self.side, + direction="send", + ), + actorvar.name, + ), + ) + decl.methodspec = MethodSpec.STATIC + return decl + + def makeSendMethodDecl(self, md, promise=False, paramsems="in"): + implicit = md.decl.type.hasImplicitActorParam() + if md.decl.type.isAsync() and md.returns: + if promise: + returnsems = "promise" + rettype = _refptr(Type(md.promiseName())) + else: + returnsems = "callback" + rettype = Type.VOID + else: + assert not promise + returnsems = "out" + rettype = Type.BOOL + decl = MethodDecl( + md.sendMethod(), + params=md.makeCxxParams( + paramsems, + returnsems=returnsems, + side=self.side, + implicit=implicit, + direction="send", + ), + warn_unused=( + (self.side == "parent" and returnsems != "callback") + or (md.decl.type.isCtor() and not md.decl.type.isAsync()) + ), + ret=rettype, + ) + if md.decl.type.isCtor(): + decl.ret = md.actorDecl().bareType(self.side) + return decl + + def logMessage(self, md, msgptr, pfx, actor=None, receiving=False): + actorname = _actorName(self.protocol.name, self.side) + return StmtCode( + """ + if (mozilla::ipc::LoggingEnabledFor(${actorname})) { + mozilla::ipc::LogMessageForProtocol( + ${actorname}, + ${actor}->ToplevelProtocol()->OtherPidMaybeInvalid(), + ${pfx}, + ${msgptr}->type(), + mozilla::ipc::MessageDirection::${direction}); + } + """, + actorname=ExprLiteral.String(actorname), + actor=actor or ExprVar.THIS, + pfx=ExprLiteral.String(pfx), + msgptr=msgptr, + direction="eReceiving" if receiving else "eSending", + ) + + def profilerLabel(self, md): + self.externalIncludes.add("mozilla/ProfilerLabels.h") + return StmtCode( + """ + AUTO_PROFILER_LABEL("${name}::${msgname}", OTHER); + """, + name=self.protocol.name, + msgname=md.prettyMsgName(), + ) + + def saveActorId(self, md): + idvar = ExprVar("id__") + if md.decl.type.hasReply(): + # only save the ID if we're actually going to use it, to + # avoid unused-variable warnings + saveIdStmts = [ + StmtDecl(Decl(_actorIdType(), idvar.name), self.protocol.routingId()) + ] + else: + saveIdStmts = [] + return idvar, saveIdStmts + + +class _GenerateProtocolParentCode(_GenerateProtocolActorCode): + def __init__(self): + _GenerateProtocolActorCode.__init__(self, "parent") + + def sendsMessage(self, md): + return not md.decl.type.isIn() + + def receivesMessage(self, md): + return md.decl.type.isInout() or md.decl.type.isIn() + + +class _GenerateProtocolChildCode(_GenerateProtocolActorCode): + def __init__(self): + _GenerateProtocolActorCode.__init__(self, "child") + + def sendsMessage(self, md): + return not md.decl.type.isOut() + + def receivesMessage(self, md): + return md.decl.type.isInout() or md.decl.type.isOut() + + +# ----------------------------------------------------------------------------- +# Utility passes +## + + +def _splitClassDeclDefn(cls): + """Destructively split |cls| methods into declarations and + definitions (if |not methodDecl.force_inline|). Return classDecl, + methodDefns.""" + defns = Block() + + for i, stmt in enumerate(cls.stmts): + if isinstance(stmt, MethodDefn) and not stmt.decl.force_inline: + decl, defn = _splitMethodDeclDefn(stmt, cls) + cls.stmts[i] = StmtDecl(decl) + if defn: + defns.addstmts([defn, Whitespace.NL]) + + return cls, defns + + +def _splitMethodDeclDefn(md, cls): + # Pure methods have decls but no defns. + if md.decl.methodspec == MethodSpec.PURE: + return md.decl, None + + saveddecl = deepcopy(md.decl) + md.decl.cls = cls + # Don't emit method specifiers on method defns. + md.decl.methodspec = MethodSpec.NONE + md.decl.warn_unused = False + md.decl.only_for_definition = True + for param in md.decl.params: + if isinstance(param, Param): + param.default = None + return saveddecl, md + + +def _splitFuncDeclDefn(fun): + assert not fun.decl.force_inline + return StmtDecl(fun.decl), fun diff --git a/ipc/ipdl/ipdl/parser.py b/ipc/ipdl/ipdl/parser.py new file mode 100644 index 0000000000..1857131868 --- /dev/null +++ b/ipc/ipdl/ipdl/parser.py @@ -0,0 +1,680 @@ +# 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 +from ply import lex, yacc + +from ipdl.ast import * + +# ----------------------------------------------------------------------------- + + +class ParseError(Exception): + def __init__(self, loc, fmt, *args): + self.loc = loc + self.error = ( + "%s%s: error: %s" % (Parser.includeStackString(), loc, fmt) + ) % args + + def __str__(self): + return self.error + + +def _safeLinenoValue(t): + lineno, value = 0, "???" + if hasattr(t, "lineno"): + lineno = t.lineno + if hasattr(t, "value"): + value = t.value + return lineno, value + + +def _error(loc, fmt, *args): + raise ParseError(loc, fmt, *args) + + +class Parser: + # when we reach an |include [protocol] foo;| statement, we need to + # save the current parser state and create a new one. this "stack" is + # where that state is saved + # + # there is one Parser per file + current = None + parseStack = [] + parsed = {} + + def __init__(self, type, name, debug=False): + assert type and name + self.type = type + self.debug = debug + self.filename = None + self.includedirs = None + self.loc = None # not always up to date + self.lexer = None + self.parser = None + self.tu = TranslationUnit(type, name) + self.direction = None + + def parse(self, input, filename, includedirs): + assert os.path.isabs(filename) + + if self.tu.name in Parser.parsed: + priorTU = Parser.parsed[self.tu.name].tu + if os.path.normcase(priorTU.filename) != os.path.normcase(filename): + _error( + Loc(filename), + "Trying to load `%s' from a file when we'd already seen it in file `%s'" + % (self.tu.name, priorTU.filename), + ) + + return priorTU + + self.lexer = lex.lex(debug=self.debug) + self.parser = yacc.yacc(debug=self.debug, write_tables=False) + self.filename = filename + self.includedirs = includedirs + self.tu.filename = filename + + Parser.parsed[self.tu.name] = self + Parser.parseStack.append(Parser.current) + Parser.current = self + + try: + ast = self.parser.parse(input=input, lexer=self.lexer, debug=self.debug) + finally: + Parser.current = Parser.parseStack.pop() + + return ast + + def resolveIncludePath(self, filepath): + """Return the absolute path from which the possibly partial + |filepath| should be read, or |None| if |filepath| cannot be located.""" + for incdir in self.includedirs + [""]: + realpath = os.path.join(incdir, filepath) + if os.path.isfile(realpath): + return os.path.abspath(realpath) + return None + + # returns a GCC-style string representation of the include stack. + # e.g., + # in file included from 'foo.ipdl', line 120: + # in file included from 'bar.ipd', line 12: + # which can be printed above a proper error message or warning + @staticmethod + def includeStackString(): + s = "" + for parse in Parser.parseStack[1:]: + s += " in file included from `%s', line %d:\n" % ( + parse.loc.filename, + parse.loc.lineno, + ) + return s + + +def locFromTok(p, num): + return Loc(Parser.current.filename, p.lineno(num)) + + +# ----------------------------------------------------------------------------- + +reserved = set( + ( + "async", + "both", + "child", + "class", + "from", + "include", + "intr", + "manager", + "manages", + "namespace", + "nullable", + "or", + "parent", + "protocol", + "returns", + "struct", + "sync", + "union", + "UniquePtr", + "using", + ) +) +tokens = [ + "COLONCOLON", + "ID", + "STRING", +] + [r.upper() for r in reserved] + +t_COLONCOLON = "::" + +literals = "(){}[]<>;:,?=" +t_ignore = " \f\t\v" + + +def t_linecomment(t): + r"//[^\n]*" + + +def t_multilinecomment(t): + r"/\*(\n|.)*?\*/" + t.lexer.lineno += t.value.count("\n") + + +def t_NL(t): + r"(?:\r\n|\n|\n)+" + t.lexer.lineno += len(t.value) + + +def t_ID(t): + r"[a-zA-Z_][a-zA-Z0-9_]*" + if t.value in reserved: + t.type = t.value.upper() + return t + + +def t_STRING(t): + r'"[^"\n]*"' + t.value = StringLiteral(Loc(Parser.current.filename, t.lineno), t.value[1:-1]) + return t + + +def t_error(t): + _error( + Loc(Parser.current.filename, t.lineno), + "lexically invalid characters `%s", + t.value, + ) + + +# ----------------------------------------------------------------------------- + + +def p_TranslationUnit(p): + """TranslationUnit : Preamble NamespacedStuff""" + tu = Parser.current.tu + tu.loc = Loc(tu.filename) + for stmt in p[1]: + if isinstance(stmt, CxxInclude): + tu.addCxxInclude(stmt) + elif isinstance(stmt, Include): + tu.addInclude(stmt) + elif isinstance(stmt, UsingStmt): + tu.addUsingStmt(stmt) + else: + assert 0 + + for thing in p[2]: + if isinstance(thing, StructDecl): + tu.addStructDecl(thing) + elif isinstance(thing, UnionDecl): + tu.addUnionDecl(thing) + elif isinstance(thing, Protocol): + if tu.protocol is not None: + _error(thing.loc, "only one protocol definition per file") + tu.protocol = thing + else: + assert 0 + + # The "canonical" namespace of the tu, what it's considered to be + # in for the purposes of C++: |#include "foo/bar/TU.h"| + if tu.protocol: + assert tu.filetype == "protocol" + tu.namespaces = tu.protocol.namespaces + tu.name = tu.protocol.name + else: + assert tu.filetype == "header" + # There's not really a canonical "thing" in headers. So + # somewhat arbitrarily use the namespace of the last + # interesting thing that was declared. + for thing in reversed(tu.structsAndUnions): + tu.namespaces = thing.namespaces + break + + p[0] = tu + + +# -------------------- +# Preamble + + +def p_Preamble(p): + """Preamble : Preamble PreambleStmt ';' + |""" + if 1 == len(p): + p[0] = [] + else: + p[1].append(p[2]) + p[0] = p[1] + + +def p_PreambleStmt(p): + """PreambleStmt : CxxIncludeStmt + | IncludeStmt + | UsingStmt""" + p[0] = p[1] + + +def p_CxxIncludeStmt(p): + """CxxIncludeStmt : INCLUDE STRING""" + p[0] = CxxInclude(locFromTok(p, 1), p[2].value) + + +def p_IncludeStmt(p): + """IncludeStmt : INCLUDE PROTOCOL ID + | INCLUDE ID""" + loc = locFromTok(p, 1) + + Parser.current.loc = loc + if 4 == len(p): + id = p[3] + type = "protocol" + else: + id = p[2] + type = "header" + inc = Include(loc, type, id) + + path = Parser.current.resolveIncludePath(inc.file) + if path is None: + raise ParseError(loc, "can't locate include file `%s'" % (inc.file)) + + inc.tu = Parser(type, id).parse(open(path).read(), path, Parser.current.includedirs) + p[0] = inc + + +def p_UsingKind(p): + """UsingKind : CLASS + | STRUCT + |""" + p[0] = p[1] if 2 == len(p) else None + + +def p_UsingStmt(p): + """UsingStmt : Attributes USING UsingKind CxxType FROM STRING""" + p[0] = UsingStmt( + locFromTok(p, 2), + attributes=p[1], + kind=p[3], + cxxTypeSpec=p[4], + cxxHeader=p[6].value, + ) + + +# -------------------- +# Namespaced stuff + + +def p_NamespacedStuff(p): + """NamespacedStuff : NamespacedStuff NamespaceThing + | NamespaceThing""" + if 2 == len(p): + p[0] = p[1] + else: + p[1].extend(p[2]) + p[0] = p[1] + + +def p_NamespaceThing(p): + """NamespaceThing : NAMESPACE ID '{' NamespacedStuff '}' + | StructDecl + | UnionDecl + | ProtocolDefn""" + if 2 == len(p): + p[0] = [p[1]] + else: + for thing in p[4]: + thing.addOuterNamespace(Namespace(locFromTok(p, 1), p[2])) + p[0] = p[4] + + +def p_StructDecl(p): + """StructDecl : Attributes STRUCT ID '{' StructFields '}' ';' + | Attributes STRUCT ID '{' '}' ';'""" + if 8 == len(p): + p[0] = StructDecl(locFromTok(p, 2), p[3], p[5], p[1]) + else: + p[0] = StructDecl(locFromTok(p, 2), p[3], [], p[1]) + + +def p_StructFields(p): + """StructFields : StructFields StructField ';' + | StructField ';'""" + if 3 == len(p): + p[0] = [p[1]] + else: + p[1].append(p[2]) + p[0] = p[1] + + +def p_StructField(p): + """StructField : Type ID""" + p[0] = StructField(locFromTok(p, 1), p[1], p[2]) + + +def p_UnionDecl(p): + """UnionDecl : Attributes UNION ID '{' ComponentTypes '}' ';'""" + p[0] = UnionDecl(locFromTok(p, 2), p[3], p[5], p[1]) + + +def p_ComponentTypes(p): + """ComponentTypes : ComponentTypes Type ';' + | Type ';'""" + if 3 == len(p): + p[0] = [p[1]] + else: + p[1].append(p[2]) + p[0] = p[1] + + +def p_ProtocolDefn(p): + """ProtocolDefn : Attributes OptionalSendSemantics \ + PROTOCOL ID '{' ProtocolBody '}' ';'""" + protocol = p[6] + protocol.loc = locFromTok(p, 3) + protocol.name = p[4] + protocol.attributes = p[1] + protocol.sendSemantics = p[2] + p[0] = protocol + + if Parser.current.type == "header": + _error( + protocol.loc, + "can't define a protocol in a header. Do it in a protocol spec instead.", + ) + + +def p_ProtocolBody(p): + """ProtocolBody : ManagersStmtOpt""" + p[0] = p[1] + + +# -------------------- +# manager/manages stmts + + +def p_ManagersStmtOpt(p): + """ManagersStmtOpt : ManagersStmt ManagesStmtsOpt + | ManagesStmtsOpt""" + if 2 == len(p): + p[0] = p[1] + else: + p[2].managers = p[1] + p[0] = p[2] + + +def p_ManagersStmt(p): + """ManagersStmt : MANAGER ManagerList ';'""" + if 1 == len(p): + p[0] = [] + else: + p[0] = p[2] + + +def p_ManagerList(p): + """ManagerList : ID + | ManagerList OR ID""" + if 2 == len(p): + p[0] = [Manager(locFromTok(p, 1), p[1])] + else: + p[1].append(Manager(locFromTok(p, 3), p[3])) + p[0] = p[1] + + +def p_ManagesStmtsOpt(p): + """ManagesStmtsOpt : ManagesStmt ManagesStmtsOpt + | MessageDeclsOpt""" + if 2 == len(p): + p[0] = p[1] + else: + p[2].managesStmts.insert(0, p[1]) + p[0] = p[2] + + +def p_ManagesStmt(p): + """ManagesStmt : MANAGES ID ';'""" + p[0] = ManagesStmt(locFromTok(p, 1), p[2]) + + +# -------------------- +# Message decls + + +def p_MessageDeclsOpt(p): + """MessageDeclsOpt : MessageDeclThing MessageDeclsOpt + |""" + if 1 == len(p): + # we fill in |loc| in the Protocol rule + p[0] = Protocol(None) + else: + p[2].messageDecls.insert(0, p[1]) + p[0] = p[2] + + +def p_MessageDeclThing(p): + """MessageDeclThing : MessageDirectionLabel ':' MessageDecl ';' + | MessageDecl ';'""" + if 3 == len(p): + p[0] = p[1] + else: + p[0] = p[3] + + +def p_MessageDirectionLabel(p): + """MessageDirectionLabel : PARENT + | CHILD + | BOTH""" + if p[1] == "parent": + Parser.current.direction = IN + elif p[1] == "child": + Parser.current.direction = OUT + elif p[1] == "both": + Parser.current.direction = INOUT + else: + assert 0 + + +def p_MessageDecl(p): + """MessageDecl : Attributes SendSemantics MessageBody""" + msg = p[3] + msg.attributes = p[1] + msg.sendSemantics = p[2] + + if Parser.current.direction is None: + _error(msg.loc, "missing message direction") + msg.direction = Parser.current.direction + + p[0] = msg + + +def p_MessageBody(p): + """MessageBody : ID MessageInParams MessageOutParams""" + # FIXME/cjones: need better loc info: use one of the quals + name = p[1] + msg = MessageDecl(locFromTok(p, 1)) + msg.name = name + msg.addInParams(p[2]) + msg.addOutParams(p[3]) + + p[0] = msg + + +def p_MessageInParams(p): + """MessageInParams : '(' ParamList ')'""" + p[0] = p[2] + + +def p_MessageOutParams(p): + """MessageOutParams : RETURNS '(' ParamList ')' + |""" + if 1 == len(p): + p[0] = [] + else: + p[0] = p[3] + + +# -------------------- +# Attributes +def p_Attributes(p): + """Attributes : '[' AttributeList ']' + |""" + p[0] = {} + if 4 == len(p): + for attr in p[2]: + if attr.name in p[0]: + _error(attr.loc, "Repeated extended attribute `%s'", attr.name) + p[0][attr.name] = attr + + +def p_AttributeList(p): + """AttributeList : Attribute ',' AttributeList + | Attribute""" + p[0] = [p[1]] + if 4 == len(p): + p[0] += p[3] + + +def p_Attribute(p): + """Attribute : ID AttributeValue""" + p[0] = Attribute(locFromTok(p, 1), p[1], p[2]) + + +def p_AttributeValue(p): + """AttributeValue : '=' ID + | '=' STRING + |""" + if 1 == len(p): + p[0] = None + else: + p[0] = p[2] + + +def p_SendSemantics(p): + """SendSemantics : ASYNC + | SYNC + | INTR""" + if p[1] == "async": + p[0] = ASYNC + elif p[1] == "sync": + p[0] = SYNC + else: + assert p[1] == "intr" + p[0] = INTR + + +def p_OptionalSendSemantics(p): + """OptionalSendSemantics : SendSemantics + |""" + if 2 == len(p): + p[0] = p[1] + else: + p[0] = ASYNC + + +# -------------------- +# Minor stuff + + +def p_ParamList(p): + """ParamList : ParamList ',' Param + | Param + |""" + if 1 == len(p): + p[0] = [] + elif 2 == len(p): + p[0] = [p[1]] + else: + p[1].append(p[3]) + p[0] = p[1] + + +def p_Param(p): + """Param : Attributes Type ID""" + p[0] = Param(locFromTok(p, 2), p[2], p[3], p[1]) + + +def p_Type(p): + """Type : MaybeNullable BasicType""" + # only some types are nullable; we check this in the type checker + p[2].nullable = p[1] + p[0] = p[2] + + +def p_BasicType(p): + """BasicType : CxxID + | CxxID '[' ']' + | CxxID '?' + | CxxUniquePtrInst""" + # ID == CxxType; we forbid qnames here, + # in favor of the |using| declaration + if not isinstance(p[1], TypeSpec): + assert (len(p[1]) == 2) or (len(p[1]) == 3) + if 2 == len(p[1]): + # p[1] is CxxID. isunique = False + p[1] = p[1] + (False,) + loc, id, isunique = p[1] + p[1] = TypeSpec(loc, id) + p[1].uniqueptr = isunique + if 4 == len(p): + p[1].array = True + if 3 == len(p): + p[1].maybe = True + p[0] = p[1] + + +def p_MaybeNullable(p): + """MaybeNullable : NULLABLE + |""" + p[0] = 2 == len(p) + + +# -------------------- +# C++ stuff + + +def p_CxxType(p): + """CxxType : QualifiedID + | CxxID""" + if isinstance(p[1], QualifiedId): + p[0] = p[1] + else: + loc, id = p[1] + p[0] = QualifiedId(loc, id) + + +def p_QualifiedID(p): + """QualifiedID : QualifiedID COLONCOLON CxxID + | CxxID COLONCOLON CxxID""" + if isinstance(p[1], QualifiedId): + loc, id = p[3] + p[1].qualify(id) + p[0] = p[1] + else: + loc1, id1 = p[1] + _, id2 = p[3] + p[0] = QualifiedId(loc1, id2, [id1]) + + +def p_CxxID(p): + """CxxID : ID + | CxxTemplateInst""" + if isinstance(p[1], tuple): + p[0] = p[1] + else: + p[0] = (locFromTok(p, 1), str(p[1])) + + +def p_CxxTemplateInst(p): + """CxxTemplateInst : ID '<' ID '>'""" + p[0] = (locFromTok(p, 1), str(p[1]) + "<" + str(p[3]) + ">") + + +def p_CxxUniquePtrInst(p): + """CxxUniquePtrInst : UNIQUEPTR '<' ID '>'""" + p[0] = (locFromTok(p, 1), str(p[3]), True) + + +def p_error(t): + lineno, value = _safeLinenoValue(t) + _error(Loc(Parser.current.filename, lineno), "bad syntax near `%s'", value) diff --git a/ipc/ipdl/ipdl/type.py b/ipc/ipdl/ipdl/type.py new file mode 100644 index 0000000000..76f908dd41 --- /dev/null +++ b/ipc/ipdl/ipdl/type.py @@ -0,0 +1,1748 @@ +# vim: set ts=4 sw=4 tw=99 et: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import sys + +from ipdl.ast import CxxInclude, Decl, Loc, QualifiedId, StructDecl +from ipdl.ast import UnionDecl, UsingStmt, Visitor, StringLiteral +from ipdl.ast import ASYNC, SYNC, INTR +from ipdl.ast import IN, OUT, INOUT +from ipdl.ast import NOT_NESTED, INSIDE_SYNC_NESTED, INSIDE_CPOW_NESTED +from ipdl.ast import priorityList +import ipdl.builtin as builtin +from ipdl.util import hash_str + +_DELETE_MSG = "__delete__" + + +class TypeVisitor: + def __init__(self): + self.visited = set() + + def defaultVisit(self, node, *args): + raise Exception( + "INTERNAL ERROR: no visitor for node type `%s'" % (node.__class__.__name__) + ) + + def visitVoidType(self, v, *args): + pass + + def visitBuiltinCType(self, b, *args): + pass + + def visitImportedCxxType(self, t, *args): + pass + + def visitMessageType(self, m, *args): + for param in m.params: + param.accept(self, *args) + for ret in m.returns: + ret.accept(self, *args) + if m.cdtype is not None: + m.cdtype.accept(self, *args) + + def visitProtocolType(self, p, *args): + # NB: don't visit manager and manages. a naive default impl + # could result in an infinite loop + pass + + def visitActorType(self, a, *args): + a.protocol.accept(self, *args) + + def visitStructType(self, s, *args): + if s in self.visited: + return + + self.visited.add(s) + for field in s.fields: + field.accept(self, *args) + + def visitUnionType(self, u, *args): + if u in self.visited: + return + + self.visited.add(u) + for component in u.components: + component.accept(self, *args) + + def visitArrayType(self, a, *args): + a.basetype.accept(self, *args) + + def visitMaybeType(self, m, *args): + m.basetype.accept(self, *args) + + def visitUniquePtrType(self, m, *args): + m.basetype.accept(self, *args) + + def visitNotNullType(self, m, *args): + m.basetype.accept(self, *args) + + def visitShmemType(self, s, *args): + pass + + def visitByteBufType(self, s, *args): + pass + + def visitShmemChmodType(self, c, *args): + c.shmem.accept(self) + + def visitFDType(self, s, *args): + pass + + def visitEndpointType(self, s, *args): + pass + + def visitManagedEndpointType(self, s, *args): + pass + + +class Type: + def __cmp__(self, o): + return cmp(self.fullname(), o.fullname()) + + def __eq__(self, o): + return self.__class__ == o.__class__ and self.fullname() == o.fullname() + + def __hash__(self): + return hash_str(self.fullname()) + + # Is this a C++ type? + def isCxx(self): + return False + + # Is this an IPDL type? + + def isIPDL(self): + return False + + # Is this type neither compound nor an array? + + def isAtom(self): + return False + + def isRefcounted(self): + return False + + # Should this type be wrapped in `NotNull<T>` unless marked `nullable`? + + def supportsNullable(self): + return False + + def typename(self): + return self.__class__.__name__ + + def name(self): + raise NotImplementedError() + + def fullname(self): + raise NotImplementedError() + + def accept(self, visitor, *args): + visit = getattr(visitor, "visit" + self.__class__.__name__, None) + if visit is None: + return getattr(visitor, "defaultVisit")(self, *args) + return visit(self, *args) + + +class VoidType(Type): + def isCxx(self): + return True + + def isIPDL(self): + return False + + def isAtom(self): + return True + + def name(self): + return "void" + + def fullname(self): + return "void" + + +VOID = VoidType() + +# -------------------- + + +class BuiltinCType(Type): + def __init__(self, name): + self._name = name + + def isCxx(self): + return True + + def isAtom(self): + return True + + def isSendMoveOnly(self): + return False + + def isDataMoveOnly(self): + return False + + def name(self): + return self._name + + def fullname(self): + return self._name + + +class ImportedCxxType(Type): + def __init__(self, qname, refcounted, sendmoveonly, datamoveonly): + assert isinstance(qname, QualifiedId) + self.loc = qname.loc + self.qname = qname + self.refcounted = refcounted + self.sendmoveonly = sendmoveonly + self.datamoveonly = datamoveonly + + def isCxx(self): + return True + + def isAtom(self): + return True + + def isRefcounted(self): + return self.refcounted + + def supportsNullable(self): + return self.refcounted + + def isSendMoveOnly(self): + return self.sendmoveonly + + def isDataMoveOnly(self): + return self.datamoveonly + + def name(self): + return self.qname.baseid + + def fullname(self): + return str(self.qname) + + +# -------------------- + + +class IPDLType(Type): + def isIPDL(self): + return True + + def isMessage(self): + return False + + def isProtocol(self): + return False + + def isActor(self): + return False + + def isStruct(self): + return False + + def isUnion(self): + return False + + def isArray(self): + return False + + def isMaybe(self): + return False + + def isUniquePtr(self): + return False + + def isNotNull(self): + return False + + def isAtom(self): + return True + + def isCompound(self): + return False + + def isShmem(self): + return False + + def isByteBuf(self): + return False + + def isFD(self): + return False + + def isEndpoint(self): + return False + + def isManagedEndpoint(self): + return False + + def hasBaseType(self): + return False + + +class SendSemanticsType(IPDLType): + def __init__(self, nestedRange, sendSemantics): + self.nestedRange = nestedRange + self.sendSemantics = sendSemantics + + def isAsync(self): + return self.sendSemantics == ASYNC + + def isSync(self): + return self.sendSemantics == SYNC + + def isInterrupt(self): + return self.sendSemantics is INTR + + def sendSemanticsSatisfiedBy(self, greater): + def _unwrap(nr): + if isinstance(nr, dict): + return _unwrap(nr["nested"]) + elif isinstance(nr, int): + return nr + else: + raise ValueError("Got unexpected nestedRange value: %s" % nr) + + lesser = self + lnr0, gnr0, lnr1, gnr1 = ( + _unwrap(lesser.nestedRange[0]), + _unwrap(greater.nestedRange[0]), + _unwrap(lesser.nestedRange[1]), + _unwrap(greater.nestedRange[1]), + ) + if lnr0 < gnr0 or lnr1 > gnr1: + return False + + # Protocols that use intr semantics are not allowed to use + # message nesting. + if greater.isInterrupt() and lesser.nestedRange != (NOT_NESTED, NOT_NESTED): + return False + + if lesser.isAsync(): + return True + elif lesser.isSync() and not greater.isAsync(): + return True + elif greater.isInterrupt(): + return True + + return False + + +class MessageType(SendSemanticsType): + def __init__( + self, + nested, + prio, + replyPrio, + sendSemantics, + direction, + ctor=False, + dtor=False, + cdtype=None, + compress=False, + tainted=False, + lazySend=False, + ): + assert not (ctor and dtor) + assert not (ctor or dtor) or cdtype is not None + + SendSemanticsType.__init__(self, (nested, nested), sendSemantics) + self.nested = nested + self.prio = prio + self.replyPrio = replyPrio + self.direction = direction + self.params = [] + self.returns = [] + self.ctor = ctor + self.dtor = dtor + self.cdtype = cdtype + self.compress = compress + self.tainted = tainted + self.lazySend = lazySend + + def isMessage(self): + return True + + def isCtor(self): + return self.ctor + + def isDtor(self): + return self.dtor + + def constructedType(self): + return self.cdtype + + def isIn(self): + return self.direction is IN + + def isOut(self): + return self.direction is OUT + + def isInout(self): + return self.direction is INOUT + + def hasReply(self): + return len(self.returns) or self.isSync() or self.isInterrupt() + + def hasImplicitActorParam(self): + return self.isCtor() + + +class ProtocolType(SendSemanticsType): + def __init__(self, qname, nested, sendSemantics, refcounted, needsotherpid): + SendSemanticsType.__init__(self, (NOT_NESTED, nested), sendSemantics) + self.qname = qname + self.managers = [] # ProtocolType + self.manages = [] + self.hasDelete = False + self.refcounted = refcounted + self.needsotherpid = needsotherpid + + def isProtocol(self): + return True + + def isRefcounted(self): + return self.refcounted + + def hasOtherPid(self): + return all(top.needsotherpid for top in self.toplevels()) + + def name(self): + return self.qname.baseid + + def fullname(self): + return str(self.qname) + + def addManager(self, mgrtype): + assert mgrtype.isIPDL() and mgrtype.isProtocol() + self.managers.append(mgrtype) + + def managedBy(self, mgr): + self.managers = list(mgr) + + def toplevel(self): + if self.isToplevel(): + return self + for mgr in self.managers: + if mgr is not self: + return mgr.toplevel() + + def toplevels(self): + if self.isToplevel(): + return [self] + toplevels = list() + for mgr in self.managers: + if mgr is not self: + toplevels.extend(mgr.toplevels()) + return set(toplevels) + + def isManagerOf(self, pt): + for managed in self.manages: + if pt is managed: + return True + return False + + def isManagedBy(self, pt): + return pt in self.managers + + def isManager(self): + return len(self.manages) > 0 + + def isManaged(self): + return 0 < len(self.managers) + + def isToplevel(self): + return not self.isManaged() + + def manager(self): + assert 1 == len(self.managers) + for mgr in self.managers: + return mgr + + +class ActorType(IPDLType): + def __init__(self, protocol): + self.protocol = protocol + + def isActor(self): + return True + + def isRefcounted(self): + return self.protocol.isRefcounted() + + def supportsNullable(self): + return True + + def name(self): + return self.protocol.name() + + def fullname(self): + return self.protocol.fullname() + + +class _CompoundType(IPDLType): + def __init__(self): + self.defined = False # bool + self.mutualRec = set() # set(_CompoundType | ArrayType) + + def isAtom(self): + return False + + def isCompound(self): + return True + + def itercomponents(self): + raise Exception('"pure virtual" method') + + def mutuallyRecursiveWith(self, t, exploring=None): + """|self| is mutually recursive with |t| iff |self| and |t| + are in a cycle in the type graph rooted at |self|. This function + looks for such a cycle and returns True if found.""" + if exploring is None: + exploring = set() + + if t.isAtom(): + return False + elif t is self or t in self.mutualRec: + return True + elif t.hasBaseType(): + isrec = self.mutuallyRecursiveWith(t.basetype, exploring) + if isrec: + self.mutualRec.add(t) + return isrec + elif t in exploring: + return False + + exploring.add(t) + for c in t.itercomponents(): + if self.mutuallyRecursiveWith(c, exploring): + self.mutualRec.add(c) + return True + exploring.remove(t) + + return False + + +class StructType(_CompoundType): + def __init__(self, qname, fields): + _CompoundType.__init__(self) + self.qname = qname + self.fields = fields # [ Type ] + + def isStruct(self): + return True + + def itercomponents(self): + for f in self.fields: + yield f + + def name(self): + return self.qname.baseid + + def fullname(self): + return str(self.qname) + + +class UnionType(_CompoundType): + def __init__(self, qname, components): + _CompoundType.__init__(self) + self.qname = qname + self.components = components # [ Type ] + + def isUnion(self): + return True + + def itercomponents(self): + for c in self.components: + yield c + + def name(self): + return self.qname.baseid + + def fullname(self): + return str(self.qname) + + +class ArrayType(IPDLType): + def __init__(self, basetype): + self.basetype = basetype + + def isAtom(self): + return False + + def isArray(self): + return True + + def hasBaseType(self): + return True + + def name(self): + return self.basetype.name() + "[]" + + def fullname(self): + return self.basetype.fullname() + "[]" + + +class MaybeType(IPDLType): + def __init__(self, basetype): + self.basetype = basetype + + def isAtom(self): + return False + + def isMaybe(self): + return True + + def hasBaseType(self): + return True + + def name(self): + return self.basetype.name() + "?" + + def fullname(self): + return self.basetype.fullname() + "?" + + +class ShmemType(IPDLType): + def __init__(self, qname): + self.qname = qname + + def isShmem(self): + return True + + def name(self): + return self.qname.baseid + + def fullname(self): + return str(self.qname) + + +class ByteBufType(IPDLType): + def __init__(self, qname): + self.qname = qname + + def isByteBuf(self): + return True + + def name(self): + return self.qname.baseid + + def fullname(self): + return str(self.qname) + + +class FDType(IPDLType): + def __init__(self, qname): + self.qname = qname + + def isFD(self): + return True + + def name(self): + return self.qname.baseid + + def fullname(self): + return str(self.qname) + + +class EndpointType(IPDLType): + def __init__(self, qname, actor): + self.qname = qname + self.actor = actor + + def isEndpoint(self): + return True + + def name(self): + return self.qname.baseid + + def fullname(self): + return str(self.qname) + + +class ManagedEndpointType(IPDLType): + def __init__(self, qname, actor): + self.qname = qname + self.actor = actor + + def isManagedEndpoint(self): + return True + + def name(self): + return self.qname.baseid + + def fullname(self): + return str(self.qname) + + +class UniquePtrType(IPDLType): + def __init__(self, basetype): + self.basetype = basetype + + def isAtom(self): + return False + + def isUniquePtr(self): + return True + + def hasBaseType(self): + return True + + def name(self): + return "UniquePtr<" + self.basetype.name() + ">" + + def fullname(self): + return "mozilla::UniquePtr<" + self.basetype.fullname() + ">" + + +class NotNullType(IPDLType): + def __init__(self, basetype): + self.basetype = basetype + + def isAtom(self): + return False + + def isNotNull(self): + return True + + def hasBaseType(self): + return True + + def name(self): + return "NotNull<" + self.basetype.name() + ">" + + def fullname(self): + return "mozilla::NotNull<" + self.basetype.fullname() + ">" + + +def iteractortypes(t, visited=None): + """Iterate over any actor(s) buried in |type|.""" + if visited is None: + visited = set() + + # XXX |yield| semantics makes it hard to use TypeVisitor + if not t.isIPDL(): + return + elif t.isActor(): + yield t + elif t.hasBaseType(): + for actor in iteractortypes(t.basetype, visited): + yield actor + elif t.isCompound() and t not in visited: + visited.add(t) + for c in t.itercomponents(): + for actor in iteractortypes(c, visited): + yield actor + + +def hasshmem(type): + """Return true iff |type| is shmem or has it buried within.""" + + class found(BaseException): + pass + + class findShmem(TypeVisitor): + def visitShmemType(self, s): + raise found() + + try: + type.accept(findShmem()) + except found: + return True + return False + + +# -------------------- +_builtinloc = Loc("<builtin>", 0) + + +def makeBuiltinUsing(tname): + quals = tname.split("::") + base = quals.pop() + quals = quals[0:] + return UsingStmt(_builtinloc, QualifiedId(_builtinloc, base, quals)) + + +builtinUsing = [makeBuiltinUsing(t) for t in builtin.Types] +builtinHeaderIncludes = [CxxInclude(_builtinloc, f) for f in builtin.HeaderIncludes] + + +def errormsg(loc, fmt, *args): + while not isinstance(loc, Loc): + if loc is None: + loc = Loc.NONE + else: + loc = loc.loc + return "%s: error: %s" % (str(loc), fmt % args) + + +# -------------------- + + +class SymbolTable: + def __init__(self, errors): + self.errors = errors + self.scopes = [{}] # stack({}) + self.currentScope = self.scopes[0] + + def enterScope(self): + assert isinstance(self.scopes[0], dict) + assert isinstance(self.currentScope, dict) + + self.scopes.append({}) + self.currentScope = self.scopes[-1] + + def exitScope(self): + symtab = self.scopes.pop() + assert self.currentScope is symtab + + self.currentScope = self.scopes[-1] + + assert isinstance(self.scopes[0], dict) + assert isinstance(self.currentScope, dict) + + def lookup(self, sym): + # NB: since IPDL doesn't allow any aliased names of different types, + # it doesn't matter in which order we walk the scope chain to resolve + # |sym| + for scope in self.scopes: + decl = scope.get(sym, None) + if decl is not None: + return decl + return None + + def declare(self, decl): + assert decl.progname or decl.shortname or decl.fullname + assert decl.loc + assert decl.type + + def tryadd(name): + olddecl = self.lookup(name) + if olddecl is not None: + self.errors.append( + errormsg( + decl.loc, + "redeclaration of symbol `%s', first declared at %s", + name, + olddecl.loc, + ) + ) + return + self.currentScope[name] = decl + decl.scope = self.currentScope + + if decl.progname: + tryadd(decl.progname) + if decl.shortname: + tryadd(decl.shortname) + if decl.fullname: + tryadd(decl.fullname) + + +class TypeCheck: + """This pass sets the .decl attribute of AST nodes for which that is relevant; + a decl says where, with what type, and under what name(s) a node was + declared. + + With this information, it type checks the AST.""" + + def __init__(self): + # NB: no IPDL compile will EVER print a warning. A program has + # one of two attributes: it is either well typed, or not well typed. + self.errors = [] # [ string ] + + def check(self, tu, errout=sys.stderr): + def runpass(tcheckpass): + tu.accept(tcheckpass) + if len(self.errors): + self.reportErrors(errout) + return False + return True + + # tag each relevant node with "decl" information, giving type, name, + # and location of declaration + if not runpass(GatherDecls(builtinUsing, self.errors)): + return False + + # now that the nodes have decls, type checking is much easier. + if not runpass(CheckTypes(self.errors)): + return False + + return True + + def reportErrors(self, errout): + for error in self.errors: + print(error, file=errout) + + +class TcheckVisitor(Visitor): + def __init__(self, errors): + self.errors = errors + + def error(self, loc, fmt, *args): + self.errors.append(errormsg(loc, fmt, *args)) + + +class GatherDecls(TcheckVisitor): + def __init__(self, builtinUsing, errors): + TcheckVisitor.__init__(self, errors) + + # |self.symtab| is the symbol table for the translation unit + # currently being visited + self.symtab = None + self.builtinUsing = builtinUsing + + def declare( + self, loc, type, shortname=None, fullname=None, progname=None, attributes={} + ): + d = Decl(loc) + d.type = type + d.progname = progname + d.shortname = shortname + d.fullname = fullname + d.attributes = attributes + self.symtab.declare(d) + return d + + # Check that only attributes allowed by an attribute spec are present + # within the given attribute dictionary. The spec value may be either + # `None`, for a valueless attribute, a list of valid attribute values, or a + # callable which returns a truthy value if the attribute is valid. + def checkAttributes(self, attributes, spec): + for attr in attributes.values(): + if attr.name not in spec: + self.error(attr.loc, "unknown attribute `%s'", attr.name) + continue + + aspec = spec[attr.name] + if aspec is None: + if attr.value is not None: + self.error( + attr.loc, + "unexpected value for valueless attribute `%s'", + attr.name, + ) + elif isinstance(aspec, (list, tuple)): + if not any( + isinstance(attr.value, s) + if isinstance(s, type) + else attr.value == s + for s in aspec + ): + self.error( + attr.loc, + "invalid value for attribute `%s', expected one of: %s", + attr.name, + ", ".join( + s.__name__ if isinstance(s, type) else str(s) for s in aspec + ), + ) + elif callable(aspec): + if not aspec(attr.value): + self.error(attr.loc, "invalid value for attribute `%s'", attr.name) + else: + raise Exception("INTERNAL ERROR: Invalid attribute spec") + + def visitTranslationUnit(self, tu): + # all TranslationUnits declare symbols in global scope + if hasattr(tu, "visited"): + return + tu.visited = True + savedSymtab = self.symtab + self.symtab = SymbolTable(self.errors) + + # pretend like the translation unit "using"-ed these for the + # sake of type checking and C++ code generation + tu.builtinUsing = self.builtinUsing + + # for everyone's sanity, enforce that the filename and tu name + # match + basefilename = os.path.basename(tu.filename) + expectedfilename = "%s.ipdl" % (tu.name) + if not tu.protocol: + # header + expectedfilename += "h" + if basefilename != expectedfilename: + self.error( + tu.loc, + "expected file for translation unit `%s' to be named `%s'; instead it's named `%s'", # NOQA: E501 + tu.name, + expectedfilename, + basefilename, + ) + + if tu.protocol: + assert tu.name == tu.protocol.name + + p = tu.protocol + + self.checkAttributes( + p.attributes, + { + "ManualDealloc": None, + "NestedUpTo": ("not", "inside_sync", "inside_cpow"), + "NeedsOtherPid": None, + "ChildImpl": ("virtual", StringLiteral), + "ParentImpl": ("virtual", StringLiteral), + }, + ) + + # FIXME/cjones: it's a little weird and counterintuitive + # to put both the namespace and non-namespaced name in the + # global scope. try to figure out something better; maybe + # a type-neutral |using| that works for C++ and protocol + # types? + qname = p.qname() + fullname = str(qname) + p.decl = self.declare( + loc=p.loc, + type=ProtocolType( + qname, + p.nestedUpTo(), + p.sendSemantics, + "ManualDealloc" not in p.attributes, + "NeedsOtherPid" in p.attributes, + ), + shortname=p.name, + fullname=fullname, + ) + + p.parentEndpointDecl = self.declare( + loc=p.loc, + type=EndpointType( + QualifiedId( + p.loc, "Endpoint<" + fullname + "Parent>", ["mozilla", "ipc"] + ), + ActorType(p.decl.type), + ), + shortname="Endpoint<" + p.name + "Parent>", + ) + p.childEndpointDecl = self.declare( + loc=p.loc, + type=EndpointType( + QualifiedId( + p.loc, "Endpoint<" + fullname + "Child>", ["mozilla", "ipc"] + ), + ActorType(p.decl.type), + ), + shortname="Endpoint<" + p.name + "Child>", + ) + + p.parentManagedEndpointDecl = self.declare( + loc=p.loc, + type=ManagedEndpointType( + QualifiedId( + p.loc, + "ManagedEndpoint<" + fullname + "Parent>", + ["mozilla", "ipc"], + ), + ActorType(p.decl.type), + ), + shortname="ManagedEndpoint<" + p.name + "Parent>", + ) + p.childManagedEndpointDecl = self.declare( + loc=p.loc, + type=ManagedEndpointType( + QualifiedId( + p.loc, + "ManagedEndpoint<" + fullname + "Child>", + ["mozilla", "ipc"], + ), + ActorType(p.decl.type), + ), + shortname="ManagedEndpoint<" + p.name + "Child>", + ) + + # XXX ugh, this sucks. but we need this information to compute + # what friend decls we need in generated C++ + p.decl.type._ast = p + + # make sure we have decls for all dependent protocols + for pinc in tu.includes: + pinc.accept(self) + + # declare imported (and builtin) C and C++ types + for ctype in builtin.CTypes: + self.declare( + loc=_builtinloc, + type=BuiltinCType(ctype), + shortname=ctype, + ) + for using in tu.builtinUsing: + using.accept(self) + for using in tu.using: + using.accept(self) + + # first pass to "forward-declare" all structs and unions in + # order to support recursive definitions + for su in tu.structsAndUnions: + self.declareStructOrUnion(su) + + # second pass to check each definition + for su in tu.structsAndUnions: + su.accept(self) + + if tu.protocol: + # grab symbols in the protocol itself + p.accept(self) + + self.symtab = savedSymtab + + def declareStructOrUnion(self, su): + if hasattr(su, "decl"): + self.symtab.declare(su.decl) + return + + qname = su.qname() + fullname = str(qname) + + if isinstance(su, StructDecl): + sutype = StructType(qname, []) + elif isinstance(su, UnionDecl): + sutype = UnionType(qname, []) + else: + assert 0 and "unknown type" + + # XXX more suckage. this time for pickling structs/unions + # declared in headers. + sutype._ast = su + + su.decl = self.declare( + loc=su.loc, type=sutype, shortname=su.name, fullname=fullname + ) + + def visitInclude(self, inc): + if inc.tu is None: + self.error( + inc.loc, + "(type checking here will be unreliable because of an earlier error)", + ) + return + inc.tu.accept(self) + if inc.tu.protocol: + self.symtab.declare(inc.tu.protocol.decl) + self.symtab.declare(inc.tu.protocol.parentEndpointDecl) + self.symtab.declare(inc.tu.protocol.childEndpointDecl) + self.symtab.declare(inc.tu.protocol.parentManagedEndpointDecl) + self.symtab.declare(inc.tu.protocol.childManagedEndpointDecl) + else: + # This is a header. Import its "exported" globals into + # our scope. + for using in inc.tu.using: + using.accept(self) + for su in inc.tu.structsAndUnions: + self.declareStructOrUnion(su) + + def visitStructDecl(self, sd): + # If we've already processed this struct, don't do it again. + if hasattr(sd, "visited"): + return + + stype = sd.decl.type + + self.symtab.enterScope() + sd.visited = True + + self.checkAttributes(sd.attributes, {"Comparable": None}) + + for f in sd.fields: + ftypedecl = self.symtab.lookup(str(f.typespec)) + if ftypedecl is None: + self.error( + f.loc, + "field `%s' of struct `%s' has unknown type `%s'", + f.name, + sd.name, + str(f.typespec), + ) + continue + + f.decl = self.declare( + loc=f.loc, + type=self._canonicalType(ftypedecl.type, f.typespec), + shortname=f.name, + fullname=None, + ) + stype.fields.append(f.decl.type) + + self.symtab.exitScope() + + def visitUnionDecl(self, ud): + utype = ud.decl.type + + # If we've already processed this union, don't do it again. + if len(utype.components): + return + + self.checkAttributes(ud.attributes, {"Comparable": None}) + + for c in ud.components: + cdecl = self.symtab.lookup(str(c)) + if cdecl is None: + self.error( + c.loc, "unknown component type `%s' of union `%s'", str(c), ud.name + ) + continue + utype.components.append(self._canonicalType(cdecl.type, c)) + + def visitUsingStmt(self, using): + fullname = str(using.type) + + self.checkAttributes( + using.attributes, + { + "MoveOnly": (None, "data", "send"), + "RefCounted": None, + }, + ) + + if fullname == "::mozilla::ipc::Shmem": + ipdltype = ShmemType(using.type) + elif fullname == "::mozilla::ipc::ByteBuf": + ipdltype = ByteBufType(using.type) + elif fullname == "::mozilla::ipc::FileDescriptor": + ipdltype = FDType(using.type) + else: + ipdltype = ImportedCxxType( + using.type, + using.isRefcounted(), + using.isSendMoveOnly(), + using.isDataMoveOnly(), + ) + existingType = self.symtab.lookup(ipdltype.fullname()) + if existingType and existingType.fullname == ipdltype.fullname(): + if ipdltype.isRefcounted() != existingType.type.isRefcounted(): + self.error( + using.loc, + "inconsistent refcounted status of type `%s`", + str(using.type), + ) + if ( + ipdltype.isSendMoveOnly() != existingType.type.isSendMoveOnly() + or ipdltype.isDataMoveOnly() != existingType.type.isDataMoveOnly() + ): + self.error( + using.loc, + "inconsistent moveonly status of type `%s`", + str(using.type), + ) + using.decl = existingType + return + using.decl = self.declare( + loc=using.loc, + type=ipdltype, + shortname=using.type.baseid, + fullname=fullname, + ) + + def visitProtocol(self, p): + # protocol scope + self.symtab.enterScope() + + seenmgrs = set() + for mgr in p.managers: + if mgr.name in seenmgrs: + self.error(mgr.loc, "manager `%s' appears multiple times", mgr.name) + continue + + seenmgrs.add(mgr.name) + mgr.of = p + mgr.accept(self) + + for managed in p.managesStmts: + managed.manager = p + managed.accept(self) + + if not (p.managers or p.messageDecls or p.managesStmts): + self.error(p.loc, "top-level protocol `%s' cannot be empty", p.name) + + setattr(self, "currentProtocolDecl", p.decl) + for msg in p.messageDecls: + msg.accept(self) + del self.currentProtocolDecl + + p.decl.type.hasDelete = not not self.symtab.lookup(_DELETE_MSG) + if not (p.decl.type.hasDelete or p.decl.type.isToplevel()): + self.error( + p.loc, + "destructor declaration `%s(...)' required for managed protocol `%s'", + _DELETE_MSG, + p.name, + ) + + if not p.decl.type.isToplevel() and p.decl.type.needsotherpid: + self.error(p.loc, "[NeedsOtherPid] only applies to toplevel protocols") + + if p.decl.type.isToplevel() and not p.decl.type.isRefcounted(): + self.error(p.loc, "Toplevel protocols cannot be [ManualDealloc]") + + # FIXME/cjones declare all the little C++ thingies that will + # be generated. they're not relevant to IPDL itself, but + # those ("invisible") symbols can clash with others in the + # IPDL spec, and we'd like to catch those before C++ compilers + # are allowed to obfuscate the error + + self.symtab.exitScope() + + def visitManager(self, mgr): + mgrdecl = self.symtab.lookup(mgr.name) + pdecl = mgr.of.decl + assert pdecl + + pname, mgrname = pdecl.shortname, mgr.name + loc = mgr.loc + + if mgrdecl is None: + self.error( + loc, + "protocol `%s' referenced as |manager| of `%s' has not been declared", + mgrname, + pname, + ) + elif not isinstance(mgrdecl.type, ProtocolType): + self.error( + loc, + "entity `%s' referenced as |manager| of `%s' is not of `protocol' type; instead it is of type `%s'", # NOQA: E501 + mgrname, + pname, + mgrdecl.type.typename(), + ) + else: + mgr.decl = mgrdecl + pdecl.type.addManager(mgrdecl.type) + + def visitManagesStmt(self, mgs): + mgsdecl = self.symtab.lookup(mgs.name) + pdecl = mgs.manager.decl + assert pdecl + + pname, mgsname = pdecl.shortname, mgs.name + loc = mgs.loc + + if mgsdecl is None: + self.error( + loc, + "protocol `%s', managed by `%s', has not been declared", + mgsname, + pname, + ) + elif not isinstance(mgsdecl.type, ProtocolType): + self.error( + loc, + "%s declares itself managing a non-`protocol' entity `%s' of type `%s'", + pname, + mgsname, + mgsdecl.type.typename(), + ) + else: + mgs.decl = mgsdecl + pdecl.type.manages.append(mgsdecl.type) + + def visitMessageDecl(self, md): + msgname = md.name + loc = md.loc + + self.checkAttributes( + md.attributes, + { + "Tainted": None, + "Compress": (None, "all"), + "Priority": priorityList, + "ReplyPriority": priorityList, + "Nested": ("not", "inside_sync", "inside_cpow"), + "LegacyIntr": None, + "VirtualSendImpl": None, + "LazySend": None, + }, + ) + + if md.sendSemantics is INTR and "LegacyIntr" not in md.attributes: + self.error( + loc, + "intr message `%s' allowed only with [LegacyIntr]; DO NOT USE IN SHIPPING CODE", + msgname, + ) + + if md.sendSemantics is INTR and "Priority" in md.attributes: + self.error(loc, "intr message `%s' cannot specify [Priority]", msgname) + + if md.sendSemantics is INTR and "Nested" in md.attributes: + self.error(loc, "intr message `%s' cannot specify [Nested]", msgname) + + if md.sendSemantics is not ASYNC and "LazySend" in md.attributes: + self.error(loc, "non-async message `%s' cannot specify [LazySend]", msgname) + + if md.sendSemantics is not ASYNC and "ReplyPriority" in md.attributes: + self.error( + loc, "non-async message `%s' cannot specify [ReplyPriority]", msgname + ) + + if not md.outParams and "ReplyPriority" in md.attributes: + self.error( + loc, "non-returns message `%s' cannot specify [ReplyPriority]", msgname + ) + + isctor = False + isdtor = False + cdtype = None + + decl = self.symtab.lookup(msgname) + if decl is not None and decl.type.isProtocol(): + # probably a ctor. we'll check validity later. + msgname += "Constructor" + isctor = True + cdtype = decl.type + elif decl is not None: + self.error( + loc, + "message name `%s' already declared as `%s'", + msgname, + decl.type.typename(), + ) + # if we error here, no big deal; move on to find more + + if _DELETE_MSG == msgname: + isdtor = True + cdtype = self.currentProtocolDecl.type + + # enter message scope + self.symtab.enterScope() + + msgtype = MessageType( + nested=md.nested(), + prio=md.priority(), + replyPrio=md.replyPriority(), + sendSemantics=md.sendSemantics, + direction=md.direction, + ctor=isctor, + dtor=isdtor, + cdtype=cdtype, + compress=md.attributes.get("Compress"), + tainted="Tainted" in md.attributes, + lazySend="LazySend" in md.attributes, + ) + + # replace inparam Param nodes with proper Decls + def paramToDecl(param): + self.checkAttributes( + param.attributes, + { + # Passback indicates that the argument is unused by the Parent and is + # merely returned to the Child later. + # AllValid indicates that the entire span of values representable by + # the type are acceptable. e.g. 0-255 in a uint8 + "NoTaint": ("passback", "allvalid") + }, + ) + + ptname = param.typespec.basename() + ploc = param.typespec.loc + + if "NoTaint" in param.attributes and "Tainted" not in md.attributes: + self.error( + ploc, + "argument typename `%s' of message `%s' has a NoTaint attribute, but the message lacks the Tainted attribute", + ptname, + msgname, + ) + + ptdecl = self.symtab.lookup(ptname) + if ptdecl is None: + self.error( + ploc, + "argument typename `%s' of message `%s' has not been declared", + ptname, + msgname, + ) + ptype = VOID + else: + ptype = self._canonicalType(ptdecl.type, param.typespec) + return self.declare( + loc=ploc, type=ptype, progname=param.name, attributes=param.attributes + ) + + for i, inparam in enumerate(md.inParams): + pdecl = paramToDecl(inparam) + msgtype.params.append(pdecl.type) + md.inParams[i] = pdecl + for i, outparam in enumerate(md.outParams): + pdecl = paramToDecl(outparam) + msgtype.returns.append(pdecl.type) + md.outParams[i] = pdecl + + self.symtab.exitScope() + + md.decl = self.declare(loc=loc, type=msgtype, progname=msgname) + md.protocolDecl = self.currentProtocolDecl + md.decl._md = md + + def _canonicalType(self, itype, typespec): + loc = typespec.loc + if typespec.uniqueptr: + itype = UniquePtrType(itype) + + if itype.isIPDL() and itype.isProtocol(): + itype = ActorType(itype) + + if itype.supportsNullable(): + if not typespec.nullable: + itype = NotNullType(itype) + elif typespec.nullable: + self.error( + loc, "`nullable' qualifier for type `%s' is unsupported", itype.name() + ) + + if typespec.array: + itype = ArrayType(itype) + + if typespec.maybe: + itype = MaybeType(itype) + + return itype + + +# ----------------------------------------------------------------------------- + + +def checkcycles(p, stack=None): + cycles = [] + + if stack is None: + stack = [] + + for cp in p.manages: + # special case for self-managed protocols + if cp is p: + continue + + if cp in stack: + return [stack + [p, cp]] + cycles += checkcycles(cp, stack + [p]) + + return cycles + + +def formatcycles(cycles): + r = [] + for cycle in cycles: + s = " -> ".join([ptype.name() for ptype in cycle]) + r.append("`%s'" % s) + return ", ".join(r) + + +def fullyDefined(t, exploring=None): + """The rules for "full definition" of a type are + defined(atom) := true + defined(array basetype) := defined(basetype) + defined(struct f1 f2...) := defined(f1) and defined(f2) and ... + defined(union c1 c2 ...) := defined(c1) or defined(c2) or ...""" + if exploring is None: + exploring = set() + + if t.isAtom(): + return True + elif t.hasBaseType(): + return fullyDefined(t.basetype, exploring) + elif t.defined: + return True + assert t.isCompound() + + if t in exploring: + return False + + exploring.add(t) + for c in t.itercomponents(): + cdefined = fullyDefined(c, exploring) + if t.isStruct() and not cdefined: + t.defined = False + break + elif t.isUnion() and cdefined: + t.defined = True + break + else: + if t.isStruct(): + t.defined = True + elif t.isUnion(): + t.defined = False + exploring.remove(t) + + return t.defined + + +class CheckTypes(TcheckVisitor): + def __init__(self, errors): + TcheckVisitor.__init__(self, errors) + self.visited = set() + self.ptype = None + + def visitInclude(self, inc): + if inc.tu.filename in self.visited: + return + self.visited.add(inc.tu.filename) + if inc.tu.protocol: + inc.tu.protocol.accept(self) + + def visitStructDecl(self, sd): + if not fullyDefined(sd.decl.type): + self.error(sd.decl.loc, "struct `%s' is only partially defined", sd.name) + + def visitUnionDecl(self, ud): + if not fullyDefined(ud.decl.type): + self.error(ud.decl.loc, "union `%s' is only partially defined", ud.name) + + def visitProtocol(self, p): + self.ptype = p.decl.type + + # check that we require no more "power" than our manager protocols + ptype, pname = p.decl.type, p.decl.shortname + + for mgrtype in ptype.managers: + if mgrtype is not None and not ptype.sendSemanticsSatisfiedBy(mgrtype): + self.error( + p.decl.loc, + "protocol `%s' requires more powerful send semantics than its manager `%s' provides", # NOQA: E501 + pname, + mgrtype.name(), + ) + + if ptype.isInterrupt() and ptype.nestedRange != (NOT_NESTED, NOT_NESTED): + self.error( + p.decl.loc, "intr protocol `%s' cannot specify [NestedUpTo]", p.name + ) + + if ptype.isToplevel(): + cycles = checkcycles(p.decl.type) + if cycles: + self.error( + p.decl.loc, + "cycle(s) detected in manager/manages hierarchy: %s", + formatcycles(cycles), + ) + + if 1 == len(ptype.managers) and ptype is ptype.manager(): + self.error( + p.decl.loc, "top-level protocol `%s' cannot manage itself", p.name + ) + + return Visitor.visitProtocol(self, p) + + def visitManagesStmt(self, mgs): + pdecl = mgs.manager.decl + ptype, pname = pdecl.type, pdecl.shortname + + mgsdecl = mgs.decl + mgstype, mgsname = mgsdecl.type, mgsdecl.shortname + + loc = mgs.loc + + # we added this information; sanity check it + assert ptype.isManagerOf(mgstype) + + # check that the "managed" protocol agrees + if not mgstype.isManagedBy(ptype): + self.error( + loc, + "|manages| declaration in protocol `%s' does not match any |manager| declaration in protocol `%s'", # NOQA: E501 + pname, + mgsname, + ) + + def visitManager(self, mgr): + pdecl = mgr.of.decl + ptype, pname = pdecl.type, pdecl.shortname + + mgrdecl = mgr.decl + mgrtype, mgrname = mgrdecl.type, mgrdecl.shortname + + # we added this information; sanity check it + assert ptype.isManagedBy(mgrtype) + + loc = mgr.loc + + # check that the "manager" protocol agrees + if not mgrtype.isManagerOf(ptype): + self.error( + loc, + "|manager| declaration in protocol `%s' does not match any |manages| declaration in protocol `%s'", # NOQA: E501 + pname, + mgrname, + ) + + def visitMessageDecl(self, md): + mtype, mname = md.decl.type, md.decl.progname + ptype, pname = md.protocolDecl.type, md.protocolDecl.shortname + + loc = md.decl.loc + + if mtype.nested == INSIDE_SYNC_NESTED and not mtype.isSync(): + self.error( + loc, + "inside_sync nested messages must be sync (here, message `%s' in protocol `%s')", + mname, + pname, + ) + + if mtype.nested == INSIDE_CPOW_NESTED and (mtype.isOut() or mtype.isInout()): + self.error( + loc, + "inside_cpow nested parent-to-child messages are verboten (here, message `%s' in protocol `%s')", # NOQA: E501 + mname, + pname, + ) + + # We allow inside_sync messages that are themselves sync to be sent from the + # parent. Normal and inside_cpow nested messages that are sync can only come from + # the child. + if ( + mtype.isSync() + and mtype.nested == NOT_NESTED + and (mtype.isOut() or mtype.isInout()) + ): + self.error( + loc, + "sync parent-to-child messages are verboten (here, message `%s' in protocol `%s')", + mname, + pname, + ) + + if not mtype.sendSemanticsSatisfiedBy(ptype): + self.error( + loc, + "message `%s' requires more powerful send semantics than its protocol `%s' provides", # NOQA: E501 + mname, + pname, + ) + + if (mtype.isCtor() or mtype.isDtor()) and mtype.isAsync() and mtype.returns: + self.error( + loc, "asynchronous ctor/dtor message `%s' declares return values", mname + ) + + if mtype.compress and (not mtype.isAsync() or mtype.isCtor() or mtype.isDtor()): + + if mtype.isCtor() or mtype.isDtor(): + message_type = "constructor" if mtype.isCtor() else "destructor" + error_message = ( + "%s messages can't use compression (here, in protocol `%s')" + % (message_type, pname) + ) + else: + error_message = ( + "message `%s' in protocol `%s' requests compression but is not async" + % (mname, pname) # NOQA: E501 + ) + + self.error(loc, error_message) + + if mtype.isCtor() and not ptype.isManagerOf(mtype.constructedType()): + self.error( + loc, + "ctor for protocol `%s', which is not managed by protocol `%s'", + mname[: -len("constructor")], + pname, + ) diff --git a/ipc/ipdl/ipdl/util.py b/ipc/ipdl/ipdl/util.py new file mode 100644 index 0000000000..60d9c904e2 --- /dev/null +++ b/ipc/ipdl/ipdl/util.py @@ -0,0 +1,12 @@ +# 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 zlib + + +# The built-in hash over the str type is non-deterministic, so we need to do +# this instead. +def hash_str(s): + assert isinstance(s, str) + return zlib.adler32(s.encode("utf-8")) diff --git a/ipc/ipdl/message-metadata.ini b/ipc/ipdl/message-metadata.ini new file mode 100644 index 0000000000..50ad642537 --- /dev/null +++ b/ipc/ipdl/message-metadata.ini @@ -0,0 +1,36 @@ +############################################################# +# # +# Any changes to this file must be reviewed by an IPC peer. # +# # +############################################################# + +[PContent::AccumulateChildKeyedHistograms] +segment_capacity = 16384 +[PContent::AccumulateChildHistograms] +segment_capacity = 16384 +[PContent::StoreAndBroadcastBlobURLRegistration] +segment_capacity = 8192 +[PHttpChannel::Redirect1Begin] +segment_capacity = 8192 +[PHttpBackgroundChannel::OnStartRequest] +segment_capacity = 8192 +[PHttpBackgroundChannel::OnTransportAndData] +segment_capacity = 8192 +[PNecko::PHttpChannelConstructor] +segment_capacity = 8192 +[PMessagePort::PostMessages] +segment_capacity = 12288 +[PMessagePort::ReceiveData] +segment_capacity = 12288 + +#------------------------------------------------------------ +# Small-size messages. +#------------------------------------------------------------ +[PCompositorBridge::DidComposite] +segment_capacity = 128 +[PBrowser::RealMouseMoveEvent] +segment_capacity = 192 +[PCompositorBridge::PTextureConstructor] +segment_capacity = 192 +[PHttpBackgroundChannel::OnStopRequest] +segment_capacity = 192 diff --git a/ipc/ipdl/moz.build b/ipc/ipdl/moz.build new file mode 100644 index 0000000000..320c2e2f85 --- /dev/null +++ b/ipc/ipdl/moz.build @@ -0,0 +1,22 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +DIRS += ["test"] + +include("/ipc/chromium/chromium-config.mozbuild") + +# Generated by ipdl.py +SOURCES += ["!IPCMessageTypeName.cpp"] + +FINAL_LIBRARY = "xul" + +# We #include some things in the dom/plugins/ directory that rely on +# toolkit libraries. +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] + +# Add libFuzzer configuration directives +include("/tools/fuzzing/libfuzzer-config.mozbuild") diff --git a/ipc/ipdl/msgtype-components b/ipc/ipdl/msgtype-components new file mode 100644 index 0000000000..4a01d3a71c --- /dev/null +++ b/ipc/ipdl/msgtype-components @@ -0,0 +1,12 @@ +#!/usr/bin/python +# 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 sys + +msgid = int(sys.argv[1]) +protocol = (msgid >> 16) +msg = (msgid - (protocol << 16)) + +print('protocol', protocol, 'message', msg) diff --git a/ipc/ipdl/sync-messages.ini b/ipc/ipdl/sync-messages.ini new file mode 100644 index 0000000000..075c0557b9 --- /dev/null +++ b/ipc/ipdl/sync-messages.ini @@ -0,0 +1,322 @@ +############################################################# +# # +# AVOID ADDING NEW MESSAGES TO THIS FILE # +# # +# New sync IPC messages will only be approved in # +# exceptional circumstances, and must be reviewed by an IPC # +# peer. Please be prepared with a detailed justification # +# before requesting IPC peer review. # +# # +############################################################# + +# gtests +[PQuotaTest::Try_Success_CustomErr_QmIpcFail] +description = Only used by gtests +[PQuotaTest::Try_Success_CustomErr_IpcFail] +description = Only used by gtests +[PQuotaTest::TryInspect_Success_CustomErr_QmIpcFail] +description = Only used by gtests +[PQuotaTest::TryInspect_Success_CustomErr_IpcFail] +description = Only used by gtests + +# A11y code +[PDocAccessible::AddToSelection] +description = Legacy a11y IPC +platform = notwin +[PDocAccessible::SyncTextChangeEvent] +description = Legacy a11y IPC +platform = win + +[PDocAccessiblePlatformExt::ApplyPostSearchFilter] +description = Filter a preliminary list of accessibles that match a predicate. Platform API is synchronous, so this needs to be too. +platform = mac + +# The rest +[PHeapSnapshotTempFileHelper::OpenHeapSnapshotTempFile] +description = legacy sync IPC - please add detailed description +[PBackgroundMutableFile::GetFileId] +description = legacy sync IPC - please add detailed description +[PBackgroundIndexedDBUtils::GetFileReferences] +description = legacy sync IPC - please add detailed description +[PBrowser::SyncMessage] +description = JS MessageManager implementation +[PBrowser::NotifyIMEMouseButtonEvent] +description = legacy sync IPC - please add detailed description +[PBrowser::RequestIMEToCommitComposition] +description = legacy sync IPC - please add detailed description +[PBrowser::GetInputContext] +description = legacy sync IPC - please add detailed description +[PBrowser::RequestNativeKeyBindings] +description = legacy sync IPC - please add detailed description +[PBrowser::DispatchWheelEvent] +description = Only used by automation tests +[PBrowser::DispatchMouseEvent] +description = Only used by automation tests +[PBrowser::DispatchKeyboardEvent] +description = Only used by automation tests +[PBrowser::DispatchTouchEvent] +description = Only used by automation tests +[PBrowser::EnsureLayersConnected] +description = legacy sync IPC - please add detailed description +[PBrowser::SetSystemFont] +description = test only +[PBrowser::GetSystemFont] +description = test only +[PContent::SyncMessage] +description = JS MessageManager implementation +[PContent::IsSecureURI] +description = legacy sync IPC - please add detailed description +[PContent::PURLClassifier] +description = legacy sync IPC - please add detailed description +[PContent::GetGfxVars] +description = legacy sync IPC - please add detailed description +[PContent::GetClipboard] +description = Legacy synchronous clipboard API +[PContent::ClipboardHasType] +description = Legacy synchronous clipboard API +[PContent::GetExternalClipboardFormats] +description = Retrieve supported clipboard formats synchronously +[PContent::GetIconForExtension] +description = legacy sync IPC - please add detailed description +[PContent::BeginDriverCrashGuard] +description = legacy sync IPC - please add detailed description +[PContent::EndDriverCrashGuard] +description = legacy sync IPC - please add detailed description +[PContent::GetGraphicsDeviceInitData] +description = Retrieve information needed to initialize the graphics device in the content process +[PContent::GetOutputColorProfileData] +description = Retrieve the contents of the output color profile file +[PContent::GetFontListShmBlock] +description = for bug 1514869 - layout code needs synchronous access to font list, but this is used only once per block, after which content directly reads the shared memory +[PContent::InitializeFamily] +description = for bug 1514869 - layout is blocked on needing sync access to a specific font family - used once per family, then the data is cached in shared memory +[PContent::InitOtherFamilyNames] +description = for bug 1514869 - layout is blocked on font lookup, needs complete family-name information - not used after loading is complete +[PContent::GetHyphDict] +description = for bug 1487212 - layout requires hyphenation data from a given omnijar resource - only called once per locale by a given content process +[PContent::RemovePermission] +description = legacy sync IPC - please add detailed description +[PGMP::StartPlugin] +description = legacy sync IPC - please add detailed description +[PGMPService::LaunchGMP] +description = legacy sync IPC - please add detailed description +[PGMPService::GetGMPNodeId] +description = legacy sync IPC - please add detailed description +[PGMPVideoDecoder::NeedShmem] +description = legacy sync IPC - please add detailed description +[PGMPVideoEncoder::NeedShmem] +description = legacy sync IPC - please add detailed description +[PRemoteDecoderManager::Readback] +description = legacy sync IPC - please add detailed description +[PBackgroundStorage::Preload] +description = legacy sync IPC - please add detailed description +[PBackgroundLSDatabase::PBackgroundLSSnapshot] +description = See corresponding comment in PBackgroundLSDatabase.ipdl +[PBackgroundLSSnapshot::SyncCheckpoint] +description = See corresponding comment in PBackgroundLSSnapshot.ipdl +[PBackgroundLSSnapshot::SyncCheckpointAndNotify] +description = See corresponding comment in PBackgroundLSSnapshot.ipdl +[PBackgroundLSSnapshot::SyncFinish] +description = See corresponding comment in PBackgroundLSSnapshot.ipdl +[PBackgroundLSSnapshot::LoadValueAndMoreItems] +description = See corresponding comment in PBackgroundLSSnapshot.ipdl +[PBackgroundLSSnapshot::LoadKeys] +description = See corresponding comment in PBackgroundLSSnapshot.ipdl +[PBackgroundLSSnapshot::IncreasePeakUsage] +description = See corresponding comment in PBackgroundLSSnapshot.ipdl +[PBackgroundSessionStorageCache::Load] +description = See corresponding comment in PBackgroundSessionStorageCache.ipdl +[PRemoteSpellcheckEngine::SetDictionary] +description = legacy sync IPC - please add detailed description +[PGPU::AddLayerTreeIdMapping] +description = legacy sync IPC - please add detailed description +[PGPU::GetDeviceStatus] +description = legacy sync IPC - please add detailed description +[PAPZInputBridge::ReceiveMultiTouchInputEvent] +description = legacy sync IPC - please add detailed description +[PAPZInputBridge::ReceiveMouseInputEvent] +description = legacy sync IPC - please add detailed description +[PAPZInputBridge::ReceivePanGestureInputEvent] +description = legacy sync IPC - please add detailed description +[PAPZInputBridge::ReceivePinchGestureInputEvent] +description = legacy sync IPC - please add detailed description +[PAPZInputBridge::ReceiveTapGestureInputEvent] +description = legacy sync IPC - please add detailed description +[PAPZInputBridge::ReceiveScrollWheelInputEvent] +description = legacy sync IPC - please add detailed description +[PAPZInputBridge::ProcessUnhandledEvent] +description = legacy sync IPC - please add detailed description +[PAPZInputBridge::ReceiveKeyboardInputEvent] +description = legacy sync IPC - please add detailed description +[PCanvasManager::GetSnapshot] +description = Retrieving canvas contents is synchronous (see also, PWebGL::GetFrontBufferSnapshot). +[PCompositorBridge::Initialize] +description = legacy sync IPC - please add detailed description +[PCompositorBridge::WillClose] +description = legacy sync IPC - please add detailed description +[PCompositorBridge::Pause] +description = legacy sync IPC - please add detailed description +[PCompositorBridge::Resume] +description = legacy sync IPC - please add detailed description +[PCompositorBridge::NotifyChildCreated] +description = legacy sync IPC - please add detailed description +[PCompositorBridge::MapAndNotifyChildCreated] +description = bug 1350660 +[PCompositorBridge::NotifyChildRecreated] +description = legacy sync IPC - please add detailed description +[PCompositorBridge::FlushRendering] +description = legacy sync IPC - please add detailed description +[PCompositorBridge::WaitOnTransactionProcessed] +description = bug 1364626 +[PCompositorBridge::StartFrameTimeRecording] +description = legacy sync IPC - please add detailed description +[PCompositorBridge::StopFrameTimeRecording] +description = legacy sync IPC - please add detailed description +[PCompositorBridge::SyncWithCompositor] +description = legacy sync IPC - please add detailed description +[PCompositorBridge::CheckContentOnlyTDR] +description = legacy sync IPC - please add detailed description +[PCompositorWidget::Initialize] +description = Fallable initialization for the widget. Must be called right after construction before any other messages are sent +platform = win +[PCompositorWidget::EnterPresentLock] +description = Obtain exclusive access to the widget surface. Used to ensure events don't change the surface while it's being used as a render target +platform = win +[PCompositorWidget::LeavePresentLock] +description = Release the exclusive lock that EnterPresentLock obtained +platform = win +[PCompositorWidget::ClearTransparentWindow] +description = Synchronously clear the widget surface. Does not rely on (Enter|Leave)PresentLock. When call returns, window surface has been fully updated with cleared pixel values. +platform = win +[PImageBridge::NewCompositable] +description = legacy sync IPC - please add detailed description +[PUiCompositorController::Pause] +description = legacy sync IPC - please add detailed description +[PUiCompositorController::Resume] +description = legacy sync IPC - please add detailed description +[PUiCompositorController::ResumeAndResize] +description = legacy sync IPC - please add detailed description +[PWebRenderBridge::EnsureConnected] +description = legacy sync IPC - please add detailed description +[PWebRenderBridge::GetSnapshot] +description = legacy sync IPC - please add detailed description +[PWebRenderBridge::SetTestSampleTime] +description = test only +[PWebRenderBridge::LeaveTestMode] +description = test only +[PWebRenderBridge::GetAnimationValue] +description = test only +[PWebRenderBridge::SetAsyncScrollOffset] +description = test only +[PWebRenderBridge::SetAsyncZoom] +description = test only +[PWebRenderBridge::GetAPZTestData] +description = test only +[PWebRenderBridge::GetFrameUniformity] +description = test only +[PWebRenderBridge::ShutdownSync] +description = bug 1377024 +[PWebRenderBridge::SyncWithCompositor] +description = WebRender equivalent for PCompositorBridge::SyncWithCompositor +[PHal::GetCurrentBatteryInformation] +description = legacy sync IPC - please add detailed description +[PHal::GetCurrentNetworkInformation] +description = legacy sync IPC - please add detailed description +[PHal::GetWakeLockInfo] +description = legacy sync IPC - please add detailed description +[PHandlerService::FillHandlerInfo] +description = legacy sync IPC - please add detailed description +[PHandlerService::GetMIMEInfoFromOS] +description = Lets unprivileged child processes synchronously get MIME type/handler information from the OS +[PHandlerService::ExistsForProtocolOS] +description = bug 1382323 +[PHandlerService::ExistsForProtocol] +description = legacy sync IPC - please add detailed description +[PHandlerService::Exists] +description = legacy sync IPC - please add detailed description +[PHandlerService::GetTypeFromExtension] +description = legacy sync IPC - please add detailed description +[PHandlerService::GetApplicationDescription] +description = Lets unprivileged child processes synchronously get a description of the app that handles a given protocol scheme +[PClientSource::WorkerSyncPing] +description = Synchronous ping allowing worker thread to confirm actor is created. Necessary to avoid racing with ClientHandle actors on main thread. +[PRemoteSandboxBroker::LaunchApp] +description = Synchronous launch of a child process that in turn launches and sandboxes another process. Called on a dedicated thread and targets a dedicated process, so this shouldn't block anything. +[PSandboxTesting::GetSpecialDirectory] +description = Testing only - get a special directory path. +# WebGL internals +[PWebGL::Initialize] +description = Initialization of WebGL contexts is synchronous by spec. +[PWebGL::GetFrontBuffer] +description = Publishing a WebGL frame for compositing is synchronous for now to ensure DOM transaction atomicity. +[PWebGL::OnMemoryPressure] +description = Synchronous to ensure immediate memory pressure relief. +# WebGL spec-synchronous functions +[PWebGL::CheckFramebufferStatus] +description = Checking framebuffer completenss must ask the driver. +[PWebGL::ClientWaitSync] +description = Checking fence-sync completenss must ask the driver. +[PWebGL::CreateOpaqueFramebuffer] +description = Must synchronously check for allocation success. +[PWebGL::DrawingBufferSize] +description = The returned size might be smaller than requested due to allocation failure. +[PWebGL::Finish] +description = Synchronous by spec, but not generally used. +[PWebGL::GetBufferSubData] +description = Retrieving buffer contents is synchronous in the worst case. +[PWebGL::GetFrontBufferSnapshot] +description = Retrieving canvas contents is synchronous. +[PWebGL::ReadPixels] +description = Retrieving WebGL framebuffer contents is synchronous. +# WebGL reflection functions +[PWebGL::GetBufferParameter] +description = Reflection is cold code, but synchronous by spec. +[PWebGL::GetCompileResult] +description = Reflection is cold code, but synchronous by spec. +[PWebGL::GetError] +description = Reflection is cold code, but synchronous by spec. +[PWebGL::GetFragDataLocation] +description = Reflection is cold code, but synchronous by spec. +[PWebGL::GetFramebufferAttachmentParameter] +description = Reflection is cold code, but synchronous by spec. +[PWebGL::GetIndexedParameter] +description = Reflection is cold code, but synchronous by spec. +[PWebGL::GetInternalformatParameter] +description = Reflection is cold code, but synchronous by spec. +[PWebGL::GetLinkResult] +description = Reflection is cold code, but synchronous by spec. +[PWebGL::GetNumber] +description = Reflection is cold code, but synchronous by spec. +[PWebGL::GetQueryParameter] +description = Reflection is cold code, but synchronous by spec. +[PWebGL::GetRenderbufferParameter] +description = Reflection is cold code, but synchronous by spec. +[PWebGL::GetSamplerParameter] +description = Reflection is cold code, but synchronous by spec. +[PWebGL::GetShaderPrecisionFormat] +description = Reflection is cold code, but synchronous by spec. +[PWebGL::GetString] +description = Reflection is cold code, but synchronous by spec. +[PWebGL::GetTexParameter] +description = Reflection is cold code, but synchronous by spec. +[PWebGL::GetUniform] +description = Reflection is cold code, but synchronous by spec. +[PWebGL::GetVertexAttrib] +description = Reflection is cold code, but synchronous by spec. +[PWebGL::IsEnabled] +description = Reflection is cold code, but synchronous by spec. +[PWebGL::ValidateProgram] +description = Reflection is cold code, but synchronous by spec. +# - +[PIPCClientCerts::FindObjects] +description = Finds certificates and private keys in the parent process. As this is called from PKCS#11, there is no way to make this asynchronous. +[PIPCClientCerts::Sign] +description = Performs a signature on given data with a key corresponding to the given identifier. This is called from PKCS#11, so there is no way to make this asynchronous. +# - +[PRemoteQuotaObject::MaybeUpdateSize] +description = This must be synchronous until quota file streams become non-blocking. The synchronous message is never used on the main thread or the PBackground thread or a DOM worker threed. + +############################################################# +# AVOID ADDING NEW MESSAGES TO THIS FILE # +# see comment at top of file # +############################################################# diff --git a/ipc/ipdl/test/README.txt b/ipc/ipdl/test/README.txt new file mode 100644 index 0000000000..ca6db8bb3c --- /dev/null +++ b/ipc/ipdl/test/README.txt @@ -0,0 +1,13 @@ +There are two major categories of tests, segregated into different +top-level directories under test/. + +The first category (ipdl/) is IPDL-compiler tests. These tests check +that the IPDL compiler is successfully compiling correct +specifications, and successfully rejecting erroneous specifications. + +To run these tests yourself locally, the correct invocation is + make -C obj-dir/ipc/ipdl/test/ipdl check + +The second category (cxx/) is C++ tests of IPDL semantics. These +tests check that async/sync/rpc semantics are implemented correctly, +ctors/dtors behave as they should, etc. diff --git a/ipc/ipdl/test/cxx/IPDLUnitTestProcessChild.cpp b/ipc/ipdl/test/cxx/IPDLUnitTestProcessChild.cpp new file mode 100644 index 0000000000..7683808535 --- /dev/null +++ b/ipc/ipdl/test/cxx/IPDLUnitTestProcessChild.cpp @@ -0,0 +1,27 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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/. */ + +#include "mozilla/ipc/IOThreadChild.h" + +#include "IPDLUnitTestProcessChild.h" +#include "IPDLUnitTests.h" + +#include "nsRegion.h" + +using mozilla::ipc::IOThreadChild; + +namespace mozilla { +namespace _ipdltest { + +bool IPDLUnitTestProcessChild::Init(int aArgc, char* aArgv[]) { + // FIXME(nika): this is quite clearly broken and needs to be fixed. + IPDLUnitTestChildInit(IOThreadChild::TakeChannel(), ParentPid(), + IOThreadChild::message_loop()); + + return true; +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/IPDLUnitTestProcessChild.h b/ipc/ipdl/test/cxx/IPDLUnitTestProcessChild.h new file mode 100644 index 0000000000..94578e3520 --- /dev/null +++ b/ipc/ipdl/test/cxx/IPDLUnitTestProcessChild.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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/. */ + +#ifndef mozilla__ipdltest_IPDLUnitTestThreadChild_h +#define mozilla__ipdltest_IPDLUnitTestThreadChild_h 1 + +#include "mozilla/ipc/ProcessChild.h" + +namespace mozilla { +namespace _ipdltest { + +class IPDLUnitTestProcessChild : public mozilla::ipc::ProcessChild { + typedef mozilla::ipc::ProcessChild ProcessChild; + + public: + using ProcessChild::ProcessChild; + + ~IPDLUnitTestProcessChild() {} + + virtual bool Init(int aArgc, char* aArgv[]) override; +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_IPDLUnitTestThreadChild_h diff --git a/ipc/ipdl/test/cxx/IPDLUnitTestSubprocess.cpp b/ipc/ipdl/test/cxx/IPDLUnitTestSubprocess.cpp new file mode 100644 index 0000000000..660e5928d3 --- /dev/null +++ b/ipc/ipdl/test/cxx/IPDLUnitTestSubprocess.cpp @@ -0,0 +1,19 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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/. */ + +#include "IPDLUnitTestSubprocess.h" + +using mozilla::ipc::GeckoChildProcessHost; + +namespace mozilla { +namespace _ipdltest { + +IPDLUnitTestSubprocess::IPDLUnitTestSubprocess() + : GeckoChildProcessHost(GeckoProcessType_IPDLUnitTest) {} + +IPDLUnitTestSubprocess::~IPDLUnitTestSubprocess() {} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/IPDLUnitTestSubprocess.h b/ipc/ipdl/test/cxx/IPDLUnitTestSubprocess.h new file mode 100644 index 0000000000..384cf25ce9 --- /dev/null +++ b/ipc/ipdl/test/cxx/IPDLUnitTestSubprocess.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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/. */ + +#ifndef mozilla__ipdltest_IPDLUnitTestTestSubprocess_h +#define mozilla__ipdltest_IPDLUnitTestTestSubprocess_h 1 + +#include "mozilla/ipc/GeckoChildProcessHost.h" + +namespace mozilla { +namespace _ipdltest { +//----------------------------------------------------------------------------- + +class IPDLUnitTestSubprocess : public mozilla::ipc::GeckoChildProcessHost { + public: + IPDLUnitTestSubprocess(); + + /** + * Asynchronously launch the plugin process. + */ + // Could override parent Launch, but don't need to here + // bool Launch(); + + private: + ~IPDLUnitTestSubprocess(); + + DISALLOW_EVIL_CONSTRUCTORS(IPDLUnitTestSubprocess); +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_IPDLUnitTestTestSubprocess_h diff --git a/ipc/ipdl/test/cxx/IPDLUnitTestTypes.h b/ipc/ipdl/test/cxx/IPDLUnitTestTypes.h new file mode 100644 index 0000000000..02696f7df7 --- /dev/null +++ b/ipc/ipdl/test/cxx/IPDLUnitTestTypes.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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/. */ + +#ifndef mozilla__ipdltest_IPDLUnitTestTypes_h +#define mozilla__ipdltest_IPDLUnitTestTypes_h + +#include "mozilla/ipc/ProtocolUtils.h" // ActorDestroyReason + +namespace mozilla { +namespace _ipdltest { + +struct DirtyRect { + int x; + int y; + int w; + int h; +}; + +} // namespace _ipdltest +} // namespace mozilla + +namespace IPC { +template <> +struct ParamTraits<mozilla::_ipdltest::DirtyRect> { + typedef mozilla::_ipdltest::DirtyRect paramType; + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.x); + WriteParam(aWriter, aParam.y); + WriteParam(aWriter, aParam.w); + WriteParam(aWriter, aParam.h); + } + static bool Read(MessageReader* aReader, void** aIter, paramType* aResult) { + return (ReadParam(aReader, aIter, &aResult->x) && + ReadParam(aReader, aIter, &aResult->y) && + ReadParam(aReader, aIter, &aResult->w) && + ReadParam(aReader, aIter, &aResult->h)); + } +}; +} // namespace IPC + +#endif // ifndef mozilla__ipdltest_IPDLUnitTestTypes_h diff --git a/ipc/ipdl/test/cxx/IPDLUnitTestUtils.h b/ipc/ipdl/test/cxx/IPDLUnitTestUtils.h new file mode 100644 index 0000000000..e454ae7b61 --- /dev/null +++ b/ipc/ipdl/test/cxx/IPDLUnitTestUtils.h @@ -0,0 +1,28 @@ + +#ifndef mozilla__ipdltest_IPDLUnitTestUtils +#define mozilla__ipdltest_IPDLUnitTestUtils 1 + +#include "ipc/IPCMessageUtils.h" + +namespace mozilla { +namespace _ipdltest { + +struct Bad {}; + +} // namespace _ipdltest +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits<mozilla::_ipdltest::Bad> { + typedef mozilla::_ipdltest::Bad paramType; + + // Defined in TestActorPunning.cpp. + static void Write(MessageWriter* aWriter, const paramType& aParam); + static bool Read(MessageReader* aReader, paramType* aResult); +}; + +} // namespace IPC + +#endif // mozilla__ipdltest_IPDLUnitTestUtils diff --git a/ipc/ipdl/test/cxx/IPDLUnitTests.h b/ipc/ipdl/test/cxx/IPDLUnitTests.h new file mode 100644 index 0000000000..28fc4ee372 --- /dev/null +++ b/ipc/ipdl/test/cxx/IPDLUnitTests.h @@ -0,0 +1,85 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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/. */ + +#ifndef mozilla__ipdltest_IPDLUnitTests_h +#define mozilla__ipdltest_IPDLUnitTests_h 1 + +#include "base/message_loop.h" +#include "base/process.h" +#include "chrome/common/ipc_channel.h" + +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsServiceManagerUtils.h" // do_GetService() +#include "nsWidgetsCID.h" // NS_APPSHELL_CID +#include "nsXULAppAPI.h" + +#define MOZ_IPDL_TESTFAIL_LABEL "TEST-UNEXPECTED-FAIL" +#define MOZ_IPDL_TESTPASS_LABEL "TEST-PASS" +#define MOZ_IPDL_TESTINFO_LABEL "TEST-INFO" + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// both processes +const char* IPDLUnitTestName(); + +// NB: these are named like the similar functions in +// xpcom/test/TestHarness.h. The names should nominally be kept in +// sync. + +inline void fail(const char* fmt, ...) { + va_list ap; + + fprintf(stderr, MOZ_IPDL_TESTFAIL_LABEL " | %s | ", IPDLUnitTestName()); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fputc('\n', stderr); + + MOZ_CRASH("failed test"); +} + +inline void passed(const char* fmt, ...) { + va_list ap; + + printf(MOZ_IPDL_TESTPASS_LABEL " | %s | ", IPDLUnitTestName()); + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + + fputc('\n', stdout); +} + +//----------------------------------------------------------------------------- +// parent process only + +class IPDLUnitTestSubprocess; + +extern void* gParentActor; +extern IPDLUnitTestSubprocess* gSubprocess; + +void IPDLUnitTestMain(void* aData); + +void QuitParent(); + +//----------------------------------------------------------------------------- +// child process only + +extern void* gChildActor; + +void IPDLUnitTestChildInit(IPC::Channel* transport, base::ProcessId parentPid, + MessageLoop* worker); + +void QuitChild(); + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_IPDLUnitTests_h diff --git a/ipc/ipdl/test/cxx/IPDLUnitTests.template.cpp b/ipc/ipdl/test/cxx/IPDLUnitTests.template.cpp new file mode 100644 index 0000000000..30eea42838 --- /dev/null +++ b/ipc/ipdl/test/cxx/IPDLUnitTests.template.cpp @@ -0,0 +1,347 @@ +// +// Autogenerated from Python template. Hands off. +// + +#include <stdlib.h> +#include <string.h> + +#include "IPDLUnitTests.h" + +#include "base/command_line.h" +#include "base/string_util.h" +#include "base/task.h" +#include "base/thread.h" + +#include "nsRegion.h" + +#include "IPDLUnitTestSubprocess.h" + +// clang-format off +//----------------------------------------------------------------------------- +//===== TEMPLATED ===== +${INCLUDES} +//----------------------------------------------------------------------------- +// clang-format on + +using namespace std; + +using base::Thread; + +namespace mozilla { +namespace _ipdltest { + +void* gParentActor; +IPDLUnitTestSubprocess* gSubprocess; + +void* gChildActor; + +// Note: in threaded mode, this will be non-null (for both parent and +// child, since they share one set of globals). +Thread* gChildThread; +MessageLoop* gParentMessageLoop; +bool gParentDone; +bool gChildDone; + +void DeleteChildActor(); + +//----------------------------------------------------------------------------- +// data/functions accessed by both parent and child processes + +char* gIPDLUnitTestName = nullptr; + +const char* IPDLUnitTestName() { + if (!gIPDLUnitTestName) { +#if defined(OS_WIN) + vector<wstring> args = CommandLine::ForCurrentProcess()->GetLooseValues(); + gIPDLUnitTestName = ::strdup(WideToUTF8(args[0]).c_str()); +#elif defined(OS_POSIX) + vector<string> argv = CommandLine::ForCurrentProcess()->argv(); + gIPDLUnitTestName = ::moz_xstrdup(argv[1].c_str()); +#else +# error Sorry +#endif + } + return gIPDLUnitTestName; +} + +} // namespace _ipdltest +} // namespace mozilla + +namespace { + +enum IPDLUnitTestType { + NoneTest = 0, + + // clang-format off +//----------------------------------------------------------------------------- +//===== TEMPLATED ===== +${ENUM_VALUES} + + LastTest = ${LAST_ENUM} +//----------------------------------------------------------------------------- +//clang-format on +}; + +IPDLUnitTestType IPDLUnitTestFromString(const char* const aString) { + if (!aString) return static_cast<IPDLUnitTestType>(0); +// clang-format off +//----------------------------------------------------------------------------- +//===== TEMPLATED ===== +${STRING_TO_ENUMS} +//----------------------------------------------------------------------------- + // clang-format on + else return static_cast<IPDLUnitTestType>(0); +} + +IPDLUnitTestType IPDLUnitTest() { + return IPDLUnitTestFromString(::mozilla::_ipdltest::IPDLUnitTestName()); +} + +} // namespace + +//----------------------------------------------------------------------------- +// parent process only + +namespace mozilla { +namespace _ipdltest { + +void DeferredParentShutdown(); + +void IPDLUnitTestThreadMain(char* testString); + +void IPDLUnitTestMain(void* aData) { + char* testString = reinterpret_cast<char*>(aData); + + // Check if we are to run the test using threads instead: + const char* prefix = "thread:"; + const int prefixLen = strlen(prefix); + if (!strncmp(testString, prefix, prefixLen)) { + IPDLUnitTestThreadMain(testString + prefixLen); + return; + } + + IPDLUnitTestType test = IPDLUnitTestFromString(testString); + if (!test) { + // use this instead of |fail()| because we don't know what the test is + fprintf(stderr, MOZ_IPDL_TESTFAIL_LABEL "| %s | unknown unit test %s\n", + "<--->", testString); + MOZ_CRASH("can't continue"); + } + gIPDLUnitTestName = testString; + + // Check whether this test is enabled for processes: + switch (test) { + // clang-format off +//----------------------------------------------------------------------------- +//===== TEMPLATED ===== +${PARENT_ENABLED_CASES_PROC} +//----------------------------------------------------------------------------- + // clang-format on + + default: + fail("not reached"); + return; // unreached + } + + printf(MOZ_IPDL_TESTINFO_LABEL "| running test | %s\n", gIPDLUnitTestName); + + std::vector<std::string> testCaseArgs; + testCaseArgs.push_back(testString); + + gSubprocess = new IPDLUnitTestSubprocess(); + if (!gSubprocess->SyncLaunch(testCaseArgs)) + fail("problem launching subprocess"); + + IPC::Channel* transport = gSubprocess->GetChannel(); + if (!transport) fail("no transport"); + + base::ProcessId child = base::GetProcId(gSubprocess->GetChildProcessHandle()); + + switch (test) { + // clang-format off +//----------------------------------------------------------------------------- +//===== TEMPLATED ===== +${PARENT_MAIN_CASES_PROC} +//----------------------------------------------------------------------------- + // clang-format on + + default: + fail("not reached"); + return; // unreached + } +} + +void IPDLUnitTestThreadMain(char* testString) { + IPDLUnitTestType test = IPDLUnitTestFromString(testString); + if (!test) { + // use this instead of |fail()| because we don't know what the test is + fprintf(stderr, MOZ_IPDL_TESTFAIL_LABEL "| %s | unknown unit test %s\n", + "<--->", testString); + MOZ_CRASH("can't continue"); + } + gIPDLUnitTestName = testString; + + // Check whether this test is enabled for threads: + switch (test) { + // clang-format off +//----------------------------------------------------------------------------- +//===== TEMPLATED ===== +${PARENT_ENABLED_CASES_THREAD} +//----------------------------------------------------------------------------- + // clang-format on + + default: + fail("not reached"); + return; // unreached + } + + printf(MOZ_IPDL_TESTINFO_LABEL "| running test | %s\n", gIPDLUnitTestName); + + std::vector<std::string> testCaseArgs; + testCaseArgs.push_back(testString); + + gChildThread = new Thread("ParentThread"); + if (!gChildThread->Start()) fail("starting parent thread"); + + gParentMessageLoop = MessageLoop::current(); + MessageLoop* childMessageLoop = gChildThread->message_loop(); + + switch (test) { + // clang-format off +//----------------------------------------------------------------------------- +//===== TEMPLATED ===== +${PARENT_MAIN_CASES_THREAD} +//----------------------------------------------------------------------------- + // clang-format on + + default: + fail("not reached"); + return; // unreached + } +} + +void DeleteParentActor() { + if (!gParentActor) return; + + switch (IPDLUnitTest()) { + // clang-format off +//----------------------------------------------------------------------------- +//===== TEMPLATED ===== +${PARENT_DELETE_CASES} +//----------------------------------------------------------------------------- + // clang-format on + default: + ::mozilla::_ipdltest::fail("???"); + } +} + +void QuitXPCOM() { + DeleteParentActor(); + + static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); + nsCOMPtr<nsIAppShell> appShell(do_GetService(kAppShellCID)); + appShell->Exit(); +} + +void DeleteSubprocess(MessageLoop* uiLoop) { + // pong to QuitXPCOM + gSubprocess->Destroy(); + gSubprocess = nullptr; + uiLoop->PostTask(NewRunnableFunction("QuitXPCOM", QuitXPCOM)); +} + +void DeferredParentShutdown() { + // ping to DeleteSubprocess + XRE_GetIOMessageLoop()->PostTask(NewRunnableFunction( + "DeleteSubprocess", DeleteSubprocess, MessageLoop::current())); +} + +void TryThreadedShutdown() { + // Stop if either: + // - the child has not finished, + // - the parent has not finished, + // - or this code has already executed. + // Remember: this TryThreadedShutdown() task is enqueued + // by both parent and child (though always on parent's msg loop). + if (!gChildDone || !gParentDone || !gChildThread) return; + + delete gChildThread; + gChildThread = 0; + DeferredParentShutdown(); +} + +void ChildCompleted() { + // Executes on the parent message loop once child has completed. + gChildDone = true; + TryThreadedShutdown(); +} + +void QuitParent() { + if (gChildThread) { + gParentDone = true; + MessageLoop::current()->PostTask( + NewRunnableFunction("TryThreadedShutdown", TryThreadedShutdown)); + } else { + // defer "real" shutdown to avoid *Channel::Close() racing with the + // deletion of the subprocess + MessageLoop::current()->PostTask( + NewRunnableFunction("DeferredParentShutdown", DeferredParentShutdown)); + } +} + +static void ChildDie() { + DeleteChildActor(); + XRE_ShutdownChildProcess(); +} + +void QuitChild() { + if (gChildThread) { // Threaded-mode test + gParentMessageLoop->PostTask( + NewRunnableFunction("ChildCompleted", ChildCompleted)); + } else { // Process-mode test + MessageLoop::current()->PostTask(NewRunnableFunction("ChildDie", ChildDie)); + } +} + +} // namespace _ipdltest +} // namespace mozilla + +//----------------------------------------------------------------------------- +// child process only + +namespace mozilla { +namespace _ipdltest { + +void DeleteChildActor() { + if (!gChildActor) return; + + switch (IPDLUnitTest()) { + // clang-format off +//----------------------------------------------------------------------------- +//===== TEMPLATED ===== +${CHILD_DELETE_CASES} +//----------------------------------------------------------------------------- + // clang-format on + default: + ::mozilla::_ipdltest::fail("???"); + } +} + +void IPDLUnitTestChildInit(IPC::Channel* transport, base::ProcessId parentPid, + MessageLoop* worker) { + switch (IPDLUnitTest()) { + // clang-format off +//----------------------------------------------------------------------------- +//===== TEMPLATED ===== +${CHILD_INIT_CASES} +//----------------------------------------------------------------------------- + // clang-format on + + default: + fail("not reached"); + return; // unreached + } +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/Makefile.in b/ipc/ipdl/test/cxx/Makefile.in new file mode 100644 index 0000000000..b32533a648 --- /dev/null +++ b/ipc/ipdl/test/cxx/Makefile.in @@ -0,0 +1,45 @@ +# 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/. + +IPDLTESTSRCS = $(filter Test%,$(CPPSRCS)) +IPDLTESTS = $(IPDLTESTSRCS:.cpp=) + +EXTRA_PROTOCOLS = \ + TestEndpointBridgeSub \ + $(NULL) + +IPDLTESTHDRS = $(addprefix $(srcdir)/,$(addsuffix .h,$(IPDLTESTS))) + +TESTER_TEMPLATE := $(srcdir)/IPDLUnitTests.template.cpp +GENTESTER := $(srcdir)/genIPDLUnitTests.py + +include $(topsrcdir)/config/rules.mk + + +IPDLUNITTEST_BIN = $(DEPTH)/dist/bin/ipdlunittest$(BIN_SUFFIX) + +IPDLUnitTests.cpp : Makefile.in moz.build $(GENTESTER) $(TESTER_TEMPLATE) $(IPDLTESTHDRS) + $(PYTHON3) $(GENTESTER) $(TESTER_TEMPLATE) -t $(IPDLTESTS) -e $(EXTRA_PROTOCOLS) > $@ + +check-proc:: + @$(EXIT_ON_ERROR) \ + for test in $(IPDLTESTS); do \ + $(RUN_TEST_PROGRAM) $(IPDLUNITTEST_BIN) $$test ; \ + done + +check-thread:: + @$(EXIT_ON_ERROR) \ + for test in $(IPDLTESTS); do \ + $(RUN_TEST_PROGRAM) $(IPDLUNITTEST_BIN) thread:$$test ; \ + done + +check:: check-proc check-thread + +check-valgrind:: + @$(EXIT_ON_ERROR) \ + for test in $(IPDLTESTS); do \ + $(RUN_TEST_PROGRAM) -g -d \ + valgrind -a '--leak-check=full --trace-children=yes -q' \ + $(IPDLUNITTEST_BIN) $$test ; \ + done diff --git a/ipc/ipdl/test/cxx/PTestActorPunning.ipdl b/ipc/ipdl/test/cxx/PTestActorPunning.ipdl new file mode 100644 index 0000000000..0aec430d9f --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestActorPunning.ipdl @@ -0,0 +1,28 @@ + +include protocol PTestActorPunningPunned; +include protocol PTestActorPunningSub; +include "mozilla/_ipdltest/IPDLUnitTestUtils.h"; +include "mozilla/_ipdltest/TestActorPunning.h"; + +using struct mozilla::_ipdltest::Bad from "mozilla/_ipdltest/IPDLUnitTestUtils.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestActorPunningChild", ParentImpl="TestActorPunningParent"] +protocol PTestActorPunning { + manages PTestActorPunningPunned; + manages PTestActorPunningSub; + +child: + async Start(); + +parent: + async PTestActorPunningPunned(); + async PTestActorPunningSub(); + async Pun(PTestActorPunningSub a, Bad bad); + async __delete__(); +}; + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestActorPunningPunned.ipdl b/ipc/ipdl/test/cxx/PTestActorPunningPunned.ipdl new file mode 100644 index 0000000000..5cca8a8ab5 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestActorPunningPunned.ipdl @@ -0,0 +1,18 @@ + +include protocol PTestActorPunning; + +include "mozilla/_ipdltest/TestActorPunning.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestActorPunningPunnedChild", ParentImpl="TestActorPunningPunnedParent"] +protocol PTestActorPunningPunned { + manager PTestActorPunning; + +child: + async __delete__(); +}; + +} // namespace mozilla +} // namespace _ipdltes diff --git a/ipc/ipdl/test/cxx/PTestActorPunningSub.ipdl b/ipc/ipdl/test/cxx/PTestActorPunningSub.ipdl new file mode 100644 index 0000000000..0cb522255c --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestActorPunningSub.ipdl @@ -0,0 +1,19 @@ + +include protocol PTestActorPunning; + +include "mozilla/_ipdltest/TestActorPunning.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestActorPunningSubChild", ParentImpl="TestActorPunningSubParent"] +protocol PTestActorPunningSub { + manager PTestActorPunning; + +child: + async Bad(); + async __delete__(); +}; + +} // namespace mozilla +} // namespace _ipdltes diff --git a/ipc/ipdl/test/cxx/PTestAsyncReturns.ipdl b/ipc/ipdl/test/cxx/PTestAsyncReturns.ipdl new file mode 100644 index 0000000000..42ef17d1e8 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestAsyncReturns.ipdl @@ -0,0 +1,20 @@ +include "mozilla/_ipdltest/TestAsyncReturns.h"; + +namespace mozilla { +namespace _ipdltest { + + +[ManualDealloc, ChildImpl="TestAsyncReturnsChild", ParentImpl="TestAsyncReturnsParent"] +protocol PTestAsyncReturns { + +child: + async Ping() returns (bool one); + async NoReturn() returns (bool unused); + +parent: + async Pong() returns (uint32_t param1, uint32_t param2); +}; + + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestBadActor.ipdl b/ipc/ipdl/test/cxx/PTestBadActor.ipdl new file mode 100644 index 0000000000..991e6eb945 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestBadActor.ipdl @@ -0,0 +1,21 @@ +include protocol PTestBadActorSub; + +include "mozilla/_ipdltest/TestBadActor.h"; + +namespace mozilla { +namespace _ipdltest { + +// Test that a parent sending a reentrant __delete__ message +// is not killed if a child's message races with the reply. + +[ManualDealloc, ChildImpl="TestBadActorChild", ParentImpl="TestBadActorParent"] +intr protocol PTestBadActor { + manages PTestBadActorSub; + +child: + async PTestBadActorSub(); + async __delete__(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestBadActorSub.ipdl b/ipc/ipdl/test/cxx/PTestBadActorSub.ipdl new file mode 100644 index 0000000000..aadd8b6739 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestBadActorSub.ipdl @@ -0,0 +1,20 @@ +include protocol PTestBadActor; + +include "mozilla/_ipdltest/TestBadActor.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestBadActorSubChild", ParentImpl="TestBadActorSubParent"] +intr protocol PTestBadActorSub { + manager PTestBadActor; + +child: + [LegacyIntr] intr __delete__(); + +parent: + async Ping(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestCancel.ipdl b/ipc/ipdl/test/cxx/PTestCancel.ipdl new file mode 100644 index 0000000000..4ad902e8e1 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestCancel.ipdl @@ -0,0 +1,39 @@ +include "mozilla/_ipdltest/TestCancel.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, NestedUpTo=inside_sync, ChildImpl="TestCancelChild", ParentImpl="TestCancelParent"] +sync protocol PTestCancel +{ +// Test1 +child: + [Nested=inside_sync] sync Test1_1(); +parent: + async Done1(); + +// Test2 +child: + async Start2(); + [Nested=inside_sync] sync Test2_2(); +parent: + sync Test2_1(); + +// Test3 +child: + [Nested=inside_sync] sync Test3_1(); +parent: + async Start3(); + [Nested=inside_sync] sync Test3_2(); + +parent: + async Done(); + +child: + [Nested=inside_sync] sync CheckChild() returns (uint32_t reply); +parent: + [Nested=inside_sync] sync CheckParent() returns (uint32_t reply); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestCrashCleanup.ipdl b/ipc/ipdl/test/cxx/PTestCrashCleanup.ipdl new file mode 100644 index 0000000000..43d955f1c7 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestCrashCleanup.ipdl @@ -0,0 +1,20 @@ +include "mozilla/_ipdltest/TestCrashCleanup.h"; + +// See bug 538586: if the top-level protocol's actor is deleted before +// the "connection error" notification comes in from the IO thread, +// IPDL teardown never occurs, even if Channel::Close() is called +// after the error. + +namespace mozilla { +namespace _ipdltest { + +// NB: needs to be RPC so that the parent blocks on the child's crash. +[ManualDealloc, ChildImpl="TestCrashCleanupChild", ParentImpl="TestCrashCleanupParent"] +intr protocol PTestCrashCleanup { +child: + [LegacyIntr] intr DIEDIEDIE(); + async __delete__(); +}; + +} +} diff --git a/ipc/ipdl/test/cxx/PTestDataStructures.ipdl b/ipc/ipdl/test/cxx/PTestDataStructures.ipdl new file mode 100644 index 0000000000..64b6970666 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestDataStructures.ipdl @@ -0,0 +1,109 @@ +include protocol PTestDataStructuresSub; +include PTestDataStructuresCommon; + +include "mozilla/GfxMessageUtils.h"; +include "mozilla/_ipdltest/TestDataStructures.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestDataStructuresChild", ParentImpl="TestDataStructuresParent"] +sync protocol PTestDataStructures { + manages PTestDataStructuresSub; + +child: + async PTestDataStructuresSub(int i); + + async Start(); + +parent: + async __delete__(); + + sync Test1(int[] i1) + returns (int[] o1); + + sync Test2(PTestDataStructuresSub[] i1) + returns (PTestDataStructuresSub[] o1); + + sync Test3(IntDouble i1, + IntDouble i2) + returns (IntDouble o1, + IntDouble o2); + + sync Test4(IntDouble[] i1) + returns (IntDouble[] o1); + + sync Test5(IntDoubleArrays i1, + IntDoubleArrays i2, + IntDoubleArrays i3) + returns (IntDoubleArrays o1, + IntDoubleArrays o2, + IntDoubleArrays o3); + + sync Test6(IntDoubleArrays[] i1) + returns (IntDoubleArrays[] o1); + + sync Test7_0(ActorWrapper a1) + returns (ActorWrapper o1); + + sync Test7(Actors i1, + Actors i2, + Actors i3) + returns (Actors o1, + Actors o2, + Actors o3); + + sync Test8(Actors[] i1) + returns (Actors[] o1); + + sync Test9(Unions i1, + Unions i2, + Unions i3, + Unions i4) + returns (Unions o1, + Unions o2, + Unions o3, + Unions o4); + + sync Test10(Unions[] i1) + returns (Unions[] o1); + + sync Test11(SIntDouble i) + returns (SIntDouble o); + + sync Test12(SIntDoubleArrays i) + returns (SIntDoubleArrays o); + + sync Test13(SActors i) + returns (SActors o); + + sync Test14(Structs i) + returns (Structs o); + + sync Test15(WithStructs i1, + WithStructs i2, + WithStructs i3, + WithStructs i4, + WithStructs i5) + returns (WithStructs o1, + WithStructs o2, + WithStructs o3, + WithStructs o4, + WithStructs o5); + + sync Test16(WithUnions i) + returns (WithUnions o); + + sync Test17(Op[] ops); + + // test that the ParamTraits<nsTArray>::Read() workaround for + // nsTArray's incorrect memmove() semantics works properly + // (nsIntRegion isn't memmove()able) + sync Test18(nsIntRegion[] ops); + + sync Dummy(ShmemUnion su) returns (ShmemUnion rsu); +}; + +} // namespace _ipdltest +} // namespace mozilla + diff --git a/ipc/ipdl/test/cxx/PTestDataStructuresCommon.ipdlh b/ipc/ipdl/test/cxx/PTestDataStructuresCommon.ipdlh new file mode 100644 index 0000000000..39d7f482b3 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestDataStructuresCommon.ipdlh @@ -0,0 +1,107 @@ +include protocol PTestDataStructuresSub; + +using struct mozilla::null_t from "mozilla/ipc/IPCCore.h"; +using nsIntRegion from "nsRegion.h"; + +namespace mozilla { +namespace _foo { + +union IntDouble { + int; + double; +}; + +struct SIntDouble { + int i; + double d; +}; + +union IntDoubleArrays { + int; + int[]; + double[]; +}; + +struct SIntDoubleArrays { + int i; + int[] ai; + double[] ad; +}; + +struct ActorWrapper { + PTestDataStructuresSub actor; +}; + +union Actors { + int; + int[]; + PTestDataStructuresSub[]; +}; + +struct SActors { + int i; + int[] ai; + PTestDataStructuresSub[] ap; +}; + +union Unions { + int; + int[]; + PTestDataStructuresSub[]; + Actors[]; +}; + +struct Structs { + int i; + int[] ai; + PTestDataStructuresSub[] ap; + SActors[] aa; +}; + +union WithStructs { + int; + int[]; + PTestDataStructuresSub[]; + SActors[]; + Structs[]; +}; + +struct WithUnions { + int i; + int[] ai; + PTestDataStructuresSub[] ap; + Actors[] aa; + Unions[] au; +}; + +struct CommonAttrs { bool dummy; }; +struct FooAttrs { int dummy; }; +struct BarAttrs { float dummy; }; +union SpecificAttrs { + FooAttrs; + BarAttrs; +}; +struct Attrs { + CommonAttrs common; + SpecificAttrs specific; +}; +struct SetAttrs { + PTestDataStructuresSub x; + Attrs attrs; +}; +union Op { null_t; SetAttrs; }; + +struct ShmemStruct { + int i; + Shmem mem; +}; + +union ShmemUnion { + int; + Shmem; +}; + +struct Empty { }; + +} // namespace _foo +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestDataStructuresSub.ipdl b/ipc/ipdl/test/cxx/PTestDataStructuresSub.ipdl new file mode 100644 index 0000000000..4f546a8d29 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestDataStructuresSub.ipdl @@ -0,0 +1,18 @@ +include PTestDataStructuresCommon; +include protocol PTestDataStructures; + +include "mozilla/_ipdltest/TestDataStructures.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestDataStructuresSub", ParentImpl="TestDataStructuresSub"] +sync protocol PTestDataStructuresSub { + manager PTestDataStructures; + +parent: + sync __delete__(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestDemon.ipdl b/ipc/ipdl/test/cxx/PTestDemon.ipdl new file mode 100644 index 0000000000..f5af2396e6 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestDemon.ipdl @@ -0,0 +1,24 @@ +include "mozilla/_ipdltest/TestDemon.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, NestedUpTo=inside_cpow, ChildImpl="TestDemonChild", ParentImpl="TestDemonParent"] +sync protocol PTestDemon +{ +child: + async Start(); + +both: + async AsyncMessage(int n); + [Nested=inside_sync] sync HiPrioSyncMessage(); + +parent: + sync SyncMessage(int n); + + [Nested=inside_cpow] async UrgentAsyncMessage(int n); + [Nested=inside_cpow] sync UrgentSyncMessage(int n); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestDesc.ipdl b/ipc/ipdl/test/cxx/PTestDesc.ipdl new file mode 100644 index 0000000000..d18d2d8598 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestDesc.ipdl @@ -0,0 +1,24 @@ +include protocol PTestDescSub; +include protocol PTestDescSubsub; + +include "mozilla/_ipdltest/TestDesc.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestDescChild", ParentImpl="TestDescParent"] +intr protocol PTestDesc { + manages PTestDescSub; +child: + [LegacyIntr] intr PTestDescSub(nullable PTestDescSubsub dummy); + + async Test(PTestDescSubsub a); + + async __delete__(); + +parent: + async Ok(PTestDescSubsub a); +}; + +} +} diff --git a/ipc/ipdl/test/cxx/PTestDescSub.ipdl b/ipc/ipdl/test/cxx/PTestDescSub.ipdl new file mode 100644 index 0000000000..a05451c639 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestDescSub.ipdl @@ -0,0 +1,21 @@ +include protocol PTestDesc; +include protocol PTestDescSubsub; + +include "mozilla/_ipdltest/TestDesc.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestDescSubChild", ParentImpl="TestDescSubParent"] +intr protocol PTestDescSub { + manager PTestDesc; + manages PTestDescSubsub; + +child: + async __delete__(); + + [LegacyIntr] intr PTestDescSubsub(); +}; + +} +} diff --git a/ipc/ipdl/test/cxx/PTestDescSubsub.ipdl b/ipc/ipdl/test/cxx/PTestDescSubsub.ipdl new file mode 100644 index 0000000000..542a03e2d2 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestDescSubsub.ipdl @@ -0,0 +1,18 @@ + +include protocol PTestDescSub; + +include "mozilla/_ipdltest/TestDesc.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestDescSubsubChild", ParentImpl="TestDescSubsubParent"] +intr protocol PTestDescSubsub { + manager PTestDescSub; + +child: + [LegacyIntr] intr __delete__(); +}; + +} +} diff --git a/ipc/ipdl/test/cxx/PTestEndpointBridgeMain.ipdl b/ipc/ipdl/test/cxx/PTestEndpointBridgeMain.ipdl new file mode 100644 index 0000000000..c51967b1fa --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestEndpointBridgeMain.ipdl @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +include protocol PTestEndpointBridgeMainSub; +include protocol PTestEndpointBridgeSub; + +include "mozilla/_ipdltest/TestEndpointBridgeMain.h"; + +namespace mozilla { +namespace _ipdltest { + + +[ManualDealloc, ChildImpl="TestEndpointBridgeMainChild", ParentImpl="TestEndpointBridgeMainParent"] +protocol PTestEndpointBridgeMain { + +child: + async Start(); + +parent: + async Bridged(Endpoint<PTestEndpointBridgeMainSubParent> endpoint); +}; + + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestEndpointBridgeMainSub.ipdl b/ipc/ipdl/test/cxx/PTestEndpointBridgeMainSub.ipdl new file mode 100644 index 0000000000..e899fca7aa --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestEndpointBridgeMainSub.ipdl @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +include protocol PTestEndpointBridgeMain; +include protocol PTestEndpointBridgeSub; + +include "mozilla/_ipdltest/TestEndpointBridgeMain.h"; + +namespace mozilla { +namespace _ipdltest { + +// (Bridge protocols can have different semantics than the endpoints +// they bridge) +[ManualDealloc, ChildImpl="TestEndpointBridgeMainSubChild", ParentImpl="TestEndpointBridgeMainSubParent"] +intr protocol PTestEndpointBridgeMainSub { +child: + async Hi(); + [LegacyIntr] intr HiRpc(); + +parent: + async Hello(); + sync HelloSync(); + [LegacyIntr] intr HelloRpc(); +}; + + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestEndpointBridgeSub.ipdl b/ipc/ipdl/test/cxx/PTestEndpointBridgeSub.ipdl new file mode 100644 index 0000000000..ab12e3bdb5 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestEndpointBridgeSub.ipdl @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +include protocol PTestEndpointBridgeMainSub; + +include "mozilla/_ipdltest/TestEndpointBridgeMain.h"; + +namespace mozilla { +namespace _ipdltest { + + +[ManualDealloc, ChildImpl="TestEndpointBridgeSubChild", ParentImpl="TestEndpointBridgeSubParent"] +protocol PTestEndpointBridgeSub { +child: + async Ping(); + + async Bridged(Endpoint<PTestEndpointBridgeMainSubChild> endpoint); + +parent: + async BridgeEm(); +}; + + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestEndpointOpens.ipdl b/ipc/ipdl/test/cxx/PTestEndpointOpens.ipdl new file mode 100644 index 0000000000..6cb20bf8eb --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestEndpointOpens.ipdl @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +include protocol PTestEndpointOpensOpened; + +include "mozilla/_ipdltest/TestEndpointOpens.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestEndpointOpensChild", ParentImpl="TestEndpointOpensParent"] +protocol PTestEndpointOpens { +child: + async Start(); + +parent: + async StartSubprotocol(Endpoint<PTestEndpointOpensOpenedParent> endpoint); + + async __delete__(); +}; + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestEndpointOpensOpened.ipdl b/ipc/ipdl/test/cxx/PTestEndpointOpensOpened.ipdl new file mode 100644 index 0000000000..7f72828c30 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestEndpointOpensOpened.ipdl @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +include "mozilla/_ipdltest/TestEndpointOpens.h"; + +namespace mozilla { +namespace _ipdltest2 { + +// (Opens protocols can have different semantics than the endpoints +// that opened them) +[ManualDealloc, ChildImpl="TestEndpointOpensOpenedChild", ParentImpl="TestEndpointOpensOpenedParent"] +intr protocol PTestEndpointOpensOpened { +child: + async Hi(); + [LegacyIntr] intr HiRpc(); + +parent: + async Hello(); + sync HelloSync(); + [LegacyIntr] intr HelloRpc(); + async __delete__(); +}; + + +} // namespace mozilla +} // namespace _ipdltest2 diff --git a/ipc/ipdl/test/cxx/PTestFailedCtor.ipdl b/ipc/ipdl/test/cxx/PTestFailedCtor.ipdl new file mode 100644 index 0000000000..0144f66dba --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestFailedCtor.ipdl @@ -0,0 +1,17 @@ +include protocol PTestFailedCtorSub; + +include "mozilla/_ipdltest/TestFailedCtor.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestFailedCtorChild", ParentImpl="TestFailedCtorParent"] +intr protocol PTestFailedCtor { + manages PTestFailedCtorSub; +child: + [LegacyIntr] intr PTestFailedCtorSub(); + async __delete__(); +}; + +} +} diff --git a/ipc/ipdl/test/cxx/PTestFailedCtorSub.ipdl b/ipc/ipdl/test/cxx/PTestFailedCtorSub.ipdl new file mode 100644 index 0000000000..97bc4d4bf8 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestFailedCtorSub.ipdl @@ -0,0 +1,21 @@ +include protocol PTestFailedCtor; +include protocol PTestFailedCtorSubsub; + +include "mozilla/_ipdltest/TestFailedCtor.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestFailedCtorSubChild", ParentImpl="TestFailedCtorSubParent"] +intr protocol PTestFailedCtorSub { + manager PTestFailedCtor; + manages PTestFailedCtorSubsub; + +parent: + async PTestFailedCtorSubsub(); + sync Sync(); + async __delete__(); +}; + +} +} diff --git a/ipc/ipdl/test/cxx/PTestFailedCtorSubsub.ipdl b/ipc/ipdl/test/cxx/PTestFailedCtorSubsub.ipdl new file mode 100644 index 0000000000..84cb2a0eb8 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestFailedCtorSubsub.ipdl @@ -0,0 +1,18 @@ + +include protocol PTestFailedCtorSub; + +include "mozilla/_ipdltest/TestFailedCtor.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestFailedCtorSubsub", ParentImpl="TestFailedCtorSubsub"] +intr protocol PTestFailedCtorSubsub { + manager PTestFailedCtorSub; + +parent: + async __delete__(); +}; + +} +} diff --git a/ipc/ipdl/test/cxx/PTestHandle.ipdl b/ipc/ipdl/test/cxx/PTestHandle.ipdl new file mode 100644 index 0000000000..b0a65f69c0 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestHandle.ipdl @@ -0,0 +1,17 @@ +include protocol PTestJSON; + +include "mozilla/_ipdltest/TestJSON.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestHandleChild", ParentImpl="TestHandleParent"] +protocol PTestHandle { + manager PTestJSON; + +child: + async __delete__(); +}; + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestHangs.ipdl b/ipc/ipdl/test/cxx/PTestHangs.ipdl new file mode 100644 index 0000000000..cadc1eda2e --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestHangs.ipdl @@ -0,0 +1,22 @@ + +include "mozilla/_ipdltest/TestHangs.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestHangsChild", ParentImpl="TestHangsParent"] +intr protocol PTestHangs { +both: + [LegacyIntr] intr StackFrame(); + +parent: + async Nonce(); + +child: + async Start(); + [LegacyIntr] intr Hang(); + async __delete__(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestHighestPrio.ipdl b/ipc/ipdl/test/cxx/PTestHighestPrio.ipdl new file mode 100644 index 0000000000..877ba427b6 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestHighestPrio.ipdl @@ -0,0 +1,21 @@ +include "mozilla/_ipdltest/TestHighestPrio.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, NestedUpTo=inside_cpow, ChildImpl="TestHighestPrioChild", ParentImpl="TestHighestPrioParent"] +sync protocol PTestHighestPrio +{ +parent: + [Nested=inside_cpow] async Msg1(); + [Nested=inside_sync] sync Msg2(); + [Nested=inside_cpow] async Msg3(); + [Nested=inside_cpow] sync Msg4(); + +child: + async Start(); + [Nested=inside_sync] sync StartInner(); +}; + +} +} diff --git a/ipc/ipdl/test/cxx/PTestIndirectProtocolParam.ipdlh b/ipc/ipdl/test/cxx/PTestIndirectProtocolParam.ipdlh new file mode 100644 index 0000000000..a81fcdee46 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestIndirectProtocolParam.ipdlh @@ -0,0 +1,15 @@ +include protocol PTestIndirectProtocolParamSecond;
+
+namespace mozilla {
+namespace _ipdltest {
+
+struct IndirectParamStruct {
+ PTestIndirectProtocolParamSecond actor;
+};
+
+union IndirectParamUnion {
+ IndirectParamStruct;
+};
+
+}
+}
diff --git a/ipc/ipdl/test/cxx/PTestIndirectProtocolParamFirst.ipdl b/ipc/ipdl/test/cxx/PTestIndirectProtocolParamFirst.ipdl new file mode 100644 index 0000000000..5364d79ffb --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestIndirectProtocolParamFirst.ipdl @@ -0,0 +1,20 @@ +include protocol PTestIndirectProtocolParamManage; +// FIXME/bug 792908 protocol PTestIndirectProtocolParamSecond is +// already included in PTestIndirectProtocolParam.ipdlh +include protocol PTestIndirectProtocolParamSecond; +include PTestIndirectProtocolParam; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual] +sync protocol PTestIndirectProtocolParamFirst { + manager PTestIndirectProtocolParamManage; +parent: + sync Test(IndirectParamUnion actor); +both: + async __delete__(); +}; + +} +} diff --git a/ipc/ipdl/test/cxx/PTestIndirectProtocolParamManage.ipdl b/ipc/ipdl/test/cxx/PTestIndirectProtocolParamManage.ipdl new file mode 100644 index 0000000000..8d3c6f81b1 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestIndirectProtocolParamManage.ipdl @@ -0,0 +1,18 @@ +include protocol PTestIndirectProtocolParamFirst; +include protocol PTestIndirectProtocolParamSecond; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual] +sync protocol PTestIndirectProtocolParamManage { + manages PTestIndirectProtocolParamFirst; + manages PTestIndirectProtocolParamSecond; +both: + async PTestIndirectProtocolParamFirst(); + async PTestIndirectProtocolParamSecond(); + async __delete__(); +}; + +} +} diff --git a/ipc/ipdl/test/cxx/PTestIndirectProtocolParamSecond.ipdl b/ipc/ipdl/test/cxx/PTestIndirectProtocolParamSecond.ipdl new file mode 100644 index 0000000000..27545ca2cb --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestIndirectProtocolParamSecond.ipdl @@ -0,0 +1,14 @@ +include protocol PTestIndirectProtocolParamManage; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual] +sync protocol PTestIndirectProtocolParamSecond { + manager PTestIndirectProtocolParamManage; +both: + async __delete__(); +}; + +} +} diff --git a/ipc/ipdl/test/cxx/PTestInterruptErrorCleanup.ipdl b/ipc/ipdl/test/cxx/PTestInterruptErrorCleanup.ipdl new file mode 100644 index 0000000000..ab6754cc1b --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestInterruptErrorCleanup.ipdl @@ -0,0 +1,14 @@ +include "mozilla/_ipdltest/TestInterruptErrorCleanup.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestInterruptErrorCleanupChild", ParentImpl="TestInterruptErrorCleanupParent"] +intr protocol PTestInterruptErrorCleanup { +child: + [LegacyIntr] intr Error(); + [LegacyIntr] intr __delete__(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestInterruptRaces.ipdl b/ipc/ipdl/test/cxx/PTestInterruptRaces.ipdl new file mode 100644 index 0000000000..aa66966938 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestInterruptRaces.ipdl @@ -0,0 +1,27 @@ +include "mozilla/_ipdltest/TestInterruptRaces.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestInterruptRacesChild", ParentImpl="TestInterruptRacesParent"] +intr protocol PTestInterruptRaces { +both: + [LegacyIntr] intr Race() returns (bool hasReply); + [LegacyIntr] intr StackFrame() returns (); + [LegacyIntr] intr StackFrame3() returns (); + +parent: + sync StartRace(); + [LegacyIntr] intr Parent(); + sync GetAnsweredParent() returns (bool answeredParent); + +child: + async Start(); + async Wakeup(); + async Wakeup3(); + [LegacyIntr] intr Child(); + async __delete__(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestInterruptShutdownRace.ipdl b/ipc/ipdl/test/cxx/PTestInterruptShutdownRace.ipdl new file mode 100644 index 0000000000..84bc6f0a97 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestInterruptShutdownRace.ipdl @@ -0,0 +1,19 @@ +include "mozilla/_ipdltest/TestInterruptShutdownRace.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestInterruptShutdownRaceChild", ParentImpl="TestInterruptShutdownRaceParent"] +intr protocol PTestInterruptShutdownRace { +parent: + sync StartDeath(); + async Orphan(); + +child: + async Start(); + [LegacyIntr] intr Exit(); + async __delete__(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestJSON.ipdl b/ipc/ipdl/test/cxx/PTestJSON.ipdl new file mode 100644 index 0000000000..c785abe77a --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestJSON.ipdl @@ -0,0 +1,49 @@ +include protocol PTestHandle; + +include "mozilla/_ipdltest/TestJSON.h"; + +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; +using struct mozilla::null_t from "mozilla/ipc/IPCCore.h"; + +namespace mozilla { +namespace _ipdltest { + +union Key { +// int; +// double; + nsString; +}; + +struct KeyValue { + Key key; + JSONVariant value; +}; + +union JSONVariant { + void_t; + null_t; + bool; + int; + double; + nsString; + PTestHandle; + KeyValue[]; + JSONVariant[]; +}; + +[ManualDealloc, ChildImpl="TestJSONChild", ParentImpl="TestJSONParent"] +sync protocol PTestJSON { + manages PTestHandle; + +child: + async Start(); + +parent: + async PTestHandle(); + sync Test(JSONVariant i) + returns (JSONVariant o); + async __delete__(); +}; + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestLatency.ipdl b/ipc/ipdl/test/cxx/PTestLatency.ipdl new file mode 100644 index 0000000000..d868d119a2 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestLatency.ipdl @@ -0,0 +1,28 @@ +include "mozilla/_ipdltest/TestLatency.h"; + +namespace mozilla { +namespace _ipdltest { + + +[ManualDealloc, ChildImpl="TestLatencyChild", ParentImpl="TestLatencyParent"] +intr protocol PTestLatency { + +child: + async __delete__(); + async Ping(); + async Ping5(); + [LegacyIntr] intr Rpc(); + async Spam(); + [LegacyIntr] intr Synchro(); + [Compress] async CompressedSpam(uint32_t seqno); + [LegacyIntr] intr Synchro2() returns (uint32_t lastSeqno, + uint32_t numMessagesDispatched); + +parent: + async Pong(); + async Pong5(); +}; + + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestManyChildAllocs.ipdl b/ipc/ipdl/test/cxx/PTestManyChildAllocs.ipdl new file mode 100644 index 0000000000..6f8a0e1187 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestManyChildAllocs.ipdl @@ -0,0 +1,22 @@ +include protocol PTestManyChildAllocsSub; + +include "mozilla/_ipdltest/TestManyChildAllocs.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestManyChildAllocsChild", ParentImpl="TestManyChildAllocsParent"] +protocol PTestManyChildAllocs { + manages PTestManyChildAllocsSub; + +child: + async Go(); // start allocating + +parent: + async Done(); + + async PTestManyChildAllocsSub(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestManyChildAllocsSub.ipdl b/ipc/ipdl/test/cxx/PTestManyChildAllocsSub.ipdl new file mode 100644 index 0000000000..e20c2badfd --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestManyChildAllocsSub.ipdl @@ -0,0 +1,22 @@ +include protocol PTestManyChildAllocs; + +include "mozilla/_ipdltest/TestManyChildAllocs.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestManyChildAllocsSubChild", ParentImpl="TestManyChildAllocsSubParent"] +protocol PTestManyChildAllocsSub { + manager PTestManyChildAllocs; + +child: + async __delete__(); + +parent: + async Hello(); + + // empty +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestMultiMgrs.ipdl b/ipc/ipdl/test/cxx/PTestMultiMgrs.ipdl new file mode 100644 index 0000000000..65272fa991 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestMultiMgrs.ipdl @@ -0,0 +1,25 @@ +include protocol PTestMultiMgrsLeft; +include protocol PTestMultiMgrsRight; + +include "mozilla/_ipdltest/TestMultiMgrs.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestMultiMgrsChild", ParentImpl="TestMultiMgrsParent"] +protocol PTestMultiMgrs { + manages PTestMultiMgrsLeft; + manages PTestMultiMgrsRight; + +parent: + async OK(); + +child: + async PTestMultiMgrsLeft(); + async PTestMultiMgrsRight(); + async Check(); + async __delete__(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestMultiMgrsBottom.ipdl b/ipc/ipdl/test/cxx/PTestMultiMgrsBottom.ipdl new file mode 100644 index 0000000000..be85b1547b --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestMultiMgrsBottom.ipdl @@ -0,0 +1,18 @@ +include protocol PTestMultiMgrsLeft; +include protocol PTestMultiMgrsRight; + +include "mozilla/_ipdltest/TestMultiMgrs.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestMultiMgrsBottomChild", ParentImpl="TestMultiMgrsBottomParent"] +protocol PTestMultiMgrsBottom { + manager PTestMultiMgrsLeft or PTestMultiMgrsRight; + +child: + async __delete__(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestMultiMgrsLeft.ipdl b/ipc/ipdl/test/cxx/PTestMultiMgrsLeft.ipdl new file mode 100644 index 0000000000..3796ff315e --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestMultiMgrsLeft.ipdl @@ -0,0 +1,21 @@ +include protocol PTestMultiMgrs; +include protocol PTestMultiMgrsBottom; + +include "mozilla/_ipdltest/TestMultiMgrs.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestMultiMgrsLeftChild", ParentImpl="TestMultiMgrsLeftParent"] +protocol PTestMultiMgrsLeft { + manager PTestMultiMgrs; + + manages PTestMultiMgrsBottom; + +child: + async PTestMultiMgrsBottom(); + async __delete__(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestMultiMgrsRight.ipdl b/ipc/ipdl/test/cxx/PTestMultiMgrsRight.ipdl new file mode 100644 index 0000000000..174ada5ca3 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestMultiMgrsRight.ipdl @@ -0,0 +1,21 @@ +include protocol PTestMultiMgrs; +include protocol PTestMultiMgrsBottom; + +include "mozilla/_ipdltest/TestMultiMgrs.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestMultiMgrsRightChild", ParentImpl="TestMultiMgrsRightParent"] +protocol PTestMultiMgrsRight { + manager PTestMultiMgrs; + + manages PTestMultiMgrsBottom; + +child: + async PTestMultiMgrsBottom(); + async __delete__(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestNestedLoops.ipdl b/ipc/ipdl/test/cxx/PTestNestedLoops.ipdl new file mode 100644 index 0000000000..168461ab41 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestNestedLoops.ipdl @@ -0,0 +1,22 @@ + +include "mozilla/_ipdltest/TestNestedLoops.h"; + +namespace mozilla { +namespace _ipdltest { + + +[ManualDealloc, ChildImpl="TestNestedLoopsChild", ParentImpl="TestNestedLoopsParent"] +intr protocol PTestNestedLoops { + +child: + async Start(); + [LegacyIntr] intr R(); + async __delete__(); + +parent: + async Nonce(); +}; + + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestPaintThread.ipdl b/ipc/ipdl/test/cxx/PTestPaintThread.ipdl new file mode 100644 index 0000000000..00d46c9757 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestPaintThread.ipdl @@ -0,0 +1,15 @@ +include "mozilla/_ipdltest/TestOffMainThreadPainting.h"; + +namespace mozilla { +namespace _ipdltest { + +// This is supposed to be analagous to PPaintingBridge. +[ManualDealloc, ChildImpl="TestPaintThreadChild", ParentImpl="TestPaintThreadParent"] +sync protocol PTestPaintThread +{ +parent: + sync FinishedPaint(uint64_t aTxnId); +}; + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestPriority.ipdl b/ipc/ipdl/test/cxx/PTestPriority.ipdl new file mode 100644 index 0000000000..8b85368072 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestPriority.ipdl @@ -0,0 +1,24 @@ +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual] +sync protocol PTestPriority { +parent: + [Priority=input] async PMsg1(); + [Priority=input] sync PMsg2(); + [Priority=vsync] async PMsg3(); + [Priority=vsync] sync PMsg4(); + [Priority=mediumhigh] async PMsg5(); + [Priority=mediumhigh] sync PMsg6(); + [Priority=control] async PMsg7(); + [Priority=control] sync PMsg8(); + +child: + [Priority=input] async CMsg1(); + [Priority=vsync] async CMsg2(); + [Priority=mediumhigh] async CMsg3(); + [Priority=control] async CMsg4(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestRPC.ipdl b/ipc/ipdl/test/cxx/PTestRPC.ipdl new file mode 100644 index 0000000000..4d459eeb03 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestRPC.ipdl @@ -0,0 +1,24 @@ +include "mozilla/_ipdltest/TestRPC.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, NestedUpTo=inside_sync, ChildImpl="TestRPCChild", ParentImpl="TestRPCParent"] +sync protocol PTestRPC +{ +parent: + [Nested=inside_sync] sync Test1_Start() returns (uint32_t result); + [Nested=inside_sync] sync Test1_InnerEvent() returns (uint32_t result); + async Test2_Start(); + [Nested=inside_sync] sync Test2_OutOfOrder(); + +child: + async Start(); + [Nested=inside_sync] sync Test1_InnerQuery() returns (uint32_t result); + [Nested=inside_sync] sync Test1_NoReenter() returns (uint32_t result); + [Nested=inside_sync] sync Test2_FirstUrgent(); + [Nested=inside_sync] sync Test2_SecondUrgent(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestRaceDeadlock.ipdl b/ipc/ipdl/test/cxx/PTestRaceDeadlock.ipdl new file mode 100644 index 0000000000..125919f9af --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestRaceDeadlock.ipdl @@ -0,0 +1,23 @@ +include "mozilla/_ipdltest/TestRaceDeadlock.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestRaceDeadlockChild", ParentImpl="TestRaceDeadlockParent"] +intr protocol PTestRaceDeadlock { +both: + async StartRace(); + +parent: + [LegacyIntr] intr Lose(); + +child: + [LegacyIntr] intr Win(); + [LegacyIntr] intr Rpc(); + async __delete__(); + +/* Tests that race resolution does not cause deadlocks */ +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestRaceDeferral.ipdl b/ipc/ipdl/test/cxx/PTestRaceDeferral.ipdl new file mode 100644 index 0000000000..f165fa1cea --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestRaceDeferral.ipdl @@ -0,0 +1,22 @@ +include "mozilla/_ipdltest/TestRaceDeferral.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestRaceDeferralChild", ParentImpl="TestRaceDeferralParent"] +intr protocol PTestRaceDeferral { +parent: + [LegacyIntr] intr Lose(); + +child: + async StartRace(); + [LegacyIntr] intr Win(); + [LegacyIntr] intr Rpc(); + async __delete__(); + +// Test that messages deferred due to race resolution are +// re-considered when the winner makes later RPCs +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestRacyInterruptReplies.ipdl b/ipc/ipdl/test/cxx/PTestRacyInterruptReplies.ipdl new file mode 100644 index 0000000000..37b7973e6d --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestRacyInterruptReplies.ipdl @@ -0,0 +1,20 @@ +include "mozilla/_ipdltest/TestRacyInterruptReplies.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestRacyInterruptRepliesChild", ParentImpl="TestRacyInterruptRepliesParent"] +intr protocol PTestRacyInterruptReplies { +child: + [LegacyIntr] intr R_() returns (int replyNum); + async _A(); + async ChildTest(); + async __delete__(); + +parent: + [LegacyIntr] intr _R() returns (int replyNum); + async A_(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestRacyReentry.ipdl b/ipc/ipdl/test/cxx/PTestRacyReentry.ipdl new file mode 100644 index 0000000000..b9579cef24 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestRacyReentry.ipdl @@ -0,0 +1,23 @@ +include "mozilla/_ipdltest/TestRacyReentry.h"; + +namespace mozilla { +namespace _ipdltest { + + +[ManualDealloc, ChildImpl="TestRacyReentryChild", ParentImpl="TestRacyReentryParent"] +intr protocol PTestRacyReentry { + +parent: + [LegacyIntr] intr E(); + async __delete__(); + +child: + async Start(); + + async N(); + [LegacyIntr] intr H(); +}; + + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestRacyUndefer.ipdl b/ipc/ipdl/test/cxx/PTestRacyUndefer.ipdl new file mode 100644 index 0000000000..6256eca9db --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestRacyUndefer.ipdl @@ -0,0 +1,30 @@ +include "mozilla/_ipdltest/TestRacyUndefer.h"; + +namespace mozilla { +namespace _ipdltest { + + +[ManualDealloc, ChildImpl="TestRacyUndeferChild", ParentImpl="TestRacyUndeferParent"] +intr protocol PTestRacyUndefer { + +child: + async Start(); + + async AwakenSpam(); + async AwakenRaceWinTwice(); + + [LegacyIntr] intr Race(); + + async __delete__(); + +parent: + + [LegacyIntr] intr Spam(); + [LegacyIntr] intr RaceWinTwice(); + + async Done(); +}; + + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestSanity.ipdl b/ipc/ipdl/test/cxx/PTestSanity.ipdl new file mode 100644 index 0000000000..3b3e130d4a --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestSanity.ipdl @@ -0,0 +1,21 @@ + +include "mozilla/_ipdltest/TestSanity.h"; + +namespace mozilla { +namespace _ipdltest { + + +[ManualDealloc, ChildImpl="TestSanityChild", ParentImpl="TestSanityParent"] +protocol PTestSanity { + +child: + async Ping(int zero, float zeroPtFive, int8_t dummy); + async __delete__(); + +parent: + async Pong(int one, float zeroPtTwoFive, uint8_t dummy); +}; + + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestSelfManage.ipdl b/ipc/ipdl/test/cxx/PTestSelfManage.ipdl new file mode 100644 index 0000000000..19f81a5ae7 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestSelfManage.ipdl @@ -0,0 +1,21 @@ +include protocol PTestSelfManageRoot; + +include "mozilla/_ipdltest/TestSelfManageRoot.h"; + +namespace mozilla { +namespace _ipdltest { + + +[ManualDealloc, ChildImpl="TestSelfManageChild", ParentImpl="TestSelfManageParent"] +protocol PTestSelfManage { + manager PTestSelfManageRoot or PTestSelfManage; + manages PTestSelfManage; + +child: + async PTestSelfManage(); + async __delete__(); +}; + + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestSelfManageRoot.ipdl b/ipc/ipdl/test/cxx/PTestSelfManageRoot.ipdl new file mode 100644 index 0000000000..29953228af --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestSelfManageRoot.ipdl @@ -0,0 +1,20 @@ +include protocol PTestSelfManage; + +include "mozilla/_ipdltest/TestSelfManageRoot.h"; + +namespace mozilla { +namespace _ipdltest { + + +[ManualDealloc, ChildImpl="TestSelfManageRootChild", ParentImpl="TestSelfManageRootParent"] +protocol PTestSelfManageRoot { + manages PTestSelfManage; + +child: + async PTestSelfManage(); + async __delete__(); +}; + + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestShmem.ipdl b/ipc/ipdl/test/cxx/PTestShmem.ipdl new file mode 100644 index 0000000000..7eb6dfdc36 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestShmem.ipdl @@ -0,0 +1,17 @@ +include "mozilla/_ipdltest/TestShmem.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestShmemChild", ParentImpl="TestShmemParent"] +protocol PTestShmem { +child: + async Give(Shmem mem, Shmem unsafe, uint32_t expectedSize); + +parent: + async Take(Shmem mem, Shmem unsafe, uint32_t expectedSize); + async __delete__(); +}; + +} +} diff --git a/ipc/ipdl/test/cxx/PTestShutdown.ipdl b/ipc/ipdl/test/cxx/PTestShutdown.ipdl new file mode 100644 index 0000000000..e57defa46a --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestShutdown.ipdl @@ -0,0 +1,29 @@ +include protocol PTestShutdownSub; + +include "mozilla/_ipdltest/TestShutdown.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestShutdownChild", ParentImpl="TestShutdownParent"] +intr protocol PTestShutdown { + manages PTestShutdownSub; + +child: + async Start(); + +parent: + // NB: we test deletion and crashing only, not shutdown, because + // crashing is the same code path as shutdown, and other IPDL unit + // tests check shutdown semantics + async PTestShutdownSub(bool expectCrash); + + // Used to synchronize between parent and child, to avoid races + // around flushing socket write queues + sync Sync(); + + async __delete__(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestShutdownSub.ipdl b/ipc/ipdl/test/cxx/PTestShutdownSub.ipdl new file mode 100644 index 0000000000..e91caca740 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestShutdownSub.ipdl @@ -0,0 +1,23 @@ +include protocol PTestShutdown; +include protocol PTestShutdownSubsub; + +include "mozilla/_ipdltest/TestShutdown.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestShutdownSubChild", ParentImpl="TestShutdownSubParent"] +intr protocol PTestShutdownSub { + manager PTestShutdown; + manages PTestShutdownSubsub; + +both: + [LegacyIntr] intr StackFrame(); + +parent: + async PTestShutdownSubsub(bool expectParentDeleted); + sync __delete__(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestShutdownSubsub.ipdl b/ipc/ipdl/test/cxx/PTestShutdownSubsub.ipdl new file mode 100644 index 0000000000..3b1cc5cb6b --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestShutdownSubsub.ipdl @@ -0,0 +1,17 @@ +include protocol PTestShutdownSub; + +include "mozilla/_ipdltest/TestShutdown.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestShutdownSubsubChild", ParentImpl="TestShutdownSubsubParent"] +sync protocol PTestShutdownSubsub { + manager PTestShutdownSub; + +parent: + sync __delete__(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestStackHooks.ipdl b/ipc/ipdl/test/cxx/PTestStackHooks.ipdl new file mode 100644 index 0000000000..3026dfbfbe --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestStackHooks.ipdl @@ -0,0 +1,28 @@ +include "mozilla/_ipdltest/TestStackHooks.h"; + +namespace mozilla { +namespace _ipdltest { + + +[ManualDealloc, ChildImpl="TestStackHooksChild", ParentImpl="TestStackHooksParent"] +intr protocol PTestStackHooks { +child: + async Start(); + + // These tests are more fruitful running child->parent, because + // children can send |sync| messages +parent: + async Async(); + sync Sync(); + [LegacyIntr] intr Rpc(); + +both: + [LegacyIntr] intr StackFrame(); + +parent: + async __delete__(); +}; + + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestSyncError.ipdl b/ipc/ipdl/test/cxx/PTestSyncError.ipdl new file mode 100644 index 0000000000..a4b6d428e8 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestSyncError.ipdl @@ -0,0 +1,20 @@ +include "mozilla/_ipdltest/TestSyncError.h"; + +namespace mozilla { +namespace _ipdltest { + + +[ManualDealloc, ChildImpl="TestSyncErrorChild", ParentImpl="TestSyncErrorParent"] +sync protocol PTestSyncError { + +child: + async Start(); + +parent: + sync Error(); + async __delete__(); +}; + + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestSyncHang.ipdl b/ipc/ipdl/test/cxx/PTestSyncHang.ipdl new file mode 100644 index 0000000000..05521d6ed2 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestSyncHang.ipdl @@ -0,0 +1,16 @@ +include "mozilla/_ipdltest/TestSyncHang.h"; + +namespace mozilla { +namespace _ipdltest { + + +[ManualDealloc, ChildImpl="TestSyncHangChild", ParentImpl="TestSyncHangParent"] +protocol PTestSyncHang { + +child: + async UnusedMessage(); +}; + + +} // namespace mozilla +} // namespace _ipdltest diff --git a/ipc/ipdl/test/cxx/PTestSyncWakeup.ipdl b/ipc/ipdl/test/cxx/PTestSyncWakeup.ipdl new file mode 100644 index 0000000000..807f558dfa --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestSyncWakeup.ipdl @@ -0,0 +1,23 @@ +include "mozilla/_ipdltest/TestSyncWakeup.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, ChildImpl="TestSyncWakeupChild", ParentImpl="TestSyncWakeupParent"] +intr protocol PTestSyncWakeup { +both: + [LegacyIntr] intr StackFrame(); + +child: + async Start(); + async Note1(); + async Note2(); + +parent: + sync Sync1(); + sync Sync2(); + async __delete__(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestUniquePtrIPC.ipdl b/ipc/ipdl/test/cxx/PTestUniquePtrIPC.ipdl new file mode 100644 index 0000000000..b436dcbbb3 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestUniquePtrIPC.ipdl @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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/. */ + +include "mozilla/_ipdltest/TestUniquePtrIPC.h"; + +namespace mozilla { +namespace _ipdltest { + +struct DummyStruct { + int x; +}; + +[ManualDealloc, ChildImpl="TestUniquePtrIPCChild", ParentImpl="TestUniquePtrIPCParent"] +protocol PTestUniquePtrIPC +{ +child: + async TestMessage(UniquePtr<int> a1, UniquePtr<DummyStruct> a2, + DummyStruct a3, UniquePtr<int> a4); + async TestSendReference(UniquePtr<DummyStruct> a); +}; + +} +} diff --git a/ipc/ipdl/test/cxx/PTestUrgency.ipdl b/ipc/ipdl/test/cxx/PTestUrgency.ipdl new file mode 100644 index 0000000000..7653d12102 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestUrgency.ipdl @@ -0,0 +1,22 @@ +include "mozilla/_ipdltest/TestUrgency.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, NestedUpTo=inside_cpow, ChildImpl="TestUrgencyChild", ParentImpl="TestUrgencyParent"] +sync protocol PTestUrgency +{ +parent: + [Nested=inside_sync] sync Test1() returns (uint32_t result); + async Test2(); + sync Test3() returns (uint32_t result); + sync FinalTest_Begin(); + +child: + async Start(); + [Nested=inside_sync] sync Reply1() returns (uint32_t result); + [Nested=inside_sync] sync Reply2() returns (uint32_t result); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/PTestUrgentHangs.ipdl b/ipc/ipdl/test/cxx/PTestUrgentHangs.ipdl new file mode 100644 index 0000000000..2c19a7afe1 --- /dev/null +++ b/ipc/ipdl/test/cxx/PTestUrgentHangs.ipdl @@ -0,0 +1,31 @@ +include "mozilla/_ipdltest/TestUrgentHangs.h"; + +namespace mozilla { +namespace _ipdltest { + +[ManualDealloc, NestedUpTo=inside_sync, ChildImpl="TestUrgentHangsChild", ParentImpl="TestUrgentHangsParent"] +sync protocol PTestUrgentHangs +{ +parent: + [Nested=inside_sync] sync Test1_2(); + + [Nested=inside_sync] sync TestInner(); + [Nested=inside_cpow] sync TestInnerUrgent(); + +child: + [Nested=inside_sync] sync Test1_1(); + [Nested=inside_sync] sync Test1_3(); + + [Nested=inside_sync] sync Test2(); + + [Nested=inside_sync] sync Test3(); + + async Test4(); + [Nested=inside_sync] sync Test4_1(); + + async Test5(); + [Nested=inside_sync] sync Test5_1(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/README.txt b/ipc/ipdl/test/cxx/README.txt new file mode 100644 index 0000000000..0fe6c07320 --- /dev/null +++ b/ipc/ipdl/test/cxx/README.txt @@ -0,0 +1,61 @@ +To add a new IPDL C++ unit test, you need to create (at least) the +following files (for a test "TestFoo"): + + - PTestFoo.ipdl, specifying the top-level protocol used for the test + + - TestFoo.h, declaring the top-level parent/child actors used for + the test + + - TestFoo.cpp, defining the top-level actors + + - (make sure all are in the namespace mozilla::_ipdltest) + +Next + + - add PTestFoo.ipdl to ipdl.mk + + - append TestFoo to the variable IPDLTESTS in Makefile.in + +You must define three methods in your |TestFooParent| class: + + - static methods |bool RunTestInProcesses()| and + |bool RunTestInThreads()|. These methods control whether + to execute the test using actors in separate processes and + threads respectively. Generally, both should return true. + + - an instance method |void Main()|. The test harness wil first + initialize the processes or threads, create and open both actors, + and then kick off the test using |Main()|. Make sure you define + it. + +If your test passes its criteria, please call +|MOZ_IPDL_TESTPASS("msg")| and "exit gracefully". + +If your tests fails, please call |MOZ_IPDL_TESTFAIL("msg")| and "exit +ungracefully", preferably by abort()ing. + + +If all goes well, running + + make -C $OBJDIR/ipc/ipdl/test/cxx + +will update the file IPDLUnitTests.cpp (the test launcher), and your +new code will be built automatically. + + +You can launch your new test by invoking one of + + make -C $OBJDIR/ipc/ipdl/test/cxx check-proc (test process-based tests) + make -C $OBJDIR/ipc/ipdl/test/cxx check-threads (test thread-based tests) + make -C $OBJDIR/ipc/ipdl/test/cxx check (tests both) + +If you want to launch only your test, run + + cd $OBJDIR/dist/bin + ./run-mozilla.sh ./ipdlunittest TestFoo (test in two processes, if appl.) + ./run-mozilla.sh ./ipdlunittest thread:TestFoo (test in two threads, if appl.) + + +For a bare-bones example of adding a test, take a look at +PTestSanity.ipdl, TestSanity.h, TestSanity.cpp, and how "TestSanity" +is included in ipdl.mk and Makefile.in. diff --git a/ipc/ipdl/test/cxx/TestActorPunning.cpp b/ipc/ipdl/test/cxx/TestActorPunning.cpp new file mode 100644 index 0000000000..5825796f54 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestActorPunning.cpp @@ -0,0 +1,125 @@ +#include "TestActorPunning.h" + +#include "IPDLUnitTests.h" // fail etc. +#include "mozilla/Unused.h" + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +void TestActorPunningParent::Main() { + if (!SendStart()) fail("sending Start"); +} + +mozilla::ipc::IPCResult TestActorPunningParent::RecvPun( + PTestActorPunningSubParent* a, const Bad& bad) { + if (a->SendBad()) fail("bad!"); + fail("shouldn't have received this message in the first place"); + return IPC_OK(); +} + +// By default, fatal errors kill the parent process, but this makes it +// hard to test, so instead we use the previous behavior and kill the +// child process. +void TestActorPunningParent::HandleFatalError(const char* aErrorMsg) { + if (!!strcmp(aErrorMsg, "Error deserializing 'PTestActorPunningSubParent'")) { + fail("wrong fatal error"); + } + + ipc::ScopedProcessHandle otherProcessHandle; + if (!base::OpenProcessHandle(OtherPid(), &otherProcessHandle.rwget())) { + fail("couldn't open child process"); + } else { + if (!base::KillProcess(otherProcessHandle, 0, false)) { + fail("terminating child process"); + } + } +} + +PTestActorPunningPunnedParent* +TestActorPunningParent::AllocPTestActorPunningPunnedParent() { + return new TestActorPunningPunnedParent(); +} + +bool TestActorPunningParent::DeallocPTestActorPunningPunnedParent( + PTestActorPunningPunnedParent* a) { + delete a; + return true; +} + +PTestActorPunningSubParent* +TestActorPunningParent::AllocPTestActorPunningSubParent() { + return new TestActorPunningSubParent(); +} + +bool TestActorPunningParent::DeallocPTestActorPunningSubParent( + PTestActorPunningSubParent* a) { + delete a; + return true; +} + +//----------------------------------------------------------------------------- +// child + +PTestActorPunningPunnedChild* +TestActorPunningChild::AllocPTestActorPunningPunnedChild() { + return new TestActorPunningPunnedChild(); +} + +bool TestActorPunningChild::DeallocPTestActorPunningPunnedChild( + PTestActorPunningPunnedChild*) { + fail("should have died by now"); + return true; +} + +PTestActorPunningSubChild* +TestActorPunningChild::AllocPTestActorPunningSubChild() { + return new TestActorPunningSubChild(); +} + +bool TestActorPunningChild::DeallocPTestActorPunningSubChild( + PTestActorPunningSubChild*) { + fail("should have died by now"); + return true; +} + +mozilla::ipc::IPCResult TestActorPunningChild::RecvStart() { + SendPTestActorPunningSubConstructor(); + SendPTestActorPunningPunnedConstructor(); + PTestActorPunningSubChild* a = SendPTestActorPunningSubConstructor(); + // We can't assert whether this succeeds or fails, due to race + // conditions. + SendPun(a, Bad()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestActorPunningSubChild::RecvBad() { + fail("things are going really badly right now"); + return IPC_OK(); +} + +} // namespace _ipdltest +} // namespace mozilla + +namespace IPC { +using namespace mozilla::_ipdltest; +using namespace mozilla::ipc; + +/*static*/ void ParamTraits<Bad>::Write(MessageWriter* aWriter, + const paramType& aParam) { + // Skip past the sentinel for the actor as well as the actor. + int32_t* ptr = aWriter->GetInt32PtrForTest(2 * sizeof(int32_t)); + ActorHandle* ah = reinterpret_cast<ActorHandle*>(ptr); + if (ah->mId != -3) + fail("guessed wrong offset (value is %d, should be -3)", ah->mId); + ah->mId = -2; +} + +/*static*/ bool ParamTraits<Bad>::Read(MessageReader* aReader, + paramType* aResult) { + return true; +} + +} // namespace IPC diff --git a/ipc/ipdl/test/cxx/TestActorPunning.h b/ipc/ipdl/test/cxx/TestActorPunning.h new file mode 100644 index 0000000000..cd88106c0c --- /dev/null +++ b/ipc/ipdl/test/cxx/TestActorPunning.h @@ -0,0 +1,100 @@ +#ifndef mozilla__ipdltest_TestActorPunning_h +#define mozilla__ipdltest_TestActorPunning_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestActorPunningParent.h" +#include "mozilla/_ipdltest/PTestActorPunningPunnedParent.h" +#include "mozilla/_ipdltest/PTestActorPunningSubParent.h" +#include "mozilla/_ipdltest/PTestActorPunningChild.h" +#include "mozilla/_ipdltest/PTestActorPunningPunnedChild.h" +#include "mozilla/_ipdltest/PTestActorPunningSubChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestActorPunningParent : public PTestActorPunningParent { + friend class PTestActorPunningParent; + + public: + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return false; } + + void Main(); + + protected: + PTestActorPunningPunnedParent* AllocPTestActorPunningPunnedParent(); + bool DeallocPTestActorPunningPunnedParent(PTestActorPunningPunnedParent* a); + + PTestActorPunningSubParent* AllocPTestActorPunningSubParent(); + bool DeallocPTestActorPunningSubParent(PTestActorPunningSubParent* a); + + mozilla::ipc::IPCResult RecvPun(PTestActorPunningSubParent* a, + const Bad& bad); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown == why) fail("should have died from error!"); + passed("ok"); + QuitParent(); + } + + virtual void HandleFatalError(const char* aErrorMsg) override; +}; + +class TestActorPunningPunnedParent : public PTestActorPunningPunnedParent { + public: + TestActorPunningPunnedParent() {} + virtual ~TestActorPunningPunnedParent() {} + + protected: + virtual void ActorDestroy(ActorDestroyReason why) override {} +}; + +class TestActorPunningSubParent : public PTestActorPunningSubParent { + public: + TestActorPunningSubParent() {} + virtual ~TestActorPunningSubParent() {} + + protected: + virtual void ActorDestroy(ActorDestroyReason why) override {} +}; + +class TestActorPunningChild : public PTestActorPunningChild { + friend class PTestActorPunningChild; + + public: + TestActorPunningChild() {} + virtual ~TestActorPunningChild() {} + + protected: + PTestActorPunningPunnedChild* AllocPTestActorPunningPunnedChild(); + bool DeallocPTestActorPunningPunnedChild(PTestActorPunningPunnedChild* a); + + PTestActorPunningSubChild* AllocPTestActorPunningSubChild(); + bool DeallocPTestActorPunningSubChild(PTestActorPunningSubChild* a); + + mozilla::ipc::IPCResult RecvStart(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + fail("should have been killed off!"); + } +}; + +class TestActorPunningPunnedChild : public PTestActorPunningPunnedChild { + public: + TestActorPunningPunnedChild() {} + virtual ~TestActorPunningPunnedChild() {} +}; + +class TestActorPunningSubChild : public PTestActorPunningSubChild { + public: + TestActorPunningSubChild() {} + virtual ~TestActorPunningSubChild() {} + + mozilla::ipc::IPCResult RecvBad(); +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestActorPunning_h diff --git a/ipc/ipdl/test/cxx/TestAsyncReturns.cpp b/ipc/ipdl/test/cxx/TestAsyncReturns.cpp new file mode 100644 index 0000000000..76597143c0 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestAsyncReturns.cpp @@ -0,0 +1,101 @@ +#include "TestAsyncReturns.h" + +#include "IPDLUnitTests.h" // fail etc. + +#include "mozilla/AbstractThread.h" +#include "mozilla/Unused.h" + +namespace mozilla { +namespace _ipdltest { + +static uint32_t sMagic1 = 0x105b59fb; +static uint32_t sMagic2 = 0x09b6f5e3; + +//----------------------------------------------------------------------------- +// parent + +TestAsyncReturnsParent::TestAsyncReturnsParent() { + MOZ_COUNT_CTOR(TestAsyncReturnsParent); +} + +TestAsyncReturnsParent::~TestAsyncReturnsParent() { + MOZ_COUNT_DTOR(TestAsyncReturnsParent); +} + +void TestAsyncReturnsParent::Main() { + SendNoReturn()->Then( + MessageLoop::current()->SerialEventTarget(), __func__, + [](bool unused) { fail("resolve handler should not be called"); }, + [](ResponseRejectReason&& aReason) { + // MozPromise asserts in debug build if the + // handler is not called + if (aReason != ResponseRejectReason::ChannelClosed) { + fail("reject with wrong reason"); + } + passed("reject handler called on channel close"); + }); + SendPing()->Then( + MessageLoop::current()->SerialEventTarget(), __func__, + [this](bool one) { + if (one) { + passed("take one argument"); + } else { + fail("get one argument but has wrong value"); + } + + // Also try with the callback-based API. + SendPing( + [this](bool one) { + if (one) { + passed("take one argument"); + } else { + fail("get one argument but has wrong value"); + } + Close(); + }, + [](ResponseRejectReason&& aReason) { fail("sending Ping"); }); + }, + [](ResponseRejectReason&& aReason) { fail("sending Ping"); }); +} + +mozilla::ipc::IPCResult TestAsyncReturnsParent::RecvPong( + PongResolver&& aResolve) { + aResolve(std::tuple<const uint32_t&, const uint32_t&>(sMagic1, sMagic2)); + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// child + +TestAsyncReturnsChild::TestAsyncReturnsChild() { + MOZ_COUNT_CTOR(TestAsyncReturnsChild); +} + +TestAsyncReturnsChild::~TestAsyncReturnsChild() { + MOZ_COUNT_DTOR(TestAsyncReturnsChild); +} + +mozilla::ipc::IPCResult TestAsyncReturnsChild::RecvNoReturn( + NoReturnResolver&& aResolve) { + // Not resolving the promise intentionally + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestAsyncReturnsChild::RecvPing( + PingResolver&& aResolve) { + SendPong()->Then( + MessageLoop::current()->SerialEventTarget(), __func__, + [aResolve](const std::tuple<uint32_t, uint32_t>& aParam) { + if (std::get<0>(aParam) == sMagic1 && std::get<1>(aParam) == sMagic2) { + passed("take two arguments"); + } else { + fail("get two argument but has wrong value"); + } + aResolve(true); + }, + [](ResponseRejectReason&& aReason) { fail("sending Pong"); }); + return IPC_OK(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestAsyncReturns.h b/ipc/ipdl/test/cxx/TestAsyncReturns.h new file mode 100644 index 0000000000..5dad3da0ab --- /dev/null +++ b/ipc/ipdl/test/cxx/TestAsyncReturns.h @@ -0,0 +1,54 @@ +#ifndef mozilla__ipdltest_TestAsyncReturns_h +#define mozilla__ipdltest_TestAsyncReturns_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestAsyncReturnsParent.h" +#include "mozilla/_ipdltest/PTestAsyncReturnsChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestAsyncReturnsParent : public PTestAsyncReturnsParent { + friend class PTestAsyncReturnsParent; + + public: + TestAsyncReturnsParent(); + virtual ~TestAsyncReturnsParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + mozilla::ipc::IPCResult RecvPong(PongResolver&& aResolve); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } +}; + +class TestAsyncReturnsChild : public PTestAsyncReturnsChild { + friend class PTestAsyncReturnsChild; + + public: + TestAsyncReturnsChild(); + virtual ~TestAsyncReturnsChild(); + + protected: + mozilla::ipc::IPCResult RecvPing(PingResolver&& aResolve); + mozilla::ipc::IPCResult RecvNoReturn(NoReturnResolver&& aResolve); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestAsyncReturns_h diff --git a/ipc/ipdl/test/cxx/TestBadActor.cpp b/ipc/ipdl/test/cxx/TestBadActor.cpp new file mode 100644 index 0000000000..6c6991dbb2 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestBadActor.cpp @@ -0,0 +1,59 @@ +#include "TestBadActor.h" +#include "IPDLUnitTests.h" +#include "mozilla/Unused.h" + +namespace mozilla { +namespace _ipdltest { + +void TestBadActorParent::Main() { + // This test is designed to test a race condition where the child sends us + // a message on an actor that we've already destroyed. The child process + // should die, and the parent process should not abort. + + PTestBadActorSubParent* child = SendPTestBadActorSubConstructor(); + if (!child) fail("Sending constructor"); + + Unused << child->Call__delete__(child); +} + +// By default, fatal errors kill the parent process, but this makes it +// hard to test, so instead we use the previous behavior and kill the +// child process. +void TestBadActorParent::HandleFatalError(const char* aErrorMsg) { + if (!!strcmp(aErrorMsg, "incoming message racing with actor deletion")) { + fail("wrong fatal error"); + } + + ipc::ScopedProcessHandle otherProcessHandle; + if (!base::OpenProcessHandle(OtherPid(), &otherProcessHandle.rwget())) { + fail("couldn't open child process"); + } else { + if (!base::KillProcess(otherProcessHandle, 0, false)) { + fail("terminating child process"); + } + } +} + +PTestBadActorSubParent* TestBadActorParent::AllocPTestBadActorSubParent() { + return new TestBadActorSubParent(); +} + +mozilla::ipc::IPCResult TestBadActorSubParent::RecvPing() { + fail("Shouldn't have received ping."); + return IPC_FAIL_NO_REASON(this); +} + +PTestBadActorSubChild* TestBadActorChild::AllocPTestBadActorSubChild() { + return new TestBadActorSubChild(); +} + +mozilla::ipc::IPCResult TestBadActorChild::RecvPTestBadActorSubConstructor( + PTestBadActorSubChild* actor) { + if (!actor->SendPing()) { + fail("Couldn't send ping to an actor which supposedly isn't dead yet."); + } + return IPC_OK(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestBadActor.h b/ipc/ipdl/test/cxx/TestBadActor.h new file mode 100644 index 0000000000..cefe0288f4 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestBadActor.h @@ -0,0 +1,84 @@ +#ifndef mozilla__ipdltest_TestBadActor_h +#define mozilla__ipdltest_TestBadActor_h + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestBadActorParent.h" +#include "mozilla/_ipdltest/PTestBadActorChild.h" + +#include "mozilla/_ipdltest/PTestBadActorSubParent.h" +#include "mozilla/_ipdltest/PTestBadActorSubChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestBadActorParent : public PTestBadActorParent { + friend class PTestBadActorParent; + + public: + TestBadActorParent() {} + virtual ~TestBadActorParent() {} + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return false; } + + void Main(); + + protected: + virtual void ActorDestroy(ActorDestroyReason why) override { + if (AbnormalShutdown != why) fail("unexpected destruction"); + passed("ok"); + QuitParent(); + } + + virtual void HandleFatalError(const char* aErrorMsg) override; + + PTestBadActorSubParent* AllocPTestBadActorSubParent(); + + bool DeallocPTestBadActorSubParent(PTestBadActorSubParent* actor) { + delete actor; + return true; + } +}; + +class TestBadActorSubParent : public PTestBadActorSubParent { + friend class PTestBadActorSubParent; + + public: + TestBadActorSubParent() {} + virtual ~TestBadActorSubParent() {} + + protected: + virtual void ActorDestroy(ActorDestroyReason why) override {} + mozilla::ipc::IPCResult RecvPing(); +}; + +class TestBadActorChild : public PTestBadActorChild { + friend class PTestBadActorChild; + + public: + TestBadActorChild() {} + virtual ~TestBadActorChild() {} + + protected: + virtual PTestBadActorSubChild* AllocPTestBadActorSubChild(); + + virtual bool DeallocPTestBadActorSubChild(PTestBadActorSubChild* actor) { + delete actor; + return true; + } + + virtual mozilla::ipc::IPCResult RecvPTestBadActorSubConstructor( + PTestBadActorSubChild* actor); +}; + +class TestBadActorSubChild : public PTestBadActorSubChild { + public: + TestBadActorSubChild() {} + virtual ~TestBadActorSubChild() {} +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // mozilla__ipdltest_TestBadActor_h diff --git a/ipc/ipdl/test/cxx/TestCancel.cpp b/ipc/ipdl/test/cxx/TestCancel.cpp new file mode 100644 index 0000000000..2f97aa2e93 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestCancel.cpp @@ -0,0 +1,115 @@ +#include "TestCancel.h" + +#include "IPDLUnitTests.h" // fail etc. + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +TestCancelParent::TestCancelParent() { MOZ_COUNT_CTOR(TestCancelParent); } + +TestCancelParent::~TestCancelParent() { MOZ_COUNT_DTOR(TestCancelParent); } + +void TestCancelParent::Main() { + if (SendTest1_1()) fail("sending Test1_1"); + + uint32_t value = 0; + if (!SendCheckChild(&value)) fail("Test1 CheckChild"); + + if (value != 12) fail("Test1 CheckChild reply"); +} + +mozilla::ipc::IPCResult TestCancelParent::RecvDone1() { + if (!SendStart2()) fail("sending Start2"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestCancelParent::RecvTest2_1() { + if (SendTest2_2()) fail("sending Test2_2"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestCancelParent::RecvStart3() { + if (SendTest3_1()) fail("sending Test3_1"); + + uint32_t value = 0; + if (!SendCheckChild(&value)) fail("Test1 CheckChild"); + + if (value != 12) fail("Test1 CheckChild reply"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestCancelParent::RecvTest3_2() { + GetIPCChannel()->CancelCurrentTransaction(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestCancelParent::RecvDone() { + MessageLoop::current()->PostTask(NewNonOwningRunnableMethod( + "ipc::IToplevelProtocol::Close", this, &TestCancelParent::Close)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestCancelParent::RecvCheckParent(uint32_t* reply) { + *reply = 12; + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// child + +mozilla::ipc::IPCResult TestCancelChild::RecvTest1_1() { + GetIPCChannel()->CancelCurrentTransaction(); + + uint32_t value = 0; + if (!SendCheckParent(&value)) fail("Test1 CheckParent"); + + if (value != 12) fail("Test1 CheckParent reply"); + + if (!SendDone1()) fail("Test1 CheckParent"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestCancelChild::RecvStart2() { + if (!SendTest2_1()) fail("sending Test2_1"); + + if (!SendStart3()) fail("sending Start3"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestCancelChild::RecvTest2_2() { + GetIPCChannel()->CancelCurrentTransaction(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestCancelChild::RecvTest3_1() { + if (SendTest3_2()) fail("sending Test3_2"); + + uint32_t value = 0; + if (!SendCheckParent(&value)) fail("Test1 CheckParent"); + + if (value != 12) fail("Test1 CheckParent reply"); + + if (!SendDone()) fail("sending Done"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestCancelChild::RecvCheckChild(uint32_t* reply) { + *reply = 12; + return IPC_OK(); +} + +TestCancelChild::TestCancelChild() { MOZ_COUNT_CTOR(TestCancelChild); } + +TestCancelChild::~TestCancelChild() { MOZ_COUNT_DTOR(TestCancelChild); } + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestCancel.h b/ipc/ipdl/test/cxx/TestCancel.h new file mode 100644 index 0000000000..7d944d6a69 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestCancel.h @@ -0,0 +1,54 @@ +#ifndef mozilla__ipdltest_TestCancel_h +#define mozilla__ipdltest_TestCancel_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestCancelParent.h" +#include "mozilla/_ipdltest/PTestCancelChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestCancelParent : public PTestCancelParent { + public: + TestCancelParent(); + virtual ~TestCancelParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return false; } + + void Main(); + + mozilla::ipc::IPCResult RecvDone1(); + mozilla::ipc::IPCResult RecvTest2_1(); + mozilla::ipc::IPCResult RecvStart3(); + mozilla::ipc::IPCResult RecvTest3_2(); + mozilla::ipc::IPCResult RecvDone(); + + mozilla::ipc::IPCResult RecvCheckParent(uint32_t* reply); + + virtual void ActorDestroy(ActorDestroyReason why) override { + passed("ok"); + QuitParent(); + } +}; + +class TestCancelChild : public PTestCancelChild { + public: + TestCancelChild(); + virtual ~TestCancelChild(); + + mozilla::ipc::IPCResult RecvTest1_1(); + mozilla::ipc::IPCResult RecvStart2(); + mozilla::ipc::IPCResult RecvTest2_2(); + mozilla::ipc::IPCResult RecvTest3_1(); + + mozilla::ipc::IPCResult RecvCheckChild(uint32_t* reply); + + virtual void ActorDestroy(ActorDestroyReason why) override { QuitChild(); } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestCancel_h diff --git a/ipc/ipdl/test/cxx/TestCrashCleanup.cpp b/ipc/ipdl/test/cxx/TestCrashCleanup.cpp new file mode 100644 index 0000000000..6a19cc0f65 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestCrashCleanup.cpp @@ -0,0 +1,100 @@ +#include "TestCrashCleanup.h" + +#include "base/task.h" +#include "mozilla/CondVar.h" +#include "mozilla/Mutex.h" + +#include "IPDLUnitTests.h" // fail etc. +#include "IPDLUnitTestSubprocess.h" + +using mozilla::CondVar; +using mozilla::Mutex; +using mozilla::MutexAutoLock; + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +namespace { + +// NB: this test does its own shutdown, rather than going through +// QuitParent(), because it's testing degenerate edge cases + +void DeleteSubprocess(Mutex* mutex, CondVar* cvar) { + MutexAutoLock lock(*mutex); + + gSubprocess->Destroy(); + gSubprocess = nullptr; + + cvar->Notify(); +} + +void DeleteTheWorld() { + delete static_cast<TestCrashCleanupParent*>(gParentActor); + gParentActor = nullptr; + + // needs to be synchronous to avoid affecting event ordering on + // the main thread + Mutex mutex MOZ_UNANNOTATED("TestCrashCleanup.DeleteTheWorld.mutex"); + CondVar cvar(mutex, "TestCrashCleanup.DeleteTheWorld.cvar"); + + MutexAutoLock lock(mutex); + + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction("DeleteSubprocess", DeleteSubprocess, &mutex, &cvar)); + + cvar.Wait(); +} + +void Done() { + static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); + nsCOMPtr<nsIAppShell> appShell(do_GetService(kAppShellCID)); + appShell->Exit(); + + passed(__FILE__); +} + +} // namespace + +TestCrashCleanupParent::TestCrashCleanupParent() : mCleanedUp(false) { + MOZ_COUNT_CTOR(TestCrashCleanupParent); +} + +TestCrashCleanupParent::~TestCrashCleanupParent() { + MOZ_COUNT_DTOR(TestCrashCleanupParent); + + if (!mCleanedUp) fail("should have been ActorDestroy()d!"); +} + +void TestCrashCleanupParent::Main() { + // NB: has to be enqueued before IO thread's error notification + MessageLoop::current()->PostTask( + NewRunnableFunction("DeleteTheWorld", DeleteTheWorld)); + + if (CallDIEDIEDIE()) fail("expected an error!"); + + Close(); + + MessageLoop::current()->PostTask(NewRunnableFunction("Done", Done)); +} + +//----------------------------------------------------------------------------- +// child + +TestCrashCleanupChild::TestCrashCleanupChild() { + MOZ_COUNT_CTOR(TestCrashCleanupChild); +} + +TestCrashCleanupChild::~TestCrashCleanupChild() { + MOZ_COUNT_DTOR(TestCrashCleanupChild); +} + +mozilla::ipc::IPCResult TestCrashCleanupChild::AnswerDIEDIEDIE() { + _exit(0); + MOZ_CRASH("unreached"); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestCrashCleanup.h b/ipc/ipdl/test/cxx/TestCrashCleanup.h new file mode 100644 index 0000000000..30371264e6 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestCrashCleanup.h @@ -0,0 +1,49 @@ +#ifndef mozilla__ipdltest_TestCrashCleanup_h +#define mozilla__ipdltest_TestCrashCleanup_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestCrashCleanupParent.h" +#include "mozilla/_ipdltest/PTestCrashCleanupChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestCrashCleanupParent : public PTestCrashCleanupParent { + public: + TestCrashCleanupParent(); + virtual ~TestCrashCleanupParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return false; } + + void Main(); + + protected: + virtual void ActorDestroy(ActorDestroyReason why) override { + if (AbnormalShutdown != why) fail("unexpected destruction!"); + mCleanedUp = true; + } + + bool mCleanedUp; +}; + +class TestCrashCleanupChild : public PTestCrashCleanupChild { + friend class PTestCrashCleanupChild; + + public: + TestCrashCleanupChild(); + virtual ~TestCrashCleanupChild(); + + protected: + mozilla::ipc::IPCResult AnswerDIEDIEDIE(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + fail("should have 'crashed'!"); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestCrashCleanup_h diff --git a/ipc/ipdl/test/cxx/TestDataStructures.cpp b/ipc/ipdl/test/cxx/TestDataStructures.cpp new file mode 100644 index 0000000000..5b1cabfe7d --- /dev/null +++ b/ipc/ipdl/test/cxx/TestDataStructures.cpp @@ -0,0 +1,888 @@ +#include "TestDataStructures.h" + +#include "mozilla/Unused.h" + +#include "IPDLUnitTests.h" // fail etc. + +typedef nsTArray<nsIntRegion> RegionArray; + +namespace mozilla { +namespace _ipdltest { + +static const uint32_t nactors = 10; + +#define test_assert(_cond, _msg) \ + if (!(_cond)) fail(_msg) + +template <typename T> +static void assert_arrays_equal(const nsTArray<T>& a, const nsTArray<T>& b) { + test_assert(a == b, "arrays equal"); +} + +inline static TestDataStructuresSub& Cast(PTestDataStructuresSubParent* a) { + return *static_cast<TestDataStructuresSub*>(a); +} + +//----------------------------------------------------------------------------- +// parent + +TestDataStructuresParent::TestDataStructuresParent() { + MOZ_COUNT_CTOR(TestDataStructuresParent); +} + +TestDataStructuresParent::~TestDataStructuresParent() { + MOZ_COUNT_DTOR(TestDataStructuresParent); +} + +void TestDataStructuresParent::Main() { + for (uint32_t i = 0; i < nactors; ++i) + if (!SendPTestDataStructuresSubConstructor(i)) fail("can't alloc actor"); + + if (!SendStart()) fail("can't send Start()"); +} + +bool TestDataStructuresParent::DeallocPTestDataStructuresSubParent( + PTestDataStructuresSubParent* actor) { + test_assert(Cast(actor).mI == Cast(mKids[0]).mI, "dtor sent to wrong actor"); + mKids.RemoveElementAt(0); + delete actor; + if (mKids.Length() > 0) return true; + + return true; +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest1(nsTArray<int>&& ia, + nsTArray<int>* oa) { + test_assert(5 == ia.Length(), "wrong length"); + for (int i = 0; i < 5; ++i) test_assert(i == ia[i], "wrong value"); + + *oa = ia; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest2( + nsTArray<PTestDataStructuresSubParent*>&& i1, + nsTArray<PTestDataStructuresSubParent*>* o1) { + test_assert(nactors == i1.Length(), "wrong #actors"); + for (uint32_t i = 0; i < i1.Length(); ++i) + test_assert(i == Cast(i1[i]).mI, "wrong mI value"); + *o1 = i1; + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest3(const IntDouble& i1, + const IntDouble& i2, + IntDouble* o1, + IntDouble* o2) { + test_assert(42 == i1.get_int(), "wrong value"); + test_assert(4.0 == i2.get_double(), "wrong value"); + + *o1 = i1; + *o2 = i2; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest4( + nsTArray<IntDouble>&& i1, nsTArray<IntDouble>* o1) { + test_assert(4 == i1.Length(), "wrong length"); + test_assert(1 == i1[0].get_int(), "wrong value"); + test_assert(2.0 == i1[1].get_double(), "wrong value"); + test_assert(3 == i1[2].get_int(), "wrong value"); + test_assert(4.0 == i1[3].get_double(), "wrong value"); + + *o1 = i1; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest5( + const IntDoubleArrays& i1, const IntDoubleArrays& i2, + const IntDoubleArrays& i3, IntDoubleArrays* o1, IntDoubleArrays* o2, + IntDoubleArrays* o3) { + test_assert(42 == i1.get_int(), "wrong value"); + + const nsTArray<int>& i2a = i2.get_ArrayOfint(); + test_assert(3 == i2a.Length(), "wrong length"); + test_assert(1 == i2a[0], "wrong value"); + test_assert(2 == i2a[1], "wrong value"); + test_assert(3 == i2a[2], "wrong value"); + + const nsTArray<double>& i3a = i3.get_ArrayOfdouble(); + test_assert(3 == i3a.Length(), "wrong length"); + test_assert(1.0 == i3a[0], "wrong value"); + test_assert(2.0 == i3a[1], "wrong value"); + test_assert(3.0 == i3a[2], "wrong value"); + + *o1 = i1; + *o2 = i2a; + *o3 = i3a; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest7_0( + const ActorWrapper& i1, ActorWrapper* o1) { + if (i1.actorChild() != nullptr) + fail("child side actor should always be null"); + + if (i1.actorParent() != mKids[0]) + fail("should have got back same actor on parent side"); + + o1->actorParent() = mKids[0]; + // malicious behavior + o1->actorChild() = + reinterpret_cast<PTestDataStructuresSubChild*>(uintptr_t(0xdeadbeef)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest6( + nsTArray<IntDoubleArrays>&& i1, nsTArray<IntDoubleArrays>* o1) { + test_assert(3 == i1.Length(), "wrong length"); + + IntDoubleArrays id1(i1[0]); + test_assert(42 == id1.get_int(), "wrong value"); + + nsTArray<int> i2a(i1[1].get_ArrayOfint()); + test_assert(3 == i2a.Length(), "wrong length"); + test_assert(1 == i2a[0], "wrong value"); + test_assert(2 == i2a[1], "wrong value"); + test_assert(3 == i2a[2], "wrong value"); + + nsTArray<double> i3a(i1[2].get_ArrayOfdouble()); + test_assert(3 == i3a.Length(), "wrong length"); + test_assert(1.0 == i3a[0], "wrong value"); + test_assert(2.0 == i3a[1], "wrong value"); + test_assert(3.0 == i3a[2], "wrong value"); + + o1->AppendElement(id1); + o1->AppendElement(IntDoubleArrays(i2a)); + o1->AppendElement(IntDoubleArrays(i3a)); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest7( + const Actors& i1, const Actors& i2, const Actors& i3, Actors* o1, + Actors* o2, Actors* o3) { + test_assert(42 == i1.get_int(), "wrong value"); + + nsTArray<int> i2a(i2.get_ArrayOfint()); + test_assert(3 == i2a.Length(), "wrong length"); + test_assert(1 == i2a[0], "wrong value"); + test_assert(2 == i2a[1], "wrong value"); + test_assert(3 == i2a[2], "wrong value"); + + assert_arrays_equal(mKids, i3.get_ArrayOfPTestDataStructuresSubParent()); + + *o1 = 42; + *o2 = i2a; + *o3 = mKids; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest8( + nsTArray<Actors>&& i1, nsTArray<Actors>* o1) { + test_assert(3 == i1.Length(), "wrong length"); + test_assert(42 == i1[0].get_int(), "wrong value"); + + const nsTArray<int>& i2a = i1[1].get_ArrayOfint(); + test_assert(3 == i2a.Length(), "wrong length"); + test_assert(1 == i2a[0], "wrong value"); + test_assert(2 == i2a[1], "wrong value"); + test_assert(3 == i2a[2], "wrong value"); + + assert_arrays_equal(mKids, i1[2].get_ArrayOfPTestDataStructuresSubParent()); + + *o1 = i1; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest9( + const Unions& i1, const Unions& i2, const Unions& i3, const Unions& i4, + Unions* o1, Unions* o2, Unions* o3, Unions* o4) { + test_assert(42 == i1.get_int(), "wrong value"); + + const nsTArray<int>& i2a = i2.get_ArrayOfint(); + test_assert(3 == i2a.Length(), "wrong length"); + test_assert(1 == i2a[0], "wrong value"); + test_assert(2 == i2a[1], "wrong value"); + test_assert(3 == i2a[2], "wrong value"); + + assert_arrays_equal(mKids, i3.get_ArrayOfPTestDataStructuresSubParent()); + + const nsTArray<PTestDataStructuresSubParent*>& i4a = + i4.get_ArrayOfActors()[0].get_ArrayOfPTestDataStructuresSubParent(); + assert_arrays_equal(mKids, i4a); + + *o1 = i1; + *o2 = i2; + *o3 = i3; + *o4 = i4; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest10( + nsTArray<Unions>&& i1, nsTArray<Unions>* o1) { + test_assert(42 == i1[0].get_int(), "wrong value"); + + const nsTArray<int>& i2a = i1[1].get_ArrayOfint(); + test_assert(3 == i2a.Length(), "wrong length"); + test_assert(1 == i2a[0], "wrong value"); + test_assert(2 == i2a[1], "wrong value"); + test_assert(3 == i2a[2], "wrong value"); + + assert_arrays_equal(mKids, i1[2].get_ArrayOfPTestDataStructuresSubParent()); + + const nsTArray<PTestDataStructuresSubParent*>& i4a = + i1[3].get_ArrayOfActors()[0].get_ArrayOfPTestDataStructuresSubParent(); + assert_arrays_equal(mKids, i4a); + + *o1 = i1; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest11( + const SIntDouble& i, SIntDouble* o) { + test_assert(1 == i.i(), "wrong value"); + test_assert(2.0 == i.d(), "wrong value"); + *o = i; + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest12( + const SIntDoubleArrays& i, SIntDoubleArrays* o) { + nsTArray<int> ai; + ai.AppendElement(1); + ai.AppendElement(2); + ai.AppendElement(3); + + nsTArray<double> ad; + ad.AppendElement(.5); + ad.AppendElement(1.0); + ad.AppendElement(2.0); + + test_assert(42 == i.i(), "wrong value"); + assert_arrays_equal(ai, i.ai()); + assert_arrays_equal(ad, i.ad()); + + *o = i; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest13(const SActors& i, + SActors* o) { + nsTArray<int> ai; + ai.AppendElement(1); + ai.AppendElement(2); + ai.AppendElement(3); + + test_assert(42 == i.i(), "wrong value"); + assert_arrays_equal(ai, i.ai()); + assert_arrays_equal(mKids, i.apParent()); + + *o = i; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest14(const Structs& i, + Structs* o) { + nsTArray<int> ai; + ai.AppendElement(1); + ai.AppendElement(2); + ai.AppendElement(3); + + test_assert(42 == i.i(), "wrong value"); + assert_arrays_equal(ai, i.ai()); + assert_arrays_equal(mKids, i.apParent()); + + const SActors& ia = i.aa()[0]; + test_assert(42 == ia.i(), "wrong value"); + assert_arrays_equal(ai, ia.ai()); + assert_arrays_equal(mKids, ia.apParent()); + + *o = i; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest15( + const WithStructs& i1, const WithStructs& i2, const WithStructs& i3, + const WithStructs& i4, const WithStructs& i5, WithStructs* o1, + WithStructs* o2, WithStructs* o3, WithStructs* o4, WithStructs* o5) { + nsTArray<int> ai; + ai.AppendElement(1); + ai.AppendElement(2); + ai.AppendElement(3); + + test_assert(i1 == int(42), "wrong value"); + assert_arrays_equal(i2.get_ArrayOfint(), ai); + assert_arrays_equal(i3.get_ArrayOfPTestDataStructuresSubParent(), mKids); + + const SActors& ia = i4.get_ArrayOfSActors()[0]; + test_assert(42 == ia.i(), "wrong value"); + assert_arrays_equal(ai, ia.ai()); + assert_arrays_equal(mKids, ia.apParent()); + + const Structs& is = i5.get_ArrayOfStructs()[0]; + test_assert(42 == is.i(), "wrong value"); + assert_arrays_equal(ai, is.ai()); + assert_arrays_equal(mKids, is.apParent()); + + const SActors& isa = is.aa()[0]; + test_assert(42 == isa.i(), "wrong value"); + assert_arrays_equal(ai, isa.ai()); + assert_arrays_equal(mKids, isa.apParent()); + + *o1 = i1; + *o2 = i2; + *o3 = i3; + *o4 = i4; + *o5 = i5; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest16( + const WithUnions& i, WithUnions* o) { + test_assert(i.i() == 42, "wrong value"); + + nsTArray<int> ai; + ai.AppendElement(1); + ai.AppendElement(2); + ai.AppendElement(3); + assert_arrays_equal(ai, i.ai()); + + assert_arrays_equal(i.apParent(), mKids); + + assert_arrays_equal(mKids, + i.aa()[0].get_ArrayOfPTestDataStructuresSubParent()); + + const nsTArray<Unions>& iau = i.au(); + test_assert(iau[0] == 42, "wrong value"); + assert_arrays_equal(ai, iau[1].get_ArrayOfint()); + assert_arrays_equal(mKids, iau[2].get_ArrayOfPTestDataStructuresSubParent()); + assert_arrays_equal( + mKids, + iau[3].get_ArrayOfActors()[0].get_ArrayOfPTestDataStructuresSubParent()); + + *o = i; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest17( + nsTArray<Op>&& sa) { + test_assert(sa.Length() == 1 && Op::TSetAttrs == sa[0].type(), "wrong value"); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDataStructuresParent::RecvTest18(RegionArray&& ra) { + for (RegionArray::index_type i = 0; i < ra.Length(); ++i) { + // if |ra| has been realloc()d and given a different allocator + // chunk, this loop will nondeterministically crash or iloop. + for (auto iter = ra[i].RectIter(); !iter.Done(); iter.Next()) { + Unused << iter.Get(); + } + } + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// child + +TestDataStructuresChild::TestDataStructuresChild() { + MOZ_COUNT_CTOR(TestDataStructuresChild); +} + +TestDataStructuresChild::~TestDataStructuresChild() { + MOZ_COUNT_DTOR(TestDataStructuresChild); +} + +mozilla::ipc::IPCResult TestDataStructuresChild::RecvStart() { + puts("[TestDataStructuresChild] starting"); + + Test1(); + Test2(); + Test3(); + Test4(); + Test5(); + Test6(); + Test7_0(); + Test7(); + Test8(); + Test9(); + Test10(); + Test11(); + Test12(); + Test13(); + Test14(); + Test15(); + Test16(); + Test17(); + if (OtherPid() != base::GetCurrentProcId()) { + // FIXME/bug 703317 allocation of nsIntRegion uses a global + // region pool which breaks threads + Test18(); + } + + for (uint32_t i = 0; i < nactors; ++i) + if (!PTestDataStructuresSubChild::Send__delete__(mKids[i])) + fail("can't send dtor"); + + Close(); + + return IPC_OK(); +} + +void TestDataStructuresChild::Test1() { + nsTArray<int> ia; + + for (int i = 0; i < 5; ++i) ia.AppendElement(i); + + nsTArray<int> oa; + if (!SendTest1(ia, &oa)) fail("can't send Test1"); + + assert_arrays_equal(ia, oa); + + printf(" passed %s\n", __FUNCTION__); +} + +void TestDataStructuresChild::Test2() { + nsTArray<PTestDataStructuresSubChild*> oa; + if (!SendTest2(mKids, &oa)) fail("can't send Test2"); + assert_arrays_equal(mKids, oa); + + printf(" passed %s\n", __FUNCTION__); +} + +void TestDataStructuresChild::Test3() { + int i1i = 42; + double i2d = 4.0; + IntDouble i1(i1i); + IntDouble i2(i2d); + IntDouble o1, o2; + + SendTest3(i1, i2, &o1, &o2); + + test_assert(i1i == o1.get_int(), "wrong value"); + test_assert(i2d == o2.get_double(), "wrong value"); + + printf(" passed %s\n", __FUNCTION__); +} + +void TestDataStructuresChild::Test4() { + nsTArray<IntDouble> i1; + i1.AppendElement(IntDouble(int(1))); + i1.AppendElement(IntDouble(2.0)); + i1.AppendElement(IntDouble(int(3))); + i1.AppendElement(IntDouble(4.0)); + + nsTArray<IntDouble> o1; + if (!SendTest4(i1, &o1)) fail("can't send Test4"); + + // TODO Union::operator==() + test_assert(i1.Length() == o1.Length(), "wrong length"); + test_assert(1 == o1[0].get_int(), "wrong value"); + test_assert(2.0 == o1[1].get_double(), "wrong value"); + test_assert(3 == o1[2].get_int(), "wrong value"); + test_assert(4.0 == o1[3].get_double(), "wrong value"); + + printf(" passed %s\n", __FUNCTION__); +} + +void TestDataStructuresChild::Test5() { + IntDoubleArrays i1(int(42)); + nsTArray<int> i2; + i2.AppendElement(1); + i2.AppendElement(2); + i2.AppendElement(3); + nsTArray<double> i3; + i3.AppendElement(1.0); + i3.AppendElement(2.0); + i3.AppendElement(3.0); + + IntDoubleArrays o1, o2, o3; + if (!SendTest5(i1, IntDoubleArrays(i2), IntDoubleArrays(i3), &o1, &o2, &o3)) + fail("can't send Test5"); + + test_assert(42 == o1.get_int(), "wrong value"); + assert_arrays_equal(i2, o2.get_ArrayOfint()); + assert_arrays_equal(i3, o3.get_ArrayOfdouble()); + + printf(" passed %s\n", __FUNCTION__); +} + +void TestDataStructuresChild::Test6() { + IntDoubleArrays id1(int(42)); + nsTArray<int> id2; + id2.AppendElement(1); + id2.AppendElement(2); + id2.AppendElement(3); + nsTArray<double> id3; + id3.AppendElement(1.0); + id3.AppendElement(2.0); + id3.AppendElement(3.0); + + nsTArray<IntDoubleArrays> i1; + i1.AppendElement(id1); + i1.AppendElement(IntDoubleArrays(id2)); + i1.AppendElement(IntDoubleArrays(id3)); + + nsTArray<IntDoubleArrays> o1; + if (!SendTest6(i1, &o1)) fail("can't send Test6"); + + test_assert(3 == o1.Length(), "wrong length"); + IntDoubleArrays od1(o1[0]); + nsTArray<int> od2(o1[1].get_ArrayOfint()); + nsTArray<double> od3(o1[2].get_ArrayOfdouble()); + + test_assert(42 == od1.get_int(), "wrong value"); + assert_arrays_equal(id2, od2); + assert_arrays_equal(id3, od3); + + printf(" passed %s\n", __FUNCTION__); +} + +void TestDataStructuresChild::Test7_0() { + ActorWrapper iaw; + if (iaw.actorChild() != nullptr || iaw.actorParent() != nullptr) + fail("actor members should be null initially"); + + iaw.actorChild() = mKids[0]; + if (iaw.actorParent() != nullptr) + fail("parent should be null on child side after set"); + + ActorWrapper oaw; + if (!SendTest7_0(iaw, &oaw)) fail("sending Test7_0"); + + if (oaw.actorParent() != nullptr) + fail( + "parent accessor on actor-struct members should always be null in " + "child"); + + if (oaw.actorChild() != mKids[0]) + fail("should have got back same child-side actor"); +} + +void TestDataStructuresChild::Test7() { + Actors i1(42); + nsTArray<int> i2a; + i2a.AppendElement(1); + i2a.AppendElement(2); + i2a.AppendElement(3); + + Actors o1, o2, o3; + if (!SendTest7(i1, Actors(i2a), Actors(mKids), &o1, &o2, &o3)) + fail("can't send Test7"); + + test_assert(42 == o1.get_int(), "wrong value"); + assert_arrays_equal(i2a, o2.get_ArrayOfint()); + assert_arrays_equal(mKids, o3.get_ArrayOfPTestDataStructuresSubChild()); + + printf(" passed %s\n", __FUNCTION__); +} + +void TestDataStructuresChild::Test8() { + Actors i1e(42); + nsTArray<int> i2a; + i2a.AppendElement(1); + i2a.AppendElement(2); + i2a.AppendElement(3); + + nsTArray<Actors> i1; + i1.AppendElement(i1e); + i1.AppendElement(i2a); + i1.AppendElement(mKids); + + nsTArray<Actors> o1; + if (!SendTest8(i1, &o1)) fail("can't send Test8"); + + test_assert(3 == o1.Length(), "wrong length"); + test_assert(42 == o1[0].get_int(), "wrong value"); + assert_arrays_equal(i2a, o1[1].get_ArrayOfint()); + assert_arrays_equal(mKids, o1[2].get_ArrayOfPTestDataStructuresSubChild()); + + printf(" passed %s\n", __FUNCTION__); +} + +void TestDataStructuresChild::Test9() { + Unions i1(int(42)); + + nsTArray<int> i2a; + i2a.AppendElement(1); + i2a.AppendElement(2); + i2a.AppendElement(3); + + nsTArray<Actors> i4a; + i4a.AppendElement(mKids); + + Unions o1, o2, o3, o4; + if (!SendTest9(i1, Unions(i2a), Unions(mKids), Unions(i4a), &o1, &o2, &o3, + &o4)) + fail("can't send Test9"); + + test_assert(42 == o1.get_int(), "wrong value"); + assert_arrays_equal(i2a, o2.get_ArrayOfint()); + assert_arrays_equal(mKids, o3.get_ArrayOfPTestDataStructuresSubChild()); + assert_arrays_equal( + mKids, + o4.get_ArrayOfActors()[0].get_ArrayOfPTestDataStructuresSubChild()); + + printf(" passed %s\n", __FUNCTION__); +} + +void TestDataStructuresChild::Test10() { + Unions i1a(int(42)); + + nsTArray<int> i2a; + i2a.AppendElement(1); + i2a.AppendElement(2); + i2a.AppendElement(3); + + nsTArray<Actors> i4a; + i4a.AppendElement(mKids); + + nsTArray<Unions> i1; + i1.AppendElement(i1a); + i1.AppendElement(Unions(i2a)); + i1.AppendElement(Unions(mKids)); + i1.AppendElement(Unions(i4a)); + + nsTArray<Unions> o1; + if (!SendTest10(i1, &o1)) fail("can't send Test10"); + + test_assert(4 == o1.Length(), "wrong length"); + test_assert(42 == o1[0].get_int(), "wrong value"); + assert_arrays_equal(i2a, o1[1].get_ArrayOfint()); + assert_arrays_equal(mKids, o1[2].get_ArrayOfPTestDataStructuresSubChild()); + assert_arrays_equal( + mKids, + o1[3].get_ArrayOfActors()[0].get_ArrayOfPTestDataStructuresSubChild()); + + printf(" passed %s\n", __FUNCTION__); +} + +void TestDataStructuresChild::Test11() { + SIntDouble i(1, 2.0); + SIntDouble o; + + if (!SendTest11(i, &o)) fail("sending Test11"); + + test_assert(1 == o.i() && 2.0 == o.d(), "wrong values"); + + printf(" passed %s\n", __FUNCTION__); +} + +void TestDataStructuresChild::Test12() { + nsTArray<int> ai; + ai.AppendElement(1); + ai.AppendElement(2); + ai.AppendElement(3); + + nsTArray<double> ad; + ad.AppendElement(.5); + ad.AppendElement(1.0); + ad.AppendElement(2.0); + + SIntDoubleArrays i(42, ai, ad); + SIntDoubleArrays o; + + if (!SendTest12(i, &o)) fail("sending Test12"); + + test_assert(42 == o.i(), "wrong value"); + assert_arrays_equal(ai, o.ai()); + assert_arrays_equal(ad, o.ad()); + + printf(" passed %s\n", __FUNCTION__); +} + +void TestDataStructuresChild::Test13() { + nsTArray<int> ai; + ai.AppendElement(1); + ai.AppendElement(2); + ai.AppendElement(3); + + SActors i; + i.i() = 42; + i.ai() = ai; + i.apChild() = mKids; + + SActors o; + if (!SendTest13(i, &o)) fail("can't send Test13"); + + test_assert(42 == o.i(), "wrong value"); + assert_arrays_equal(ai, o.ai()); + assert_arrays_equal(mKids, o.apChild()); + + printf(" passed %s\n", __FUNCTION__); +} + +void TestDataStructuresChild::Test14() { + nsTArray<int> ai; + ai.AppendElement(1); + ai.AppendElement(2); + ai.AppendElement(3); + + SActors ia; + ia.i() = 42; + ia.ai() = ai; + ia.apChild() = mKids; + nsTArray<SActors> aa; + aa.AppendElement(ia); + + Structs i; + i.i() = 42; + i.ai() = ai; + i.apChild() = mKids; + i.aa() = aa; + + Structs o; + if (!SendTest14(i, &o)) fail("can't send Test14"); + + test_assert(42 == o.i(), "wrong value"); + assert_arrays_equal(ai, o.ai()); + assert_arrays_equal(mKids, o.apChild()); + + const SActors& os = o.aa()[0]; + test_assert(42 == os.i(), "wrong value"); + assert_arrays_equal(ai, os.ai()); + assert_arrays_equal(mKids, os.apChild()); + + printf(" passed %s\n", __FUNCTION__); +} + +void TestDataStructuresChild::Test15() { + nsTArray<int> ai; + ai.AppendElement(1); + ai.AppendElement(2); + ai.AppendElement(3); + + SActors ia; + ia.i() = 42; + ia.ai() = ai; + ia.apChild() = mKids; + nsTArray<SActors> iaa; + iaa.AppendElement(ia); + + Structs is; + is.i() = 42; + is.ai() = ai; + is.apChild() = mKids; + is.aa() = iaa; + nsTArray<Structs> isa; + isa.AppendElement(is); + + WithStructs o1, o2, o3, o4, o5; + if (!SendTest15(WithStructs(42), WithStructs(ai), WithStructs(mKids), + WithStructs(iaa), WithStructs(isa), &o1, &o2, &o3, &o4, &o5)) + fail("sending Test15"); + + test_assert(o1 == int(42), "wrong value"); + assert_arrays_equal(o2.get_ArrayOfint(), ai); + assert_arrays_equal(o3.get_ArrayOfPTestDataStructuresSubChild(), mKids); + + const SActors& oa = o4.get_ArrayOfSActors()[0]; + test_assert(42 == oa.i(), "wrong value"); + assert_arrays_equal(ai, oa.ai()); + assert_arrays_equal(mKids, oa.apChild()); + + const Structs& os = o5.get_ArrayOfStructs()[0]; + test_assert(42 == os.i(), "wrong value"); + assert_arrays_equal(ai, os.ai()); + assert_arrays_equal(mKids, os.apChild()); + + const SActors& osa = os.aa()[0]; + test_assert(42 == osa.i(), "wrong value"); + assert_arrays_equal(ai, osa.ai()); + assert_arrays_equal(mKids, osa.apChild()); + + printf(" passed %s\n", __FUNCTION__); +} + +void TestDataStructuresChild::Test16() { + WithUnions i; + + i.i() = 42; + + nsTArray<int> ai; + ai.AppendElement(1); + ai.AppendElement(2); + ai.AppendElement(3); + i.ai() = ai; + + i.apChild() = mKids; + + nsTArray<Actors> iaa; + iaa.AppendElement(mKids); + i.aa() = iaa; + + nsTArray<Unions> iau; + iau.AppendElement(int(42)); + iau.AppendElement(ai); + iau.AppendElement(mKids); + iau.AppendElement(iaa); + i.au() = iau; + + WithUnions o; + if (!SendTest16(i, &o)) fail("sending Test16"); + + test_assert(42 == o.i(), "wrong value"); + assert_arrays_equal(o.ai(), ai); + assert_arrays_equal(o.apChild(), mKids); + + const Actors& oaa = o.aa()[0]; + assert_arrays_equal(oaa.get_ArrayOfPTestDataStructuresSubChild(), mKids); + + const nsTArray<Unions>& oau = o.au(); + test_assert(oau[0] == 42, "wrong value"); + assert_arrays_equal(oau[1].get_ArrayOfint(), ai); + assert_arrays_equal(oau[2].get_ArrayOfPTestDataStructuresSubChild(), mKids); + assert_arrays_equal( + oau[3].get_ArrayOfActors()[0].get_ArrayOfPTestDataStructuresSubChild(), + mKids); + + printf(" passed %s\n", __FUNCTION__); +} + +void TestDataStructuresChild::Test17() { + Attrs attrs; + attrs.common() = CommonAttrs(true); + attrs.specific() = BarAttrs(1.0f); + + nsTArray<Op> ops; + ops.AppendElement(SetAttrs(nullptr, mKids[0], attrs)); + + if (!SendTest17(ops)) fail("sending Test17"); + + printf(" passed %s\n", __FUNCTION__); +} + +void TestDataStructuresChild::Test18() { + const int nelements = 1000; + RegionArray ra; + // big enough to hopefully force a realloc to a different chunk of + // memory on the receiving side, if the workaround isn't working + // correctly. But SetCapacity() here because we don't want to + // crash on the sending side. + ra.SetCapacity(nelements); + for (int i = 0; i < nelements; ++i) { + nsIntRegion r; + r.Or(nsIntRect(0, 0, 10, 10), nsIntRect(10, 10, 10, 10)); + ra.AppendElement(r); + } + + if (!SendTest18(ra)) fail("sending Test18"); + + printf(" passed %s\n", __FUNCTION__); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestDataStructures.h b/ipc/ipdl/test/cxx/TestDataStructures.h new file mode 100644 index 0000000000..dc25b5282c --- /dev/null +++ b/ipc/ipdl/test/cxx/TestDataStructures.h @@ -0,0 +1,180 @@ +#ifndef mozilla__ipdltest_TestDataStructures_h +#define mozilla__ipdltest_TestDataStructures_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestDataStructuresParent.h" +#include "mozilla/_ipdltest/PTestDataStructuresChild.h" + +#include "mozilla/_ipdltest/PTestDataStructuresSubParent.h" +#include "mozilla/_ipdltest/PTestDataStructuresSubChild.h" + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// Subprotocol actors + +class TestDataStructuresSub : public PTestDataStructuresSubParent, + public PTestDataStructuresSubChild { + public: + explicit TestDataStructuresSub(uint32_t i) : mI(i) {} + virtual ~TestDataStructuresSub() {} + virtual void ActorDestroy(ActorDestroyReason why) override { + if (Deletion != why) fail("unexpected destruction!"); + } + uint32_t mI; +}; + +//----------------------------------------------------------------------------- +// Main actors + +class TestDataStructuresParent : public PTestDataStructuresParent { + friend class PTestDataStructuresParent; + + public: + TestDataStructuresParent(); + virtual ~TestDataStructuresParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + PTestDataStructuresSubParent* AllocPTestDataStructuresSubParent( + const int& i) { + PTestDataStructuresSubParent* actor = new TestDataStructuresSub(i); + mKids.AppendElement(actor); + return actor; + } + + bool DeallocPTestDataStructuresSubParent(PTestDataStructuresSubParent* actor); + + mozilla::ipc::IPCResult RecvTest1(nsTArray<int>&& i1, nsTArray<int>* o1); + + mozilla::ipc::IPCResult RecvTest2( + nsTArray<PTestDataStructuresSubParent*>&& i1, + nsTArray<PTestDataStructuresSubParent*>* o1); + + mozilla::ipc::IPCResult RecvTest3(const IntDouble& i1, const IntDouble& i2, + IntDouble* o1, IntDouble* o2); + + mozilla::ipc::IPCResult RecvTest4(nsTArray<IntDouble>&& i1, + nsTArray<IntDouble>* o1); + + mozilla::ipc::IPCResult RecvTest5(const IntDoubleArrays& i1, + const IntDoubleArrays& i2, + const IntDoubleArrays& i3, + IntDoubleArrays* o1, IntDoubleArrays* o2, + IntDoubleArrays* o3); + + mozilla::ipc::IPCResult RecvTest6(nsTArray<IntDoubleArrays>&& i1, + nsTArray<IntDoubleArrays>* o1); + + mozilla::ipc::IPCResult RecvTest7_0(const ActorWrapper& i1, ActorWrapper* o1); + + mozilla::ipc::IPCResult RecvTest7(const Actors& i1, const Actors& i2, + const Actors& i3, Actors* o1, Actors* o2, + Actors* o3); + + mozilla::ipc::IPCResult RecvTest8(nsTArray<Actors>&& i1, + nsTArray<Actors>* o1); + + mozilla::ipc::IPCResult RecvTest9(const Unions& i1, const Unions& i2, + const Unions& i3, const Unions& i4, + Unions* o1, Unions* o2, Unions* o3, + Unions* o4); + + mozilla::ipc::IPCResult RecvTest10(nsTArray<Unions>&& i1, + nsTArray<Unions>* o1); + + mozilla::ipc::IPCResult RecvTest11(const SIntDouble& i, SIntDouble* o); + + mozilla::ipc::IPCResult RecvTest12(const SIntDoubleArrays& i, + SIntDoubleArrays* o); + + mozilla::ipc::IPCResult RecvTest13(const SActors& i, SActors* o); + + mozilla::ipc::IPCResult RecvTest14(const Structs& i, Structs* o); + + mozilla::ipc::IPCResult RecvTest15( + const WithStructs& i1, const WithStructs& i2, const WithStructs& i3, + const WithStructs& i4, const WithStructs& i5, WithStructs* o1, + WithStructs* o2, WithStructs* o3, WithStructs* o4, WithStructs* o5); + + mozilla::ipc::IPCResult RecvTest16(const WithUnions& i, WithUnions* o); + + mozilla::ipc::IPCResult RecvTest17(nsTArray<Op>&& sa); + + mozilla::ipc::IPCResult RecvTest18(nsTArray<nsIntRegion>&& ra); + + mozilla::ipc::IPCResult RecvDummy(const ShmemUnion& su, ShmemUnion* rsu) { + *rsu = su; + return IPC_OK(); + } + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } + + private: + nsTArray<PTestDataStructuresSubParent*> mKids; +}; + +class TestDataStructuresChild : public PTestDataStructuresChild { + friend class PTestDataStructuresChild; + + public: + TestDataStructuresChild(); + virtual ~TestDataStructuresChild(); + + protected: + PTestDataStructuresSubChild* AllocPTestDataStructuresSubChild(const int& i) { + PTestDataStructuresSubChild* actor = new TestDataStructuresSub(i); + mKids.AppendElement(actor); + return actor; + } + + bool DeallocPTestDataStructuresSubChild(PTestDataStructuresSubChild* actor) { + delete actor; + return true; + } + + mozilla::ipc::IPCResult RecvStart(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } + + private: + void Test1(); + void Test2(); + void Test3(); + void Test4(); + void Test5(); + void Test6(); + void Test7_0(); + void Test7(); + void Test8(); + void Test9(); + void Test10(); + void Test11(); + void Test12(); + void Test13(); + void Test14(); + void Test15(); + void Test16(); + void Test17(); + void Test18(); + + nsTArray<PTestDataStructuresSubChild*> mKids; +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestDataStructures_h diff --git a/ipc/ipdl/test/cxx/TestDemon.cpp b/ipc/ipdl/test/cxx/TestDemon.cpp new file mode 100644 index 0000000000..811f6c3314 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestDemon.cpp @@ -0,0 +1,362 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + */ +#include "TestDemon.h" + +#include <stdlib.h> + +#include "IPDLUnitTests.h" // fail etc. +#if defined(OS_POSIX) +# include <sys/time.h> +# include <unistd.h> +#else +# include <time.h> +# include <windows.h> +#endif + +namespace mozilla { +namespace _ipdltest { + +const int kMaxStackHeight = 4; + +static LazyLogModule sLogModule("demon"); + +#define DEMON_LOG(...) MOZ_LOG(sLogModule, LogLevel::Debug, (__VA_ARGS__)) + +static int gStackHeight = 0; +static bool gFlushStack = false; + +static int Choose(int count) { +#if defined(OS_POSIX) + return random() % count; +#else + return rand() % count; +#endif +} + +//----------------------------------------------------------------------------- +// parent + +TestDemonParent::TestDemonParent() : mDone(false), mIncoming(), mOutgoing() { + MOZ_COUNT_CTOR(TestDemonParent); +} + +TestDemonParent::~TestDemonParent() { MOZ_COUNT_DTOR(TestDemonParent); } + +void TestDemonParent::Main() { + if (!getenv("MOZ_TEST_IPC_DEMON")) { + QuitParent(); + return; + } +#if defined(OS_POSIX) + srandom(time(nullptr)); +#else + srand(time(nullptr)); +#endif + + DEMON_LOG("Start demon"); + + if (!SendStart()) fail("sending Start"); + + RunUnlimitedSequence(); +} + +#ifdef DEBUG +bool TestDemonParent::ShouldContinueFromReplyTimeout() { + return Choose(2) == 0; +} + +bool TestDemonParent::ArtificialTimeout() { return Choose(5) == 0; } + +void TestDemonParent::ArtificialSleep() { + if (Choose(2) == 0) { + // Sleep for anywhere from 0 to 100 milliseconds. + unsigned micros = Choose(100) * 1000; +# ifdef OS_POSIX + usleep(micros); +# else + Sleep(micros / 1000); +# endif + } +} +#endif + +mozilla::ipc::IPCResult TestDemonParent::RecvAsyncMessage(const int& n) { + DEMON_LOG("Start RecvAsync [%d]", n); + + MOZ_ASSERT(n == mIncoming[0]); + mIncoming[0]++; + + RunLimitedSequence(); + + DEMON_LOG("End RecvAsync [%d]", n); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDemonParent::RecvHiPrioSyncMessage() { + DEMON_LOG("Start RecvHiPrioSyncMessage"); + RunLimitedSequence(); + DEMON_LOG("End RecvHiPrioSyncMessage"); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDemonParent::RecvSyncMessage(const int& n) { + DEMON_LOG("Start RecvSync [%d]", n); + + MOZ_ASSERT(n == mIncoming[0]); + mIncoming[0]++; + + RunLimitedSequence(ASYNC_ONLY); + + DEMON_LOG("End RecvSync [%d]", n); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDemonParent::RecvUrgentAsyncMessage(const int& n) { + DEMON_LOG("Start RecvUrgentAsyncMessage [%d]", n); + + MOZ_ASSERT(n == mIncoming[2]); + mIncoming[2]++; + + RunLimitedSequence(ASYNC_ONLY); + + DEMON_LOG("End RecvUrgentAsyncMessage [%d]", n); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDemonParent::RecvUrgentSyncMessage(const int& n) { + DEMON_LOG("Start RecvUrgentSyncMessage [%d]", n); + + MOZ_ASSERT(n == mIncoming[2]); + mIncoming[2]++; + + RunLimitedSequence(ASYNC_ONLY); + + DEMON_LOG("End RecvUrgentSyncMessage [%d]", n); + return IPC_OK(); +} + +void TestDemonParent::RunUnlimitedSequence() { + if (mDone) { + return; + } + + gFlushStack = false; + DoAction(); + + MessageLoop::current()->PostTask(NewNonOwningRunnableMethod( + "_ipdltest::TestDemonParent::RunUnlimitedSequence", this, + &TestDemonParent::RunUnlimitedSequence)); +} + +void TestDemonParent::RunLimitedSequence(int flags) { + if (gStackHeight >= kMaxStackHeight) { + return; + } + gStackHeight++; + + int count = Choose(20); + for (int i = 0; i < count; i++) { + if (!DoAction(flags)) { + gFlushStack = true; + } + if (gFlushStack) { + gStackHeight--; + return; + } + } + + gStackHeight--; +} + +static bool AllowAsync(int outgoing, int incoming) { + return incoming >= outgoing - 5; +} + +bool TestDemonParent::DoAction(int flags) { + if (flags & ASYNC_ONLY) { + if (AllowAsync(mOutgoing[0], mIncoming[0])) { + DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]); + return SendAsyncMessage(mOutgoing[0]++); + } else { + return true; + } + } else { + switch (Choose(3)) { + case 0: + if (AllowAsync(mOutgoing[0], mIncoming[0])) { + DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]); + return SendAsyncMessage(mOutgoing[0]++); + } else { + return true; + } + + case 1: { + DEMON_LOG("Start SendHiPrioSyncMessage"); + bool r = SendHiPrioSyncMessage(); + DEMON_LOG("End SendHiPrioSyncMessage result=%d", r); + return r; + } + + case 2: + DEMON_LOG("Cancel"); + GetIPCChannel()->CancelCurrentTransaction(); + return true; + } + } + MOZ_CRASH(); + return false; +} + +//----------------------------------------------------------------------------- +// child + +TestDemonChild::TestDemonChild() : mIncoming(), mOutgoing() { + MOZ_COUNT_CTOR(TestDemonChild); +} + +TestDemonChild::~TestDemonChild() { MOZ_COUNT_DTOR(TestDemonChild); } + +mozilla::ipc::IPCResult TestDemonChild::RecvStart() { +#ifdef OS_POSIX + srandom(time(nullptr)); +#else + srand(time(nullptr)); +#endif + + DEMON_LOG("RecvStart"); + + RunUnlimitedSequence(); + return IPC_OK(); +} + +#ifdef DEBUG +void TestDemonChild::ArtificialSleep() { + if (Choose(2) == 0) { + // Sleep for anywhere from 0 to 100 milliseconds. + unsigned micros = Choose(100) * 1000; +# ifdef OS_POSIX + usleep(micros); +# else + Sleep(micros / 1000); +# endif + } +} +#endif + +mozilla::ipc::IPCResult TestDemonChild::RecvAsyncMessage(const int& n) { + DEMON_LOG("Start RecvAsyncMessage [%d]", n); + + MOZ_ASSERT(n == mIncoming[0]); + mIncoming[0]++; + + RunLimitedSequence(); + + DEMON_LOG("End RecvAsyncMessage [%d]", n); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestDemonChild::RecvHiPrioSyncMessage() { + DEMON_LOG("Start RecvHiPrioSyncMessage"); + RunLimitedSequence(); + DEMON_LOG("End RecvHiPrioSyncMessage"); + return IPC_OK(); +} + +void TestDemonChild::RunUnlimitedSequence() { + gFlushStack = false; + DoAction(); + + MessageLoop::current()->PostTask(NewNonOwningRunnableMethod( + "_ipdltest::TestDemonChild::RunUnlimitedSequence", this, + &TestDemonChild::RunUnlimitedSequence)); +} + +void TestDemonChild::RunLimitedSequence() { + if (gStackHeight >= kMaxStackHeight) { + return; + } + gStackHeight++; + + int count = Choose(20); + for (int i = 0; i < count; i++) { + if (!DoAction()) { + gFlushStack = true; + } + if (gFlushStack) { + gStackHeight--; + return; + } + } + + gStackHeight--; +} + +bool TestDemonChild::DoAction() { + switch (Choose(6)) { + case 0: + if (AllowAsync(mOutgoing[0], mIncoming[0])) { + DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]); + return SendAsyncMessage(mOutgoing[0]++); + } else { + return true; + } + + case 1: { + DEMON_LOG("Start SendHiPrioSyncMessage"); + bool r = SendHiPrioSyncMessage(); + DEMON_LOG("End SendHiPrioSyncMessage result=%d", r); + return r; + } + + case 2: { + DEMON_LOG("Start SendSyncMessage [%d]", mOutgoing[0]); + bool r = SendSyncMessage(mOutgoing[0]++); + switch (GetIPCChannel()->LastSendError()) { + case SyncSendError::PreviousTimeout: + case SyncSendError::SendingCPOWWhileDispatchingSync: + case SyncSendError::SendingCPOWWhileDispatchingUrgent: + case SyncSendError::NotConnectedBeforeSend: + case SyncSendError::CancelledBeforeSend: + mOutgoing[0]--; + break; + default: + break; + } + DEMON_LOG("End SendSyncMessage result=%d", r); + return r; + } + + case 3: + DEMON_LOG("SendUrgentAsyncMessage [%d]", mOutgoing[2]); + return SendUrgentAsyncMessage(mOutgoing[2]++); + + case 4: { + DEMON_LOG("Start SendUrgentSyncMessage [%d]", mOutgoing[2]); + bool r = SendUrgentSyncMessage(mOutgoing[2]++); + switch (GetIPCChannel()->LastSendError()) { + case SyncSendError::PreviousTimeout: + case SyncSendError::SendingCPOWWhileDispatchingSync: + case SyncSendError::SendingCPOWWhileDispatchingUrgent: + case SyncSendError::NotConnectedBeforeSend: + case SyncSendError::CancelledBeforeSend: + mOutgoing[2]--; + break; + default: + break; + } + DEMON_LOG("End SendUrgentSyncMessage result=%d", r); + return r; + } + + case 5: + DEMON_LOG("Cancel"); + GetIPCChannel()->CancelCurrentTransaction(); + return true; + } + MOZ_CRASH(); + return false; +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestDemon.h b/ipc/ipdl/test/cxx/TestDemon.h new file mode 100644 index 0000000000..ba5b003436 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestDemon.h @@ -0,0 +1,91 @@ +#ifndef mozilla__ipdltest_TestDemon_h +#define mozilla__ipdltest_TestDemon_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestDemonParent.h" +#include "mozilla/_ipdltest/PTestDemonChild.h" + +using namespace mozilla::ipc; + +namespace mozilla { +namespace _ipdltest { + +class TestDemonParent : public PTestDemonParent { + public: + TestDemonParent(); + virtual ~TestDemonParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + +#ifdef DEBUG + bool ShouldContinueFromReplyTimeout() override; + bool ArtificialTimeout() override; + + bool NeedArtificialSleep() override { return true; } + void ArtificialSleep() override; +#endif + + mozilla::ipc::IPCResult RecvAsyncMessage(const int& n); + mozilla::ipc::IPCResult RecvHiPrioSyncMessage(); + + mozilla::ipc::IPCResult RecvSyncMessage(const int& n); + mozilla::ipc::IPCResult RecvUrgentAsyncMessage(const int& n); + mozilla::ipc::IPCResult RecvUrgentSyncMessage(const int& n); + + virtual void ActorDestroy(ActorDestroyReason why) override { + mDone = true; + printf("Parent ActorDestroy\n"); + passed("ok"); + QuitParent(); + } + + private: + bool mDone; + int mIncoming[3]; + int mOutgoing[3]; + + enum { + ASYNC_ONLY = 1, + }; + + void RunUnlimitedSequence(); + void RunLimitedSequence(int flags = 0); + bool DoAction(int flags = 0); +}; + +class TestDemonChild : public PTestDemonChild { + public: + TestDemonChild(); + virtual ~TestDemonChild(); + + mozilla::ipc::IPCResult RecvStart(); + +#ifdef DEBUG + bool NeedArtificialSleep() override { return true; } + void ArtificialSleep() override; +#endif + + mozilla::ipc::IPCResult RecvAsyncMessage(const int& n); + mozilla::ipc::IPCResult RecvHiPrioSyncMessage(); + + virtual void ActorDestroy(ActorDestroyReason why) override { _exit(0); } + + virtual void IntentionalCrash() override { _exit(0); } + + private: + int mIncoming[3]; + int mOutgoing[3]; + + void RunUnlimitedSequence(); + void RunLimitedSequence(); + bool DoAction(); +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestDemon_h diff --git a/ipc/ipdl/test/cxx/TestDesc.cpp b/ipc/ipdl/test/cxx/TestDesc.cpp new file mode 100644 index 0000000000..2ae199457d --- /dev/null +++ b/ipc/ipdl/test/cxx/TestDesc.cpp @@ -0,0 +1,78 @@ +#include "TestDesc.h" + +#include "IPDLUnitTests.h" // fail etc. + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent +void TestDescParent::Main() { + PTestDescSubParent* p = CallPTestDescSubConstructor(0); + if (!p) fail("can't allocate Sub"); + + PTestDescSubsubParent* pp = p->CallPTestDescSubsubConstructor(); + if (!pp) fail("can't allocate Subsub"); + + if (!SendTest(pp)) fail("can't send Subsub"); +} + +mozilla::ipc::IPCResult TestDescParent::RecvOk(PTestDescSubsubParent* a) { + if (!a) fail("didn't receive Subsub"); + + if (!PTestDescSubsubParent::Call__delete__(a)) fail("deleting Subsub"); + + Close(); + + return IPC_OK(); +} + +PTestDescSubParent* TestDescParent::AllocPTestDescSubParent( + PTestDescSubsubParent* dummy) { + if (dummy) fail("actor supposed to be null"); + return new TestDescSubParent(); +} +bool TestDescParent::DeallocPTestDescSubParent(PTestDescSubParent* actor) { + delete actor; + return true; +} + +PTestDescSubsubParent* TestDescSubParent::AllocPTestDescSubsubParent() { + return new TestDescSubsubParent(); +} +bool TestDescSubParent::DeallocPTestDescSubsubParent( + PTestDescSubsubParent* actor) { + delete actor; + return true; +} + +//----------------------------------------------------------------------------- +// child + +mozilla::ipc::IPCResult TestDescChild::RecvTest(PTestDescSubsubChild* a) { + if (!a) fail("didn't receive Subsub"); + if (!SendOk(a)) fail("couldn't send Ok()"); + return IPC_OK(); +} + +PTestDescSubChild* TestDescChild::AllocPTestDescSubChild( + PTestDescSubsubChild* dummy) { + if (dummy) fail("actor supposed to be null"); + return new TestDescSubChild(); +} +bool TestDescChild::DeallocPTestDescSubChild(PTestDescSubChild* actor) { + delete actor; + return true; +} + +PTestDescSubsubChild* TestDescSubChild::AllocPTestDescSubsubChild() { + return new TestDescSubsubChild(); +} +bool TestDescSubChild::DeallocPTestDescSubsubChild( + PTestDescSubsubChild* actor) { + delete actor; + return true; +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestDesc.h b/ipc/ipdl/test/cxx/TestDesc.h new file mode 100644 index 0000000000..c9fa04f89c --- /dev/null +++ b/ipc/ipdl/test/cxx/TestDesc.h @@ -0,0 +1,115 @@ +#ifndef mozilla_ipdltest_TestDesc_h +#define mozilla_ipdltest_TestDesc_h + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestDescParent.h" +#include "mozilla/_ipdltest/PTestDescChild.h" + +#include "mozilla/_ipdltest/PTestDescSubParent.h" +#include "mozilla/_ipdltest/PTestDescSubChild.h" + +#include "mozilla/_ipdltest/PTestDescSubsubParent.h" +#include "mozilla/_ipdltest/PTestDescSubsubChild.h" + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// Top-level +// +class TestDescParent : public PTestDescParent { + friend class PTestDescParent; + + public: + TestDescParent() {} + virtual ~TestDescParent() {} + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + mozilla::ipc::IPCResult RecvOk(PTestDescSubsubParent* a); + + protected: + PTestDescSubParent* AllocPTestDescSubParent(PTestDescSubsubParent*); + bool DeallocPTestDescSubParent(PTestDescSubParent* actor); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } +}; + +class TestDescChild : public PTestDescChild { + friend class PTestDescChild; + + public: + TestDescChild() {} + virtual ~TestDescChild() {} + + protected: + PTestDescSubChild* AllocPTestDescSubChild(PTestDescSubsubChild*); + + bool DeallocPTestDescSubChild(PTestDescSubChild* actor); + + mozilla::ipc::IPCResult RecvTest(PTestDescSubsubChild* a); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } +}; + +//----------------------------------------------------------------------------- +// First descendent +// +class TestDescSubParent : public PTestDescSubParent { + friend class PTestDescSubParent; + + public: + TestDescSubParent() {} + virtual ~TestDescSubParent() {} + + protected: + virtual void ActorDestroy(ActorDestroyReason why) override {} + PTestDescSubsubParent* AllocPTestDescSubsubParent(); + bool DeallocPTestDescSubsubParent(PTestDescSubsubParent* actor); +}; + +class TestDescSubChild : public PTestDescSubChild { + friend class PTestDescSubChild; + + public: + TestDescSubChild() {} + virtual ~TestDescSubChild() {} + + protected: + PTestDescSubsubChild* AllocPTestDescSubsubChild(); + bool DeallocPTestDescSubsubChild(PTestDescSubsubChild* actor); +}; + +//----------------------------------------------------------------------------- +// Grand-descendent +// +class TestDescSubsubParent : public PTestDescSubsubParent { + public: + TestDescSubsubParent() {} + virtual ~TestDescSubsubParent() {} + + protected: + virtual void ActorDestroy(ActorDestroyReason why) override {} +}; + +class TestDescSubsubChild : public PTestDescSubsubChild { + public: + TestDescSubsubChild() {} + virtual ~TestDescSubsubChild() {} +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla_ipdltest_TestDesc_h diff --git a/ipc/ipdl/test/cxx/TestEndpointBridgeMain.cpp b/ipc/ipdl/test/cxx/TestEndpointBridgeMain.cpp new file mode 100644 index 0000000000..4a4366f0ba --- /dev/null +++ b/ipc/ipdl/test/cxx/TestEndpointBridgeMain.cpp @@ -0,0 +1,226 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +#include "TestEndpointBridgeMain.h" + +#include "base/task.h" +#include "IPDLUnitTests.h" // fail etc. +#include "IPDLUnitTestSubprocess.h" + +using namespace std; + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// main process +void TestEndpointBridgeMainParent::Main() { + if (!SendStart()) { + fail("sending Start"); + } +} + +mozilla::ipc::IPCResult TestEndpointBridgeMainParent::RecvBridged( + Endpoint<PTestEndpointBridgeMainSubParent>&& endpoint) { + TestEndpointBridgeMainSubParent* a = new TestEndpointBridgeMainSubParent(); + if (!endpoint.Bind(a)) { + fail("Bind failed"); + } + return IPC_OK(); +} + +void TestEndpointBridgeMainParent::ActorDestroy(ActorDestroyReason why) { + if (NormalShutdown != why) { + fail("unexpected destruction!"); + } + passed("ok"); + QuitParent(); +} + +mozilla::ipc::IPCResult TestEndpointBridgeMainSubParent::RecvHello() { + if (!SendHi()) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestEndpointBridgeMainSubParent::RecvHelloSync() { + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestEndpointBridgeMainSubParent::AnswerHelloRpc() { + if (!CallHiRpc()) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +void TestEndpointBridgeMainSubParent::ActorDestroy(ActorDestroyReason why) { + if (NormalShutdown != why) { + fail("unexpected destruction!"); + } + + // ActorDestroy() is just a callback from IPDL-generated code, + // which needs the top-level actor (this) to stay alive a little + // longer so other things can be cleaned up. + MessageLoop::current()->PostTask( + do_AddRef(new DeleteTask<TestEndpointBridgeMainSubParent>(this))); +} + +//----------------------------------------------------------------------------- +// sub process --- child of main +TestEndpointBridgeMainChild* gEndpointBridgeMainChild; + +TestEndpointBridgeMainChild::TestEndpointBridgeMainChild() + : mSubprocess(nullptr) { + gEndpointBridgeMainChild = this; +} + +mozilla::ipc::IPCResult TestEndpointBridgeMainChild::RecvStart() { + vector<string> subsubArgs; + subsubArgs.push_back("TestEndpointBridgeSub"); + + mSubprocess = new IPDLUnitTestSubprocess(); + if (!mSubprocess->SyncLaunch(subsubArgs)) { + fail("problem launching subprocess"); + } + + IPC::Channel* transport = mSubprocess->GetChannel(); + if (!transport) { + fail("no transport"); + } + + TestEndpointBridgeSubParent* bsp = new TestEndpointBridgeSubParent(); + bsp->Open(transport, base::GetProcId(mSubprocess->GetChildProcessHandle())); + + bsp->Main(); + return IPC_OK(); +} + +void TestEndpointBridgeMainChild::ActorDestroy(ActorDestroyReason why) { + if (NormalShutdown != why) { + fail("unexpected destruction!"); + } + // NB: this is kosher because QuitChild() joins with the IO thread + mSubprocess->Destroy(); + mSubprocess = nullptr; + QuitChild(); +} + +void TestEndpointBridgeSubParent::Main() { + if (!SendPing()) { + fail("sending Ping"); + } +} + +mozilla::ipc::IPCResult TestEndpointBridgeSubParent::RecvBridgeEm() { + Endpoint<PTestEndpointBridgeMainSubParent> parent; + Endpoint<PTestEndpointBridgeMainSubChild> child; + nsresult rv; + rv = PTestEndpointBridgeMainSub::CreateEndpoints( + gEndpointBridgeMainChild->OtherPid(), OtherPid(), &parent, &child); + if (NS_FAILED(rv)) { + fail("opening PTestEndpointOpensOpened"); + } + + if (!gEndpointBridgeMainChild->SendBridged(std::move(parent))) { + fail("SendBridge failed for parent"); + } + if (!SendBridged(std::move(child))) { + fail("SendBridge failed for child"); + } + + return IPC_OK(); +} + +void TestEndpointBridgeSubParent::ActorDestroy(ActorDestroyReason why) { + if (NormalShutdown != why) { + fail("unexpected destruction!"); + } + gEndpointBridgeMainChild->Close(); + + // ActorDestroy() is just a callback from IPDL-generated code, + // which needs the top-level actor (this) to stay alive a little + // longer so other things can be cleaned up. + MessageLoop::current()->PostTask( + do_AddRef(new DeleteTask<TestEndpointBridgeSubParent>(this))); +} + +//----------------------------------------------------------------------------- +// subsub process --- child of sub + +static TestEndpointBridgeSubChild* gBridgeSubChild; + +TestEndpointBridgeSubChild::TestEndpointBridgeSubChild() { + gBridgeSubChild = this; +} + +mozilla::ipc::IPCResult TestEndpointBridgeSubChild::RecvPing() { + if (!SendBridgeEm()) { + fail("sending BridgeEm"); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestEndpointBridgeSubChild::RecvBridged( + Endpoint<PTestEndpointBridgeMainSubChild>&& endpoint) { + TestEndpointBridgeMainSubChild* a = new TestEndpointBridgeMainSubChild(); + + if (!endpoint.Bind(a)) { + fail("failed to Bind"); + } + + if (!a->SendHello()) { + fail("sending Hello"); + } + + return IPC_OK(); +} + +void TestEndpointBridgeSubChild::ActorDestroy(ActorDestroyReason why) { + if (NormalShutdown != why) { + fail("unexpected destruction!"); + } + QuitChild(); +} + +mozilla::ipc::IPCResult TestEndpointBridgeMainSubChild::RecvHi() { + if (!SendHelloSync()) { + fail("sending HelloSync"); + } + if (!CallHelloRpc()) { + fail("calling HelloRpc"); + } + if (!mGotHi) { + fail("didn't answer HiRpc"); + } + + // Need to close the channel without message-processing frames on + // the C++ stack + MessageLoop::current()->PostTask( + NewNonOwningRunnableMethod("ipc::IToplevelProtocol::Close", this, + &TestEndpointBridgeMainSubChild::Close)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestEndpointBridgeMainSubChild::AnswerHiRpc() { + mGotHi = true; // d00d + return IPC_OK(); +} + +void TestEndpointBridgeMainSubChild::ActorDestroy(ActorDestroyReason why) { + if (NormalShutdown != why) { + fail("unexpected destruction!"); + } + + gBridgeSubChild->Close(); + + // ActorDestroy() is just a callback from IPDL-generated code, + // which needs the top-level actor (this) to stay alive a little + // longer so other things can be cleaned up. + MessageLoop::current()->PostTask( + do_AddRef(new DeleteTask<TestEndpointBridgeMainSubChild>(this))); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestEndpointBridgeMain.h b/ipc/ipdl/test/cxx/TestEndpointBridgeMain.h new file mode 100644 index 0000000000..88ac12d71b --- /dev/null +++ b/ipc/ipdl/test/cxx/TestEndpointBridgeMain.h @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +#ifndef mozilla__ipdltest_TestEndpointBridgeMain_h +#define mozilla__ipdltest_TestEndpointBridgeMain_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestEndpointBridgeMainParent.h" +#include "mozilla/_ipdltest/PTestEndpointBridgeMainChild.h" + +#include "mozilla/_ipdltest/PTestEndpointBridgeSubParent.h" +#include "mozilla/_ipdltest/PTestEndpointBridgeSubChild.h" + +#include "mozilla/_ipdltest/PTestEndpointBridgeMainSubParent.h" +#include "mozilla/_ipdltest/PTestEndpointBridgeMainSubChild.h" + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// "Main" process +// +class TestEndpointBridgeMainParent : public PTestEndpointBridgeMainParent { + friend class PTestEndpointBridgeMainParent; + + public: + TestEndpointBridgeMainParent() {} + virtual ~TestEndpointBridgeMainParent() {} + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return false; } + + void Main(); + + protected: + mozilla::ipc::IPCResult RecvBridged( + mozilla::ipc::Endpoint<PTestEndpointBridgeMainSubParent>&& endpoint); + + virtual void ActorDestroy(ActorDestroyReason why) override; +}; + +class TestEndpointBridgeMainSubParent + : public PTestEndpointBridgeMainSubParent { + friend class PTestEndpointBridgeMainSubParent; + + public: + explicit TestEndpointBridgeMainSubParent() {} + virtual ~TestEndpointBridgeMainSubParent() {} + + protected: + mozilla::ipc::IPCResult RecvHello(); + mozilla::ipc::IPCResult RecvHelloSync(); + mozilla::ipc::IPCResult AnswerHelloRpc(); + + virtual void ActorDestroy(ActorDestroyReason why) override; +}; + +//----------------------------------------------------------------------------- +// "Sub" process --- child of "main" +// +class TestEndpointBridgeSubParent; + +class TestEndpointBridgeMainChild : public PTestEndpointBridgeMainChild { + friend class PTestEndpointBridgeMainChild; + + public: + TestEndpointBridgeMainChild(); + virtual ~TestEndpointBridgeMainChild() {} + + protected: + mozilla::ipc::IPCResult RecvStart(); + + virtual void ActorDestroy(ActorDestroyReason why) override; + + IPDLUnitTestSubprocess* mSubprocess; +}; + +class TestEndpointBridgeSubParent : public PTestEndpointBridgeSubParent { + friend class PTestEndpointBridgeSubParent; + + public: + TestEndpointBridgeSubParent() {} + virtual ~TestEndpointBridgeSubParent() {} + + void Main(); + + protected: + mozilla::ipc::IPCResult RecvBridgeEm(); + + virtual void ActorDestroy(ActorDestroyReason why) override; +}; + +//----------------------------------------------------------------------------- +// "Subsub" process --- child of "sub" +// +class TestEndpointBridgeSubChild : public PTestEndpointBridgeSubChild { + friend class PTestEndpointBridgeSubChild; + + public: + TestEndpointBridgeSubChild(); + virtual ~TestEndpointBridgeSubChild() {} + + protected: + mozilla::ipc::IPCResult RecvPing(); + + mozilla::ipc::IPCResult RecvBridged( + Endpoint<PTestEndpointBridgeMainSubChild>&& endpoint); + + virtual void ActorDestroy(ActorDestroyReason why) override; +}; + +class TestEndpointBridgeMainSubChild : public PTestEndpointBridgeMainSubChild { + friend class PTestEndpointBridgeMainSubChild; + + public: + explicit TestEndpointBridgeMainSubChild() : mGotHi(false) {} + virtual ~TestEndpointBridgeMainSubChild() {} + + protected: + mozilla::ipc::IPCResult RecvHi(); + mozilla::ipc::IPCResult AnswerHiRpc(); + + virtual void ActorDestroy(ActorDestroyReason why) override; + + bool mGotHi; +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestEndpointBridgeMain_h diff --git a/ipc/ipdl/test/cxx/TestEndpointOpens.cpp b/ipc/ipdl/test/cxx/TestEndpointOpens.cpp new file mode 100644 index 0000000000..ca141f7b58 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestEndpointOpens.cpp @@ -0,0 +1,242 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +#include "base/task.h" +#include "base/thread.h" + +#include "TestEndpointOpens.h" + +#include "IPDLUnitTests.h" // fail etc. + +using namespace mozilla::ipc; + +using base::ProcessHandle; +using base::Thread; + +namespace mozilla { +// NB: this is generally bad style, but I am lazy. +using namespace _ipdltest; +using namespace _ipdltest2; + +static MessageLoop* gMainThread; + +static void AssertNotMainThread() { + if (!gMainThread) { + fail("gMainThread is not initialized"); + } + if (MessageLoop::current() == gMainThread) { + fail("unexpectedly called on the main thread"); + } +} + +//----------------------------------------------------------------------------- +// parent + +// Thread on which TestEndpointOpensOpenedParent runs +static Thread* gParentThread; + +void TestEndpointOpensParent::Main() { + if (!SendStart()) { + fail("sending Start"); + } +} + +static void OpenParent(TestEndpointOpensOpenedParent* aParent, + Endpoint<PTestEndpointOpensOpenedParent>&& aEndpoint) { + AssertNotMainThread(); + + // Open the actor on the off-main thread to park it there. + // Messages will be delivered to this thread's message loop + // instead of the main thread's. + if (!aEndpoint.Bind(aParent)) { + fail("binding Parent"); + } +} + +mozilla::ipc::IPCResult TestEndpointOpensParent::RecvStartSubprotocol( + mozilla::ipc::Endpoint<PTestEndpointOpensOpenedParent>&& endpoint) { + gMainThread = MessageLoop::current(); + + gParentThread = new Thread("ParentThread"); + if (!gParentThread->Start()) { + fail("starting parent thread"); + } + + TestEndpointOpensOpenedParent* a = new TestEndpointOpensOpenedParent(); + gParentThread->message_loop()->PostTask( + NewRunnableFunction("OpenParent", OpenParent, a, std::move(endpoint))); + + return IPC_OK(); +} + +void TestEndpointOpensParent::ActorDestroy(ActorDestroyReason why) { + // Stops the thread and joins it + delete gParentThread; + + if (NormalShutdown != why) { + fail("unexpected destruction A!"); + } + passed("ok"); + QuitParent(); +} + +mozilla::ipc::IPCResult TestEndpointOpensOpenedParent::RecvHello() { + AssertNotMainThread(); + if (!SendHi()) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestEndpointOpensOpenedParent::RecvHelloSync() { + AssertNotMainThread(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestEndpointOpensOpenedParent::AnswerHelloRpc() { + AssertNotMainThread(); + if (!CallHiRpc()) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +static void ShutdownTestEndpointOpensOpenedParent( + TestEndpointOpensOpenedParent* parent, Transport* transport) { + delete parent; +} + +void TestEndpointOpensOpenedParent::ActorDestroy(ActorDestroyReason why) { + AssertNotMainThread(); + + if (NormalShutdown != why) { + fail("unexpected destruction B!"); + } + + // ActorDestroy() is just a callback from IPDL-generated code, + // which needs the top-level actor (this) to stay alive a little + // longer so other things can be cleaned up. + gParentThread->message_loop()->PostTask(NewRunnableFunction( + "ShutdownTestEndpointOpensOpenedParent", + ShutdownTestEndpointOpensOpenedParent, this, GetTransport())); +} + +//----------------------------------------------------------------------------- +// child + +static TestEndpointOpensChild* gOpensChild; +// Thread on which TestEndpointOpensOpenedChild runs +static Thread* gChildThread; + +TestEndpointOpensChild::TestEndpointOpensChild() { gOpensChild = this; } + +static void OpenChild(TestEndpointOpensOpenedChild* aChild, + Endpoint<PTestEndpointOpensOpenedChild>&& endpoint) { + AssertNotMainThread(); + + // Open the actor on the off-main thread to park it there. + // Messages will be delivered to this thread's message loop + // instead of the main thread's. + if (!endpoint.Bind(aChild)) { + fail("binding child endpoint"); + } + + // Kick off the unit tests + if (!aChild->SendHello()) { + fail("sending Hello"); + } +} + +mozilla::ipc::IPCResult TestEndpointOpensChild::RecvStart() { + Endpoint<PTestEndpointOpensOpenedParent> parent; + Endpoint<PTestEndpointOpensOpenedChild> child; + nsresult rv; + rv = PTestEndpointOpensOpened::CreateEndpoints( + OtherPid(), base::GetCurrentProcId(), &parent, &child); + if (NS_FAILED(rv)) { + fail("opening PTestEndpointOpensOpened"); + } + + gMainThread = MessageLoop::current(); + + gChildThread = new Thread("ChildThread"); + if (!gChildThread->Start()) { + fail("starting child thread"); + } + + TestEndpointOpensOpenedChild* a = new TestEndpointOpensOpenedChild(); + gChildThread->message_loop()->PostTask( + NewRunnableFunction("OpenChild", OpenChild, a, std::move(child))); + + if (!SendStartSubprotocol(std::move(parent))) { + fail("send StartSubprotocol"); + } + + return IPC_OK(); +} + +void TestEndpointOpensChild::ActorDestroy(ActorDestroyReason why) { + // Stops the thread and joins it + delete gChildThread; + + if (NormalShutdown != why) { + fail("unexpected destruction C!"); + } + QuitChild(); +} + +mozilla::ipc::IPCResult TestEndpointOpensOpenedChild::RecvHi() { + AssertNotMainThread(); + + if (!SendHelloSync()) { + fail("sending HelloSync"); + } + if (!CallHelloRpc()) { + fail("calling HelloRpc"); + } + if (!mGotHi) { + fail("didn't answer HiRpc"); + } + + // Need to close the channel without message-processing frames on + // the C++ stack + MessageLoop::current()->PostTask( + NewNonOwningRunnableMethod("ipc::IToplevelProtocol::Close", this, + &TestEndpointOpensOpenedChild::Close)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestEndpointOpensOpenedChild::AnswerHiRpc() { + AssertNotMainThread(); + + mGotHi = true; // d00d + return IPC_OK(); +} + +static void ShutdownTestEndpointOpensOpenedChild( + TestEndpointOpensOpenedChild* child, Transport* transport) { + delete child; + + // Kick off main-thread shutdown. + gMainThread->PostTask( + NewNonOwningRunnableMethod("ipc::IToplevelProtocol::Close", gOpensChild, + &TestEndpointOpensChild::Close)); +} + +void TestEndpointOpensOpenedChild::ActorDestroy(ActorDestroyReason why) { + AssertNotMainThread(); + + if (NormalShutdown != why) { + fail("unexpected destruction D!"); + } + + // ActorDestroy() is just a callback from IPDL-generated code, + // which needs the top-level actor (this) to stay alive a little + // longer so other things can be cleaned up. Defer shutdown to + // let cleanup finish. + gChildThread->message_loop()->PostTask(NewRunnableFunction( + "ShutdownTestEndpointOpensOpenedChild", + ShutdownTestEndpointOpensOpenedChild, this, GetTransport())); +} + +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestEndpointOpens.h b/ipc/ipdl/test/cxx/TestEndpointOpens.h new file mode 100644 index 0000000000..6721274937 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestEndpointOpens.h @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +#ifndef mozilla__ipdltest_TestEndpointOpens_h +#define mozilla__ipdltest_TestEndpointOpens_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestEndpointOpensParent.h" +#include "mozilla/_ipdltest/PTestEndpointOpensChild.h" + +#include "mozilla/_ipdltest2/PTestEndpointOpensOpenedParent.h" +#include "mozilla/_ipdltest2/PTestEndpointOpensOpenedChild.h" + +namespace mozilla { + +// parent process + +namespace _ipdltest { + +class TestEndpointOpensParent : public PTestEndpointOpensParent { + friend class PTestEndpointOpensParent; + + public: + TestEndpointOpensParent() {} + virtual ~TestEndpointOpensParent() {} + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return false; } + + void Main(); + + protected: + mozilla::ipc::IPCResult RecvStartSubprotocol( + mozilla::ipc::Endpoint<PTestEndpointOpensOpenedParent>&& endpoint); + + virtual void ActorDestroy(ActorDestroyReason why) override; +}; + +} // namespace _ipdltest + +namespace _ipdltest2 { + +class TestEndpointOpensOpenedParent : public PTestEndpointOpensOpenedParent { + friend class PTestEndpointOpensOpenedParent; + + public: + explicit TestEndpointOpensOpenedParent() {} + virtual ~TestEndpointOpensOpenedParent() {} + + protected: + mozilla::ipc::IPCResult RecvHello(); + mozilla::ipc::IPCResult RecvHelloSync(); + mozilla::ipc::IPCResult AnswerHelloRpc(); + + virtual void ActorDestroy(ActorDestroyReason why) override; +}; + +} // namespace _ipdltest2 + +// child process + +namespace _ipdltest { + +class TestEndpointOpensChild : public PTestEndpointOpensChild { + friend class PTestEndpointOpensChild; + + public: + TestEndpointOpensChild(); + virtual ~TestEndpointOpensChild() {} + + protected: + mozilla::ipc::IPCResult RecvStart(); + + virtual void ActorDestroy(ActorDestroyReason why) override; +}; + +} // namespace _ipdltest + +namespace _ipdltest2 { + +class TestEndpointOpensOpenedChild : public PTestEndpointOpensOpenedChild { + friend class PTestEndpointOpensOpenedChild; + + public: + explicit TestEndpointOpensOpenedChild() : mGotHi(false) {} + virtual ~TestEndpointOpensOpenedChild() {} + + protected: + mozilla::ipc::IPCResult RecvHi(); + mozilla::ipc::IPCResult AnswerHiRpc(); + + virtual void ActorDestroy(ActorDestroyReason why) override; + + bool mGotHi; +}; + +} // namespace _ipdltest2 + +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestEndpointOpens_h diff --git a/ipc/ipdl/test/cxx/TestFailedCtor.cpp b/ipc/ipdl/test/cxx/TestFailedCtor.cpp new file mode 100644 index 0000000000..2f08d2eeb0 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestFailedCtor.cpp @@ -0,0 +1,112 @@ +#include "TestFailedCtor.h" + +#include "IPDLUnitTests.h" // fail etc. + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent +void TestFailedCtorParent::Main() { + PTestFailedCtorSubParent* p = CallPTestFailedCtorSubConstructor(); + if (p) fail("expected ctor to fail"); + + Close(); +} + +PTestFailedCtorSubParent* +TestFailedCtorParent::AllocPTestFailedCtorSubParent() { + return new TestFailedCtorSubParent(); +} +bool TestFailedCtorParent::DeallocPTestFailedCtorSubParent( + PTestFailedCtorSubParent* actor) { + delete actor; + return true; +} + +PTestFailedCtorSubsubParent* +TestFailedCtorSubParent::AllocPTestFailedCtorSubsubParent() { + TestFailedCtorSubsub* a = new TestFailedCtorSubsub(); + if (!mOne) { + return mOne = a; + } else if (!mTwo) { + return mTwo = a; + } else if (!mThree) { + return mThree = a; + } else { + fail("unexpected Alloc()"); + return nullptr; + } +} +bool TestFailedCtorSubParent::DeallocPTestFailedCtorSubsubParent( + PTestFailedCtorSubsubParent* actor) { + static_cast<TestFailedCtorSubsub*>(actor)->mDealloced = true; + return true; +} + +void TestFailedCtorSubParent::ActorDestroy(ActorDestroyReason why) { + if (mOne->mWhy != Deletion) fail("Subsub one got wrong ActorDestroyReason"); + if (mTwo->mWhy != AncestorDeletion) + fail("Subsub two got wrong ActorDestroyReason"); + if (mThree->mWhy != AncestorDeletion) + fail("Subsub three got wrong ActorDestroyReason"); + + if (FailedConstructor != why) fail("unexpected destruction!"); +} + +TestFailedCtorSubParent::~TestFailedCtorSubParent() { + if (!(mOne->mDealloced && mTwo->mDealloced && mThree->mDealloced)) + fail("Not all subsubs were Dealloc'd"); + delete mOne; + delete mTwo; + delete mThree; +} + +//----------------------------------------------------------------------------- +// child + +PTestFailedCtorSubChild* TestFailedCtorChild::AllocPTestFailedCtorSubChild() { + return new TestFailedCtorSubChild(); +} + +mozilla::ipc::IPCResult +TestFailedCtorChild::AnswerPTestFailedCtorSubConstructor( + PTestFailedCtorSubChild* actor) { + PTestFailedCtorSubsubChild* c1 = + actor->SendPTestFailedCtorSubsubConstructor(); + PTestFailedCtorSubsubChild::Send__delete__(c1); + + if (!actor->SendPTestFailedCtorSubsubConstructor() || + !actor->SendPTestFailedCtorSubsubConstructor() || !actor->SendSync()) + fail("setting up test"); + + // This causes our process to die + return IPC_FAIL_NO_REASON(this); +} + +bool TestFailedCtorChild::DeallocPTestFailedCtorSubChild( + PTestFailedCtorSubChild* actor) { + delete actor; + return true; +} + +void TestFailedCtorChild::ProcessingError(Result aCode, const char* aReason) { + if (OtherPid() != base::GetCurrentProcId()) // thread-mode + _exit(0); +} + +PTestFailedCtorSubsubChild* +TestFailedCtorSubChild::AllocPTestFailedCtorSubsubChild() { + return new TestFailedCtorSubsub(); +} + +bool TestFailedCtorSubChild::DeallocPTestFailedCtorSubsubChild( + PTestFailedCtorSubsubChild* actor) { + delete actor; + return true; +} + +void TestFailedCtorSubChild::ActorDestroy(ActorDestroyReason why) {} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestFailedCtor.h b/ipc/ipdl/test/cxx/TestFailedCtor.h new file mode 100644 index 0000000000..6fac4eb278 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestFailedCtor.h @@ -0,0 +1,127 @@ +#ifndef mozilla_ipdltest_TestFailedCtor_h +#define mozilla_ipdltest_TestFailedCtor_h + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestFailedCtorParent.h" +#include "mozilla/_ipdltest/PTestFailedCtorChild.h" + +#include "mozilla/_ipdltest/PTestFailedCtorSubParent.h" +#include "mozilla/_ipdltest/PTestFailedCtorSubChild.h" + +#include "mozilla/_ipdltest/PTestFailedCtorSubsubParent.h" +#include "mozilla/_ipdltest/PTestFailedCtorSubsubChild.h" + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// Top-level +// +class TestFailedCtorParent : public PTestFailedCtorParent { + friend class PTestFailedCtorParent; + + public: + TestFailedCtorParent() {} + virtual ~TestFailedCtorParent() {} + + static bool RunTestInProcesses() { return true; } + + // FIXME/bug 703322 Disabled because child calls exit() to end + // test, not clear how to handle failed ctor in + // threaded mode. + static bool RunTestInThreads() { return false; } + + void Main(); + + protected: + PTestFailedCtorSubParent* AllocPTestFailedCtorSubParent(); + bool DeallocPTestFailedCtorSubParent(PTestFailedCtorSubParent* actor); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (AbnormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } +}; + +class TestFailedCtorChild : public PTestFailedCtorChild { + friend class PTestFailedCtorChild; + + public: + TestFailedCtorChild() {} + virtual ~TestFailedCtorChild() {} + + protected: + PTestFailedCtorSubChild* AllocPTestFailedCtorSubChild(); + + mozilla::ipc::IPCResult AnswerPTestFailedCtorSubConstructor( + PTestFailedCtorSubChild* actor) override; + + bool DeallocPTestFailedCtorSubChild(PTestFailedCtorSubChild* actor); + + virtual void ProcessingError(Result aCode, const char* aReason) override; + + virtual void ActorDestroy(ActorDestroyReason why) override { + fail("should have _exit()ed"); + } +}; + +//----------------------------------------------------------------------------- +// First descendent +// +class TestFailedCtorSubsub; + +class TestFailedCtorSubParent : public PTestFailedCtorSubParent { + friend class PTestFailedCtorSubParent; + + public: + TestFailedCtorSubParent() : mOne(nullptr), mTwo(nullptr), mThree(nullptr) {} + virtual ~TestFailedCtorSubParent(); + + protected: + PTestFailedCtorSubsubParent* AllocPTestFailedCtorSubsubParent(); + + bool DeallocPTestFailedCtorSubsubParent(PTestFailedCtorSubsubParent* actor); + mozilla::ipc::IPCResult RecvSync() { return IPC_OK(); } + + virtual void ActorDestroy(ActorDestroyReason why) override; + + TestFailedCtorSubsub* mOne; + TestFailedCtorSubsub* mTwo; + TestFailedCtorSubsub* mThree; +}; + +class TestFailedCtorSubChild : public PTestFailedCtorSubChild { + friend class PTestFailedCtorSubChild; + + public: + TestFailedCtorSubChild() {} + virtual ~TestFailedCtorSubChild() {} + + protected: + PTestFailedCtorSubsubChild* AllocPTestFailedCtorSubsubChild(); + bool DeallocPTestFailedCtorSubsubChild(PTestFailedCtorSubsubChild* actor); + + virtual void ActorDestroy(ActorDestroyReason why) override; +}; + +//----------------------------------------------------------------------------- +// Grand-descendent +// +class TestFailedCtorSubsub : public PTestFailedCtorSubsubParent, + public PTestFailedCtorSubsubChild { + public: + TestFailedCtorSubsub() : mWhy(ActorDestroyReason(-1)), mDealloced(false) {} + virtual ~TestFailedCtorSubsub() {} + + virtual void ActorDestroy(ActorDestroyReason why) override { mWhy = why; } + + ActorDestroyReason mWhy; + bool mDealloced; +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla_ipdltest_TestFailedCtor_h diff --git a/ipc/ipdl/test/cxx/TestHangs.cpp b/ipc/ipdl/test/cxx/TestHangs.cpp new file mode 100644 index 0000000000..2694cbfdab --- /dev/null +++ b/ipc/ipdl/test/cxx/TestHangs.cpp @@ -0,0 +1,127 @@ +#include "base/process_util.h" + +#include "TestHangs.h" + +#include "IPDLUnitTests.h" // fail etc. + +using base::KillProcess; + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +TestHangsParent::TestHangsParent() + : mDetectedHang(false), mNumAnswerStackFrame(0) { + MOZ_COUNT_CTOR(TestHangsParent); +} + +TestHangsParent::~TestHangsParent() { MOZ_COUNT_DTOR(TestHangsParent); } + +void TestHangsParent::Main() { + // Here we try to set things up to test the following sequence of events: + // + // - subprocess causes an OnMaybeDequeueOne() task to be posted to + // this thread + // + // - subprocess hangs just long enough for the hang timer to expire + // + // - hang-kill code in the parent starts running + // + // - subprocess replies to message while hang code runs + // + // - reply is processed in OnMaybeDequeueOne() before Close() has + // been called or the channel error notification has been posted + + // this tells the subprocess to send us Nonce() + if (!SendStart()) fail("sending Start"); + + // now we sleep here for a while awaiting the Nonce() message from + // the child. since we're not blocked on anything, the IO thread + // will enqueue an OnMaybeDequeueOne() task to process that + // message + // + // NB: PR_Sleep is exactly what we want, only the current thread + // sleeping + PR_Sleep(5000); + + // when we call into this, we'll pull the Nonce() message out of + // the mPending queue, but that doesn't matter ... the + // OnMaybeDequeueOne() event will remain + if (CallStackFrame() && mDetectedHang) fail("should have timed out!"); + + // the Close() task in the queue will shut us down +} + +bool TestHangsParent::ShouldContinueFromReplyTimeout() { + mDetectedHang = true; + + // so we've detected a timeout after 2 ms ... now we cheat and + // sleep for a long time, to allow the subprocess's reply to come + // in + + PR_Sleep(5000); + + // reply should be here; we'll post a task to shut things down. + // This must be after OnMaybeDequeueOne() in the event queue. + MessageLoop::current()->PostTask(NewNonOwningRunnableMethod( + "_ipdltest::TestHangsParent::CleanUp", this, &TestHangsParent::CleanUp)); + + GetIPCChannel()->CloseWithTimeout(); + + return false; +} + +mozilla::ipc::IPCResult TestHangsParent::AnswerStackFrame() { + ++mNumAnswerStackFrame; + + if (mNumAnswerStackFrame == 1) { + if (CallStackFrame()) { + fail("should have timed out!"); + } + } else if (mNumAnswerStackFrame == 2) { + // minimum possible, 2 ms. We want to detecting a hang to race + // with the reply coming in, as reliably as possible + SetReplyTimeoutMs(2); + + if (CallHang()) fail("should have timed out!"); + } else { + fail("unexpected state"); + } + + return IPC_OK(); +} + +void TestHangsParent::CleanUp() { + ipc::ScopedProcessHandle otherProcessHandle; + if (!base::OpenProcessHandle(OtherPid(), &otherProcessHandle.rwget())) { + fail("couldn't open child process"); + } else { + if (!KillProcess(otherProcessHandle, 0, false)) { + fail("terminating child process"); + } + } + Close(); +} + +//----------------------------------------------------------------------------- +// child + +TestHangsChild::TestHangsChild() { MOZ_COUNT_CTOR(TestHangsChild); } + +TestHangsChild::~TestHangsChild() { MOZ_COUNT_DTOR(TestHangsChild); } + +mozilla::ipc::IPCResult TestHangsChild::AnswerHang() { + puts(" (child process is 'hanging' now)"); + + // just sleep until we're reasonably confident the 1ms hang + // detector fired in the parent process and it's sleeping in + // ShouldContinueFromReplyTimeout() + PR_Sleep(1000); + + return IPC_OK(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestHangs.h b/ipc/ipdl/test/cxx/TestHangs.h new file mode 100644 index 0000000000..a3dfb991f5 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestHangs.h @@ -0,0 +1,75 @@ +#ifndef mozilla__ipdltest_TestHangs_h +#define mozilla__ipdltest_TestHangs_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestHangsParent.h" +#include "mozilla/_ipdltest/PTestHangsChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestHangsParent : public PTestHangsParent { + friend class PTestHangsParent; + + public: + TestHangsParent(); + virtual ~TestHangsParent(); + + static bool RunTestInProcesses() { return true; } + + // FIXME/bug 703320 Disabled because parent kills child proc, not + // clear how that should work in threads. + static bool RunTestInThreads() { return false; } + + void Main(); + + protected: + virtual bool ShouldContinueFromReplyTimeout() override; + + mozilla::ipc::IPCResult RecvNonce() { return IPC_OK(); } + + mozilla::ipc::IPCResult AnswerStackFrame(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (AbnormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } + + void CleanUp(); + + bool mDetectedHang; + int32_t mNumAnswerStackFrame; +}; + +class TestHangsChild : public PTestHangsChild { + friend class PTestHangsChild; + + public: + TestHangsChild(); + virtual ~TestHangsChild(); + + protected: + mozilla::ipc::IPCResult RecvStart() { + if (!SendNonce()) fail("sending Nonce"); + return IPC_OK(); + } + + mozilla::ipc::IPCResult AnswerStackFrame() { + if (CallStackFrame()) fail("should have failed"); + return IPC_OK(); + } + + mozilla::ipc::IPCResult AnswerHang(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (AbnormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestHangs_h diff --git a/ipc/ipdl/test/cxx/TestHighestPrio.cpp b/ipc/ipdl/test/cxx/TestHighestPrio.cpp new file mode 100644 index 0000000000..e6715fba8e --- /dev/null +++ b/ipc/ipdl/test/cxx/TestHighestPrio.cpp @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=2 ts=4 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/. */ + +#include "TestHighestPrio.h" + +#include "IPDLUnitTests.h" // fail etc. +#if defined(OS_POSIX) +# include <unistd.h> +#else +# include <windows.h> +#endif + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +TestHighestPrioParent::TestHighestPrioParent() : msg_num_(0) { + MOZ_COUNT_CTOR(TestHighestPrioParent); +} + +TestHighestPrioParent::~TestHighestPrioParent() { + MOZ_COUNT_DTOR(TestHighestPrioParent); +} + +void TestHighestPrioParent::Main() { + if (!SendStart()) fail("sending Start"); +} + +mozilla::ipc::IPCResult TestHighestPrioParent::RecvMsg1() { + MOZ_ASSERT(msg_num_ == 0); + msg_num_ = 1; + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestHighestPrioParent::RecvMsg2() { + MOZ_ASSERT(msg_num_ == 1); + msg_num_ = 2; + + if (!SendStartInner()) fail("sending StartInner"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestHighestPrioParent::RecvMsg3() { + MOZ_ASSERT(msg_num_ == 2); + msg_num_ = 3; + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestHighestPrioParent::RecvMsg4() { + MOZ_ASSERT(msg_num_ == 3); + msg_num_ = 4; + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// child + +TestHighestPrioChild::TestHighestPrioChild() { + MOZ_COUNT_CTOR(TestHighestPrioChild); +} + +TestHighestPrioChild::~TestHighestPrioChild() { + MOZ_COUNT_DTOR(TestHighestPrioChild); +} + +mozilla::ipc::IPCResult TestHighestPrioChild::RecvStart() { + if (!SendMsg1()) fail("sending Msg1"); + + if (!SendMsg2()) fail("sending Msg2"); + + Close(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestHighestPrioChild::RecvStartInner() { + if (!SendMsg3()) fail("sending Msg3"); + + if (!SendMsg4()) fail("sending Msg4"); + + return IPC_OK(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestHighestPrio.h b/ipc/ipdl/test/cxx/TestHighestPrio.h new file mode 100644 index 0000000000..34563d7708 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestHighestPrio.h @@ -0,0 +1,55 @@ +#ifndef mozilla__ipdltest_TestHighestPrio_h +#define mozilla__ipdltest_TestHighestPrio_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestHighestPrioParent.h" +#include "mozilla/_ipdltest/PTestHighestPrioChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestHighestPrioParent : public PTestHighestPrioParent { + public: + TestHighestPrioParent(); + virtual ~TestHighestPrioParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return false; } + + void Main(); + + mozilla::ipc::IPCResult RecvMsg1(); + mozilla::ipc::IPCResult RecvMsg2(); + mozilla::ipc::IPCResult RecvMsg3(); + mozilla::ipc::IPCResult RecvMsg4(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + if (msg_num_ != 4) fail("missed IPC call"); + passed("ok"); + QuitParent(); + } + + private: + int msg_num_; +}; + +class TestHighestPrioChild : public PTestHighestPrioChild { + public: + TestHighestPrioChild(); + virtual ~TestHighestPrioChild(); + + mozilla::ipc::IPCResult RecvStart(); + mozilla::ipc::IPCResult RecvStartInner(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestHighestPrio_h diff --git a/ipc/ipdl/test/cxx/TestInterruptErrorCleanup.cpp b/ipc/ipdl/test/cxx/TestInterruptErrorCleanup.cpp new file mode 100644 index 0000000000..3af7ad620d --- /dev/null +++ b/ipc/ipdl/test/cxx/TestInterruptErrorCleanup.cpp @@ -0,0 +1,138 @@ +#include "TestInterruptErrorCleanup.h" + +#include "base/task.h" +#include "mozilla/CondVar.h" +#include "mozilla/Mutex.h" + +#include "IPDLUnitTests.h" // fail etc. +#include "IPDLUnitTestSubprocess.h" + +using mozilla::CondVar; +using mozilla::Mutex; +using mozilla::MutexAutoLock; + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +namespace { + +// NB: this test does its own shutdown, rather than going through +// QuitParent(), because it's testing degenerate edge cases + +void DeleteSubprocess(Mutex* mutex, CondVar* cvar) { + MutexAutoLock lock(*mutex); + + gSubprocess->Destroy(); + gSubprocess = nullptr; + + cvar->Notify(); +} + +void DeleteTheWorld() { + delete static_cast<TestInterruptErrorCleanupParent*>(gParentActor); + gParentActor = nullptr; + + // needs to be synchronous to avoid affecting event ordering on + // the main thread + Mutex mutex MOZ_UNANNOTATED("TestInterruptErrorCleanup.DeleteTheWorld.mutex"); + CondVar cvar(mutex, "TestInterruptErrorCleanup.DeleteTheWorld.cvar"); + + MutexAutoLock lock(mutex); + + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction("DeleteSubprocess", DeleteSubprocess, &mutex, &cvar)); + + cvar.Wait(); +} + +void Done() { + static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); + nsCOMPtr<nsIAppShell> appShell(do_GetService(kAppShellCID)); + appShell->Exit(); + + passed(__FILE__); +} + +} // namespace + +TestInterruptErrorCleanupParent::TestInterruptErrorCleanupParent() + : mGotProcessingError(false) { + MOZ_COUNT_CTOR(TestInterruptErrorCleanupParent); +} + +TestInterruptErrorCleanupParent::~TestInterruptErrorCleanupParent() { + MOZ_COUNT_DTOR(TestInterruptErrorCleanupParent); +} + +void TestInterruptErrorCleanupParent::Main() { + // This test models the following sequence of events + // + // (1) Parent: Interrupt out-call + // (2) Child: crash + // --[Parent-only hereafter]-- + // (3) Interrupt out-call return false + // (4) Close() + // --[event loop]-- + // (5) delete parentActor + // (6) delete childProcess + // --[event loop]-- + // (7) Channel::OnError notification + // --[event loop]-- + // (8) Done, quit + // + // See bug 535298 and friends; this seqeunce of events captures + // three differnent potential errors + // - Close()-after-error (semantic error previously) + // - use-after-free of parentActor + // - use-after-free of channel + // + // Because of legacy constraints related to nsNPAPI* code, we need + // to ensure that this sequence of events can occur without + // errors/crashes. + + MessageLoop::current()->PostTask( + NewRunnableFunction("DeleteTheWorld", DeleteTheWorld)); + + // it's a failure if this *succeeds* + if (CallError()) fail("expected an error!"); + + if (!mGotProcessingError) fail("expected a ProcessingError() notification"); + + // it's OK to Close() a channel after an error, because nsNPAPI* + // wants to do this + Close(); + + // we know that this event *must* be after the MaybeError + // notification enqueued by AsyncChannel, because that event is + // enqueued within the same mutex that ends up signaling the + // wakeup-on-error of |CallError()| above + MessageLoop::current()->PostTask(NewRunnableFunction("Done", Done)); +} + +void TestInterruptErrorCleanupParent::ProcessingError(Result aCode, + const char* aReason) { + if (aCode != MsgDropped) fail("unexpected processing error"); + mGotProcessingError = true; +} + +//----------------------------------------------------------------------------- +// child + +TestInterruptErrorCleanupChild::TestInterruptErrorCleanupChild() { + MOZ_COUNT_CTOR(TestInterruptErrorCleanupChild); +} + +TestInterruptErrorCleanupChild::~TestInterruptErrorCleanupChild() { + MOZ_COUNT_DTOR(TestInterruptErrorCleanupChild); +} + +mozilla::ipc::IPCResult TestInterruptErrorCleanupChild::AnswerError() { + _exit(0); + MOZ_CRASH("unreached"); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestInterruptErrorCleanup.h b/ipc/ipdl/test/cxx/TestInterruptErrorCleanup.h new file mode 100644 index 0000000000..9ae0493cf0 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestInterruptErrorCleanup.h @@ -0,0 +1,52 @@ +#ifndef mozilla__ipdltest_TestInterruptErrorCleanup_h +#define mozilla__ipdltest_TestInterruptErrorCleanup_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestInterruptErrorCleanupParent.h" +#include "mozilla/_ipdltest/PTestInterruptErrorCleanupChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestInterruptErrorCleanupParent + : public PTestInterruptErrorCleanupParent { + public: + TestInterruptErrorCleanupParent(); + virtual ~TestInterruptErrorCleanupParent(); + + static bool RunTestInProcesses() { return true; } + // FIXME/bug 703323 Could work if modified + static bool RunTestInThreads() { return false; } + + void Main(); + + protected: + virtual void ActorDestroy(ActorDestroyReason why) override { + if (AbnormalShutdown != why) fail("unexpected destruction!"); + } + + virtual void ProcessingError(Result aCode, const char* aReason) override; + + bool mGotProcessingError; +}; + +class TestInterruptErrorCleanupChild : public PTestInterruptErrorCleanupChild { + friend class PTestInterruptErrorCleanupChild; + + public: + TestInterruptErrorCleanupChild(); + virtual ~TestInterruptErrorCleanupChild(); + + protected: + mozilla::ipc::IPCResult AnswerError(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + fail("should have 'crashed'!"); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestInterruptErrorCleanup_h diff --git a/ipc/ipdl/test/cxx/TestInterruptRaces.cpp b/ipc/ipdl/test/cxx/TestInterruptRaces.cpp new file mode 100644 index 0000000000..02b42cfbc3 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestInterruptRaces.cpp @@ -0,0 +1,169 @@ +#include "TestInterruptRaces.h" + +#include "IPDLUnitTests.h" // fail etc. + +using mozilla::ipc::MessageChannel; + +namespace mozilla { +namespace _ipdltest { + +ipc::RacyInterruptPolicy MediateRace(const MessageChannel::MessageInfo& parent, + const MessageChannel::MessageInfo& child) { + return (PTestInterruptRaces::Msg_Child__ID == parent.type()) + ? ipc::RIPParentWins + : ipc::RIPChildWins; +} + +//----------------------------------------------------------------------------- +// parent +void TestInterruptRacesParent::Main() { + if (!SendStart()) fail("sending Start()"); +} + +mozilla::ipc::IPCResult TestInterruptRacesParent::RecvStartRace() { + MessageLoop::current()->PostTask(NewNonOwningRunnableMethod( + "_ipdltest::TestInterruptRacesParent::OnRaceTime", this, + &TestInterruptRacesParent::OnRaceTime)); + return IPC_OK(); +} + +void TestInterruptRacesParent::OnRaceTime() { + if (!CallRace(&mChildHasReply)) fail("problem calling Race()"); + + if (!mChildHasReply) fail("child should have got a reply already"); + + mHasReply = true; + + MessageLoop::current()->PostTask( + NewNonOwningRunnableMethod("_ipdltest::TestInterruptRacesParent::Test2", + this, &TestInterruptRacesParent::Test2)); +} + +mozilla::ipc::IPCResult TestInterruptRacesParent::AnswerRace(bool* hasReply) { + if (mHasReply) fail("apparently the parent won the Interrupt race!"); + *hasReply = hasReply; + return IPC_OK(); +} + +void TestInterruptRacesParent::Test2() { + puts(" passed"); + puts("Test 2"); + + mHasReply = false; + mChildHasReply = false; + + if (!CallStackFrame()) fail("can't set up a stack frame"); + + puts(" passed"); + + MessageLoop::current()->PostTask( + NewNonOwningRunnableMethod("_ipdltest::TestInterruptRacesParent::Test3", + this, &TestInterruptRacesParent::Test3)); +} + +mozilla::ipc::IPCResult TestInterruptRacesParent::AnswerStackFrame() { + if (!SendWakeup()) fail("can't wake up the child"); + + if (!CallRace(&mChildHasReply)) fail("can't set up race condition"); + mHasReply = true; + + if (!mChildHasReply) fail("child should have got a reply already"); + + return IPC_OK(); +} + +void TestInterruptRacesParent::Test3() { + puts("Test 3"); + + if (!CallStackFrame3()) fail("can't set up a stack frame"); + + puts(" passed"); + + Close(); +} + +mozilla::ipc::IPCResult TestInterruptRacesParent::AnswerStackFrame3() { + if (!SendWakeup3()) fail("can't wake up the child"); + + if (!CallChild()) fail("can't set up race condition"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestInterruptRacesParent::AnswerParent() { + mAnsweredParent = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestInterruptRacesParent::RecvGetAnsweredParent( + bool* answeredParent) { + *answeredParent = mAnsweredParent; + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// child +mozilla::ipc::IPCResult TestInterruptRacesChild::RecvStart() { + puts("Test 1"); + + if (!SendStartRace()) fail("problem sending StartRace()"); + + bool dontcare; + if (!CallRace(&dontcare)) fail("problem calling Race()"); + + mHasReply = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestInterruptRacesChild::AnswerRace(bool* hasReply) { + if (!mHasReply) fail("apparently the child lost the Interrupt race!"); + + *hasReply = mHasReply; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestInterruptRacesChild::AnswerStackFrame() { + // reset for the second test + mHasReply = false; + + if (!CallStackFrame()) fail("can't set up stack frame"); + + if (!mHasReply) fail("should have had reply by now"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestInterruptRacesChild::RecvWakeup() { + bool dontcare; + if (!CallRace(&dontcare)) fail("can't set up race condition"); + + mHasReply = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestInterruptRacesChild::AnswerStackFrame3() { + if (!CallStackFrame3()) fail("can't set up stack frame"); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestInterruptRacesChild::RecvWakeup3() { + if (!CallParent()) fail("can't set up race condition"); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestInterruptRacesChild::AnswerChild() { + bool parentAnsweredParent; + // the parent is supposed to win the race, which means its + // message, Child(), is supposed to be processed before the + // child's message, Parent() + if (!SendGetAnsweredParent(&parentAnsweredParent)) + fail("sending GetAnsweredParent"); + + if (parentAnsweredParent) fail("parent was supposed to win the race!"); + + return IPC_OK(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestInterruptRaces.h b/ipc/ipdl/test/cxx/TestInterruptRaces.h new file mode 100644 index 0000000000..6fa184da1a --- /dev/null +++ b/ipc/ipdl/test/cxx/TestInterruptRaces.h @@ -0,0 +1,104 @@ +#ifndef mozilla__ipdltest_TestInterruptRaces_h +#define mozilla__ipdltest_TestInterruptRaces_h + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestInterruptRacesParent.h" +#include "mozilla/_ipdltest/PTestInterruptRacesChild.h" + +namespace mozilla { +namespace _ipdltest { + +mozilla::ipc::RacyInterruptPolicy MediateRace( + const mozilla::ipc::MessageChannel::MessageInfo& parent, + const mozilla::ipc::MessageChannel::MessageInfo& child); + +class TestInterruptRacesParent : public PTestInterruptRacesParent { + friend class PTestInterruptRacesParent; + + public: + TestInterruptRacesParent() + : mHasReply(false), mChildHasReply(false), mAnsweredParent(false) {} + virtual ~TestInterruptRacesParent() {} + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + mozilla::ipc::IPCResult RecvStartRace(); + + mozilla::ipc::IPCResult AnswerRace(bool* hasRace); + + mozilla::ipc::IPCResult AnswerStackFrame(); + + mozilla::ipc::IPCResult AnswerStackFrame3(); + + mozilla::ipc::IPCResult AnswerParent(); + + mozilla::ipc::IPCResult RecvGetAnsweredParent(bool* answeredParent); + + mozilla::ipc::RacyInterruptPolicy MediateInterruptRace( + const MessageInfo& parent, const MessageInfo& child) override { + return MediateRace(parent, child); + } + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + if (!(mHasReply && mChildHasReply)) fail("both sides should have replies!"); + passed("ok"); + QuitParent(); + } + + private: + void OnRaceTime(); + + void Test2(); + void Test3(); + + bool mHasReply; + bool mChildHasReply; + bool mAnsweredParent; +}; + +class TestInterruptRacesChild : public PTestInterruptRacesChild { + friend class PTestInterruptRacesChild; + + public: + TestInterruptRacesChild() : mHasReply(false) {} + virtual ~TestInterruptRacesChild() {} + + protected: + mozilla::ipc::IPCResult RecvStart(); + + mozilla::ipc::IPCResult AnswerRace(bool* hasRace); + + mozilla::ipc::IPCResult AnswerStackFrame(); + + mozilla::ipc::IPCResult AnswerStackFrame3(); + + mozilla::ipc::IPCResult RecvWakeup(); + + mozilla::ipc::IPCResult RecvWakeup3(); + + mozilla::ipc::IPCResult AnswerChild(); + + virtual mozilla::ipc::RacyInterruptPolicy MediateInterruptRace( + const MessageInfo& parent, const MessageInfo& child) override { + return MediateRace(parent, child); + } + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } + + private: + bool mHasReply; +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestInterruptRaces_h diff --git a/ipc/ipdl/test/cxx/TestInterruptShutdownRace.cpp b/ipc/ipdl/test/cxx/TestInterruptShutdownRace.cpp new file mode 100644 index 0000000000..5482893995 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestInterruptShutdownRace.cpp @@ -0,0 +1,111 @@ +#include "TestInterruptShutdownRace.h" + +#include "base/task.h" +#include "IPDLUnitTests.h" // fail etc. +#include "IPDLUnitTestSubprocess.h" + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +namespace { + +// NB: this test does its own shutdown, rather than going through +// QuitParent(), because it's testing degenerate edge cases + +void DeleteSubprocess() { + gSubprocess->Destroy(); + gSubprocess = nullptr; +} + +void Done() { + passed(__FILE__); + QuitParent(); +} + +} // namespace + +TestInterruptShutdownRaceParent::TestInterruptShutdownRaceParent() { + MOZ_COUNT_CTOR(TestInterruptShutdownRaceParent); +} + +TestInterruptShutdownRaceParent::~TestInterruptShutdownRaceParent() { + MOZ_COUNT_DTOR(TestInterruptShutdownRaceParent); +} + +void TestInterruptShutdownRaceParent::Main() { + if (!SendStart()) fail("sending Start"); +} + +mozilla::ipc::IPCResult TestInterruptShutdownRaceParent::RecvStartDeath() { + // this will be ordered before the OnMaybeDequeueOne event of + // Orphan in the queue + MessageLoop::current()->PostTask(NewNonOwningRunnableMethod( + "_ipdltest::TestInterruptShutdownRaceParent::StartShuttingDown", this, + &TestInterruptShutdownRaceParent::StartShuttingDown)); + return IPC_OK(); +} + +void TestInterruptShutdownRaceParent::StartShuttingDown() { + // NB: we sleep here to try and avoid receiving the Orphan message + // while waiting for the CallExit() reply. if we fail at that, it + // will cause the test to pass spuriously, because there won't be + // an OnMaybeDequeueOne task for Orphan + PR_Sleep(2000); + + if (CallExit()) fail("connection was supposed to be interrupted"); + + Close(); + + delete static_cast<TestInterruptShutdownRaceParent*>(gParentActor); + gParentActor = nullptr; + + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction("DeleteSubprocess", DeleteSubprocess)); + + // this is ordered after the OnMaybeDequeueOne event in the queue + MessageLoop::current()->PostTask(NewRunnableFunction("Done", Done)); + + // |this| has been deleted, be mindful +} + +mozilla::ipc::IPCResult TestInterruptShutdownRaceParent::RecvOrphan() { + // it would be nice to fail() here, but we'll process this message + // while waiting for the reply CallExit(). The OnMaybeDequeueOne + // task will still be in the queue, it just wouldn't have had any + // work to do, if we hadn't deleted ourself + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// child + +TestInterruptShutdownRaceChild::TestInterruptShutdownRaceChild() { + MOZ_COUNT_CTOR(TestInterruptShutdownRaceChild); +} + +TestInterruptShutdownRaceChild::~TestInterruptShutdownRaceChild() { + MOZ_COUNT_DTOR(TestInterruptShutdownRaceChild); +} + +mozilla::ipc::IPCResult TestInterruptShutdownRaceChild::RecvStart() { + if (!SendStartDeath()) fail("sending StartDeath"); + + // See comment in StartShuttingDown(): we want to send Orphan() + // while the parent is in its PR_Sleep() + PR_Sleep(1000); + + if (!SendOrphan()) fail("sending Orphan"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestInterruptShutdownRaceChild::AnswerExit() { + _exit(0); + MOZ_CRASH("unreached"); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestInterruptShutdownRace.h b/ipc/ipdl/test/cxx/TestInterruptShutdownRace.h new file mode 100644 index 0000000000..17425fc06c --- /dev/null +++ b/ipc/ipdl/test/cxx/TestInterruptShutdownRace.h @@ -0,0 +1,56 @@ +#ifndef mozilla__ipdltest_TestInterruptShutdownRace_h +#define mozilla__ipdltest_TestInterruptShutdownRace_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestInterruptShutdownRaceParent.h" +#include "mozilla/_ipdltest/PTestInterruptShutdownRaceChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestInterruptShutdownRaceParent + : public PTestInterruptShutdownRaceParent { + public: + TestInterruptShutdownRaceParent(); + virtual ~TestInterruptShutdownRaceParent(); + + static bool RunTestInProcesses() { return true; } + // FIXME/bug 703323 Could work if modified + static bool RunTestInThreads() { return false; } + + void Main(); + + mozilla::ipc::IPCResult RecvStartDeath(); + + mozilla::ipc::IPCResult RecvOrphan(); + + protected: + void StartShuttingDown(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (AbnormalShutdown != why) fail("unexpected destruction!"); + } +}; + +class TestInterruptShutdownRaceChild : public PTestInterruptShutdownRaceChild { + friend class PTestInterruptShutdownRaceChild; + + public: + TestInterruptShutdownRaceChild(); + virtual ~TestInterruptShutdownRaceChild(); + + protected: + mozilla::ipc::IPCResult RecvStart(); + + mozilla::ipc::IPCResult AnswerExit(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + fail("should have 'crashed'!"); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestInterruptShutdownRace_h diff --git a/ipc/ipdl/test/cxx/TestJSON.cpp b/ipc/ipdl/test/cxx/TestJSON.cpp new file mode 100644 index 0000000000..42fac919d1 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestJSON.cpp @@ -0,0 +1,113 @@ +#include "TestJSON.h" + +#include "IPDLUnitTests.h" // fail etc. + +#define test_assert(_cond, _msg) \ + if (!(_cond)) fail(_msg) + +namespace mozilla { +namespace _ipdltest { + +static nsString String(const char* const str) { + return NS_ConvertUTF8toUTF16(str); +} + +static void Array123(nsTArray<JSONVariant>& a123) { + a123.AppendElement(1); + a123.AppendElement(2); + a123.AppendElement(3); + + test_assert(a123 == a123, "operator== is broken"); +} + +template <class HandleT> +JSONVariant MakeTestVariant(HandleT* handle) { + // In JS syntax: + // + // return [ + // undefined, null, true, 1.25, "test string", + // handle, + // [ 1, 2, 3 ], + // { "undefined" : undefined, + // "null" : null, + // "true" : true, + // "1.25" : 1.25, + // "string" : "string" + // "handle" : handle, + // "array" : [ 1, 2, 3 ] + // } + // ] + // + nsTArray<JSONVariant> outer; + + outer.AppendElement(void_t()); + outer.AppendElement(null_t()); + outer.AppendElement(true); + outer.AppendElement(1.25); + outer.AppendElement(String("test string")); + + outer.AppendElement(handle); + + nsTArray<JSONVariant> tmp; + Array123(tmp); + outer.AppendElement(tmp); + + nsTArray<KeyValue> obj; + obj.AppendElement(KeyValue(String("undefined"), void_t())); + obj.AppendElement(KeyValue(String("null"), null_t())); + obj.AppendElement(KeyValue(String("true"), true)); + obj.AppendElement(KeyValue(String("1.25"), 1.25)); + obj.AppendElement(KeyValue(String("string"), String("value"))); + obj.AppendElement(KeyValue(String("handle"), handle)); + nsTArray<JSONVariant> tmp2; + Array123(tmp2); + obj.AppendElement(KeyValue(String("array"), tmp2)); + + outer.AppendElement(obj); + + test_assert(outer == outer, "operator== is broken"); + + return JSONVariant(outer); +} + +//----------------------------------------------------------------------------- +// parent + +void TestJSONParent::Main() { + if (!SendStart()) fail("sending Start"); +} + +mozilla::ipc::IPCResult TestJSONParent::RecvTest(const JSONVariant& i, + JSONVariant* o) { + test_assert(i == i, "operator== is broken"); + test_assert(i == MakeTestVariant(mKid), "inparam mangled en route"); + + *o = i; + + test_assert(i == *o, "operator= is broken"); + + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// child + +mozilla::ipc::IPCResult TestJSONChild::RecvStart() { + if (!SendPTestHandleConstructor()) fail("sending Handle ctor"); + + JSONVariant i(MakeTestVariant(mKid)); + test_assert(i == i, "operator== is broken"); + test_assert(i == MakeTestVariant(mKid), "copy ctor is broken"); + + JSONVariant o; + if (!SendTest(i, &o)) fail("sending Test"); + + test_assert(i == o, "round-trip mangled input data"); + test_assert(o == MakeTestVariant(mKid), "outparam mangled en route"); + + Close(); + return IPC_OK(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestJSON.h b/ipc/ipdl/test/cxx/TestJSON.h new file mode 100644 index 0000000000..eaf7516525 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestJSON.h @@ -0,0 +1,93 @@ +#ifndef mozilla__ipdltest_TestJSON_h +#define mozilla__ipdltest_TestJSON_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestJSONParent.h" +#include "mozilla/_ipdltest/PTestJSONChild.h" + +#include "mozilla/_ipdltest/PTestHandleParent.h" +#include "mozilla/_ipdltest/PTestHandleChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestHandleParent : public PTestHandleParent { + public: + TestHandleParent() {} + virtual ~TestHandleParent() {} + + protected: + virtual void ActorDestroy(ActorDestroyReason why) override {} +}; + +class TestJSONParent : public PTestJSONParent { + friend class PTestJSONParent; + + public: + TestJSONParent() {} + virtual ~TestJSONParent() {} + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + mozilla::ipc::IPCResult RecvTest(const JSONVariant& i, JSONVariant* o); + + PTestHandleParent* AllocPTestHandleParent() { + return mKid = new TestHandleParent(); + } + + bool DeallocPTestHandleParent(PTestHandleParent* actor) { + delete actor; + return true; + } + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } + + PTestHandleParent* mKid; +}; + +class TestHandleChild : public PTestHandleChild { + public: + TestHandleChild() {} + virtual ~TestHandleChild() {} +}; + +class TestJSONChild : public PTestJSONChild { + friend class PTestJSONChild; + + public: + TestJSONChild() {} + virtual ~TestJSONChild() {} + + protected: + mozilla::ipc::IPCResult RecvStart(); + + PTestHandleChild* AllocPTestHandleChild() { + return mKid = new TestHandleChild(); + } + + bool DeallocPTestHandleChild(PTestHandleChild* actor) { + delete actor; + return true; + } + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } + + PTestHandleChild* mKid; +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestJSON_h diff --git a/ipc/ipdl/test/cxx/TestLatency.cpp b/ipc/ipdl/test/cxx/TestLatency.cpp new file mode 100644 index 0000000000..f88d8ea644 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestLatency.cpp @@ -0,0 +1,210 @@ +#include "TestLatency.h" + +#include "IPDLUnitTests.h" // fail etc. + +// A ping/pong trial takes O(100us) or more, so if we don't have 10us +// resolution or better, the results will not be terribly useful +static const double kTimingResolutionCutoff = 0.00001; // 10us + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +TestLatencyParent::TestLatencyParent() + : mStart(), + mPPTimeTotal(), + mPP5TimeTotal(), + mRpcTimeTotal(), + mPPTrialsToGo(NR_TRIALS), + mPP5TrialsToGo(NR_TRIALS), + mNumChildProcessedCompressedSpams(0), + mWhichPong5(0) { + MOZ_COUNT_CTOR(TestLatencyParent); +} + +TestLatencyParent::~TestLatencyParent() { MOZ_COUNT_DTOR(TestLatencyParent); } + +void TestLatencyParent::Main() { + TimeDuration resolution = TimeDuration::Resolution(); + if (resolution.ToSeconds() > kTimingResolutionCutoff) { + puts(" (skipping TestLatency, timing resolution is too poor)"); + Close(); + return; + } + + printf(" timing resolution: %g seconds\n", resolution.ToSecondsSigDigits()); + + if (mozilla::ipc::LoggingEnabled()) + MOZ_CRASH( + "you really don't want to log all IPC messages during this test, trust " + "me"); + + PingPongTrial(); +} + +void TestLatencyParent::PingPongTrial() { + mStart = TimeStamp::Now(); + if (!SendPing()) fail("sending Ping()"); +} + +void TestLatencyParent::Ping5Pong5Trial() { + mStart = TimeStamp::Now(); + + if (!SendPing5() || !SendPing5() || !SendPing5() || !SendPing5() || + !SendPing5()) + fail("sending Ping5()"); +} + +mozilla::ipc::IPCResult TestLatencyParent::RecvPong() { + TimeDuration thisTrial = (TimeStamp::Now() - mStart); + mPPTimeTotal += thisTrial; + + if (0 == (mPPTrialsToGo % 1000)) + printf(" PP trial %d: %g\n", mPPTrialsToGo, + thisTrial.ToSecondsSigDigits()); + + if (--mPPTrialsToGo > 0) + PingPongTrial(); + else + Ping5Pong5Trial(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestLatencyParent::RecvPong5() { + ++mWhichPong5; + + if (mWhichPong5 < 5) { + return IPC_OK(); + } + + mWhichPong5 = 0; + + TimeDuration thisTrial = (TimeStamp::Now() - mStart); + mPP5TimeTotal += thisTrial; + + if (0 == (mPP5TrialsToGo % 1000)) + printf(" PP5 trial %d: %g\n", mPP5TrialsToGo, + thisTrial.ToSecondsSigDigits()); + + if (0 < --mPP5TrialsToGo) + Ping5Pong5Trial(); + else + RpcTrials(); + + return IPC_OK(); +} + +void TestLatencyParent::RpcTrials() { + TimeStamp start = TimeStamp::Now(); + for (int i = 0; i < NR_TRIALS; ++i) { + if (!CallRpc()) fail("can't call Rpc()"); + if (0 == (i % 1000)) printf(" Rpc trial %d\n", i); + } + mRpcTimeTotal = (TimeStamp::Now() - start); + + SpamTrial(); +} + +void TestLatencyParent::SpamTrial() { + TimeStamp start = TimeStamp::Now(); + for (int i = 0; i < NR_SPAMS - 1; ++i) { + if (!SendSpam()) fail("sending Spam()"); + if (0 == (i % 10000)) printf(" Spam trial %d\n", i); + } + + // Synchronize with the child process to ensure all messages have + // been processed. This adds the overhead of a reply message from + // child-->here, but should be insignificant compared to >> + // NR_SPAMS. + if (!CallSynchro()) fail("calling Synchro()"); + + mSpamTimeTotal = (TimeStamp::Now() - start); + + CompressedSpamTrial(); +} + +void TestLatencyParent::CompressedSpamTrial() { + for (int i = 0; i < NR_SPAMS; ++i) { + if (!SendCompressedSpam(i + 1)) fail("sending CompressedSpam()"); + if (0 == (i % 10000)) printf(" CompressedSpam trial %d\n", i); + } + + uint32_t lastSeqno; + if (!CallSynchro2(&lastSeqno, &mNumChildProcessedCompressedSpams)) + fail("calling Synchro2()"); + + if (lastSeqno != NR_SPAMS) + fail("last seqno was %u, expected %u", lastSeqno, NR_SPAMS); + + // NB: since this is testing an optimization, it's somewhat bogus. + // Need to make a warning if it actually intermittently fails in + // practice, which is doubtful. + if (!(mNumChildProcessedCompressedSpams < NR_SPAMS)) + fail("Didn't compress any messages?"); + + Exit(); +} + +void TestLatencyParent::Exit() { Close(); } + +//----------------------------------------------------------------------------- +// child + +TestLatencyChild::TestLatencyChild() + : mLastSeqno(0), mNumProcessedCompressedSpams(0), mWhichPing5(0) { + MOZ_COUNT_CTOR(TestLatencyChild); +} + +TestLatencyChild::~TestLatencyChild() { MOZ_COUNT_DTOR(TestLatencyChild); } + +mozilla::ipc::IPCResult TestLatencyChild::RecvPing() { + SendPong(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestLatencyChild::RecvPing5() { + ++mWhichPing5; + + if (mWhichPing5 < 5) { + return IPC_OK(); + } + + mWhichPing5 = 0; + + if (!SendPong5() || !SendPong5() || !SendPong5() || !SendPong5() || + !SendPong5()) + fail("sending Pong5()"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestLatencyChild::AnswerRpc() { return IPC_OK(); } + +mozilla::ipc::IPCResult TestLatencyChild::RecvSpam() { + // no-op + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestLatencyChild::AnswerSynchro() { return IPC_OK(); } + +mozilla::ipc::IPCResult TestLatencyChild::RecvCompressedSpam( + const uint32_t& seqno) { + if (seqno <= mLastSeqno) + fail("compressed seqnos must monotonically increase"); + + mLastSeqno = seqno; + ++mNumProcessedCompressedSpams; + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestLatencyChild::AnswerSynchro2( + uint32_t* lastSeqno, uint32_t* numMessagesDispatched) { + *lastSeqno = mLastSeqno; + *numMessagesDispatched = mNumProcessedCompressedSpams; + return IPC_OK(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestLatency.h b/ipc/ipdl/test/cxx/TestLatency.h new file mode 100644 index 0000000000..687b107808 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestLatency.h @@ -0,0 +1,107 @@ +#ifndef mozilla__ipdltest_TestLatency_h +#define mozilla__ipdltest_TestLatency_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestLatencyParent.h" +#include "mozilla/_ipdltest/PTestLatencyChild.h" + +#include "mozilla/TimeStamp.h" + +#define NR_TRIALS 10000 +#define NR_SPAMS 25000 + +namespace mozilla { +namespace _ipdltest { + +class TestLatencyParent : public PTestLatencyParent { + friend class PTestLatencyParent; + + private: + typedef mozilla::TimeStamp TimeStamp; + typedef mozilla::TimeDuration TimeDuration; + + public: + TestLatencyParent(); + virtual ~TestLatencyParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + mozilla::ipc::IPCResult RecvPong(); + mozilla::ipc::IPCResult RecvPong5(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + + passed( + "\n" + " average #ping-pong/sec: %g\n" + " average #ping5-pong5/sec: %g\n" + " average #RPC call-answer/sec: %g\n" + " average #spams/sec: %g\n" + " pct. spams compressed away: %g\n", + double(NR_TRIALS) / mPPTimeTotal.ToSecondsSigDigits(), + double(NR_TRIALS) / mPP5TimeTotal.ToSecondsSigDigits(), + double(NR_TRIALS) / mRpcTimeTotal.ToSecondsSigDigits(), + double(NR_SPAMS) / mSpamTimeTotal.ToSecondsSigDigits(), + 100.0 * (double(NR_SPAMS - mNumChildProcessedCompressedSpams) / + double(NR_SPAMS))); + + QuitParent(); + } + + private: + void PingPongTrial(); + void Ping5Pong5Trial(); + void RpcTrials(); + void SpamTrial(); + void CompressedSpamTrial(); + void Exit(); + + TimeStamp mStart; + TimeDuration mPPTimeTotal; + TimeDuration mPP5TimeTotal; + TimeDuration mRpcTimeTotal; + TimeDuration mSpamTimeTotal; + + int mPPTrialsToGo; + int mPP5TrialsToGo; + uint32_t mNumChildProcessedCompressedSpams; + uint32_t mWhichPong5; +}; + +class TestLatencyChild : public PTestLatencyChild { + friend class PTestLatencyChild; + + public: + TestLatencyChild(); + virtual ~TestLatencyChild(); + + protected: + mozilla::ipc::IPCResult RecvPing(); + mozilla::ipc::IPCResult RecvPing5(); + mozilla::ipc::IPCResult AnswerRpc(); + mozilla::ipc::IPCResult RecvSpam(); + mozilla::ipc::IPCResult AnswerSynchro(); + mozilla::ipc::IPCResult RecvCompressedSpam(const uint32_t& seqno); + mozilla::ipc::IPCResult AnswerSynchro2(uint32_t* lastSeqno, + uint32_t* numMessagesDispatched); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } + + uint32_t mLastSeqno; + uint32_t mNumProcessedCompressedSpams; + uint32_t mWhichPing5; +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestLatency_h diff --git a/ipc/ipdl/test/cxx/TestManyChildAllocs.cpp b/ipc/ipdl/test/cxx/TestManyChildAllocs.cpp new file mode 100644 index 0000000000..77d5786cfa --- /dev/null +++ b/ipc/ipdl/test/cxx/TestManyChildAllocs.cpp @@ -0,0 +1,83 @@ +#include "TestManyChildAllocs.h" + +#include "IPDLUnitTests.h" // fail etc. + +#define NALLOCS 10 + +namespace mozilla { +namespace _ipdltest { + +// parent code + +TestManyChildAllocsParent::TestManyChildAllocsParent() { + MOZ_COUNT_CTOR(TestManyChildAllocsParent); +} + +TestManyChildAllocsParent::~TestManyChildAllocsParent() { + MOZ_COUNT_DTOR(TestManyChildAllocsParent); +} + +void TestManyChildAllocsParent::Main() { + if (!SendGo()) fail("can't send Go()"); +} + +mozilla::ipc::IPCResult TestManyChildAllocsParent::RecvDone() { + // explicitly *not* cleaning up, so we can sanity-check IPDL's + // auto-shutdown/cleanup handling + Close(); + + return IPC_OK(); +} + +bool TestManyChildAllocsParent::DeallocPTestManyChildAllocsSubParent( + PTestManyChildAllocsSubParent* __a) { + delete __a; + return true; +} + +PTestManyChildAllocsSubParent* +TestManyChildAllocsParent::AllocPTestManyChildAllocsSubParent() { + return new TestManyChildAllocsSubParent(); +} + +// child code + +TestManyChildAllocsChild::TestManyChildAllocsChild() { + MOZ_COUNT_CTOR(TestManyChildAllocsChild); +} + +TestManyChildAllocsChild::~TestManyChildAllocsChild() { + MOZ_COUNT_DTOR(TestManyChildAllocsChild); +} + +mozilla::ipc::IPCResult TestManyChildAllocsChild::RecvGo() { + for (int i = 0; i < NALLOCS; ++i) { + PTestManyChildAllocsSubChild* child = + SendPTestManyChildAllocsSubConstructor(); + + if (!child) fail("can't send ctor()"); + + if (!child->SendHello()) fail("can't send Hello()"); + } + + size_t len = ManagedPTestManyChildAllocsSubChild().Count(); + if (NALLOCS != len) fail("expected %lu kids, got %lu", NALLOCS, len); + + if (!SendDone()) fail("can't send Done()"); + + return IPC_OK(); +} + +bool TestManyChildAllocsChild::DeallocPTestManyChildAllocsSubChild( + PTestManyChildAllocsSubChild* __a) { + delete __a; + return true; +} + +PTestManyChildAllocsSubChild* +TestManyChildAllocsChild::AllocPTestManyChildAllocsSubChild() { + return new TestManyChildAllocsSubChild(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestManyChildAllocs.h b/ipc/ipdl/test/cxx/TestManyChildAllocs.h new file mode 100644 index 0000000000..3da6461810 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestManyChildAllocs.h @@ -0,0 +1,82 @@ +#ifndef mozilla__ipdltest_TestManyChildAllocs_h +#define mozilla__ipdltest_TestManyChildAllocs_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestManyChildAllocsParent.h" +#include "mozilla/_ipdltest/PTestManyChildAllocsChild.h" + +#include "mozilla/_ipdltest/PTestManyChildAllocsSubParent.h" +#include "mozilla/_ipdltest/PTestManyChildAllocsSubChild.h" + +namespace mozilla { +namespace _ipdltest { + +// top-level protocol + +class TestManyChildAllocsParent : public PTestManyChildAllocsParent { + friend class PTestManyChildAllocsParent; + + public: + TestManyChildAllocsParent(); + virtual ~TestManyChildAllocsParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + mozilla::ipc::IPCResult RecvDone(); + bool DeallocPTestManyChildAllocsSubParent(PTestManyChildAllocsSubParent* __a); + PTestManyChildAllocsSubParent* AllocPTestManyChildAllocsSubParent(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } +}; + +class TestManyChildAllocsChild : public PTestManyChildAllocsChild { + friend class PTestManyChildAllocsChild; + + public: + TestManyChildAllocsChild(); + virtual ~TestManyChildAllocsChild(); + + protected: + mozilla::ipc::IPCResult RecvGo(); + bool DeallocPTestManyChildAllocsSubChild(PTestManyChildAllocsSubChild* __a); + PTestManyChildAllocsSubChild* AllocPTestManyChildAllocsSubChild(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } +}; + +// do-nothing sub-protocol actors + +class TestManyChildAllocsSubParent : public PTestManyChildAllocsSubParent { + friend class PTestManyChildAllocsSubParent; + + public: + TestManyChildAllocsSubParent() {} + virtual ~TestManyChildAllocsSubParent() {} + + protected: + virtual void ActorDestroy(ActorDestroyReason why) override {} + mozilla::ipc::IPCResult RecvHello() { return IPC_OK(); } +}; + +class TestManyChildAllocsSubChild : public PTestManyChildAllocsSubChild { + public: + TestManyChildAllocsSubChild() {} + virtual ~TestManyChildAllocsSubChild() {} +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestManyChildAllocs_h diff --git a/ipc/ipdl/test/cxx/TestMultiMgrs.cpp b/ipc/ipdl/test/cxx/TestMultiMgrs.cpp new file mode 100644 index 0000000000..f09bb9ca7b --- /dev/null +++ b/ipc/ipdl/test/cxx/TestMultiMgrs.cpp @@ -0,0 +1,83 @@ +#include "TestMultiMgrs.h" + +#include "IPDLUnitTests.h" // fail etc. +#include "mozilla/ipc/ProtocolUtils.h" + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +void TestMultiMgrsParent::Main() { + TestMultiMgrsLeftParent* leftie = new TestMultiMgrsLeftParent(); + if (!SendPTestMultiMgrsLeftConstructor(leftie)) fail("error sending ctor"); + + TestMultiMgrsRightParent* rightie = new TestMultiMgrsRightParent(); + if (!SendPTestMultiMgrsRightConstructor(rightie)) fail("error sending ctor"); + + TestMultiMgrsBottomParent* bottomL = new TestMultiMgrsBottomParent(); + if (!leftie->SendPTestMultiMgrsBottomConstructor(bottomL)) + fail("error sending ctor"); + + TestMultiMgrsBottomParent* bottomR = new TestMultiMgrsBottomParent(); + if (!rightie->SendPTestMultiMgrsBottomConstructor(bottomR)) + fail("error sending ctor"); + + if (!leftie->HasChild(bottomL)) + fail("leftie didn't have a child it was supposed to!"); + if (leftie->HasChild(bottomR)) fail("leftie had rightie's child!"); + + if (!rightie->HasChild(bottomR)) + fail("rightie didn't have a child it was supposed to!"); + if (rightie->HasChild(bottomL)) fail("rightie had rightie's child!"); + + if (!SendCheck()) fail("couldn't kick off the child-side check"); +} + +mozilla::ipc::IPCResult TestMultiMgrsParent::RecvOK() { + Close(); + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// child + +mozilla::ipc::IPCResult +TestMultiMgrsLeftChild::RecvPTestMultiMgrsBottomConstructor( + PTestMultiMgrsBottomChild* actor) { + static_cast<TestMultiMgrsChild*>(Manager())->mBottomL = actor; + return IPC_OK(); +} + +mozilla::ipc::IPCResult +TestMultiMgrsRightChild::RecvPTestMultiMgrsBottomConstructor( + PTestMultiMgrsBottomChild* actor) { + static_cast<TestMultiMgrsChild*>(Manager())->mBottomR = actor; + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestMultiMgrsChild::RecvCheck() { + if (1 != ManagedPTestMultiMgrsLeftChild().Count()) fail("where's leftie?"); + if (1 != ManagedPTestMultiMgrsRightChild().Count()) fail("where's rightie?"); + + TestMultiMgrsLeftChild* leftie = static_cast<TestMultiMgrsLeftChild*>( + LoneManagedOrNullAsserts(ManagedPTestMultiMgrsLeftChild())); + TestMultiMgrsRightChild* rightie = static_cast<TestMultiMgrsRightChild*>( + LoneManagedOrNullAsserts(ManagedPTestMultiMgrsRightChild())); + + if (!leftie->HasChild(mBottomL)) + fail("leftie didn't have a child it was supposed to!"); + if (leftie->HasChild(mBottomR)) fail("leftie had rightie's child!"); + + if (!rightie->HasChild(mBottomR)) + fail("rightie didn't have a child it was supposed to!"); + if (rightie->HasChild(mBottomL)) fail("rightie had leftie's child!"); + + if (!SendOK()) fail("couldn't send OK()"); + + return IPC_OK(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestMultiMgrs.h b/ipc/ipdl/test/cxx/TestMultiMgrs.h new file mode 100644 index 0000000000..71826d42a7 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestMultiMgrs.h @@ -0,0 +1,222 @@ +#ifndef mozilla__ipdltest_TestMultiMgrs_h +#define mozilla__ipdltest_TestMultiMgrs_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestMultiMgrsParent.h" +#include "mozilla/_ipdltest/PTestMultiMgrsChild.h" +#include "mozilla/_ipdltest/PTestMultiMgrsBottomParent.h" +#include "mozilla/_ipdltest/PTestMultiMgrsBottomChild.h" +#include "mozilla/_ipdltest/PTestMultiMgrsLeftParent.h" +#include "mozilla/_ipdltest/PTestMultiMgrsLeftChild.h" +#include "mozilla/_ipdltest/PTestMultiMgrsRightParent.h" +#include "mozilla/_ipdltest/PTestMultiMgrsRightChild.h" + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// Parent side +// + +class TestMultiMgrsBottomParent : public PTestMultiMgrsBottomParent { + public: + TestMultiMgrsBottomParent() {} + virtual ~TestMultiMgrsBottomParent() {} + + protected: + virtual void ActorDestroy(ActorDestroyReason why) override {} +}; + +class TestMultiMgrsLeftParent : public PTestMultiMgrsLeftParent { + friend class PTestMultiMgrsLeftParent; + + public: + TestMultiMgrsLeftParent() {} + virtual ~TestMultiMgrsLeftParent() {} + + bool HasChild(TestMultiMgrsBottomParent* c) { + return ManagedPTestMultiMgrsBottomParent().Contains(c); + } + + protected: + virtual void ActorDestroy(ActorDestroyReason why) override {} + + PTestMultiMgrsBottomParent* AllocPTestMultiMgrsBottomParent() { + return new TestMultiMgrsBottomParent(); + } + + bool DeallocPTestMultiMgrsBottomParent(PTestMultiMgrsBottomParent* actor) { + delete actor; + return true; + } +}; + +class TestMultiMgrsRightParent : public PTestMultiMgrsRightParent { + friend class PTestMultiMgrsRightParent; + + public: + TestMultiMgrsRightParent() {} + virtual ~TestMultiMgrsRightParent() {} + + bool HasChild(TestMultiMgrsBottomParent* c) { + return ManagedPTestMultiMgrsBottomParent().Contains(c); + } + + protected: + virtual void ActorDestroy(ActorDestroyReason why) override {} + + PTestMultiMgrsBottomParent* AllocPTestMultiMgrsBottomParent() { + return new TestMultiMgrsBottomParent(); + } + + bool DeallocPTestMultiMgrsBottomParent(PTestMultiMgrsBottomParent* actor) { + delete actor; + return true; + } +}; + +class TestMultiMgrsParent : public PTestMultiMgrsParent { + friend class PTestMultiMgrsParent; + + public: + TestMultiMgrsParent() {} + virtual ~TestMultiMgrsParent() {} + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + mozilla::ipc::IPCResult RecvOK(); + + PTestMultiMgrsLeftParent* AllocPTestMultiMgrsLeftParent() { + return new TestMultiMgrsLeftParent(); + } + + bool DeallocPTestMultiMgrsLeftParent(PTestMultiMgrsLeftParent* actor) { + delete actor; + return true; + } + + PTestMultiMgrsRightParent* AllocPTestMultiMgrsRightParent() { + return new TestMultiMgrsRightParent(); + } + + bool DeallocPTestMultiMgrsRightParent(PTestMultiMgrsRightParent* actor) { + delete actor; + return true; + } + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } +}; + +//----------------------------------------------------------------------------- +// Child side +// + +class TestMultiMgrsBottomChild : public PTestMultiMgrsBottomChild { + public: + TestMultiMgrsBottomChild() {} + virtual ~TestMultiMgrsBottomChild() {} +}; + +class TestMultiMgrsLeftChild : public PTestMultiMgrsLeftChild { + friend class PTestMultiMgrsLeftChild; + + public: + TestMultiMgrsLeftChild() {} + virtual ~TestMultiMgrsLeftChild() {} + + bool HasChild(PTestMultiMgrsBottomChild* c) { + return ManagedPTestMultiMgrsBottomChild().Contains(c); + } + + protected: + virtual mozilla::ipc::IPCResult RecvPTestMultiMgrsBottomConstructor( + PTestMultiMgrsBottomChild* actor) override; + + PTestMultiMgrsBottomChild* AllocPTestMultiMgrsBottomChild() { + return new TestMultiMgrsBottomChild(); + } + + bool DeallocPTestMultiMgrsBottomChild(PTestMultiMgrsBottomChild* actor) { + delete actor; + return true; + } +}; + +class TestMultiMgrsRightChild : public PTestMultiMgrsRightChild { + friend class PTestMultiMgrsRightChild; + + public: + TestMultiMgrsRightChild() {} + virtual ~TestMultiMgrsRightChild() {} + + bool HasChild(PTestMultiMgrsBottomChild* c) { + return ManagedPTestMultiMgrsBottomChild().Contains(c); + } + + protected: + virtual mozilla::ipc::IPCResult RecvPTestMultiMgrsBottomConstructor( + PTestMultiMgrsBottomChild* actor) override; + + PTestMultiMgrsBottomChild* AllocPTestMultiMgrsBottomChild() { + return new TestMultiMgrsBottomChild(); + } + + bool DeallocPTestMultiMgrsBottomChild(PTestMultiMgrsBottomChild* actor) { + delete actor; + return true; + } +}; + +class TestMultiMgrsChild : public PTestMultiMgrsChild { + friend class PTestMultiMgrsChild; + + public: + TestMultiMgrsChild() {} + virtual ~TestMultiMgrsChild() {} + + void Main(); + + PTestMultiMgrsBottomChild* mBottomL; + PTestMultiMgrsBottomChild* mBottomR; + + protected: + mozilla::ipc::IPCResult RecvCheck(); + + PTestMultiMgrsLeftChild* AllocPTestMultiMgrsLeftChild() { + return new TestMultiMgrsLeftChild(); + } + + bool DeallocPTestMultiMgrsLeftChild(PTestMultiMgrsLeftChild* actor) { + delete actor; + return true; + } + + PTestMultiMgrsRightChild* AllocPTestMultiMgrsRightChild() { + return new TestMultiMgrsRightChild(); + } + + bool DeallocPTestMultiMgrsRightChild(PTestMultiMgrsRightChild* actor) { + delete actor; + return true; + } + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitChild(); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestMultiMgrs_h diff --git a/ipc/ipdl/test/cxx/TestNestedLoops.cpp b/ipc/ipdl/test/cxx/TestNestedLoops.cpp new file mode 100644 index 0000000000..3658d7dfb6 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestNestedLoops.cpp @@ -0,0 +1,78 @@ +#include "base/basictypes.h" + +#include "nsThreadUtils.h" + +#include "TestNestedLoops.h" + +#include "IPDLUnitTests.h" // fail etc. + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +TestNestedLoopsParent::TestNestedLoopsParent() : mBreakNestedLoop(false) { + MOZ_COUNT_CTOR(TestNestedLoopsParent); +} + +TestNestedLoopsParent::~TestNestedLoopsParent() { + MOZ_COUNT_DTOR(TestNestedLoopsParent); +} + +void TestNestedLoopsParent::Main() { + if (!SendStart()) fail("sending Start"); + + // sigh ... spin for a while to let Nonce arrive + puts(" (sleeping to wait for nonce ... sorry)"); + PR_Sleep(5000); + + // while waiting for the reply to R, we'll receive Nonce + if (!CallR()) fail("calling R"); + + Close(); +} + +mozilla::ipc::IPCResult TestNestedLoopsParent::RecvNonce() { + // if we have an OnMaybeDequeueOne waiting for us (we may not, due + // to the inherent race condition in this test, then this event + // must be ordered after it in the queue + MessageLoop::current()->PostTask(NewNonOwningRunnableMethod( + "_ipdltest::TestNestedLoopsParent::BreakNestedLoop", this, + &TestNestedLoopsParent::BreakNestedLoop)); + + // sigh ... spin for a while to let the reply to R arrive + puts(" (sleeping to wait for reply to R ... sorry)"); + PR_Sleep(5000); + + // sigh ... we have no idea when code might do this + do { + if (!NS_ProcessNextEvent(nullptr, false)) + fail("expected at least one pending event"); + } while (!mBreakNestedLoop); + + return IPC_OK(); +} + +void TestNestedLoopsParent::BreakNestedLoop() { mBreakNestedLoop = true; } + +//----------------------------------------------------------------------------- +// child + +TestNestedLoopsChild::TestNestedLoopsChild() { + MOZ_COUNT_CTOR(TestNestedLoopsChild); +} + +TestNestedLoopsChild::~TestNestedLoopsChild() { + MOZ_COUNT_DTOR(TestNestedLoopsChild); +} + +mozilla::ipc::IPCResult TestNestedLoopsChild::RecvStart() { + if (!SendNonce()) fail("sending Nonce"); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestNestedLoopsChild::AnswerR() { return IPC_OK(); } + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestNestedLoops.h b/ipc/ipdl/test/cxx/TestNestedLoops.h new file mode 100644 index 0000000000..001b571494 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestNestedLoops.h @@ -0,0 +1,59 @@ +#ifndef mozilla__ipdltest_TestNestedLoops_h +#define mozilla__ipdltest_TestNestedLoops_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestNestedLoopsParent.h" +#include "mozilla/_ipdltest/PTestNestedLoopsChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestNestedLoopsParent : public PTestNestedLoopsParent { + friend class PTestNestedLoopsParent; + + public: + TestNestedLoopsParent(); + virtual ~TestNestedLoopsParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + mozilla::ipc::IPCResult RecvNonce(); + + void BreakNestedLoop(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } + + bool mBreakNestedLoop; +}; + +class TestNestedLoopsChild : public PTestNestedLoopsChild { + friend class PTestNestedLoopsChild; + + public: + TestNestedLoopsChild(); + virtual ~TestNestedLoopsChild(); + + protected: + mozilla::ipc::IPCResult RecvStart(); + + mozilla::ipc::IPCResult AnswerR(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestNestedLoops_h diff --git a/ipc/ipdl/test/cxx/TestRPC.cpp b/ipc/ipdl/test/cxx/TestRPC.cpp new file mode 100644 index 0000000000..404de01aa8 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestRPC.cpp @@ -0,0 +1,107 @@ +#include "TestRPC.h" + +#include "IPDLUnitTests.h" // fail etc. +#if defined(OS_POSIX) +# include <unistd.h> +#else +# include <windows.h> +#endif + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +TestRPCParent::TestRPCParent() + : reentered_(false), resolved_first_cpow_(false) { + MOZ_COUNT_CTOR(TestRPCParent); +} + +TestRPCParent::~TestRPCParent() { MOZ_COUNT_DTOR(TestRPCParent); } + +void TestRPCParent::Main() { + if (!SendStart()) fail("sending Start"); +} + +mozilla::ipc::IPCResult TestRPCParent::RecvTest1_Start(uint32_t* aResult) { + uint32_t result; + if (!SendTest1_InnerQuery(&result)) fail("SendTest1_InnerQuery"); + if (result != 300) fail("Wrong result (expected 300)"); + + *aResult = 100; + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRPCParent::RecvTest1_InnerEvent(uint32_t* aResult) { + uint32_t result; + if (!SendTest1_NoReenter(&result)) fail("SendTest1_NoReenter"); + if (result != 400) fail("Wrong result (expected 400)"); + + *aResult = 200; + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRPCParent::RecvTest2_Start() { + // Send a CPOW. During this time, we must NOT process the RPC message, as + // we could start receiving CPOW replies out-of-order. + if (!SendTest2_FirstUrgent()) fail("SendTest2_FirstUrgent"); + + MOZ_ASSERT(!reentered_); + resolved_first_cpow_ = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRPCParent::RecvTest2_OutOfOrder() { + // Send a CPOW. If this RPC call was initiated while waiting for the first + // CPOW to resolve, replies will be processed out of order, and we'll crash. + if (!SendTest2_SecondUrgent()) fail("SendTest2_SecondUrgent"); + + reentered_ = true; + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// child + +TestRPCChild::TestRPCChild() { MOZ_COUNT_CTOR(TestRPCChild); } + +TestRPCChild::~TestRPCChild() { MOZ_COUNT_DTOR(TestRPCChild); } + +mozilla::ipc::IPCResult TestRPCChild::RecvStart() { + uint32_t result; + if (!SendTest1_Start(&result)) fail("SendTest1_Start"); + if (result != 100) fail("Wrong result (expected 100)"); + + if (!SendTest2_Start()) fail("SendTest2_Start"); + + if (!SendTest2_OutOfOrder()) fail("SendTest2_OutOfOrder"); + + Close(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRPCChild::RecvTest1_InnerQuery(uint32_t* aResult) { + uint32_t result; + if (!SendTest1_InnerEvent(&result)) fail("SendTest1_InnerEvent"); + if (result != 200) fail("Wrong result (expected 200)"); + + *aResult = 300; + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRPCChild::RecvTest1_NoReenter(uint32_t* aResult) { + *aResult = 400; + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRPCChild::RecvTest2_FirstUrgent() { + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRPCChild::RecvTest2_SecondUrgent() { + return IPC_OK(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestRPC.h b/ipc/ipdl/test/cxx/TestRPC.h new file mode 100644 index 0000000000..907d3fde36 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestRPC.h @@ -0,0 +1,60 @@ +#ifndef mozilla__ipdltest_TestRPC_h +#define mozilla__ipdltest_TestRPC_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestRPCParent.h" +#include "mozilla/_ipdltest/PTestRPCChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestRPCParent : public PTestRPCParent { + public: + TestRPCParent(); + virtual ~TestRPCParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return false; } + + void Main(); + + mozilla::ipc::IPCResult RecvTest1_Start(uint32_t* aResult); + mozilla::ipc::IPCResult RecvTest1_InnerEvent(uint32_t* aResult); + mozilla::ipc::IPCResult RecvTest2_Start(); + mozilla::ipc::IPCResult RecvTest2_OutOfOrder(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + if (!reentered_) fail("never processed raced RPC call!"); + if (!resolved_first_cpow_) fail("never resolved first CPOW!"); + passed("ok"); + QuitParent(); + } + + private: + bool reentered_; + bool resolved_first_cpow_; +}; + +class TestRPCChild : public PTestRPCChild { + public: + TestRPCChild(); + virtual ~TestRPCChild(); + + mozilla::ipc::IPCResult RecvStart(); + mozilla::ipc::IPCResult RecvTest1_InnerQuery(uint32_t* aResult); + mozilla::ipc::IPCResult RecvTest1_NoReenter(uint32_t* aResult); + mozilla::ipc::IPCResult RecvTest2_FirstUrgent(); + mozilla::ipc::IPCResult RecvTest2_SecondUrgent(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestRPC_h diff --git a/ipc/ipdl/test/cxx/TestRaceDeadlock.cpp b/ipc/ipdl/test/cxx/TestRaceDeadlock.cpp new file mode 100644 index 0000000000..76defe97a5 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestRaceDeadlock.cpp @@ -0,0 +1,102 @@ +#include "TestRaceDeadlock.h" + +#include "IPDLUnitTests.h" // fail etc. + +// #define TEST_TIMEOUT 5000 + +using namespace mozilla::ipc; +typedef mozilla::ipc::MessageChannel::Message Message; +typedef mozilla::ipc::MessageChannel::MessageInfo MessageInfo; + +namespace mozilla { +namespace _ipdltest { + +static RacyInterruptPolicy MediateRace(const MessageInfo& parent, + const MessageInfo& child) { + return (PTestRaceDeadlock::Msg_Win__ID == parent.type()) ? RIPParentWins + : RIPChildWins; +} + +//----------------------------------------------------------------------------- +// parent + +TestRaceDeadlockParent::TestRaceDeadlockParent() { + MOZ_COUNT_CTOR(TestRaceDeadlockParent); +} + +TestRaceDeadlockParent::~TestRaceDeadlockParent() { + MOZ_COUNT_DTOR(TestRaceDeadlockParent); +} + +void TestRaceDeadlockParent::Main() { + Test1(); + + Close(); +} + +bool TestRaceDeadlockParent::ShouldContinueFromReplyTimeout() { + fail("This test should not hang"); + GetIPCChannel()->CloseWithTimeout(); + return false; +} + +void TestRaceDeadlockParent::Test1() { +#if defined(TEST_TIMEOUT) + SetReplyTimeoutMs(TEST_TIMEOUT); +#endif + if (!SendStartRace()) { + fail("sending StartRace"); + } + if (!CallRpc()) { + fail("calling Rpc"); + } +} + +mozilla::ipc::IPCResult TestRaceDeadlockParent::AnswerLose() { + return IPC_OK(); +} + +RacyInterruptPolicy TestRaceDeadlockParent::MediateInterruptRace( + const MessageInfo& parent, const MessageInfo& child) { + return MediateRace(parent, child); +} + +//----------------------------------------------------------------------------- +// child + +TestRaceDeadlockChild::TestRaceDeadlockChild() { + MOZ_COUNT_CTOR(TestRaceDeadlockChild); +} + +TestRaceDeadlockChild::~TestRaceDeadlockChild() { + MOZ_COUNT_DTOR(TestRaceDeadlockChild); +} + +mozilla::ipc::IPCResult TestRaceDeadlockParent::RecvStartRace() { + if (!CallWin()) { + fail("calling Win"); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRaceDeadlockChild::RecvStartRace() { + if (!SendStartRace()) { + fail("calling SendStartRace"); + } + if (!CallLose()) { + fail("calling Lose"); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRaceDeadlockChild::AnswerWin() { return IPC_OK(); } + +mozilla::ipc::IPCResult TestRaceDeadlockChild::AnswerRpc() { return IPC_OK(); } + +RacyInterruptPolicy TestRaceDeadlockChild::MediateInterruptRace( + const MessageInfo& parent, const MessageInfo& child) { + return MediateRace(parent, child); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestRaceDeadlock.h b/ipc/ipdl/test/cxx/TestRaceDeadlock.h new file mode 100644 index 0000000000..2c5617130d --- /dev/null +++ b/ipc/ipdl/test/cxx/TestRaceDeadlock.h @@ -0,0 +1,68 @@ +#ifndef mozilla__ipdltest_TestRaceDeadlock_h +#define mozilla__ipdltest_TestRaceDeadlock_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestRaceDeadlockParent.h" +#include "mozilla/_ipdltest/PTestRaceDeadlockChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestRaceDeadlockParent : public PTestRaceDeadlockParent { + friend class PTestRaceDeadlockParent; + + public: + TestRaceDeadlockParent(); + virtual ~TestRaceDeadlockParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + virtual bool ShouldContinueFromReplyTimeout() override; + + void Test1(); + + mozilla::ipc::IPCResult RecvStartRace(); + mozilla::ipc::IPCResult AnswerLose(); + + virtual mozilla::ipc::RacyInterruptPolicy MediateInterruptRace( + const MessageInfo& parent, const MessageInfo& child) override; + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } +}; + +class TestRaceDeadlockChild : public PTestRaceDeadlockChild { + friend class PTestRaceDeadlockChild; + + public: + TestRaceDeadlockChild(); + virtual ~TestRaceDeadlockChild(); + + protected: + mozilla::ipc::IPCResult RecvStartRace(); + + mozilla::ipc::IPCResult AnswerWin(); + + mozilla::ipc::IPCResult AnswerRpc(); + + virtual mozilla::ipc::RacyInterruptPolicy MediateInterruptRace( + const MessageInfo& parent, const MessageInfo& child) override; + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestRaceDeadlock_h diff --git a/ipc/ipdl/test/cxx/TestRaceDeferral.cpp b/ipc/ipdl/test/cxx/TestRaceDeferral.cpp new file mode 100644 index 0000000000..c327b57c16 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestRaceDeferral.cpp @@ -0,0 +1,84 @@ +#include "TestRaceDeferral.h" + +#include "IPDLUnitTests.h" // fail etc. + +using namespace mozilla::ipc; +typedef mozilla::ipc::MessageChannel::Message Message; +typedef mozilla::ipc::MessageChannel::MessageInfo MessageInfo; + +namespace mozilla { +namespace _ipdltest { + +static RacyInterruptPolicy MediateRace(const MessageInfo& parent, + const MessageInfo& child) { + return (PTestRaceDeferral::Msg_Win__ID == parent.type()) ? RIPParentWins + : RIPChildWins; +} + +//----------------------------------------------------------------------------- +// parent + +TestRaceDeferralParent::TestRaceDeferralParent() : mProcessedLose(false) { + MOZ_COUNT_CTOR(TestRaceDeferralParent); +} + +TestRaceDeferralParent::~TestRaceDeferralParent() { + MOZ_COUNT_DTOR(TestRaceDeferralParent); + + if (!mProcessedLose) fail("never processed Lose"); +} + +void TestRaceDeferralParent::Main() { + Test1(); + + Close(); +} + +void TestRaceDeferralParent::Test1() { + if (!SendStartRace()) fail("sending StartRace"); + + if (!CallWin()) fail("calling Win"); + if (mProcessedLose) fail("Lose didn't lose"); + + if (!CallRpc()) fail("calling Rpc"); + if (!mProcessedLose) fail("didn't resolve Rpc vs. Lose 'race' correctly"); +} + +mozilla::ipc::IPCResult TestRaceDeferralParent::AnswerLose() { + if (mProcessedLose) fail("processed Lose twice"); + mProcessedLose = true; + return IPC_OK(); +} + +RacyInterruptPolicy TestRaceDeferralParent::MediateInterruptRace( + const MessageInfo& parent, const MessageInfo& child) { + return MediateRace(parent, child); +} + +//----------------------------------------------------------------------------- +// child + +TestRaceDeferralChild::TestRaceDeferralChild() { + MOZ_COUNT_CTOR(TestRaceDeferralChild); +} + +TestRaceDeferralChild::~TestRaceDeferralChild() { + MOZ_COUNT_DTOR(TestRaceDeferralChild); +} + +mozilla::ipc::IPCResult TestRaceDeferralChild::RecvStartRace() { + if (!CallLose()) fail("calling Lose"); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRaceDeferralChild::AnswerWin() { return IPC_OK(); } + +mozilla::ipc::IPCResult TestRaceDeferralChild::AnswerRpc() { return IPC_OK(); } + +RacyInterruptPolicy TestRaceDeferralChild::MediateInterruptRace( + const MessageInfo& parent, const MessageInfo& child) { + return MediateRace(parent, child); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestRaceDeferral.h b/ipc/ipdl/test/cxx/TestRaceDeferral.h new file mode 100644 index 0000000000..b30264fb64 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestRaceDeferral.h @@ -0,0 +1,67 @@ +#ifndef mozilla__ipdltest_TestRaceDeferral_h +#define mozilla__ipdltest_TestRaceDeferral_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestRaceDeferralParent.h" +#include "mozilla/_ipdltest/PTestRaceDeferralChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestRaceDeferralParent : public PTestRaceDeferralParent { + friend class PTestRaceDeferralParent; + + public: + TestRaceDeferralParent(); + virtual ~TestRaceDeferralParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + void Test1(); + + mozilla::ipc::IPCResult AnswerLose(); + + virtual mozilla::ipc::RacyInterruptPolicy MediateInterruptRace( + const MessageInfo& parent, const MessageInfo& child) override; + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } + + bool mProcessedLose; +}; + +class TestRaceDeferralChild : public PTestRaceDeferralChild { + friend class PTestRaceDeferralChild; + + public: + TestRaceDeferralChild(); + virtual ~TestRaceDeferralChild(); + + protected: + mozilla::ipc::IPCResult RecvStartRace(); + + mozilla::ipc::IPCResult AnswerWin(); + + mozilla::ipc::IPCResult AnswerRpc(); + + virtual mozilla::ipc::RacyInterruptPolicy MediateInterruptRace( + const MessageInfo& parent, const MessageInfo& child) override; + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestRaceDeferral_h diff --git a/ipc/ipdl/test/cxx/TestRacyInterruptReplies.cpp b/ipc/ipdl/test/cxx/TestRacyInterruptReplies.cpp new file mode 100644 index 0000000000..cda950b1a1 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestRacyInterruptReplies.cpp @@ -0,0 +1,94 @@ +#include "TestRacyInterruptReplies.h" + +#include "IPDLUnitTests.h" // fail etc. + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +TestRacyInterruptRepliesParent::TestRacyInterruptRepliesParent() + : mReplyNum(0) { + MOZ_COUNT_CTOR(TestRacyInterruptRepliesParent); +} + +TestRacyInterruptRepliesParent::~TestRacyInterruptRepliesParent() { + MOZ_COUNT_DTOR(TestRacyInterruptRepliesParent); +} + +void TestRacyInterruptRepliesParent::Main() { + int replyNum = -1; + if (!CallR_(&replyNum)) fail("calling R()"); + + if (1 != replyNum) fail("this should have been the first reply to R()"); + + if (!SendChildTest()) fail("sending ChildStart"); +} + +mozilla::ipc::IPCResult TestRacyInterruptRepliesParent::RecvA_() { + int replyNum = -1; + // this R() call races with the reply being generated by the other + // side to the R() call from Main(). This is a pretty nasty edge + // case for which one could argue we're breaking in-order message + // delivery, since this side will process the second reply to R() + // before the first. + if (!CallR_(&replyNum)) fail("calling R()"); + + if (2 != replyNum) fail("this should have been the second reply to R()"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRacyInterruptRepliesParent::Answer_R( + int* replyNum) { + *replyNum = ++mReplyNum; + + if (1 == *replyNum) + if (!Send_A()) fail("sending _A()"); + + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// child + +TestRacyInterruptRepliesChild::TestRacyInterruptRepliesChild() : mReplyNum(0) { + MOZ_COUNT_CTOR(TestRacyInterruptRepliesChild); +} + +TestRacyInterruptRepliesChild::~TestRacyInterruptRepliesChild() { + MOZ_COUNT_DTOR(TestRacyInterruptRepliesChild); +} + +mozilla::ipc::IPCResult TestRacyInterruptRepliesChild::AnswerR_(int* replyNum) { + *replyNum = ++mReplyNum; + + if (1 == *replyNum) SendA_(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRacyInterruptRepliesChild::RecvChildTest() { + int replyNum = -1; + if (!Call_R(&replyNum)) fail("calling R()"); + + if (1 != replyNum) fail("this should have been the first reply to R()"); + + Close(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRacyInterruptRepliesChild::Recv_A() { + int replyNum = -1; + + if (!Call_R(&replyNum)) fail("calling _R()"); + + if (2 != replyNum) fail("this should have been the second reply to R()"); + + return IPC_OK(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestRacyInterruptReplies.h b/ipc/ipdl/test/cxx/TestRacyInterruptReplies.h new file mode 100644 index 0000000000..182c07f314 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestRacyInterruptReplies.h @@ -0,0 +1,65 @@ +#ifndef mozilla__ipdltest_TestRacyInterruptReplies_h +#define mozilla__ipdltest_TestRacyInterruptReplies_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestRacyInterruptRepliesParent.h" +#include "mozilla/_ipdltest/PTestRacyInterruptRepliesChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestRacyInterruptRepliesParent : public PTestRacyInterruptRepliesParent { + friend class PTestRacyInterruptRepliesParent; + + public: + TestRacyInterruptRepliesParent(); + virtual ~TestRacyInterruptRepliesParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + mozilla::ipc::IPCResult RecvA_(); + + mozilla::ipc::IPCResult Answer_R(int* replyNum); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } + + private: + int mReplyNum; +}; + +class TestRacyInterruptRepliesChild : public PTestRacyInterruptRepliesChild { + friend class PTestRacyInterruptRepliesChild; + + public: + TestRacyInterruptRepliesChild(); + virtual ~TestRacyInterruptRepliesChild(); + + protected: + mozilla::ipc::IPCResult AnswerR_(int* replyNum); + + mozilla::ipc::IPCResult RecvChildTest(); + + mozilla::ipc::IPCResult Recv_A(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } + + private: + int mReplyNum; +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestRacyInterruptReplies_h diff --git a/ipc/ipdl/test/cxx/TestRacyReentry.cpp b/ipc/ipdl/test/cxx/TestRacyReentry.cpp new file mode 100644 index 0000000000..4224018e11 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestRacyReentry.cpp @@ -0,0 +1,63 @@ +#include "TestRacyReentry.h" + +#include "IPDLUnitTests.h" // fail etc. + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +TestRacyReentryParent::TestRacyReentryParent() : mRecvdE(false) { + MOZ_COUNT_CTOR(TestRacyReentryParent); +} + +TestRacyReentryParent::~TestRacyReentryParent() { + MOZ_COUNT_DTOR(TestRacyReentryParent); +} + +void TestRacyReentryParent::Main() { + if (!SendStart()) fail("sending Start"); + + if (!SendN()) fail("sending N"); +} + +mozilla::ipc::IPCResult TestRacyReentryParent::AnswerE() { + if (!mRecvdE) { + mRecvdE = true; + return IPC_OK(); + } + + if (!CallH()) fail("calling H"); + + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// child + +TestRacyReentryChild::TestRacyReentryChild() { + MOZ_COUNT_CTOR(TestRacyReentryChild); +} + +TestRacyReentryChild::~TestRacyReentryChild() { + MOZ_COUNT_DTOR(TestRacyReentryChild); +} + +mozilla::ipc::IPCResult TestRacyReentryChild::RecvStart() { + if (!CallE()) fail("calling E"); + + Close(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRacyReentryChild::RecvN() { + if (!CallE()) fail("calling E"); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRacyReentryChild::AnswerH() { return IPC_OK(); } + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestRacyReentry.h b/ipc/ipdl/test/cxx/TestRacyReentry.h new file mode 100644 index 0000000000..e31f90c0e3 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestRacyReentry.h @@ -0,0 +1,59 @@ +#ifndef mozilla__ipdltest_TestRacyReentry_h +#define mozilla__ipdltest_TestRacyReentry_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestRacyReentryParent.h" +#include "mozilla/_ipdltest/PTestRacyReentryChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestRacyReentryParent : public PTestRacyReentryParent { + friend class PTestRacyReentryParent; + + public: + TestRacyReentryParent(); + virtual ~TestRacyReentryParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + mozilla::ipc::IPCResult AnswerE(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } + + bool mRecvdE; +}; + +class TestRacyReentryChild : public PTestRacyReentryChild { + friend class PTestRacyReentryChild; + + public: + TestRacyReentryChild(); + virtual ~TestRacyReentryChild(); + + protected: + mozilla::ipc::IPCResult RecvStart(); + + mozilla::ipc::IPCResult RecvN(); + + mozilla::ipc::IPCResult AnswerH(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestRacyReentry_h diff --git a/ipc/ipdl/test/cxx/TestRacyUndefer.cpp b/ipc/ipdl/test/cxx/TestRacyUndefer.cpp new file mode 100644 index 0000000000..a46db5618e --- /dev/null +++ b/ipc/ipdl/test/cxx/TestRacyUndefer.cpp @@ -0,0 +1,83 @@ +#include "base/basictypes.h" + +#include "TestRacyUndefer.h" + +#include "IPDLUnitTests.h" // fail etc. + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +TestRacyUndeferParent::TestRacyUndeferParent() { + MOZ_COUNT_CTOR(TestRacyUndeferParent); +} + +TestRacyUndeferParent::~TestRacyUndeferParent() { + MOZ_COUNT_DTOR(TestRacyUndeferParent); +} + +void TestRacyUndeferParent::Main() { + if (!SendStart()) fail("sending Start"); +} + +mozilla::ipc::IPCResult TestRacyUndeferParent::AnswerSpam() { + static bool spammed = false; + static bool raced = false; + if (!spammed) { + spammed = true; + + if (!SendAwakenSpam()) fail("sending AwakenSpam"); + } else if (!raced) { + raced = true; + + if (!SendAwakenRaceWinTwice()) fail("sending WinRaceTwice"); + + if (!CallRace()) fail("calling Race1"); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRacyUndeferParent::AnswerRaceWinTwice() { + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRacyUndeferParent::RecvDone() { + Close(); + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// child + +TestRacyUndeferChild::TestRacyUndeferChild() { + MOZ_COUNT_CTOR(TestRacyUndeferChild); +} + +TestRacyUndeferChild::~TestRacyUndeferChild() { + MOZ_COUNT_DTOR(TestRacyUndeferChild); +} + +mozilla::ipc::IPCResult TestRacyUndeferChild::RecvStart() { + if (!CallSpam()) fail("calling Spam"); + + if (!SendDone()) fail("sending Done"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRacyUndeferChild::RecvAwakenSpam() { + if (!CallSpam()) fail("calling Spam"); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRacyUndeferChild::RecvAwakenRaceWinTwice() { + if (!CallRaceWinTwice()) fail("calling RaceWinTwice"); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestRacyUndeferChild::AnswerRace() { return IPC_OK(); } + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestRacyUndefer.h b/ipc/ipdl/test/cxx/TestRacyUndefer.h new file mode 100644 index 0000000000..1e157a541b --- /dev/null +++ b/ipc/ipdl/test/cxx/TestRacyUndefer.h @@ -0,0 +1,62 @@ +#ifndef mozilla__ipdltest_TestRacyUndefer_h +#define mozilla__ipdltest_TestRacyUndefer_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestRacyUndeferParent.h" +#include "mozilla/_ipdltest/PTestRacyUndeferChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestRacyUndeferParent : public PTestRacyUndeferParent { + friend class PTestRacyUndeferParent; + + public: + TestRacyUndeferParent(); + virtual ~TestRacyUndeferParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + mozilla::ipc::IPCResult AnswerSpam(); + + mozilla::ipc::IPCResult AnswerRaceWinTwice(); + + mozilla::ipc::IPCResult RecvDone(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } +}; + +class TestRacyUndeferChild : public PTestRacyUndeferChild { + friend class PTestRacyUndeferChild; + + public: + TestRacyUndeferChild(); + virtual ~TestRacyUndeferChild(); + + protected: + mozilla::ipc::IPCResult RecvStart(); + + mozilla::ipc::IPCResult RecvAwakenSpam(); + mozilla::ipc::IPCResult RecvAwakenRaceWinTwice(); + + mozilla::ipc::IPCResult AnswerRace(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestRacyUndefer_h diff --git a/ipc/ipdl/test/cxx/TestSanity.cpp b/ipc/ipdl/test/cxx/TestSanity.cpp new file mode 100644 index 0000000000..ba1920fdcc --- /dev/null +++ b/ipc/ipdl/test/cxx/TestSanity.cpp @@ -0,0 +1,52 @@ +#include "TestSanity.h" + +#include "IPDLUnitTests.h" // fail etc. + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +TestSanityParent::TestSanityParent() { MOZ_COUNT_CTOR(TestSanityParent); } + +TestSanityParent::~TestSanityParent() { MOZ_COUNT_DTOR(TestSanityParent); } + +void TestSanityParent::Main() { + if (!SendPing(0, 0.5f, 0)) fail("sending Ping"); +} + +mozilla::ipc::IPCResult TestSanityParent::RecvPong(const int& one, + const float& zeroPtTwoFive, + const uint8_t& /*unused*/) { + if (1 != one) fail("invalid argument `%d', should have been `1'", one); + + if (0.25f != zeroPtTwoFive) + fail("invalid argument `%g', should have been `0.25'", zeroPtTwoFive); + + Close(); + + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// child + +TestSanityChild::TestSanityChild() { MOZ_COUNT_CTOR(TestSanityChild); } + +TestSanityChild::~TestSanityChild() { MOZ_COUNT_DTOR(TestSanityChild); } + +mozilla::ipc::IPCResult TestSanityChild::RecvPing(const int& zero, + const float& zeroPtFive, + const int8_t& /*unused*/) { + if (0 != zero) fail("invalid argument `%d', should have been `0'", zero); + + if (0.5f != zeroPtFive) + fail("invalid argument `%g', should have been `0.5'", zeroPtFive); + + if (!SendPong(1, 0.25f, 0)) fail("sending Pong"); + return IPC_OK(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestSanity.h b/ipc/ipdl/test/cxx/TestSanity.h new file mode 100644 index 0000000000..ca29d67672 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestSanity.h @@ -0,0 +1,55 @@ +#ifndef mozilla__ipdltest_TestSanity_h +#define mozilla__ipdltest_TestSanity_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestSanityParent.h" +#include "mozilla/_ipdltest/PTestSanityChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestSanityParent : public PTestSanityParent { + friend class PTestSanityParent; + + public: + TestSanityParent(); + virtual ~TestSanityParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + mozilla::ipc::IPCResult RecvPong(const int& one, const float& zeroPtTwoFive, + const uint8_t& dummy); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } +}; + +class TestSanityChild : public PTestSanityChild { + friend class PTestSanityChild; + + public: + TestSanityChild(); + virtual ~TestSanityChild(); + + protected: + mozilla::ipc::IPCResult RecvPing(const int& zero, const float& zeroPtFive, + const int8_t& dummy); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestSanity_h diff --git a/ipc/ipdl/test/cxx/TestSelfManageRoot.cpp b/ipc/ipdl/test/cxx/TestSelfManageRoot.cpp new file mode 100644 index 0000000000..5e09aa4d7e --- /dev/null +++ b/ipc/ipdl/test/cxx/TestSelfManageRoot.cpp @@ -0,0 +1,54 @@ +#include "TestSelfManageRoot.h" + +#include "IPDLUnitTests.h" // fail etc. + +#define ASSERT(c) \ + do { \ + if (!(c)) fail(#c); \ + } while (0) + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +void TestSelfManageRootParent::Main() { + TestSelfManageParent* a = + static_cast<TestSelfManageParent*>(SendPTestSelfManageConstructor()); + if (!a) fail("constructing PTestSelfManage"); + + ASSERT(1 == ManagedPTestSelfManageParent().Count()); + + TestSelfManageParent* aa = + static_cast<TestSelfManageParent*>(a->SendPTestSelfManageConstructor()); + if (!aa) fail("constructing PTestSelfManage"); + + ASSERT(1 == ManagedPTestSelfManageParent().Count() && + 1 == a->ManagedPTestSelfManageParent().Count()); + + if (!PTestSelfManageParent::Send__delete__(aa)) + fail("destroying PTestSelfManage"); + ASSERT(Deletion == aa->mWhy && 1 == ManagedPTestSelfManageParent().Count() && + 0 == a->ManagedPTestSelfManageParent().Count()); + delete aa; + + aa = static_cast<TestSelfManageParent*>(a->SendPTestSelfManageConstructor()); + if (!aa) fail("constructing PTestSelfManage"); + + ASSERT(1 == ManagedPTestSelfManageParent().Count() && + 1 == a->ManagedPTestSelfManageParent().Count()); + + if (!PTestSelfManageParent::Send__delete__(a)) + fail("destroying PTestSelfManage"); + ASSERT(Deletion == a->mWhy && AncestorDeletion == aa->mWhy && + 0 == ManagedPTestSelfManageParent().Count() && + 0 == a->ManagedPTestSelfManageParent().Count()); + delete a; + delete aa; + + Close(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestSelfManageRoot.h b/ipc/ipdl/test/cxx/TestSelfManageRoot.h new file mode 100644 index 0000000000..5cea09b10d --- /dev/null +++ b/ipc/ipdl/test/cxx/TestSelfManageRoot.h @@ -0,0 +1,117 @@ +#ifndef mozilla__ipdltest_TestSelfManageRoot_h +#define mozilla__ipdltest_TestSelfManageRoot_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestSelfManageRootParent.h" +#include "mozilla/_ipdltest/PTestSelfManageRootChild.h" +#include "mozilla/_ipdltest/PTestSelfManageParent.h" +#include "mozilla/_ipdltest/PTestSelfManageChild.h" + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// Parent side + +class TestSelfManageParent : public PTestSelfManageParent { + friend class PTestSelfManageParent; + + public: + MOZ_COUNTED_DEFAULT_CTOR(TestSelfManageParent) + MOZ_COUNTED_DTOR_OVERRIDE(TestSelfManageParent) + + ActorDestroyReason mWhy; + + protected: + PTestSelfManageParent* AllocPTestSelfManageParent() { + return new TestSelfManageParent(); + } + + bool DeallocPTestSelfManageParent(PTestSelfManageParent* a) { return true; } + + virtual void ActorDestroy(ActorDestroyReason why) override { mWhy = why; } +}; + +class TestSelfManageRootParent : public PTestSelfManageRootParent { + friend class PTestSelfManageRootParent; + + public: + MOZ_COUNTED_DEFAULT_CTOR(TestSelfManageRootParent) + virtual ~TestSelfManageRootParent() { + MOZ_COUNT_DTOR(TestSelfManageRootParent); + } + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + PTestSelfManageParent* AllocPTestSelfManageParent() { + return new TestSelfManageParent(); + } + + bool DeallocPTestSelfManageParent(PTestSelfManageParent* a) { return true; } + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } +}; + +//----------------------------------------------------------------------------- +// Child side + +class TestSelfManageChild : public PTestSelfManageChild { + friend class PTestSelfManageChild; + + public: + MOZ_COUNTED_DEFAULT_CTOR(TestSelfManageChild) + MOZ_COUNTED_DTOR_OVERRIDE(TestSelfManageChild) + + protected: + PTestSelfManageChild* AllocPTestSelfManageChild() { + return new TestSelfManageChild(); + } + + bool DeallocPTestSelfManageChild(PTestSelfManageChild* a) { + delete a; + return true; + } + + virtual void ActorDestroy(ActorDestroyReason why) override {} +}; + +class TestSelfManageRootChild : public PTestSelfManageRootChild { + friend class PTestSelfManageRootChild; + + public: + MOZ_COUNTED_DEFAULT_CTOR(TestSelfManageRootChild) + virtual ~TestSelfManageRootChild() { + MOZ_COUNT_DTOR(TestSelfManageRootChild); + } + + void Main(); + + protected: + PTestSelfManageChild* AllocPTestSelfManageChild() { + return new TestSelfManageChild(); + } + + bool DeallocPTestSelfManageChild(PTestSelfManageChild* a) { + delete a; + return true; + } + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestSelfManageRoot_h diff --git a/ipc/ipdl/test/cxx/TestShmem.cpp b/ipc/ipdl/test/cxx/TestShmem.cpp new file mode 100644 index 0000000000..0a778e5322 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestShmem.cpp @@ -0,0 +1,106 @@ +#include "TestShmem.h" + +#include "IPDLUnitTests.h" // fail etc. + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// Parent + +void TestShmemParent::Main() { + Shmem mem; + Shmem unsafe; + + size_t size = 12345; + if (!AllocShmem(size, &mem)) fail("can't alloc shmem"); + if (!AllocUnsafeShmem(size, &unsafe)) fail("can't alloc shmem"); + + if (mem.Size<char>() != size) + fail("shmem is wrong size: expected %lu, got %lu", size, mem.Size<char>()); + if (unsafe.Size<char>() != size) + fail("shmem is wrong size: expected %lu, got %lu", size, + unsafe.Size<char>()); + + char* ptr = mem.get<char>(); + memcpy(ptr, "Hello!", sizeof("Hello!")); + + char* unsafeptr = unsafe.get<char>(); + memcpy(unsafeptr, "Hello!", sizeof("Hello!")); + + Shmem unsafecopy = unsafe; + if (!SendGive(std::move(mem), std::move(unsafe), size)) + fail("can't send Give()"); + + // uncomment the following line for a (nondeterministic) surprise! + // char c1 = *ptr; (void)c1; + + // uncomment the following line for a deterministic surprise! + // char c2 = *mem.get<char>(); (void)c2; + + // unsafe shmem gets rid of those checks + char uc1 = *unsafeptr; + (void)uc1; + char uc2 = *unsafecopy.get<char>(); + (void)uc2; +} + +mozilla::ipc::IPCResult TestShmemParent::RecvTake(Shmem&& mem, Shmem&& unsafe, + const size_t& expectedSize) { + if (mem.Size<char>() != expectedSize) + fail("expected shmem size %lu, but it has size %lu", expectedSize, + mem.Size<char>()); + if (unsafe.Size<char>() != expectedSize) + fail("expected shmem size %lu, but it has size %lu", expectedSize, + unsafe.Size<char>()); + + if (strcmp(mem.get<char>(), "And yourself!")) + fail("expected message was not written"); + if (strcmp(unsafe.get<char>(), "And yourself!")) + fail("expected message was not written"); + + if (!DeallocShmem(mem)) fail("DeallocShmem"); + if (!DeallocShmem(unsafe)) fail("DeallocShmem"); + + Close(); + + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// Child + +mozilla::ipc::IPCResult TestShmemChild::RecvGive(Shmem&& mem, Shmem&& unsafe, + const size_t& expectedSize) { + if (mem.Size<char>() != expectedSize) + fail("expected shmem size %lu, but it has size %lu", expectedSize, + mem.Size<char>()); + if (unsafe.Size<char>() != expectedSize) + fail("expected shmem size %lu, but it has size %lu", expectedSize, + unsafe.Size<char>()); + + if (strcmp(mem.get<char>(), "Hello!")) + fail("expected message was not written"); + if (strcmp(unsafe.get<char>(), "Hello!")) + fail("expected message was not written"); + + char* unsafeptr = unsafe.get<char>(); + + memcpy(mem.get<char>(), "And yourself!", sizeof("And yourself!")); + memcpy(unsafeptr, "And yourself!", sizeof("And yourself!")); + + Shmem unsafecopy = unsafe; + if (!SendTake(std::move(mem), std::move(unsafe), expectedSize)) + fail("can't send Take()"); + + // these checks also shouldn't fail in the child + char uc1 = *unsafeptr; + (void)uc1; + char uc2 = *unsafecopy.get<char>(); + (void)uc2; + + return IPC_OK(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestShmem.h b/ipc/ipdl/test/cxx/TestShmem.h new file mode 100644 index 0000000000..4ef1f4fd7b --- /dev/null +++ b/ipc/ipdl/test/cxx/TestShmem.h @@ -0,0 +1,55 @@ +#ifndef mozilla__ipdltest_TestShmem_h +#define mozilla__ipdltest_TestShmem_h + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestShmemParent.h" +#include "mozilla/_ipdltest/PTestShmemChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestShmemParent : public PTestShmemParent { + friend class PTestShmemParent; + + public: + TestShmemParent() {} + virtual ~TestShmemParent() {} + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + mozilla::ipc::IPCResult RecvTake(Shmem&& mem, Shmem&& unsafe, + const size_t& expectedSize); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } +}; + +class TestShmemChild : public PTestShmemChild { + friend class PTestShmemChild; + + public: + TestShmemChild() {} + virtual ~TestShmemChild() {} + + protected: + mozilla::ipc::IPCResult RecvGive(Shmem&& mem, Shmem&& unsafe, + const size_t& expectedSize); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestShmem_h diff --git a/ipc/ipdl/test/cxx/TestShutdown.cpp b/ipc/ipdl/test/cxx/TestShutdown.cpp new file mode 100644 index 0000000000..502695bd88 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestShutdown.cpp @@ -0,0 +1,185 @@ +#include "TestShutdown.h" + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// Parent side +void TestShutdownParent::Main() { + if (!SendStart()) fail("sending Start()"); +} + +void TestShutdownParent::ActorDestroy(ActorDestroyReason why) { + if (AbnormalShutdown != why) fail("should have ended test with crash!"); + + passed("ok"); + + QuitParent(); +} + +void TestShutdownSubParent::ActorDestroy(ActorDestroyReason why) { + if (Manager()->ManagedPTestShutdownSubParent().Count() == 0) + fail("manager should still have managees!"); + + if (mExpectCrash && AbnormalShutdown != why) + fail("expected crash!"); + else if (!mExpectCrash && AbnormalShutdown == why) + fail("wasn't expecting crash!"); + + if (mExpectCrash && 0 == ManagedPTestShutdownSubsubParent().Count()) + fail("expected to *still* have kids"); +} + +void TestShutdownSubsubParent::ActorDestroy(ActorDestroyReason why) { + if (Manager()->ManagedPTestShutdownSubsubParent().Count() == 0) + fail("manager should still have managees!"); + + if (mExpectParentDeleted && AncestorDeletion != why) + fail("expected ParentDeleted == why"); + else if (!mExpectParentDeleted && AncestorDeletion == why) + fail("wasn't expecting parent delete"); +} + +//----------------------------------------------------------------------------- +// Child side + +mozilla::ipc::IPCResult TestShutdownChild::RecvStart() { + // test 1: alloc some actors and subactors, delete in + // managee-before-manager order + { + bool expectCrash = false, expectParentDeleted = false; + + PTestShutdownSubChild* c1 = SendPTestShutdownSubConstructor(expectCrash); + if (!c1) fail("problem sending ctor"); + + PTestShutdownSubChild* c2 = SendPTestShutdownSubConstructor(expectCrash); + if (!c2) fail("problem sending ctor"); + + PTestShutdownSubsubChild* c1s1 = + c1->SendPTestShutdownSubsubConstructor(expectParentDeleted); + if (!c1s1) fail("problem sending ctor"); + PTestShutdownSubsubChild* c1s2 = + c1->SendPTestShutdownSubsubConstructor(expectParentDeleted); + if (!c1s2) fail("problem sending ctor"); + + PTestShutdownSubsubChild* c2s1 = + c2->SendPTestShutdownSubsubConstructor(expectParentDeleted); + if (!c2s1) fail("problem sending ctor"); + PTestShutdownSubsubChild* c2s2 = + c2->SendPTestShutdownSubsubConstructor(expectParentDeleted); + if (!c2s2) fail("problem sending ctor"); + + if (!PTestShutdownSubsubChild::Send__delete__(c1s1)) + fail("problem sending dtor"); + if (!PTestShutdownSubsubChild::Send__delete__(c1s2)) + fail("problem sending dtor"); + if (!PTestShutdownSubsubChild::Send__delete__(c2s1)) + fail("problem sending dtor"); + if (!PTestShutdownSubsubChild::Send__delete__(c2s2)) + fail("problem sending dtor"); + + if (!c1->CallStackFrame()) fail("problem creating dummy stack frame"); + if (!c2->CallStackFrame()) fail("problem creating dummy stack frame"); + } + + // test 2: alloc some actors and subactors, delete managers first + { + bool expectCrash = false, expectParentDeleted = true; + + PTestShutdownSubChild* c1 = SendPTestShutdownSubConstructor(expectCrash); + if (!c1) fail("problem sending ctor"); + + PTestShutdownSubChild* c2 = SendPTestShutdownSubConstructor(expectCrash); + if (!c2) fail("problem sending ctor"); + + PTestShutdownSubsubChild* c1s1 = + c1->SendPTestShutdownSubsubConstructor(expectParentDeleted); + if (!c1s1) fail("problem sending ctor"); + PTestShutdownSubsubChild* c1s2 = + c1->SendPTestShutdownSubsubConstructor(expectParentDeleted); + if (!c1s2) fail("problem sending ctor"); + + PTestShutdownSubsubChild* c2s1 = + c2->SendPTestShutdownSubsubConstructor(expectParentDeleted); + if (!c2s1) fail("problem sending ctor"); + PTestShutdownSubsubChild* c2s2 = + c2->SendPTestShutdownSubsubConstructor(expectParentDeleted); + if (!c2s2) fail("problem sending ctor"); + + // delete parents without deleting kids + if (!c1->CallStackFrame()) fail("problem creating dummy stack frame"); + if (!c2->CallStackFrame()) fail("problem creating dummy stack frame"); + } + + // test 3: alloc some actors and subactors, then crash + { + bool expectCrash = true, expectParentDeleted = false; + + PTestShutdownSubChild* c1 = SendPTestShutdownSubConstructor(expectCrash); + if (!c1) fail("problem sending ctor"); + + PTestShutdownSubChild* c2 = SendPTestShutdownSubConstructor(expectCrash); + if (!c2) fail("problem sending ctor"); + + PTestShutdownSubsubChild* c1s1 = + c1->SendPTestShutdownSubsubConstructor(expectParentDeleted); + if (!c1s1) fail("problem sending ctor"); + PTestShutdownSubsubChild* c1s2 = + c1->SendPTestShutdownSubsubConstructor(expectParentDeleted); + if (!c1s2) fail("problem sending ctor"); + + PTestShutdownSubsubChild* c2s1 = + c2->SendPTestShutdownSubsubConstructor(expectParentDeleted); + if (!c2s1) fail("problem sending ctor"); + PTestShutdownSubsubChild* c2s2 = + c2->SendPTestShutdownSubsubConstructor(expectParentDeleted); + if (!c2s2) fail("problem sending ctor"); + + // make sure the ctors have been processed by the other side; + // the write end of the socket may temporarily be unwriteable + if (!SendSync()) fail("can't synchronize with parent"); + + // "crash", but without tripping tinderbox assert/abort + // detectors + _exit(0); + } +} + +void TestShutdownChild::ActorDestroy(ActorDestroyReason why) { + fail("hey wait ... we should have crashed!"); +} + +mozilla::ipc::IPCResult TestShutdownSubChild::AnswerStackFrame() { + if (!PTestShutdownSubChild::Send__delete__(this)) + fail("problem sending dtor"); + + // WATCH OUT! |this| has just deleted + + return IPC_OK(); +} + +void TestShutdownSubChild::ActorDestroy(ActorDestroyReason why) { + if (Manager()->ManagedPTestShutdownSubChild().Count() == 0) + fail("manager should still have managees!"); + + if (mExpectCrash && AbnormalShutdown != why) + fail("expected crash!"); + else if (!mExpectCrash && AbnormalShutdown == why) + fail("wasn't expecting crash!"); + + if (mExpectCrash && 0 == ManagedPTestShutdownSubsubChild().Count()) + fail("expected to *still* have kids"); +} + +void TestShutdownSubsubChild::ActorDestroy(ActorDestroyReason why) { + if (Manager()->ManagedPTestShutdownSubsubChild().Count() == 0) + fail("manager should still have managees!"); + + if (mExpectParentDeleted && AncestorDeletion != why) + fail("expected ParentDeleted == why"); + else if (!mExpectParentDeleted && AncestorDeletion == why) + fail("wasn't expecting parent delete"); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestShutdown.h b/ipc/ipdl/test/cxx/TestShutdown.h new file mode 100644 index 0000000000..224e42496d --- /dev/null +++ b/ipc/ipdl/test/cxx/TestShutdown.h @@ -0,0 +1,168 @@ +#ifndef mozilla__ipdltest_TestShutdown_h +#define mozilla__ipdltest_TestShutdown_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestShutdownParent.h" +#include "mozilla/_ipdltest/PTestShutdownChild.h" + +#include "mozilla/_ipdltest/PTestShutdownSubParent.h" +#include "mozilla/_ipdltest/PTestShutdownSubChild.h" + +#include "mozilla/_ipdltest/PTestShutdownSubsubParent.h" +#include "mozilla/_ipdltest/PTestShutdownSubsubChild.h" + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// Parent side + +class TestShutdownSubsubParent : public PTestShutdownSubsubParent { + public: + explicit TestShutdownSubsubParent(bool expectParentDeleted) + : mExpectParentDeleted(expectParentDeleted) {} + + virtual ~TestShutdownSubsubParent() {} + + protected: + virtual void ActorDestroy(ActorDestroyReason why) override; + + private: + bool mExpectParentDeleted; +}; + +class TestShutdownSubParent : public PTestShutdownSubParent { + friend class PTestShutdownSubParent; + + public: + explicit TestShutdownSubParent(bool expectCrash) + : mExpectCrash(expectCrash), mDeletedCount(0) {} + + virtual ~TestShutdownSubParent() { + if (2 != mDeletedCount) fail("managees outliving manager!"); + } + + protected: + mozilla::ipc::IPCResult AnswerStackFrame() { + if (!CallStackFrame()) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); + } + + PTestShutdownSubsubParent* AllocPTestShutdownSubsubParent( + const bool& expectParentDelete) { + return new TestShutdownSubsubParent(expectParentDelete); + } + + bool DeallocPTestShutdownSubsubParent(PTestShutdownSubsubParent* actor) { + delete actor; + ++mDeletedCount; + return true; + } + + virtual void ActorDestroy(ActorDestroyReason why) override; + + private: + bool mExpectCrash; + int mDeletedCount; +}; + +class TestShutdownParent : public PTestShutdownParent { + friend class PTestShutdownParent; + + public: + TestShutdownParent() {} + virtual ~TestShutdownParent() {} + + static bool RunTestInProcesses() { return true; } + // FIXME/bug 703323 Could work if modified + static bool RunTestInThreads() { return false; } + + void Main(); + + protected: + mozilla::ipc::IPCResult RecvSync() { return IPC_OK(); } + + PTestShutdownSubParent* AllocPTestShutdownSubParent(const bool& expectCrash) { + return new TestShutdownSubParent(expectCrash); + } + + bool DeallocPTestShutdownSubParent(PTestShutdownSubParent* actor) { + delete actor; + return true; + } + + virtual void ActorDestroy(ActorDestroyReason why) override; +}; + +//----------------------------------------------------------------------------- +// Child side + +class TestShutdownSubsubChild : public PTestShutdownSubsubChild { + public: + explicit TestShutdownSubsubChild(bool expectParentDeleted) + : mExpectParentDeleted(expectParentDeleted) {} + virtual ~TestShutdownSubsubChild() {} + + protected: + virtual void ActorDestroy(ActorDestroyReason why) override; + + private: + bool mExpectParentDeleted; +}; + +class TestShutdownSubChild : public PTestShutdownSubChild { + friend class PTestShutdownSubChild; + + public: + explicit TestShutdownSubChild(bool expectCrash) : mExpectCrash(expectCrash) {} + + virtual ~TestShutdownSubChild() {} + + protected: + mozilla::ipc::IPCResult AnswerStackFrame(); + + PTestShutdownSubsubChild* AllocPTestShutdownSubsubChild( + const bool& expectParentDelete) { + return new TestShutdownSubsubChild(expectParentDelete); + } + + bool DeallocPTestShutdownSubsubChild(PTestShutdownSubsubChild* actor) { + delete actor; + return true; + } + + virtual void ActorDestroy(ActorDestroyReason why) override; + + private: + bool mExpectCrash; +}; + +class TestShutdownChild : public PTestShutdownChild { + friend class PTestShutdownChild; + + public: + TestShutdownChild() {} + virtual ~TestShutdownChild() {} + + protected: + mozilla::ipc::IPCResult RecvStart(); + + PTestShutdownSubChild* AllocPTestShutdownSubChild(const bool& expectCrash) { + return new TestShutdownSubChild(expectCrash); + } + + bool DeallocPTestShutdownSubChild(PTestShutdownSubChild* actor) { + delete actor; + return true; + } + + virtual void ActorDestroy(ActorDestroyReason why) override; +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestShutdown_h diff --git a/ipc/ipdl/test/cxx/TestStackHooks.cpp b/ipc/ipdl/test/cxx/TestStackHooks.cpp new file mode 100644 index 0000000000..9800ac54e7 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestStackHooks.cpp @@ -0,0 +1,122 @@ +#include "TestStackHooks.h" + +#include "base/task.h" +#include "IPDLUnitTests.h" // fail etc. + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +TestStackHooksParent::TestStackHooksParent() + : mOnStack(false), mIncallDepth(0) { + MOZ_COUNT_CTOR(TestStackHooksParent); +} + +TestStackHooksParent::~TestStackHooksParent() { + MOZ_COUNT_DTOR(TestStackHooksParent); +} + +void TestStackHooksParent::Main() { + if (!SendStart()) fail("sending Start()"); +} + +mozilla::ipc::IPCResult TestStackHooksParent::AnswerStackFrame() { + if (!mOnStack) fail("not on C++ stack?!"); + + if (!CallStackFrame()) fail("calling StackFrame()"); + + if (!mOnStack) fail("not on C++ stack?!"); + + if (1 != mIncallDepth) fail("missed EnteredCall or ExitedCall hook"); + + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// child + +TestStackHooksChild::TestStackHooksChild() + : mOnStack(false), + mEntered(0), + mExited(0), + mIncallDepth(0), + mNumAnswerStackFrame(0) { + MOZ_COUNT_CTOR(TestStackHooksChild); +} + +TestStackHooksChild::~TestStackHooksChild() { + MOZ_COUNT_DTOR(TestStackHooksChild); +} + +namespace { +void RunTestsFn() { + static_cast<TestStackHooksChild*>(gChildActor)->RunTests(); +} +} // namespace + +mozilla::ipc::IPCResult TestStackHooksChild::RecvStart() { + if (!mOnStack) fail("missed stack notification"); + + if (0 != mIncallDepth) fail("EnteredCall/ExitedCall malfunction"); + + // kick off tests from a runnable so that we can start with + // MessageChannel code on the C++ stack + MessageLoop::current()->PostTask( + NewRunnableFunction("RunTestsFn", RunTestsFn)); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestStackHooksChild::AnswerStackFrame() { + ++mNumAnswerStackFrame; + + if (!mOnStack) fail("missed stack notification"); + + if (1 != mIncallDepth) fail("missed EnteredCall or ExitedCall hook"); + + if (mNumAnswerStackFrame == 1) { + if (!SendAsync()) fail("sending Async()"); + } else if (mNumAnswerStackFrame == 2) { + if (!SendSync()) fail("sending Sync()"); + } else { + fail("unexpected state"); + } + + if (!mOnStack) fail("bad stack exit notification"); + + return IPC_OK(); +} + +void TestStackHooksChild::RunTests() { + // 1 because of RecvStart() + if (1 != mEntered) fail("missed stack notification"); + if (mOnStack) fail("spurious stack notification"); + if (0 != mIncallDepth) fail("EnteredCall/ExitedCall malfunction"); + + if (!SendAsync()) fail("sending Async()"); + if (mOnStack) fail("spurious stack notification"); + if (0 != mIncallDepth) fail("EnteredCall/ExitedCall malfunction"); + if (2 != mEntered) fail("missed stack notification"); + + if (!SendSync()) fail("sending Sync()"); + if (mOnStack) fail("spurious stack notification"); + if (0 != mIncallDepth) fail("EnteredCall/ExitedCall malfunction"); + if (3 != mEntered) fail("missed stack notification"); + + if (!CallRpc()) fail("calling RPC()"); + if (mOnStack) fail("spurious stack notification"); + if (0 != mIncallDepth) fail("EnteredCall/ExitedCall malfunction"); + if (4 != mEntered) fail("missed stack notification"); + + if (!CallStackFrame()) fail("calling StackFrame()"); + if (mOnStack) fail("spurious stack notification"); + if (0 != mIncallDepth) fail("EnteredCall/ExitedCall malfunction"); + if (5 != mEntered) fail("missed stack notification"); + + Close(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestStackHooks.h b/ipc/ipdl/test/cxx/TestStackHooks.h new file mode 100644 index 0000000000..315a3c1ae5 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestStackHooks.h @@ -0,0 +1,107 @@ +#ifndef mozilla__ipdltest_TestStackHooks_h +#define mozilla__ipdltest_TestStackHooks_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestStackHooksParent.h" +#include "mozilla/_ipdltest/PTestStackHooksChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestStackHooksParent : public PTestStackHooksParent { + friend class PTestStackHooksParent; + + public: + TestStackHooksParent(); + virtual ~TestStackHooksParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + mozilla::ipc::IPCResult RecvAsync() { + if (!mOnStack) fail("not on C++ stack?!"); + return IPC_OK(); + } + + mozilla::ipc::IPCResult RecvSync() { + if (!mOnStack) fail("not on C++ stack?!"); + return IPC_OK(); + } + + mozilla::ipc::IPCResult AnswerRpc() { + if (!mOnStack) fail("not on C++ stack?!"); + return IPC_OK(); + } + + mozilla::ipc::IPCResult AnswerStackFrame(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } + + virtual void EnteredCxxStack() override { mOnStack = true; } + virtual void ExitedCxxStack() override { mOnStack = false; } + + virtual void EnteredCall() override { ++mIncallDepth; } + virtual void ExitedCall() override { --mIncallDepth; } + + private: + bool mOnStack; + int mIncallDepth; +}; + +class TestStackHooksChild : public PTestStackHooksChild { + friend class PTestStackHooksChild; + + public: + TestStackHooksChild(); + virtual ~TestStackHooksChild(); + + void RunTests(); + + protected: + mozilla::ipc::IPCResult RecvStart(); + + mozilla::ipc::IPCResult AnswerStackFrame(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + + if (mEntered != mExited) fail("unbalanced enter/exit notifications"); + + if (mOnStack) + fail("computing mOnStack went awry; should have failed above assertion"); + + QuitChild(); + } + + virtual void EnteredCxxStack() override { + ++mEntered; + mOnStack = true; + } + virtual void ExitedCxxStack() override { + ++mExited; + mOnStack = false; + } + + virtual void EnteredCall() override { ++mIncallDepth; } + virtual void ExitedCall() override { --mIncallDepth; } + + private: + bool mOnStack; + int mEntered; + int mExited; + int mIncallDepth; + int32_t mNumAnswerStackFrame; +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestStackHooks_h diff --git a/ipc/ipdl/test/cxx/TestSyncError.cpp b/ipc/ipdl/test/cxx/TestSyncError.cpp new file mode 100644 index 0000000000..6c21590a87 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestSyncError.cpp @@ -0,0 +1,45 @@ +#include "TestSyncError.h" + +#include "IPDLUnitTests.h" // fail etc. + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +TestSyncErrorParent::TestSyncErrorParent() { + MOZ_COUNT_CTOR(TestSyncErrorParent); +} + +TestSyncErrorParent::~TestSyncErrorParent() { + MOZ_COUNT_DTOR(TestSyncErrorParent); +} + +void TestSyncErrorParent::Main() { + if (!SendStart()) fail("sending Start"); +} + +mozilla::ipc::IPCResult TestSyncErrorParent::RecvError() { + return IPC_FAIL_NO_REASON(this); +} + +//----------------------------------------------------------------------------- +// child + +TestSyncErrorChild::TestSyncErrorChild() { MOZ_COUNT_CTOR(TestSyncErrorChild); } + +TestSyncErrorChild::~TestSyncErrorChild() { + MOZ_COUNT_DTOR(TestSyncErrorChild); +} + +mozilla::ipc::IPCResult TestSyncErrorChild::RecvStart() { + if (SendError()) fail("Error() should have return false"); + + Close(); + + return IPC_OK(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestSyncError.h b/ipc/ipdl/test/cxx/TestSyncError.h new file mode 100644 index 0000000000..8b84ed5e0b --- /dev/null +++ b/ipc/ipdl/test/cxx/TestSyncError.h @@ -0,0 +1,61 @@ +#ifndef mozilla__ipdltest_TestSyncError_h +#define mozilla__ipdltest_TestSyncError_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestSyncErrorParent.h" +#include "mozilla/_ipdltest/PTestSyncErrorChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestSyncErrorParent : public PTestSyncErrorParent { + friend class PTestSyncErrorParent; + + public: + TestSyncErrorParent(); + virtual ~TestSyncErrorParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + mozilla::ipc::IPCResult RecvError(); + + virtual void ProcessingError(Result aCode, const char* aReason) override { + // Ignore errors + } + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } +}; + +class TestSyncErrorChild : public PTestSyncErrorChild { + friend class PTestSyncErrorChild; + + public: + TestSyncErrorChild(); + virtual ~TestSyncErrorChild(); + + protected: + mozilla::ipc::IPCResult RecvStart(); + + virtual void ProcessingError(Result aCode, const char* aReason) override { + // Ignore errors + } + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestSyncError_h diff --git a/ipc/ipdl/test/cxx/TestSyncHang.cpp b/ipc/ipdl/test/cxx/TestSyncHang.cpp new file mode 100644 index 0000000000..f53054787b --- /dev/null +++ b/ipc/ipdl/test/cxx/TestSyncHang.cpp @@ -0,0 +1,62 @@ +#include "TestSyncHang.h" +#include "base/task.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" + +#include "IPDLUnitTests.h" // fail etc. + +using std::string; +using std::vector; + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +mozilla::ipc::GeckoChildProcessHost* gSyncHangSubprocess; + +TestSyncHangParent::TestSyncHangParent() { MOZ_COUNT_CTOR(TestSyncHangParent); } + +TestSyncHangParent::~TestSyncHangParent() { + MOZ_COUNT_DTOR(TestSyncHangParent); +} + +void DeleteSyncHangSubprocess(MessageLoop* uiLoop) { + gSyncHangSubprocess->Destroy(); + gSyncHangSubprocess = nullptr; +} + +void DeferredSyncHangParentShutdown() { + // ping to DeleteSubprocess + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction("DeleteSyncHangSubprocess", DeleteSyncHangSubprocess, + MessageLoop::current())); +} + +void TestSyncHangParent::Main() { + vector<string> args; + args.push_back("fake/path"); + gSyncHangSubprocess = + new mozilla::ipc::GeckoChildProcessHost(GeckoProcessType_Plugin); + bool launched = gSyncHangSubprocess->SyncLaunch(args, 2); + if (launched) + fail("Calling SyncLaunch with an invalid path should return false"); + + MessageLoop::current()->PostTask(NewRunnableFunction( + "DeferredSyncHangParentShutdown", DeferredSyncHangParentShutdown)); + Close(); +} + +//----------------------------------------------------------------------------- +// child + +TestSyncHangChild::TestSyncHangChild() { MOZ_COUNT_CTOR(TestSyncHangChild); } + +TestSyncHangChild::~TestSyncHangChild() { MOZ_COUNT_DTOR(TestSyncHangChild); } + +mozilla::ipc::IPCResult TestSyncHangChild::RecvUnusedMessage() { + return IPC_OK(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestSyncHang.h b/ipc/ipdl/test/cxx/TestSyncHang.h new file mode 100644 index 0000000000..911f05cd31 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestSyncHang.h @@ -0,0 +1,50 @@ +#ifndef mozilla__ipdltest_TestSyncHang_h +#define mozilla__ipdltest_TestSyncHang_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestSyncHangParent.h" +#include "mozilla/_ipdltest/PTestSyncHangChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestSyncHangParent : public PTestSyncHangParent { + public: + TestSyncHangParent(); + virtual ~TestSyncHangParent(); + + static bool RunTestInProcesses() { return true; } + // FIXME/bug 703323 Could work if modified + static bool RunTestInThreads() { return false; } + + void Main(); + + protected: + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } +}; + +class TestSyncHangChild : public PTestSyncHangChild { + friend class PTestSyncHangChild; + + public: + TestSyncHangChild(); + virtual ~TestSyncHangChild(); + + protected: + mozilla::ipc::IPCResult RecvUnusedMessage(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestSyncHang_h diff --git a/ipc/ipdl/test/cxx/TestSyncWakeup.cpp b/ipc/ipdl/test/cxx/TestSyncWakeup.cpp new file mode 100644 index 0000000000..4c99aa1bab --- /dev/null +++ b/ipc/ipdl/test/cxx/TestSyncWakeup.cpp @@ -0,0 +1,106 @@ +#if defined(OS_POSIX) +# include <unistd.h> // sleep() +#endif + +#include "TestSyncWakeup.h" + +#include "IPDLUnitTests.h" // fail etc. + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +TestSyncWakeupParent::TestSyncWakeupParent() { + MOZ_COUNT_CTOR(TestSyncWakeupParent); +} + +TestSyncWakeupParent::~TestSyncWakeupParent() { + MOZ_COUNT_DTOR(TestSyncWakeupParent); +} + +void TestSyncWakeupParent::Main() { + if (!SendStart()) fail("sending Start()"); +} + +mozilla::ipc::IPCResult TestSyncWakeupParent::AnswerStackFrame() { + if (!CallStackFrame()) fail("calling StackFrame()"); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestSyncWakeupParent::RecvSync1() { + if (!SendNote1()) fail("sending Note1()"); + + // XXX ugh ... need to ensure that the async message and sync + // reply come in "far enough" apart that this test doesn't pass on + // accident +#if defined(OS_POSIX) + // NB: can't use PR_Sleep (i.e. Sleep() on windows) because it's + // only spec'd to block the current thread, not the current + // process. We need the IO thread to sleep as well. + puts(" (sleeping for 5 seconds. sorry!)"); + sleep(5); +#endif + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestSyncWakeupParent::RecvSync2() { + if (!SendNote2()) fail("sending Note2()"); + +#if defined(OS_POSIX) + // see above + sleep(5); + puts(" (sleeping for 5 seconds. sorry!)"); +#endif + + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// child + +TestSyncWakeupChild::TestSyncWakeupChild() : mDone(false) { + MOZ_COUNT_CTOR(TestSyncWakeupChild); +} + +TestSyncWakeupChild::~TestSyncWakeupChild() { + MOZ_COUNT_DTOR(TestSyncWakeupChild); +} + +mozilla::ipc::IPCResult TestSyncWakeupChild::RecvStart() { + // First test: the parent fires back an async message while + // replying to a sync one + if (!SendSync1()) fail("sending Sync()"); + + // drop back into the event loop to get Note1(), then kick off the + // second test + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestSyncWakeupChild::RecvNote1() { + // Second test: the parent fires back an async message while + // replying to a sync one, with a frame on the RPC stack + if (!CallStackFrame()) fail("calling StackFrame()"); + + if (!mDone) fail("should have received Note2()!"); + + Close(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestSyncWakeupChild::AnswerStackFrame() { + if (!SendSync2()) fail("sending Sync()"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestSyncWakeupChild::RecvNote2() { + mDone = true; + return IPC_OK(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestSyncWakeup.h b/ipc/ipdl/test/cxx/TestSyncWakeup.h new file mode 100644 index 0000000000..5666307929 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestSyncWakeup.h @@ -0,0 +1,66 @@ +#ifndef mozilla__ipdltest_TestSyncWakeup_h +#define mozilla__ipdltest_TestSyncWakeup_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestSyncWakeupParent.h" +#include "mozilla/_ipdltest/PTestSyncWakeupChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestSyncWakeupParent : public PTestSyncWakeupParent { + friend class PTestSyncWakeupParent; + + public: + TestSyncWakeupParent(); + virtual ~TestSyncWakeupParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return true; } + + void Main(); + + protected: + mozilla::ipc::IPCResult AnswerStackFrame(); + + mozilla::ipc::IPCResult RecvSync1(); + + mozilla::ipc::IPCResult RecvSync2(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + passed("ok"); + QuitParent(); + } +}; + +class TestSyncWakeupChild : public PTestSyncWakeupChild { + friend class PTestSyncWakeupChild; + + public: + TestSyncWakeupChild(); + virtual ~TestSyncWakeupChild(); + + protected: + mozilla::ipc::IPCResult RecvStart(); + + mozilla::ipc::IPCResult RecvNote1(); + + mozilla::ipc::IPCResult AnswerStackFrame(); + + mozilla::ipc::IPCResult RecvNote2(); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) fail("unexpected destruction!"); + QuitChild(); + } + + private: + bool mDone; +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestSyncWakeup_h diff --git a/ipc/ipdl/test/cxx/TestUniquePtrIPC.cpp b/ipc/ipdl/test/cxx/TestUniquePtrIPC.cpp new file mode 100644 index 0000000000..7d0e8dd6ca --- /dev/null +++ b/ipc/ipdl/test/cxx/TestUniquePtrIPC.cpp @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "TestUniquePtrIPC.h" + +namespace mozilla { +namespace _ipdltest { + +// --------------------------------------------------------------------------- +// PARENT PROCESS +// --------------------------------------------------------------------------- + +void TestUniquePtrIPCParent::Main() { + UniquePtr<int> a1 = MakeUnique<int>(1); + UniquePtr<DummyStruct> a2 = MakeUnique<DummyStruct>(2); + DummyStruct a3(3); + UniquePtr<int> a4; + + if (!SendTestMessage(std::move(a1), std::move(a2), a3, std::move(a4))) { + fail("failed sending UniquePtr items"); + } + + if (a1 || a2) { + fail("did not move TestMessage items in parent"); + } + + if (a4) { + fail("somehow turned null ptr into non-null by sending it"); + } + + // Pass UniquePtr by reference + UniquePtr<DummyStruct> b = MakeUnique<DummyStruct>(1); + + if (!SendTestSendReference(std::move(b))) { + fail("failed sending UniquePtr by reference"); + } + if (b) { + fail("did not move UniquePtr sent by reference"); + } +} + +// --------------------------------------------------------------------------- +// CHILD PROCESS +// --------------------------------------------------------------------------- + +mozilla::ipc::IPCResult TestUniquePtrIPCChild::RecvTestMessage( + UniquePtr<int>&& aA1, UniquePtr<DummyStruct>&& aA2, const DummyStruct& aA3, + UniquePtr<int>&& aA4) { + if ((!aA1) || (!aA2)) { + fail("TestMessage received NULL items in child"); + } + + if (aA4) { + fail("TestMessage received non-NULL when expecting NULL"); + } + + if ((*aA1 != 1) || (aA2->x() != 2) || (aA3.x() != 3)) { + fail("TestMessage received incorrect items in child"); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestUniquePtrIPCChild::RecvTestSendReference( + UniquePtr<DummyStruct>&& aA) { + if (!aA) { + fail("TestSendReference received NULL item in child"); + } + + if (*aA != 1) { + fail("TestSendReference received incorrect item in child"); + } + + Close(); + return IPC_OK(); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestUniquePtrIPC.h b/ipc/ipdl/test/cxx/TestUniquePtrIPC.h new file mode 100644 index 0000000000..3c9de33df3 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestUniquePtrIPC.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_TestUniquePtrIPC_h +#define mozilla_TestUniquePtrIPC_h + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestUniquePtrIPCParent.h" +#include "mozilla/_ipdltest/PTestUniquePtrIPCChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestUniquePtrIPCParent : public PTestUniquePtrIPCParent { + public: + MOZ_COUNTED_DEFAULT_CTOR(TestUniquePtrIPCParent) + MOZ_COUNTED_DTOR_OVERRIDE(TestUniquePtrIPCParent) + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return false; } + + void Main(); + + bool ShouldContinueFromReplyTimeout() override { return false; } + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) { + fail("Abnormal shutdown of parent"); + } + passed("ok"); + QuitParent(); + } +}; + +class TestUniquePtrIPCChild : public PTestUniquePtrIPCChild { + public: + MOZ_COUNTED_DEFAULT_CTOR(TestUniquePtrIPCChild) + MOZ_COUNTED_DTOR_OVERRIDE(TestUniquePtrIPCChild) + + mozilla::ipc::IPCResult RecvTestMessage(UniquePtr<int>&& aA1, + UniquePtr<DummyStruct>&& aA2, + const DummyStruct& aA3, + UniquePtr<int>&& aA4); + + mozilla::ipc::IPCResult RecvTestSendReference(UniquePtr<DummyStruct>&& aA); + + virtual void ActorDestroy(ActorDestroyReason why) override { + if (NormalShutdown != why) { + fail("Abnormal shutdown of child"); + } + QuitChild(); + } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // mozilla_TestUniquePtrIPC_h diff --git a/ipc/ipdl/test/cxx/TestUrgency.cpp b/ipc/ipdl/test/cxx/TestUrgency.cpp new file mode 100644 index 0000000000..5845f8210d --- /dev/null +++ b/ipc/ipdl/test/cxx/TestUrgency.cpp @@ -0,0 +1,117 @@ +#include "TestUrgency.h" + +#include "IPDLUnitTests.h" // fail etc. +#if defined(OS_POSIX) +# include <unistd.h> +#else +# include <windows.h> +#endif + +namespace mozilla { +namespace _ipdltest { + +#if defined(OS_POSIX) +static void Sleep(int ms) { sleep(ms / 1000); } +#endif + +//----------------------------------------------------------------------------- +// parent + +TestUrgencyParent::TestUrgencyParent() : inreply_(false) { + MOZ_COUNT_CTOR(TestUrgencyParent); +} + +TestUrgencyParent::~TestUrgencyParent() { MOZ_COUNT_DTOR(TestUrgencyParent); } + +void TestUrgencyParent::Main() { + if (!SendStart()) fail("sending Start"); +} + +mozilla::ipc::IPCResult TestUrgencyParent::RecvTest1(uint32_t* value) { + if (!SendReply1(value)) fail("sending Reply1"); + if (*value != 99) fail("bad value"); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestUrgencyParent::RecvTest2() { + uint32_t value; + inreply_ = true; + if (!SendReply2(&value)) fail("sending Reply2"); + inreply_ = false; + if (value != 500) fail("bad value"); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestUrgencyParent::RecvTest3(uint32_t* value) { + if (inreply_) fail("nested non-urgent on top of urgent rpc"); + *value = 1000; + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestUrgencyParent::RecvFinalTest_Begin() { + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// child + +enum { + kFirstTestBegin = 1, + kFirstTestGotReply, + kSecondTestBegin, + kSecondTestGotReply, +}; + +mozilla::ipc::IPCResult TestUrgencyChild::RecvStart() { + uint32_t result; + + // Send a synchronous message, expect to get an urgent message while + // blocked. + test_ = kFirstTestBegin; + if (!SendTest1(&result)) fail("calling SendTest1"); + if (result != 99) fail("bad result in RecvStart"); + if (test_ != kFirstTestGotReply) fail("never received urgent message"); + + // Initiate the next test by sending an asynchronous message, then becoming + // blocked. This tests that the urgent message is still delivered properly, + // and that the parent does not try to service the sync + test_ = kSecondTestBegin; + if (!SendTest2()) fail("calling SendTest2"); + if (!SendTest3(&result)) fail("calling SendTest3"); + if (test_ != kSecondTestGotReply) fail("never received urgent message #2"); + if (result != 1000) fail("wrong value from test3"); + + if (!SendFinalTest_Begin()) fail("Final test should have succeeded"); + + Close(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestUrgencyChild::RecvReply1(uint32_t* reply) { + if (test_ != kFirstTestBegin) fail("wrong test # in RecvReply1"); + + *reply = 99; + test_ = kFirstTestGotReply; + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestUrgencyChild::RecvReply2(uint32_t* reply) { + if (test_ != kSecondTestBegin) fail("wrong test # in RecvReply2"); + + // sleep for 5 seconds so the parent process tries to deliver more messages. + Sleep(5000); + + *reply = 500; + test_ = kSecondTestGotReply; + return IPC_OK(); +} + +TestUrgencyChild::TestUrgencyChild() : test_(0) { + MOZ_COUNT_CTOR(TestUrgencyChild); +} + +TestUrgencyChild::~TestUrgencyChild() { MOZ_COUNT_DTOR(TestUrgencyChild); } + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestUrgency.h b/ipc/ipdl/test/cxx/TestUrgency.h new file mode 100644 index 0000000000..6af73177ee --- /dev/null +++ b/ipc/ipdl/test/cxx/TestUrgency.h @@ -0,0 +1,57 @@ +#ifndef mozilla__ipdltest_TestUrgency_h +#define mozilla__ipdltest_TestUrgency_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestUrgencyParent.h" +#include "mozilla/_ipdltest/PTestUrgencyChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestUrgencyParent : public PTestUrgencyParent { + public: + TestUrgencyParent(); + virtual ~TestUrgencyParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return false; } + + void Main(); + + mozilla::ipc::IPCResult RecvTest1(uint32_t* value); + mozilla::ipc::IPCResult RecvTest2(); + mozilla::ipc::IPCResult RecvTest3(uint32_t* value); + mozilla::ipc::IPCResult RecvTest4_Begin(); + mozilla::ipc::IPCResult RecvTest4_NestedSync(); + mozilla::ipc::IPCResult RecvFinalTest_Begin(); + + bool ShouldContinueFromReplyTimeout() override { return false; } + virtual void ActorDestroy(ActorDestroyReason why) override { + passed("ok"); + QuitParent(); + } + + private: + bool inreply_; +}; + +class TestUrgencyChild : public PTestUrgencyChild { + public: + TestUrgencyChild(); + virtual ~TestUrgencyChild(); + + mozilla::ipc::IPCResult RecvStart(); + mozilla::ipc::IPCResult RecvReply1(uint32_t* reply); + mozilla::ipc::IPCResult RecvReply2(uint32_t* reply); + + virtual void ActorDestroy(ActorDestroyReason why) override { QuitChild(); } + + private: + uint32_t test_; +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestUrgency_h diff --git a/ipc/ipdl/test/cxx/TestUrgentHangs.cpp b/ipc/ipdl/test/cxx/TestUrgentHangs.cpp new file mode 100644 index 0000000000..e6139e38c5 --- /dev/null +++ b/ipc/ipdl/test/cxx/TestUrgentHangs.cpp @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=2 ts=4 et : + */ +#include "TestUrgentHangs.h" + +#include "IPDLUnitTests.h" // fail etc. +#include "prthread.h" +#if defined(OS_POSIX) +# include <unistd.h> +#else +# include <windows.h> +#endif + +namespace mozilla { +namespace _ipdltest { + +//----------------------------------------------------------------------------- +// parent + +TestUrgentHangsParent::TestUrgentHangsParent() + : mInnerCount(0), mInnerUrgentCount(0) { + MOZ_COUNT_CTOR(TestUrgentHangsParent); +} + +TestUrgentHangsParent::~TestUrgentHangsParent() { + MOZ_COUNT_DTOR(TestUrgentHangsParent); +} + +void TestUrgentHangsParent::Main() { + SetReplyTimeoutMs(1000); + + // Should succeed despite the nested sleep call because the content process + // responded to the transaction. + if (!SendTest1_1()) fail("sending Test1_1"); + + // Fails with a timeout. + if (SendTest2()) fail("sending Test2"); + + // Also fails since we haven't gotten a response for Test2 yet. + if (SendTest3()) fail("sending Test3"); + + // Do a second round of testing once the reply to Test2 comes back. + MessageLoop::current()->PostDelayedTask( + NewNonOwningRunnableMethod( + "_ipdltest::TestUrgentHangsParent::SecondStage", this, + &TestUrgentHangsParent::SecondStage), + 3000); +} + +void TestUrgentHangsParent::SecondStage() { + // Send an async message that waits 2 seconds and then sends a sync message + // (which should be processed). + if (!SendTest4()) fail("sending Test4"); + + // Send a sync message that will time out because the child is waiting + // inside RecvTest4. + if (SendTest4_1()) fail("sending Test4_1"); + + MessageLoop::current()->PostDelayedTask( + NewNonOwningRunnableMethod("_ipdltest::TestUrgentHangsParent::ThirdStage", + this, &TestUrgentHangsParent::ThirdStage), + 3000); +} + +void TestUrgentHangsParent::ThirdStage() { + // The third stage does the same thing as the second stage except that the + // child sends an urgent message to us. In this case, we actually answer + // that message unconditionally. + + // Send an async message that waits 2 seconds and then sends a sync message + // (which should be processed). + if (!SendTest5()) fail("sending Test5"); + + // Send a sync message that will time out because the child is waiting + // inside RecvTest5. + if (SendTest5_1()) fail("sending Test5_1"); + + // Close the channel after the child finishes its work in RecvTest5. + MessageLoop::current()->PostDelayedTask( + NewNonOwningRunnableMethod("ipc::IToplevelProtocol::Close", this, + &TestUrgentHangsParent::Close), + 3000); +} + +mozilla::ipc::IPCResult TestUrgentHangsParent::RecvTest1_2() { + if (!SendTest1_3()) fail("sending Test1_3"); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestUrgentHangsParent::RecvTestInner() { + mInnerCount++; + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestUrgentHangsParent::RecvTestInnerUrgent() { + mInnerUrgentCount++; + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// child + +mozilla::ipc::IPCResult TestUrgentHangsChild::RecvTest1_1() { + if (!SendTest1_2()) fail("sending Test1_2"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestUrgentHangsChild::RecvTest1_3() { + PR_Sleep(PR_SecondsToInterval(2)); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestUrgentHangsChild::RecvTest2() { + PR_Sleep(PR_SecondsToInterval(2)); + + // Should fail because of the timeout. + if (SendTestInner()) fail("sending TestInner"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestUrgentHangsChild::RecvTest3() { + fail("RecvTest3 should never be called"); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestUrgentHangsChild::RecvTest4() { + PR_Sleep(PR_SecondsToInterval(2)); + + // This won't fail because we should handle Test4_1 here before actually + // sending TestInner to the parent. + if (!SendTestInner()) fail("sending TestInner"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestUrgentHangsChild::RecvTest4_1() { + // This should fail because Test4_1 timed out and hasn't gotten a response + // yet. + if (SendTestInner()) fail("sending TestInner"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestUrgentHangsChild::RecvTest5() { + PR_Sleep(PR_SecondsToInterval(2)); + + // This message will actually be handled by the parent even though it's in + // the timeout state. + if (!SendTestInnerUrgent()) fail("sending TestInner"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult TestUrgentHangsChild::RecvTest5_1() { + // This message will actually be handled by the parent even though it's in + // the timeout state. + if (!SendTestInnerUrgent()) fail("sending TestInner"); + + return IPC_OK(); +} + +TestUrgentHangsChild::TestUrgentHangsChild() { + MOZ_COUNT_CTOR(TestUrgentHangsChild); +} + +TestUrgentHangsChild::~TestUrgentHangsChild() { + MOZ_COUNT_DTOR(TestUrgentHangsChild); +} + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/cxx/TestUrgentHangs.h b/ipc/ipdl/test/cxx/TestUrgentHangs.h new file mode 100644 index 0000000000..facda522bf --- /dev/null +++ b/ipc/ipdl/test/cxx/TestUrgentHangs.h @@ -0,0 +1,64 @@ +#ifndef mozilla__ipdltest_TestUrgentHangs_h +#define mozilla__ipdltest_TestUrgentHangs_h 1 + +#include "mozilla/_ipdltest/IPDLUnitTests.h" + +#include "mozilla/_ipdltest/PTestUrgentHangsParent.h" +#include "mozilla/_ipdltest/PTestUrgentHangsChild.h" + +namespace mozilla { +namespace _ipdltest { + +class TestUrgentHangsParent : public PTestUrgentHangsParent { + public: + TestUrgentHangsParent(); + virtual ~TestUrgentHangsParent(); + + static bool RunTestInProcesses() { return true; } + static bool RunTestInThreads() { return false; } + + void Main(); + void SecondStage(); + void ThirdStage(); + + mozilla::ipc::IPCResult RecvTest1_2(); + mozilla::ipc::IPCResult RecvTestInner(); + mozilla::ipc::IPCResult RecvTestInnerUrgent(); + + bool ShouldContinueFromReplyTimeout() override { return false; } + virtual void ActorDestroy(ActorDestroyReason why) override { + if (mInnerCount != 1) { + fail("wrong mInnerCount"); + } + if (mInnerUrgentCount != 2) { + fail("wrong mInnerUrgentCount"); + } + passed("ok"); + QuitParent(); + } + + private: + size_t mInnerCount, mInnerUrgentCount; +}; + +class TestUrgentHangsChild : public PTestUrgentHangsChild { + public: + TestUrgentHangsChild(); + virtual ~TestUrgentHangsChild(); + + mozilla::ipc::IPCResult RecvTest1_1(); + mozilla::ipc::IPCResult RecvTest1_3(); + mozilla::ipc::IPCResult RecvTest2(); + mozilla::ipc::IPCResult RecvTest3(); + mozilla::ipc::IPCResult RecvTest4(); + mozilla::ipc::IPCResult RecvTest4_1(); + mozilla::ipc::IPCResult RecvTest5(); + mozilla::ipc::IPCResult RecvTest5_1(); + + virtual void ActorDestroy(ActorDestroyReason why) override { QuitChild(); } +}; + +} // namespace _ipdltest +} // namespace mozilla + +#endif // ifndef mozilla__ipdltest_TestUrgentHangs_h diff --git a/ipc/ipdl/test/cxx/app/TestIPDL.cpp b/ipc/ipdl/test/cxx/app/TestIPDL.cpp new file mode 100644 index 0000000000..3891aead62 --- /dev/null +++ b/ipc/ipdl/test/cxx/app/TestIPDL.cpp @@ -0,0 +1,24 @@ +/* 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/. */ + +#define MOZ_IPDL_TESTS +#include "mozilla/Bootstrap.h" + +#if defined(XP_WIN) +# include <windows.h> +# include "nsWindowsWMain.cpp" +#endif + +using namespace mozilla; + +int main(int argc, char** argv) { + // the first argument specifies which IPDL test case/suite to load + if (argc < 2) return 1; + + Bootstrap::UniquePtr bootstrap = GetBootstrap(); + if (!bootstrap) { + return 2; + } + return bootstrap->XRE_RunIPDLTest(argc, argv); +} diff --git a/ipc/ipdl/test/cxx/app/moz.build b/ipc/ipdl/test/cxx/app/moz.build new file mode 100644 index 0000000000..909360062a --- /dev/null +++ b/ipc/ipdl/test/cxx/app/moz.build @@ -0,0 +1,20 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +GeckoProgram("ipdlunittest", linkage="dependent") + +SOURCES += [ + "TestIPDL.cpp", +] +include("/ipc/chromium/chromium-config.mozbuild") + +LOCAL_INCLUDES += [ + "/toolkit/xre", + "/xpcom/base", +] + +if CONFIG["CC_TYPE"] == "clang-cl": + WIN32_EXE_LDFLAGS += ["-ENTRY:wmainCRTStartup"] diff --git a/ipc/ipdl/test/cxx/genIPDLUnitTests.py b/ipc/ipdl/test/cxx/genIPDLUnitTests.py new file mode 100644 index 0000000000..160b90a031 --- /dev/null +++ b/ipc/ipdl/test/cxx/genIPDLUnitTests.py @@ -0,0 +1,193 @@ +# 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 string +import sys + + +def usage(): + print( + """ +%s template_file -t unit_tests... -e extra_protocols... + + TEMPLATE_FILE is used to generate to generate the unit-tester .cpp + UNIT_TESTS are the top-level protocols defining unit tests + EXTRA_PROTOCOLS are top-level protocols for subprocesses that can be + spawned in tests but are not unit tests in and of + themselves +""" + % (sys.argv[0]), + file=sys.stderr, + ) + sys.exit(1) + + +def main(argv): + template = argv[1] + + if argv[2] != "-t": + usage() + i = 3 + unittests = [] + while argv[i] != "-e": + unittests.append(argv[i]) + i += 1 + + extras = argv[(i + 1) :] + + includes = "\n".join(['#include "%s.h"' % (t) for t in unittests]) + + enum_values = "\n".join([" %s," % (t) for t in unittests + extras]) + last_enum = unittests[-1] + + string_to_enums = "\n".join( + [ + """ else if (!strcmp(aString, "%s")) + return %s;""" + % (t, t) + for t in unittests + extras + ] + ) + + enum_to_strings = "\n".join( + [ + """ case %s: + return "%s";""" + % (t, t) + for t in unittests + extras + ] + ) + + parent_delete_cases = "\n".join( + [ + """ case %s: { + delete reinterpret_cast<%sParent*>(gParentActor); + return; + } +""" + % (t, t) + for t in unittests + ] + ) + + parent_enabled_cases_proc = "\n".join( + [ + """ case %s: { + if (!%sParent::RunTestInProcesses()) { + passed("N/A to proc"); + DeferredParentShutdown(); + return; + } + break; + } +""" + % (t, t) + for t in unittests + ] + ) + + parent_main_cases_proc = "\n".join( + [ + """ case %s: { + %sParent** parent = + reinterpret_cast<%sParent**>(&gParentActor); + *parent = new %sParent(); + (*parent)->Open(transport, child); + return (*parent)->Main(); + } +""" + % (t, t, t, t) + for t in unittests + ] + ) + + parent_enabled_cases_thread = "\n".join( + [ + """ case %s: { + if (!%sParent::RunTestInThreads()) { + passed("N/A to threads"); + DeferredParentShutdown(); + return; + } + break; + } +""" + % (t, t) + for t in unittests + ] + ) + + parent_main_cases_thread = "\n".join( + [ + """ case %s: { + %sParent** parent = + reinterpret_cast<%sParent**>(&gParentActor); + *parent = new %sParent(); + + %sChild** child = + reinterpret_cast<%sChild**>(&gChildActor); + *child = new %sChild(); + + ::mozilla::ipc::MessageChannel *childChannel = (*child)->GetIPCChannel(); + ::mozilla::ipc::Side parentSide = + ::mozilla::ipc::ParentSide; + + (*parent)->Open(childChannel, childMessageLoop, parentSide); + return (*parent)->Main(); + } +""" + % (t, t, t, t, t, t, t) + for t in unittests + ] + ) + + child_delete_cases = "\n".join( + [ + """ case %s: { + delete reinterpret_cast<%sChild*>(gChildActor); + return; + } +""" + % (t, t) + for t in unittests + extras + ] + ) + + child_init_cases = "\n".join( + [ + """ case %s: { + %sChild** child = + reinterpret_cast<%sChild**>(&gChildActor); + *child = new %sChild(); + (*child)->Open(transport, parentPid, worker); + return; + } +""" + % (t, t, t, t) + for t in unittests + extras + ] + ) + + templatefile = open(template, "r", encoding="utf-8") + sys.stdout.write( + string.Template(templatefile.read()).substitute( + INCLUDES=includes, + ENUM_VALUES=enum_values, + LAST_ENUM=last_enum, + STRING_TO_ENUMS=string_to_enums, + ENUM_TO_STRINGS=enum_to_strings, + PARENT_DELETE_CASES=parent_delete_cases, + PARENT_ENABLED_CASES_PROC=parent_enabled_cases_proc, + PARENT_MAIN_CASES_PROC=parent_main_cases_proc, + PARENT_ENABLED_CASES_THREAD=parent_enabled_cases_thread, + PARENT_MAIN_CASES_THREAD=parent_main_cases_thread, + CHILD_DELETE_CASES=child_delete_cases, + CHILD_INIT_CASES=child_init_cases, + ) + ) + templatefile.close() + + +if __name__ == "__main__": + main(sys.argv) diff --git a/ipc/ipdl/test/cxx/moz.build b/ipc/ipdl/test/cxx/moz.build new file mode 100644 index 0000000000..e2650df0aa --- /dev/null +++ b/ipc/ipdl/test/cxx/moz.build @@ -0,0 +1,172 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +DIRS += ["app"] + +EXPORTS.mozilla._ipdltest += [ + "IPDLUnitTestProcessChild.h", + "IPDLUnitTests.h", + "IPDLUnitTestTypes.h", + "IPDLUnitTestUtils.h", + "TestActorPunning.h", + "TestAsyncReturns.h", + "TestBadActor.h", + "TestCancel.h", + "TestCrashCleanup.h", + "TestDataStructures.h", + "TestDemon.h", + "TestDesc.h", + "TestEndpointBridgeMain.h", + "TestEndpointOpens.h", + "TestFailedCtor.h", + "TestHangs.h", + "TestHighestPrio.h", + "TestInterruptErrorCleanup.h", + "TestInterruptRaces.h", + "TestInterruptShutdownRace.h", + "TestJSON.h", + "TestLatency.h", + "TestManyChildAllocs.h", + "TestMultiMgrs.h", + "TestNestedLoops.h", + "TestOffMainThreadPainting.h", + "TestRaceDeadlock.h", + "TestRaceDeferral.h", + "TestRacyInterruptReplies.h", + "TestRacyReentry.h", + "TestRacyUndefer.h", + "TestRPC.h", + "TestSanity.h", + "TestSelfManageRoot.h", + "TestShmem.h", + "TestShutdown.h", + "TestStackHooks.h", + "TestSyncError.h", + "TestSyncHang.h", + "TestSyncWakeup.h", + "TestUniquePtrIPC.h", + "TestUrgency.h", + "TestUrgentHangs.h", +] + +SOURCES += [ + "TestActorPunning.cpp", + "TestAsyncReturns.cpp", + "TestBadActor.cpp", + "TestCancel.cpp", + "TestCrashCleanup.cpp", + "TestDataStructures.cpp", + "TestDemon.cpp", + "TestDesc.cpp", + "TestEndpointBridgeMain.cpp", + "TestEndpointOpens.cpp", + "TestFailedCtor.cpp", + "TestHangs.cpp", + "TestHighestPrio.cpp", + "TestInterruptErrorCleanup.cpp", + "TestInterruptRaces.cpp", + "TestInterruptShutdownRace.cpp", + "TestJSON.cpp", + "TestLatency.cpp", + "TestManyChildAllocs.cpp", + "TestMultiMgrs.cpp", + "TestNestedLoops.cpp", + "TestOffMainThreadPainting.cpp", + "TestRaceDeadlock.cpp", + "TestRaceDeferral.cpp", + "TestRacyInterruptReplies.cpp", + "TestRacyReentry.cpp", + "TestRacyUndefer.cpp", + "TestRPC.cpp", + "TestSanity.cpp", + "TestSelfManageRoot.cpp", + "TestShmem.cpp", + "TestShutdown.cpp", + "TestStackHooks.cpp", + "TestSyncError.cpp", + "TestSyncHang.cpp", + "TestSyncWakeup.cpp", + "TestUniquePtrIPC.cpp", + "TestUrgency.cpp", + "TestUrgentHangs.cpp", +] + +SOURCES += [ + "!IPDLUnitTests.cpp", + "IPDLUnitTestProcessChild.cpp", + "IPDLUnitTestSubprocess.cpp", +] + +IPDL_SOURCES += [ + "PTestActorPunning.ipdl", + "PTestActorPunningPunned.ipdl", + "PTestActorPunningSub.ipdl", + "PTestAsyncReturns.ipdl", + "PTestBadActor.ipdl", + "PTestBadActorSub.ipdl", + "PTestCancel.ipdl", + "PTestCrashCleanup.ipdl", + "PTestDataStructures.ipdl", + "PTestDataStructuresCommon.ipdlh", + "PTestDataStructuresSub.ipdl", + "PTestDemon.ipdl", + "PTestDesc.ipdl", + "PTestDescSub.ipdl", + "PTestDescSubsub.ipdl", + "PTestEndpointBridgeMain.ipdl", + "PTestEndpointBridgeMainSub.ipdl", + "PTestEndpointBridgeSub.ipdl", + "PTestEndpointOpens.ipdl", + "PTestEndpointOpensOpened.ipdl", + "PTestFailedCtor.ipdl", + "PTestFailedCtorSub.ipdl", + "PTestFailedCtorSubsub.ipdl", + "PTestHandle.ipdl", + "PTestHangs.ipdl", + "PTestHighestPrio.ipdl", + "PTestIndirectProtocolParam.ipdlh", + "PTestIndirectProtocolParamFirst.ipdl", + "PTestIndirectProtocolParamManage.ipdl", + "PTestIndirectProtocolParamSecond.ipdl", + "PTestInterruptErrorCleanup.ipdl", + "PTestInterruptRaces.ipdl", + "PTestInterruptShutdownRace.ipdl", + "PTestJSON.ipdl", + "PTestLatency.ipdl", + "PTestManyChildAllocs.ipdl", + "PTestManyChildAllocsSub.ipdl", + "PTestMultiMgrs.ipdl", + "PTestMultiMgrsBottom.ipdl", + "PTestMultiMgrsLeft.ipdl", + "PTestMultiMgrsRight.ipdl", + "PTestNestedLoops.ipdl", + "PTestPaintThread.ipdl", + "PTestPriority.ipdl", + "PTestRaceDeadlock.ipdl", + "PTestRaceDeferral.ipdl", + "PTestRacyInterruptReplies.ipdl", + "PTestRacyReentry.ipdl", + "PTestRacyUndefer.ipdl", + "PTestRPC.ipdl", + "PTestSanity.ipdl", + "PTestSelfManage.ipdl", + "PTestSelfManageRoot.ipdl", + "PTestShmem.ipdl", + "PTestShutdown.ipdl", + "PTestShutdownSub.ipdl", + "PTestShutdownSubsub.ipdl", + "PTestStackHooks.ipdl", + "PTestSyncError.ipdl", + "PTestSyncHang.ipdl", + "PTestSyncWakeup.ipdl", + "PTestUniquePtrIPC.ipdl", + "PTestUrgency.ipdl", + "PTestUrgentHangs.ipdl", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" diff --git a/ipc/ipdl/test/gtest/IPDLUnitTest.cpp b/ipc/ipdl/test/gtest/IPDLUnitTest.cpp new file mode 100644 index 0000000000..66e0149dcb --- /dev/null +++ b/ipc/ipdl/test/gtest/IPDLUnitTest.cpp @@ -0,0 +1,308 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "gtest/gtest.h" + +#include "mozilla/_ipdltest/IPDLUnitTest.h" +#include "mozilla/ipc/IOThreadChild.h" +#include "mozilla/ipc/NodeController.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/ipc/ProcessChild.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsDebugImpl.h" +#include "nsThreadManager.h" + +#include <string> + +#ifdef MOZ_WIDGET_ANDROID +# include "nsIAppShell.h" +# include "nsServiceManagerUtils.h" +# include "nsWidgetsCID.h" +#endif + +namespace mozilla::_ipdltest { + +static std::unordered_map<std::string_view, ipc::IToplevelProtocol* (*)()> + sAllocChildActorRegistry; + +const char* RegisterAllocChildActor(const char* aName, + ipc::IToplevelProtocol* (*aFunc)()) { + sAllocChildActorRegistry[aName] = aFunc; + return aName; +} + +already_AddRefed<IPDLUnitTestParent> IPDLUnitTestParent::CreateCrossProcess() { +#ifdef MOZ_WIDGET_ANDROID + // Force-initialize the appshell on android, as android child process + // launching depends on widget component initialization. + nsCOMPtr<nsIAppShell> _appShell = do_GetService(NS_APPSHELL_CID); +#endif + + RefPtr<IPDLUnitTestParent> parent = new IPDLUnitTestParent(); + parent->mSubprocess = + new ipc::GeckoChildProcessHost(GeckoProcessType_IPDLUnitTest); + + std::vector<std::string> extraArgs; + + auto prefSerializer = MakeUnique<ipc::SharedPreferenceSerializer>(); + if (!prefSerializer->SerializeToSharedMemory(GeckoProcessType_IPDLUnitTest, + /* remoteType */ ""_ns)) { + ADD_FAILURE() + << "SharedPreferenceSerializer::SerializeToSharedMemory failed"; + return nullptr; + } + prefSerializer->AddSharedPrefCmdLineArgs(*parent->mSubprocess, extraArgs); + + if (!parent->mSubprocess->SyncLaunch(extraArgs)) { + ADD_FAILURE() << "Subprocess launch failed"; + return nullptr; + } + + if (!parent->mSubprocess->TakeInitialEndpoint().Bind(parent.get())) { + ADD_FAILURE() << "Opening the parent actor failed"; + return nullptr; + } + + EXPECT_TRUE(parent->CanSend()); + return parent.forget(); +} + +already_AddRefed<IPDLUnitTestParent> IPDLUnitTestParent::CreateCrossThread() { + RefPtr<IPDLUnitTestParent> parent = new IPDLUnitTestParent(); + RefPtr<IPDLUnitTestChild> child = new IPDLUnitTestChild(); + + nsresult rv = + NS_NewNamedThread("IPDL UnitTest", getter_AddRefs(parent->mOtherThread)); + if (NS_FAILED(rv)) { + ADD_FAILURE() << "Failed to create IPDLUnitTest thread"; + return nullptr; + } + if (!parent->Open(child, parent->mOtherThread)) { + ADD_FAILURE() << "Opening the actor failed"; + return nullptr; + } + + EXPECT_TRUE(parent->CanSend()); + return parent.forget(); +} + +IPDLUnitTestParent::~IPDLUnitTestParent() { + if (mSubprocess) { + mSubprocess->Destroy(); + mSubprocess = nullptr; + } + if (mOtherThread) { + mOtherThread->Shutdown(); + } +} + +bool IPDLUnitTestParent::Start(const char* aName, + ipc::IToplevelProtocol* aActor) { + nsID channelId = nsID::GenerateUUID(); + auto [parentPort, childPort] = + ipc::NodeController::GetSingleton()->CreatePortPair(); + if (!SendStart(nsDependentCString(aName), std::move(childPort), channelId)) { + ADD_FAILURE() << "IPDLUnitTestParent::SendStart failed"; + return false; + } + if (!aActor->Open(std::move(parentPort), channelId, OtherPid())) { + ADD_FAILURE() << "Unable to open parent actor"; + return false; + } + return true; +} + +ipc::IPCResult IPDLUnitTestParent::RecvReport(const TestPartResult& aReport) { + if (!aReport.failed()) { + return IPC_OK(); + } + + // Report the failure + ADD_FAILURE_AT(aReport.filename().get(), aReport.lineNumber()) + << "[child " << OtherPid() << "] " << aReport.summary(); + + // If the failure was fatal, kill the child process to avoid hangs. + if (aReport.fatal()) { + KillHard(); + } + return IPC_OK(); +} + +ipc::IPCResult IPDLUnitTestParent::RecvComplete() { + mComplete = true; + Close(); + return IPC_OK(); +} + +void IPDLUnitTestParent::KillHard() { + if (mCalledKillHard) { + return; + } + mCalledKillHard = true; + + // We can't effectively kill a same-process situation, but we can trigger + // shutdown early to avoid hanging. + if (mOtherThread) { + Close(); + nsCOMPtr<nsIThread> otherThread = mOtherThread.forget(); + otherThread->Shutdown(); + } + + if (mSubprocess) { + ProcessHandle handle = mSubprocess->GetChildProcessHandle(); + if (!base::KillProcess(handle, base::PROCESS_END_KILLED_BY_USER)) { + NS_WARNING("failed to kill subprocess!"); + } + mSubprocess->SetAlreadyDead(); + } +} + +ipc::IPCResult IPDLUnitTestChild::RecvStart(const nsCString& aName, + ipc::ScopedPort aPort, + const nsID& aMessageChannelId) { + auto* allocChildActor = + sAllocChildActorRegistry[std::string_view{aName.get()}]; + if (!allocChildActor) { + ADD_FAILURE() << "No AllocChildActor for name " << aName.get() + << " registered!"; + return IPC_FAIL(this, "No AllocChildActor registered!"); + } + + // Store references to the node & port to watch for test completion. + RefPtr<ipc::NodeController> controller = aPort.Controller(); + mojo::core::ports::PortRef port = aPort.Port(); + + auto* child = allocChildActor(); + if (!child->Open(std::move(aPort), aMessageChannelId, OtherPid())) { + ADD_FAILURE() << "Unable to open child actor"; + return IPC_FAIL(this, "Unable to open child actor"); + } + + // Wait for the port which was created for this actor to be fully torn down. + SpinEventLoopUntil("IPDLUnitTestChild::RecvStart"_ns, + [&] { return controller->GetStatus(port).isNothing(); }); + + // Tear down the test actor to end the test. + SendComplete(); + return IPC_OK(); +} + +void IPDLUnitTestChild::ActorDestroy(ActorDestroyReason aWhy) { + if (!XRE_IsParentProcess()) { + XRE_ShutdownChildProcess(); + } +} + +void IPDLTestHelper::TestWrapper(bool aCrossProcess) { + // Create the host and start the test actor with it. + RefPtr<IPDLUnitTestParent> host = + aCrossProcess ? IPDLUnitTestParent::CreateCrossProcess() + : IPDLUnitTestParent::CreateCrossThread(); + ASSERT_TRUE(host); + if (!host->Start(GetName(), GetActor())) { + FAIL(); + } + + // XXX: Consider adding a test timeout? + + // Run the test body. This will send the initial messages to our actor, which + // will eventually clean itself up. + TestBody(); + + // Spin the event loop until the test wrapper host has fully shut down. + SpinEventLoopUntil("IPDLTestHelper::TestWrapper"_ns, + [&] { return !host->CanSend(); }); + + EXPECT_TRUE(host->ReportedComplete()) + << "child process exited without signalling completion"; +} + +// Listener registered within the IPDLUnitTest process used to relay GTest +// failures to the parent process, so that the are marked as failing the overall +// gtest. +class IPDLChildProcessTestListener : public testing::EmptyTestEventListener { + public: + explicit IPDLChildProcessTestListener(IPDLUnitTestChild* aActor) + : mActor(aActor) {} + + virtual void OnTestPartResult( + const testing::TestPartResult& aTestPartResult) override { + mActor->SendReport(TestPartResult( + aTestPartResult.failed(), aTestPartResult.fatally_failed(), + nsDependentCString(aTestPartResult.file_name()), + aTestPartResult.line_number(), + nsDependentCString(aTestPartResult.summary()), + nsDependentCString(aTestPartResult.message()))); + } + + RefPtr<IPDLUnitTestChild> mActor; +}; + +// ProcessChild instance used to run the IPDLUnitTest process. +class IPDLUnitTestProcessChild : public ipc::ProcessChild { + public: + using ipc::ProcessChild::ProcessChild; + bool Init(int aArgc, char* aArgv[]) override { + nsDebugImpl::SetMultiprocessMode("IPDLUnitTest"); + + if (!ProcessChild::InitPrefs(aArgc, aArgv)) { + MOZ_CRASH("InitPrefs failed"); + return false; + } + + if (NS_WARN_IF(NS_FAILED(nsThreadManager::get().Init()))) { + MOZ_CRASH("nsThreadManager initialization failed"); + return false; + } + + RefPtr<IPDLUnitTestChild> child = new IPDLUnitTestChild(); + if (!TakeInitialEndpoint().Bind(child.get())) { + MOZ_CRASH("Bind of IPDLUnitTestChild failed"); + return false; + } + + // Register a listener to forward test results from the child process to the + // parent process to be handled there. + mListener = new IPDLChildProcessTestListener(child); + testing::UnitTest::GetInstance()->listeners().Append(mListener); + + if (NS_FAILED(NS_InitMinimalXPCOM())) { + MOZ_CRASH("NS_InitMinimalXPCOM failed"); + return false; + } + + ipc::SetThisProcessName("IPDLUnitTest"); + return true; + } + + void CleanUp() override { + // Clean up the test listener we registered to get a clean shutdown. + if (mListener) { + testing::UnitTest::GetInstance()->listeners().Release(mListener); + delete mListener; + } + + NS_ShutdownXPCOM(nullptr); + } + + IPDLChildProcessTestListener* mListener = nullptr; +}; + +// Defined in nsEmbedFunctions.cpp +extern UniquePtr<ipc::ProcessChild> (*gMakeIPDLUnitTestProcessChild)( + base::ProcessId, const nsID&); + +// Initialize gMakeIPDLUnitTestProcessChild in a static constructor. +int _childProcessEntryPointStaticConstructor = ([] { + gMakeIPDLUnitTestProcessChild = + [](base::ProcessId aParentPid, + const nsID& aMessageChannelId) -> UniquePtr<ipc::ProcessChild> { + return MakeUnique<IPDLUnitTestProcessChild>(aParentPid, aMessageChannelId); + }; + return 0; +})(); + +} // namespace mozilla::_ipdltest diff --git a/ipc/ipdl/test/gtest/IPDLUnitTest.h b/ipc/ipdl/test/gtest/IPDLUnitTest.h new file mode 100644 index 0000000000..a5fb72e2fe --- /dev/null +++ b/ipc/ipdl/test/gtest/IPDLUnitTest.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla__ipdltest_IPDLUnitTest_h +#define mozilla__ipdltest_IPDLUnitTest_h + +#include "mozilla/_ipdltest/IPDLUnitTestChild.h" +#include "mozilla/_ipdltest/IPDLUnitTestParent.h" +#include "mozilla/ipc/ProtocolUtils.h" + +namespace mozilla::_ipdltest { + +// Should be called from static constructors to register a child actor +// constructor so that it can be called from the child process. +const char* RegisterAllocChildActor( + const char* aName, mozilla::ipc::IToplevelProtocol* (*aFunc)()); + +// Internal helper type used to declare IPDL tests. +class IPDLTestHelper { + public: + void TestWrapper(bool aCrossProcess); + virtual const char* GetName() = 0; + virtual ipc::IToplevelProtocol* GetActor() = 0; + virtual void TestBody() = 0; +}; + +#define IPDL_TEST_CLASS_NAME_(actorname) IPDL_TEST_##actorname + +#define IPDL_TEST_HEAD_(actorname) \ + class IPDL_TEST_CLASS_NAME_(actorname) \ + : public ::mozilla::_ipdltest::IPDLTestHelper { \ + public: \ + IPDL_TEST_CLASS_NAME_(actorname)() : mActor(new actorname##Parent) {} \ + \ + private: \ + void TestBody() override; \ + const char* GetName() override { return sName; }; \ + actorname##Parent* GetActor() override { return mActor; }; \ + \ + actorname##Parent* mActor; \ + static const char* sName; \ + }; \ + const char* IPDL_TEST_CLASS_NAME_(actorname)::sName = \ + ::mozilla::_ipdltest::RegisterAllocChildActor( \ + #actorname, []() -> ::mozilla::ipc::IToplevelProtocol* { \ + return new actorname##Child; \ + }); + +#define IPDL_TEST_DECL_(testgroup, actorname, crossprocess) \ + TEST(testgroup, actorname) \ + { \ + IPDL_TEST_CLASS_NAME_(actorname) test; \ + test.TestWrapper(crossprocess); \ + } + +#define IPDL_TEST_BODY_SEGUE_(actorname) \ + void IPDL_TEST_CLASS_NAME_(actorname)::TestBody() + +// Declare a basic IPDL unit test which will be run in both both cross-thread +// and cross-process configurations. The actor `actorname` will be instantiated +// by default-constructing the parent & child actors in the relevant processes, +// and the test block will be executed, with `mActor` being a pointer to the +// parent actor. The test is asynchronous, and will end when the IPDL connection +// between the created actors is destroyed. +// +// GTest assertions fired in the child process will be relayed to the parent +// process, and should generally function correctly. +#define IPDL_TEST(actorname) \ + IPDL_TEST_HEAD_(actorname) \ + IPDL_TEST_DECL_(IPDLTest_CrossProcess, actorname, true) \ + IPDL_TEST_DECL_(IPDLTest_CrossThread, actorname, false) \ + IPDL_TEST_BODY_SEGUE_(actorname) + +} // namespace mozilla::_ipdltest + +#endif // mozilla__ipdltest_IPDLUnitTest_h diff --git a/ipc/ipdl/test/gtest/IPDLUnitTestChild.h b/ipc/ipdl/test/gtest/IPDLUnitTestChild.h new file mode 100644 index 0000000000..3f9243b274 --- /dev/null +++ b/ipc/ipdl/test/gtest/IPDLUnitTestChild.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla__ipdltest_IPDLUnitTestChild_h +#define mozilla__ipdltest_IPDLUnitTestChild_h + +#include "mozilla/_ipdltest/PIPDLUnitTestChild.h" +#include "nsISupportsImpl.h" + +namespace mozilla::_ipdltest { + +class IPDLUnitTestChild : public PIPDLUnitTestChild { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(IPDLUnitTestChild, override) + + private: + friend class PIPDLUnitTestChild; + + ipc::IPCResult RecvStart(const nsCString& aName, ipc::ScopedPort aPort, + const nsID& aChannelId); + + void ActorDestroy(ActorDestroyReason aReason) override; + + ~IPDLUnitTestChild() = default; +}; + +} // namespace mozilla::_ipdltest + +#endif // mozilla__ipdltest_IPDLUnitTestChild_h diff --git a/ipc/ipdl/test/gtest/IPDLUnitTestParent.h b/ipc/ipdl/test/gtest/IPDLUnitTestParent.h new file mode 100644 index 0000000000..c292506932 --- /dev/null +++ b/ipc/ipdl/test/gtest/IPDLUnitTestParent.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla__ipdltest_IPDLUnitTestParent_h +#define mozilla__ipdltest_IPDLUnitTestParent_h + +#include "mozilla/_ipdltest/PIPDLUnitTestParent.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "nsISupportsImpl.h" + +namespace mozilla::_ipdltest { + +class IPDLUnitTestParent : public PIPDLUnitTestParent { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(IPDLUnitTestParent, override) + + static already_AddRefed<IPDLUnitTestParent> CreateCrossProcess(); + static already_AddRefed<IPDLUnitTestParent> CreateCrossThread(); + + // Try to start a connection with the given name and open `aParentActor` with + // it. Fails the current test and returns false on failure. + bool Start(const char* aName, IToplevelProtocol* aParentActor); + + bool ReportedComplete() const { return mComplete; } + + private: + friend class PIPDLUnitTestParent; + + ipc::IPCResult RecvReport(const TestPartResult& aResult); + ipc::IPCResult RecvComplete(); + + void KillHard(); + + ~IPDLUnitTestParent(); + + // Only one of these two will be set depending. + nsCOMPtr<nsIThread> mOtherThread; + mozilla::ipc::GeckoChildProcessHost* mSubprocess = nullptr; + + // Set to true when the test is complete. + bool mComplete = false; + bool mCalledKillHard = false; +}; + +} // namespace mozilla::_ipdltest + +#endif // mozilla__ipdltest_IPDLUnitTestParent_h diff --git a/ipc/ipdl/test/gtest/PIPDLUnitTest.ipdl b/ipc/ipdl/test/gtest/PIPDLUnitTest.ipdl new file mode 100644 index 0000000000..2f06bccac8 --- /dev/null +++ b/ipc/ipdl/test/gtest/PIPDLUnitTest.ipdl @@ -0,0 +1,33 @@ +/* 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/. */ + +using mozilla::dom::NativeThreadId from "mozilla/dom/NativeThreadId.h"; +[MoveOnly] using class mozilla::ipc::ScopedPort from "mozilla/ipc/ScopedPort.h"; +using struct nsID from "nsID.h"; + +namespace mozilla { +namespace _ipdltest { + +// IPDL representation of GTest's TestPartResult type. +struct TestPartResult { + bool failed; + bool fatal; + nsCString filename; + int32_t lineNumber; + nsCString summary; + nsCString message; +}; + +// Primary actor for the IPDLUnitTest process and thread. +[NeedsOtherPid] async protocol PIPDLUnitTest { + child: + async Start(nsCString name, ScopedPort port, nsID channelId); + + parent: + async Report(TestPartResult result); + async Complete(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/gtest/PTestBasic.ipdl b/ipc/ipdl/test/gtest/PTestBasic.ipdl new file mode 100644 index 0000000000..25f32cacbd --- /dev/null +++ b/ipc/ipdl/test/gtest/PTestBasic.ipdl @@ -0,0 +1,14 @@ +/* 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/. */ + +namespace mozilla { +namespace _ipdltest { + +async protocol PTestBasic { +child: + async Hello(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/gtest/PTestManyHandles.ipdl b/ipc/ipdl/test/gtest/PTestManyHandles.ipdl new file mode 100644 index 0000000000..b1db633fdb --- /dev/null +++ b/ipc/ipdl/test/gtest/PTestManyHandles.ipdl @@ -0,0 +1,15 @@ +/* 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/. */ + +namespace mozilla { +namespace _ipdltest { + +[ParentImpl=virtual, ChildImpl=virtual] +async protocol PTestManyHandles { +child: + async ManyHandles(FileDescriptor[] descrs); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/gtest/TestBasic.cpp b/ipc/ipdl/test/gtest/TestBasic.cpp new file mode 100644 index 0000000000..36ad55ba99 --- /dev/null +++ b/ipc/ipdl/test/gtest/TestBasic.cpp @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "gtest/gtest.h" + +#include "mozilla/_ipdltest/IPDLUnitTest.h" +#include "mozilla/_ipdltest/TestBasicChild.h" +#include "mozilla/_ipdltest/TestBasicParent.h" + +using namespace mozilla::ipc; + +namespace mozilla::_ipdltest { + +IPCResult TestBasicChild::RecvHello() { + EXPECT_TRUE(CanSend()); + Close(); + EXPECT_FALSE(CanSend()); + return IPC_OK(); +} + +IPDL_TEST(TestBasic) { + bool ok = mActor->SendHello(); + ASSERT_TRUE(ok); +} + +} // namespace mozilla::_ipdltest diff --git a/ipc/ipdl/test/gtest/TestBasicChild.h b/ipc/ipdl/test/gtest/TestBasicChild.h new file mode 100644 index 0000000000..9971073f4c --- /dev/null +++ b/ipc/ipdl/test/gtest/TestBasicChild.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla__ipdltest_TestBasicChild_h +#define mozilla__ipdltest_TestBasicChild_h + +#include "mozilla/_ipdltest/PTestBasicChild.h" + +namespace mozilla::_ipdltest { + +class TestBasicChild : public PTestBasicChild { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestBasicChild, override) + + public: + mozilla::ipc::IPCResult RecvHello(); + + private: + ~TestBasicChild() = default; +}; + +} // namespace mozilla::_ipdltest + +#endif // mozilla__ipdltest_TestBasicChild_h diff --git a/ipc/ipdl/test/gtest/TestBasicParent.h b/ipc/ipdl/test/gtest/TestBasicParent.h new file mode 100644 index 0000000000..22b07375ba --- /dev/null +++ b/ipc/ipdl/test/gtest/TestBasicParent.h @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla__ipdltest_TestBasicParent_h +#define mozilla__ipdltest_TestBasicParent_h + +#include "mozilla/_ipdltest/PTestBasicParent.h" + +namespace mozilla::_ipdltest { + +class TestBasicParent : public PTestBasicParent { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestBasicParent, override) + + private: + ~TestBasicParent() = default; +}; + +} // namespace mozilla::_ipdltest + +#endif // mozilla__ipdltest_TestBasicParent_h diff --git a/ipc/ipdl/test/gtest/TestManyHandles.cpp b/ipc/ipdl/test/gtest/TestManyHandles.cpp new file mode 100644 index 0000000000..ead8af22b1 --- /dev/null +++ b/ipc/ipdl/test/gtest/TestManyHandles.cpp @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "gtest/gtest.h" + +#include "mozilla/_ipdltest/IPDLUnitTest.h" +#include "mozilla/_ipdltest/PTestManyHandlesChild.h" +#include "mozilla/_ipdltest/PTestManyHandlesParent.h" + +using namespace mozilla::ipc; + +namespace mozilla::_ipdltest { + +class TestManyHandlesChild : public PTestManyHandlesChild { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestManyHandlesChild, override) + + public: + IPCResult RecvManyHandles(nsTArray<FileDescriptor>&& aDescrs) override { + EXPECT_EQ(aDescrs.Length(), 500u); + for (int i = 0; i < static_cast<int>(aDescrs.Length()); ++i) { + UniqueFileHandle handle = aDescrs[i].TakePlatformHandle(); + int value; + const int size = sizeof(value); +#ifdef XP_WIN + DWORD numberOfBytesRead; + EXPECT_TRUE( + ::ReadFile(handle.get(), &value, size, &numberOfBytesRead, nullptr)); + EXPECT_EQ(numberOfBytesRead, (DWORD)size); +#else + EXPECT_EQ(read(handle.get(), &value, size), size); +#endif + EXPECT_EQ(value, i); + } + Close(); + return IPC_OK(); + } + + private: + ~TestManyHandlesChild() = default; +}; + +class TestManyHandlesParent : public PTestManyHandlesParent { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestManyHandlesParent, override) + + private: + ~TestManyHandlesParent() = default; +}; + +IPDL_TEST(TestManyHandles) { + nsTArray<FileDescriptor> descrs; + for (int i = 0; i < 500; ++i) { + const int size = sizeof(i); + UniqueFileHandle readPipe; + UniqueFileHandle writePipe; +#ifdef XP_WIN + ASSERT_TRUE(::CreatePipe(getter_Transfers(readPipe), + getter_Transfers(writePipe), nullptr, size)); + DWORD numberOfBytesWritten; + ASSERT_TRUE( + ::WriteFile(writePipe.get(), &i, size, &numberOfBytesWritten, nullptr)); + ASSERT_EQ(numberOfBytesWritten, (DWORD)size); +#else + int fds[2]; + ASSERT_EQ(pipe(fds), 0); + readPipe.reset(fds[0]); + writePipe.reset(fds[1]); + ASSERT_EQ(write(writePipe.get(), &i, size), size); +#endif + descrs.AppendElement(FileDescriptor(std::move(readPipe))); + } + bool ok = mActor->SendManyHandles(descrs); + ASSERT_TRUE(ok); +} + +} // namespace mozilla::_ipdltest diff --git a/ipc/ipdl/test/gtest/moz.build b/ipc/ipdl/test/gtest/moz.build new file mode 100644 index 0000000000..1abd039e36 --- /dev/null +++ b/ipc/ipdl/test/gtest/moz.build @@ -0,0 +1,31 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Library("ipdltest") + +EXPORTS.mozilla._ipdltest += [ + "IPDLUnitTest.h", + "IPDLUnitTestChild.h", + "IPDLUnitTestParent.h", + "TestBasicChild.h", + "TestBasicParent.h", +] + +SOURCES += [ + "IPDLUnitTest.cpp", + "TestBasic.cpp", + "TestManyHandles.cpp", +] + +IPDL_SOURCES += [ + "PIPDLUnitTest.ipdl", + "PTestBasic.ipdl", + "PTestManyHandles.ipdl", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" diff --git a/ipc/ipdl/test/ipdl/IPDLCompile.py b/ipc/ipdl/test/ipdl/IPDLCompile.py new file mode 100644 index 0000000000..bfaf7860d6 --- /dev/null +++ b/ipc/ipdl/test/ipdl/IPDLCompile.py @@ -0,0 +1,78 @@ +import copy +import os +import re +import subprocess +import tempfile + +# We test the compiler indirectly, rather than reaching into the ipdl/ +# module, to make the testing framework as general as possible. + + +class IPDLCompile: + def __init__(self, specfilename, ipdlargv=["python", "ipdl.py"]): + self.argv = copy.deepcopy(ipdlargv) + self.specfilename = specfilename + self.stdout = None + self.stderr = None + self.returncode = None + + def run(self): + """Run |self.specfilename| through the IPDL compiler.""" + assert self.returncode is None + + tmpoutdir = tempfile.mkdtemp(prefix="ipdl_unit_test") + + try: + self.argv.extend(["-d", tmpoutdir, self.specfilename]) + + proc = subprocess.Popen( + args=self.argv, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + ) + self.stdout, self.stderr = proc.communicate() + + self.returncode = proc.returncode + assert self.returncode is not None + + finally: + for root, dirs, files in os.walk(tmpoutdir, topdown=0): + for name in files: + os.remove(os.path.join(root, name)) + for name in dirs: + os.rmdir(os.path.join(root, name)) + os.rmdir(tmpoutdir) + + if proc.returncode is None: + proc.kill() + + def completed(self): + return ( + self.returncode is not None + and isinstance(self.stdout, str) + and isinstance(self.stderr, str) + ) + + def error(self, expectedError): + """Return True iff compiling self.specstring resulted in an + IPDL compiler error.""" + assert self.completed() + + errorRe = re.compile(re.escape(expectedError)) + return None is not re.search(errorRe, self.stderr) + + def exception(self): + """Return True iff compiling self.specstring resulted in a Python + exception being raised.""" + assert self.completed() + + return None is not re.search(r"Traceback (most recent call last):", self.stderr) + + def ok(self): + """Return True iff compiling self.specstring was successful.""" + assert self.completed() + + return ( + not self.exception() and not self.error("error:") and (0 == self.returncode) + ) diff --git a/ipc/ipdl/test/ipdl/Makefile.in b/ipc/ipdl/test/ipdl/Makefile.in new file mode 100644 index 0000000000..67f241775b --- /dev/null +++ b/ipc/ipdl/test/ipdl/Makefile.in @@ -0,0 +1,17 @@ +# 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/. + +include $(topsrcdir)/config/rules.mk + +OKTESTS := $(wildcard $(srcdir)/ok/*.ipdl) $(wildcard $(srcdir)/ok/*.ipdlh) +ERRORTESTS := $(wildcard $(srcdir)/error/*.ipdl) $(wildcard $(srcdir)/error/*.ipdlh) + +check:: + @$(PYTHON3) $(srcdir)/runtests.py \ + $(srcdir)/ok $(srcdir)/error \ + $(PYTHON3) $(topsrcdir)/ipc/ipdl/ipdl.py \ + --sync-msg-list=$(srcdir)/sync-messages.ini \ + --msg-metadata=$(srcdir)/message-metadata.ini \ + OKTESTS $(OKTESTS) \ + ERRORTESTS $(ERRORTESTS) diff --git a/ipc/ipdl/test/ipdl/error/AsyncCtorReturns.ipdl b/ipc/ipdl/test/ipdl/error/AsyncCtorReturns.ipdl new file mode 100644 index 0000000000..831d4d1159 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/AsyncCtorReturns.ipdl @@ -0,0 +1,10 @@ +//error: asynchronous ctor/dtor message `AsyncCtorReturnsManageeConstructor' declares return values + +include protocol AsyncCtorReturnsManagee; + +protocol AsyncCtorReturns { + manages AsyncCtorReturnsManagee; + +child: + async AsyncCtorReturnsManagee() returns (bool unused); +}; diff --git a/ipc/ipdl/test/ipdl/error/AsyncCtorReturnsManagee.ipdl b/ipc/ipdl/test/ipdl/error/AsyncCtorReturnsManagee.ipdl new file mode 100644 index 0000000000..f38e2bd0ea --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/AsyncCtorReturnsManagee.ipdl @@ -0,0 +1,10 @@ +//error: asynchronous ctor/dtor message `AsyncCtorReturnsManageeConstructor' declares return values + +include protocol AsyncCtorReturns; + +protocol AsyncCtorReturnsManagee { + manager AsyncCtorReturns; + +parent: + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/AsyncInsideSync.ipdl b/ipc/ipdl/test/ipdl/error/AsyncInsideSync.ipdl new file mode 100644 index 0000000000..d2fa481e7f --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/AsyncInsideSync.ipdl @@ -0,0 +1,9 @@ +// inside_sync nested messages must be sync + +//error: inside_sync nested messages must be sync (here, message `Msg' in protocol `AsyncInsideSync') +//error: message `Msg' requires more powerful send semantics than its protocol `AsyncInsideSync' provides + +protocol AsyncInsideSync { +child: + [Nested=inside_sync] async Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/error/ExtendedAttrBadValue.ipdlh b/ipc/ipdl/test/ipdl/error/ExtendedAttrBadValue.ipdlh new file mode 100644 index 0000000000..880bf54179 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/ExtendedAttrBadValue.ipdlh @@ -0,0 +1,5 @@ +//error: unexpected value for valueless attribute `RefCounted' + +[RefCounted=Invalid] using SomeType from "SomeFile.h"; + +struct ExtendedAttrBadValue {};
\ No newline at end of file diff --git a/ipc/ipdl/test/ipdl/error/ExtendedAttrEmpty.ipdlh b/ipc/ipdl/test/ipdl/error/ExtendedAttrEmpty.ipdlh new file mode 100644 index 0000000000..625bedc58a --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/ExtendedAttrEmpty.ipdlh @@ -0,0 +1,5 @@ +//error: bad syntax near `]' + +[ ] using SomeType from "SomeFile.h"; + +struct ExtendedAttrEmpty {}; diff --git a/ipc/ipdl/test/ipdl/error/ExtendedAttrInvalidNestedValue.ipdl b/ipc/ipdl/test/ipdl/error/ExtendedAttrInvalidNestedValue.ipdl new file mode 100644 index 0000000000..e58d5d5533 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/ExtendedAttrInvalidNestedValue.ipdl @@ -0,0 +1,6 @@ +//error: invalid value for attribute `Nested', expected one of: not, inside_sync, inside_cpow + +protocol ExtendedAttrInvalidNestedValue { +child: + [Nested=invalid] async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/ExtendedAttrInvalidPriorityValue.ipdl b/ipc/ipdl/test/ipdl/error/ExtendedAttrInvalidPriorityValue.ipdl new file mode 100644 index 0000000000..7625a85e13 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/ExtendedAttrInvalidPriorityValue.ipdl @@ -0,0 +1,6 @@ +//error: invalid value for attribute `Priority', expected one of: normal, input, vsync, mediumhigh, control + +protocol ExtendedAttrInvalidPriorityValue { +child: + [Priority=invalid] async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/ExtendedAttrRepeated.ipdlh b/ipc/ipdl/test/ipdl/error/ExtendedAttrRepeated.ipdlh new file mode 100644 index 0000000000..33defc8ca4 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/ExtendedAttrRepeated.ipdlh @@ -0,0 +1,5 @@ +//error: Repeated extended attribute `RefCounted' + +[RefCounted, RefCounted] using SomeType from "SomeFile.h"; + +struct ExtendedAttrRepeated {}; diff --git a/ipc/ipdl/test/ipdl/error/ExtendedAttrUnknown.ipdlh b/ipc/ipdl/test/ipdl/error/ExtendedAttrUnknown.ipdlh new file mode 100644 index 0000000000..c41e91008f --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/ExtendedAttrUnknown.ipdlh @@ -0,0 +1,5 @@ +//error: unknown attribute `InvalidAttribute' + +[InvalidAttribute] using SomeType from "SomeFile.h"; + +struct ExtendedAttrUnknown {}; diff --git a/ipc/ipdl/test/ipdl/error/ForgottenManagee.ipdl b/ipc/ipdl/test/ipdl/error/ForgottenManagee.ipdl new file mode 100644 index 0000000000..35f6f02e4d --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/ForgottenManagee.ipdl @@ -0,0 +1,12 @@ +//error: |manager| declaration in protocol `ForgottenManagee' does not match any |manages| declaration in protocol `ManagerForgot' + +include protocol ManagerForgot; + +// This protocol says ManagerForgot manages it, +// but ManagerForgot does not manage it. + +protocol ForgottenManagee { + manager ManagerForgot; +child: + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/ForgottenManager.ipdl b/ipc/ipdl/test/ipdl/error/ForgottenManager.ipdl new file mode 100644 index 0000000000..4335dc8dc7 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/ForgottenManager.ipdl @@ -0,0 +1,11 @@ +//error: |manages| declaration in protocol `ForgottenManager' does not match any |manager| declaration in protocol `ManageeForgot' + +include protocol ManageeForgot; + +// ManageeForgot should have this protocol as its manager. + +protocol ForgottenManager { + manages ManageeForgot; +child: + async ManageeForgot(); +}; diff --git a/ipc/ipdl/test/ipdl/error/InsideCpowToChild.ipdl b/ipc/ipdl/test/ipdl/error/InsideCpowToChild.ipdl new file mode 100644 index 0000000000..4338555952 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/InsideCpowToChild.ipdl @@ -0,0 +1,9 @@ +// inside_cpow nested parent-to-child messages are verboten + +//error: inside_cpow nested parent-to-child messages are verboten (here, message `Msg' in protocol `InsideCpowToChild') +//error: message `Msg' requires more powerful send semantics than its protocol `InsideCpowToChild' provides + +protocol InsideCpowToChild { +child: + [Nested=inside_cpow] sync Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/error/IntrAsyncManagee.ipdl b/ipc/ipdl/test/ipdl/error/IntrAsyncManagee.ipdl new file mode 100644 index 0000000000..527c007ec4 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/IntrAsyncManagee.ipdl @@ -0,0 +1,9 @@ +//error: protocol `IntrAsyncManagee' requires more powerful send semantics than its manager `IntrAsyncManager' provides + +include protocol IntrAsyncManager; + +intr protocol IntrAsyncManagee { + manager IntrAsyncManager; +child: + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/IntrAsyncManager.ipdl b/ipc/ipdl/test/ipdl/error/IntrAsyncManager.ipdl new file mode 100644 index 0000000000..7bf413ef61 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/IntrAsyncManager.ipdl @@ -0,0 +1,9 @@ +//error: protocol `IntrAsyncManagee' requires more powerful send semantics than its manager `IntrAsyncManager' provides + +include protocol IntrAsyncManagee; + +async protocol IntrAsyncManager { + manages IntrAsyncManagee; +parent: + async IntrAsyncManagee(); +}; diff --git a/ipc/ipdl/test/ipdl/error/IntrSyncManagee.ipdl b/ipc/ipdl/test/ipdl/error/IntrSyncManagee.ipdl new file mode 100644 index 0000000000..d0b1462e86 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/IntrSyncManagee.ipdl @@ -0,0 +1,9 @@ +//error: protocol `IntrSyncManagee' requires more powerful send semantics than its manager `IntrSyncManager' provides + +include protocol IntrSyncManager; + +intr protocol IntrSyncManagee { + manager IntrSyncManager; +child: + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/IntrSyncManager.ipdl b/ipc/ipdl/test/ipdl/error/IntrSyncManager.ipdl new file mode 100644 index 0000000000..80a82ab6d8 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/IntrSyncManager.ipdl @@ -0,0 +1,9 @@ +//error: protocol `IntrSyncManagee' requires more powerful send semantics than its manager `IntrSyncManager' provides + +include protocol IntrSyncManagee; + +sync protocol IntrSyncManager { + manages IntrSyncManagee; +parent: + async IntrSyncManagee(); +}; diff --git a/ipc/ipdl/test/ipdl/error/ManageeForgot.ipdl b/ipc/ipdl/test/ipdl/error/ManageeForgot.ipdl new file mode 100644 index 0000000000..babe2f0658 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/ManageeForgot.ipdl @@ -0,0 +1,11 @@ +//error: |manages| declaration in protocol `ForgottenManager' does not match any |manager| declaration in protocol `ManageeForgot' + +include protocol ForgottenManager; + +// See ForgottenManager. This includes ForgottenManager to ensure that +// loading this file fails. + +protocol ManageeForgot { +child: + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/ManagerForgot.ipdl b/ipc/ipdl/test/ipdl/error/ManagerForgot.ipdl new file mode 100644 index 0000000000..00c08ebe87 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/ManagerForgot.ipdl @@ -0,0 +1,11 @@ +//error: |manager| declaration in protocol `ForgottenManagee' does not match any |manages| declaration in protocol `ManagerForgot' + +include protocol ForgottenManagee; + +// See ForgottenManagee.ipdl. This includes ForgottenManagee to +// ensure that loading this file fails. + +protocol ManagerForgot { +child: + async Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/error/Nullable.ipdl b/ipc/ipdl/test/ipdl/error/Nullable.ipdl new file mode 100644 index 0000000000..7761f73646 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/Nullable.ipdl @@ -0,0 +1,6 @@ +//error: `nullable' qualifier for type `int' is unsupported + +protocol PNullable { +child: + async Msg(nullable int i); +}; diff --git a/ipc/ipdl/test/ipdl/error/Nullable2.ipdl b/ipc/ipdl/test/ipdl/error/Nullable2.ipdl new file mode 100644 index 0000000000..8248b7cb7c --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/Nullable2.ipdl @@ -0,0 +1,10 @@ +//error: `nullable' qualifier for type `int' is unsupported + +union Union { + nullable int; +}; + +protocol Nullable2 { +child: + async Msg(Union i); +}; diff --git a/ipc/ipdl/test/ipdl/error/PBadArrayBase.ipdl b/ipc/ipdl/test/ipdl/error/PBadArrayBase.ipdl new file mode 100644 index 0000000000..4afdcb4c8e --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PBadArrayBase.ipdl @@ -0,0 +1,7 @@ +//error: argument typename `X' of message `Test' has not been declared + +protocol PBadArrayBase { +child: + async Test(X[] x); + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/PBadNestedManagee.ipdl b/ipc/ipdl/test/ipdl/error/PBadNestedManagee.ipdl new file mode 100644 index 0000000000..eca24e00d4 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PBadNestedManagee.ipdl @@ -0,0 +1,9 @@ +//error: protocol `PBadNestedManagee' requires more powerful send semantics than its manager `PBadNestedManager' provides + +include protocol PBadNestedManager; + +[NestedUpTo=inside_sync] async protocol PBadNestedManagee { + manager PBadNestedManager; +child: + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/PBadNestedManager.ipdl b/ipc/ipdl/test/ipdl/error/PBadNestedManager.ipdl new file mode 100644 index 0000000000..9fc744f13a --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PBadNestedManager.ipdl @@ -0,0 +1,9 @@ +//error: protocol `PBadNestedManagee' requires more powerful send semantics than its manager `PBadNestedManager' provides + +include protocol PBadNestedManagee; + +[NestedUpTo=not] async protocol PBadNestedManager { + manages PBadNestedManagee; +parent: + async PBadNestedManagee(); +}; diff --git a/ipc/ipdl/test/ipdl/error/PBadSideImpl.ipdl b/ipc/ipdl/test/ipdl/error/PBadSideImpl.ipdl new file mode 100644 index 0000000000..75aebd4670 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PBadSideImpl.ipdl @@ -0,0 +1,8 @@ +//error: invalid value for attribute `ParentImpl', expected one of: virtual, StringLiteral +//error: invalid value for attribute `ChildImpl', expected one of: virtual, StringLiteral + +[ParentImpl=NotQuoted, ChildImpl] +async protocol PBadSideImpl { + parent: + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/PBadUniquePtrBase.ipdl b/ipc/ipdl/test/ipdl/error/PBadUniquePtrBase.ipdl new file mode 100644 index 0000000000..b93b20742d --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PBadUniquePtrBase.ipdl @@ -0,0 +1,7 @@ +//error: argument typename `UndeclaredType' of message `Test' has not been declared + +protocol PBadUniquePtrBase { +child: + async Test(UniquePtr<UndeclaredType> x); + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/PCompressInvalid.ipdl b/ipc/ipdl/test/ipdl/error/PCompressInvalid.ipdl new file mode 100644 index 0000000000..5ceaaad9ac --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PCompressInvalid.ipdl @@ -0,0 +1,8 @@ +//error: invalid value for attribute `Compress', expected one of: None, all + +include protocol compressCtor; + +async protocol PCompressInvalid { +child: + [Compress=Invalid] async Message(); +}; diff --git a/ipc/ipdl/test/ipdl/error/PDouble.ipdl b/ipc/ipdl/test/ipdl/error/PDouble.ipdl new file mode 100644 index 0000000000..e08ff1d063 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PDouble.ipdl @@ -0,0 +1,11 @@ +//error: Trying to load `PDouble' from a file when we'd already seen it in file + +// This will load extra/PDouble.ipdl because extra/ is earlier +// in the list of include directories than the current working +// directory. Loading the same protocol from two files is +// obviously bad. +include protocol PDouble; + +protocol PDouble { +child: async Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/error/PExtendedAttrInvalidValue.ipdl b/ipc/ipdl/test/ipdl/error/PExtendedAttrInvalidValue.ipdl new file mode 100644 index 0000000000..9720acdd41 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PExtendedAttrInvalidValue.ipdl @@ -0,0 +1,6 @@ +//error: invalid value for attribute `NestedUpTo', expected one of: not, inside_sync, inside_cpow + +[NestedUpTo=invalid] async protocol PExtendedAttrInvalidValue { +parent: + async Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/error/PExtendedAttrRepeated.ipdl b/ipc/ipdl/test/ipdl/error/PExtendedAttrRepeated.ipdl new file mode 100644 index 0000000000..e33411a877 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PExtendedAttrRepeated.ipdl @@ -0,0 +1,6 @@ +//error: Repeated extended attribute `RefCounted' + +[RefCounted, RefCounted] async protocol PExtendedAttrRepeated { +parent: + async Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/error/PExtendedAttrUnexpectedValue.ipdl b/ipc/ipdl/test/ipdl/error/PExtendedAttrUnexpectedValue.ipdl new file mode 100644 index 0000000000..de1d81c0eb --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PExtendedAttrUnexpectedValue.ipdl @@ -0,0 +1,6 @@ +//error: unexpected value for valueless attribute `ManualDealloc' + +[ManualDealloc=invalid] async protocol PExtendedAttrUnexpectedValue { +parent: + async Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/error/PExtendedAttrUnknownAttribute.ipdl b/ipc/ipdl/test/ipdl/error/PExtendedAttrUnknownAttribute.ipdl new file mode 100644 index 0000000000..414763efdd --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PExtendedAttrUnknownAttribute.ipdl @@ -0,0 +1,6 @@ +//error: unknown attribute `InvalidAttr' + +[InvalidAttr] async protocol PExtendedAttrUnknownValue { +parent: + async Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/error/PInconsistentMoveOnly.ipdl b/ipc/ipdl/test/ipdl/error/PInconsistentMoveOnly.ipdl new file mode 100644 index 0000000000..4e88a82050 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PInconsistentMoveOnly.ipdl @@ -0,0 +1,13 @@ +//error: inconsistent moveonly status of type `::mozilla::ipc::SomeMoveonlyType` +//error: inconsistent moveonly status of type `::mozilla::ipc::SomeMoveonlySendType` + +[MoveOnly] using class mozilla::ipc::SomeMoveonlyType from "SomeFile.h"; +using class mozilla::ipc::SomeMoveonlyType from "SomeFile.h"; + +[MoveOnly=send] using class mozilla::ipc::SomeMoveonlySendType from "SomeFile.h"; +[MoveOnly=data] using class mozilla::ipc::SomeMoveonlySendType from "SomeFile.h"; + +protocol PInconsistentMoveOnly { +child: + async SomeMessage(); +}; diff --git a/ipc/ipdl/test/ipdl/error/PIntrNested.ipdl b/ipc/ipdl/test/ipdl/error/PIntrNested.ipdl new file mode 100644 index 0000000000..948cdc1118 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PIntrNested.ipdl @@ -0,0 +1,6 @@ +//error: intr message `Msg' cannot specify [Nested] + +intr protocol PIntrNested { +child: + [Nested=inside_sync, LegacyIntr] intr Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/error/PIntrNestedProtocol.ipdl b/ipc/ipdl/test/ipdl/error/PIntrNestedProtocol.ipdl new file mode 100644 index 0000000000..4267bf7a84 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PIntrNestedProtocol.ipdl @@ -0,0 +1,6 @@ +//error: intr protocol `PIntrNestedProtocol' cannot specify [NestedUpTo] + +[NestedUpTo=inside_sync] intr protocol PIntrNestedProtocol { +child: + [LegacyIntr] intr Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/error/PIntrPriority.ipdl b/ipc/ipdl/test/ipdl/error/PIntrPriority.ipdl new file mode 100644 index 0000000000..5bc539983a --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PIntrPriority.ipdl @@ -0,0 +1,6 @@ +//error: intr message `Msg' cannot specify [Priority] + +intr protocol PIntrPriority { +child: + [Priority=vsync, LegacyIntr] intr Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/error/PLazySendInvalid.ipdl b/ipc/ipdl/test/ipdl/error/PLazySendInvalid.ipdl new file mode 100644 index 0000000000..282a007add --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PLazySendInvalid.ipdl @@ -0,0 +1,6 @@ +//error: unexpected value for valueless attribute `LazySend' + +async protocol PLazySendInvalid { +child: + [LazySend=Invalid] async Message(); +}; diff --git a/ipc/ipdl/test/ipdl/error/PLazySendSync.ipdl b/ipc/ipdl/test/ipdl/error/PLazySendSync.ipdl new file mode 100644 index 0000000000..308b734fe5 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PLazySendSync.ipdl @@ -0,0 +1,6 @@ +//error: non-async message `Message' cannot specify [LazySend] + +sync protocol PLazySendSync { +child: + [LazySend] sync Message() returns (bool ok); +}; diff --git a/ipc/ipdl/test/ipdl/error/PNoTaintWithoutTainted.ipdl b/ipc/ipdl/test/ipdl/error/PNoTaintWithoutTainted.ipdl new file mode 100644 index 0000000000..cfdde1f02c --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PNoTaintWithoutTainted.ipdl @@ -0,0 +1,6 @@ +//error: argument typename `int' of message `foo' has a NoTaint attribute, but the message lacks the Tainted attribute + +intr protocol PNoTaintWithoutTainted { +child: + async foo([NoTaint=passback] int id); +}; diff --git a/ipc/ipdl/test/ipdl/error/PToplevelManualDealloc.ipdl b/ipc/ipdl/test/ipdl/error/PToplevelManualDealloc.ipdl new file mode 100644 index 0000000000..97822b65bd --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PToplevelManualDealloc.ipdl @@ -0,0 +1,6 @@ +//error: Toplevel protocols cannot be [ManualDealloc] + +[ManualDealloc] async protocol PToplevelManualDealloc { +child: + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/PUniquePtrRecursive.ipdl b/ipc/ipdl/test/ipdl/error/PUniquePtrRecursive.ipdl new file mode 100644 index 0000000000..83c9459ec6 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PUniquePtrRecursive.ipdl @@ -0,0 +1,5 @@ +//error: bad syntax near `UniquePtr' + +protocol PUniquePtrRecursive { +child: async Msg( UniquePtr< UniquePtr<int> > aa); +}; diff --git a/ipc/ipdl/test/ipdl/error/PUniquePtrSelfRecStruct.ipdl b/ipc/ipdl/test/ipdl/error/PUniquePtrSelfRecStruct.ipdl new file mode 100644 index 0000000000..c9a54b22b2 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PUniquePtrSelfRecStruct.ipdl @@ -0,0 +1,9 @@ +//error: struct `X' is only partially defined + +struct X { + UniquePtr<X> x; +}; + +protocol PUniquePtrSelfRecStruct { +child: async Msg(UniquePtr<X> aa); +}; diff --git a/ipc/ipdl/test/ipdl/error/PUniquePtrSelfRecUnion.ipdl b/ipc/ipdl/test/ipdl/error/PUniquePtrSelfRecUnion.ipdl new file mode 100644 index 0000000000..5dba38263f --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PUniquePtrSelfRecUnion.ipdl @@ -0,0 +1,7 @@ +//error: union `X' is only partially defined + +union X { UniquePtr<X>; }; + +protocol PUniquePtrSelfRecUnion { +child: async Msg(UniquePtr<X> aa); +}; diff --git a/ipc/ipdl/test/ipdl/error/PasyncMessageListed.ipdl b/ipc/ipdl/test/ipdl/error/PasyncMessageListed.ipdl new file mode 100644 index 0000000000..6a3cdad0ee --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/PasyncMessageListed.ipdl @@ -0,0 +1,6 @@ +//error: IPC message PasyncMessageListed::Msg is async, can be delisted + +protocol PasyncMessageListed { +parent: + async Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/error/ReplyPrioWithoutReturns.ipdl b/ipc/ipdl/test/ipdl/error/ReplyPrioWithoutReturns.ipdl new file mode 100644 index 0000000000..0d1cd51217 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/ReplyPrioWithoutReturns.ipdl @@ -0,0 +1,6 @@ +//error: non-returns message `NormalPrio' cannot specify [ReplyPriority] +async protocol ReplyPrioWithoutReturns +{ +child: + [ReplyPriority=normal] async NormalPrio(); +}; diff --git a/ipc/ipdl/test/ipdl/error/SyncAsyncManagee.ipdl b/ipc/ipdl/test/ipdl/error/SyncAsyncManagee.ipdl new file mode 100644 index 0000000000..166881ae1e --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/SyncAsyncManagee.ipdl @@ -0,0 +1,9 @@ +//error: protocol `SyncAsyncManagee' requires more powerful send semantics than its manager `SyncAsyncManager' provides + +include protocol SyncAsyncManager; + +sync protocol SyncAsyncManagee { + manager SyncAsyncManager; +child: + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/SyncAsyncManager.ipdl b/ipc/ipdl/test/ipdl/error/SyncAsyncManager.ipdl new file mode 100644 index 0000000000..9dbc7a6318 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/SyncAsyncManager.ipdl @@ -0,0 +1,9 @@ +//error: protocol `SyncAsyncManagee' requires more powerful send semantics than its manager `SyncAsyncManager' provides + +include protocol SyncAsyncManagee; + +async protocol SyncAsyncManager { + manages SyncAsyncManagee; +parent: + async SyncAsyncManagee(); +}; diff --git a/ipc/ipdl/test/ipdl/error/SyncPrio.ipdl b/ipc/ipdl/test/ipdl/error/SyncPrio.ipdl new file mode 100644 index 0000000000..cbf833b620 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/SyncPrio.ipdl @@ -0,0 +1,6 @@ +//error: non-async message `NormalPrio' cannot specify [ReplyPriority] +sync protocol SyncPrio +{ +child: + [ReplyPriority=normal] sync NormalPrio() returns (bool aValue); +}; diff --git a/ipc/ipdl/test/ipdl/error/array_Recursive.ipdl b/ipc/ipdl/test/ipdl/error/array_Recursive.ipdl new file mode 100644 index 0000000000..f5027e2299 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/array_Recursive.ipdl @@ -0,0 +1,5 @@ +//error: bad syntax near `[' + +protocol array_Recursive { +child: async Msg(int[][] aa); +}; diff --git a/ipc/ipdl/test/ipdl/error/badProtocolInclude.ipdl b/ipc/ipdl/test/ipdl/error/badProtocolInclude.ipdl new file mode 100644 index 0000000000..3f82bef9b2 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/badProtocolInclude.ipdl @@ -0,0 +1,9 @@ +//error: can't locate include file `IDONTEXIST.ipdl' + +include protocol IDONTEXIST; + +// error: nonexistent protocol ^^^ + +protocol badProtocolInclude { +child: async Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/error/compressCtor.ipdl b/ipc/ipdl/test/ipdl/error/compressCtor.ipdl new file mode 100644 index 0000000000..9db05d495d --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/compressCtor.ipdl @@ -0,0 +1,11 @@ +//error: destructor messages can't use compression (here, in protocol `compressCtorManagee') +//error: constructor messages can't use compression (here, in protocol `compressCtor') + +include protocol compressCtorManagee; + +intr protocol compressCtor { + manages compressCtorManagee; + +parent: + [Compress] async compressCtorManagee(); +}; diff --git a/ipc/ipdl/test/ipdl/error/compressCtorManagee.ipdl b/ipc/ipdl/test/ipdl/error/compressCtorManagee.ipdl new file mode 100644 index 0000000000..52ec2cc1cd --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/compressCtorManagee.ipdl @@ -0,0 +1,11 @@ +//error: constructor messages can't use compression (here, in protocol `compressCtor') +//error: destructor messages can't use compression (here, in protocol `compressCtorManagee') + +include protocol compressCtor; + +intr protocol compressCtorManagee { + manager compressCtor; + +child: + [Compress] async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/conflictProtocolMsg.ipdl b/ipc/ipdl/test/ipdl/error/conflictProtocolMsg.ipdl new file mode 100644 index 0000000000..b223dab783 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/conflictProtocolMsg.ipdl @@ -0,0 +1,9 @@ +//error: ctor for protocol `conflictProtocolMsg', which is not managed by protocol `conflictProtocolMsg' + +protocol conflictProtocolMsg { + + // it's an error to re-use the protocol name as a message ID; these + // are reserved +child: async conflictProtocolMsg(); + +}; diff --git a/ipc/ipdl/test/ipdl/error/cyclecheck_Child.ipdl b/ipc/ipdl/test/ipdl/error/cyclecheck_Child.ipdl new file mode 100644 index 0000000000..dd27586d8b --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/cyclecheck_Child.ipdl @@ -0,0 +1,15 @@ +//error: cycle(s) detected in manager/manages hierarchy: `cyclecheck_Parent -> cyclecheck_Child -> cyclecheck_Grandchild -> cyclecheck_Parent' +//error: |manages| declaration in protocol `cyclecheck_Grandchild' does not match any |manager| declaration in protocol `cyclecheck_Parent' + +include protocol cyclecheck_Parent; +include protocol cyclecheck_Grandchild; + +protocol cyclecheck_Child { + manager cyclecheck_Parent; + manages cyclecheck_Grandchild; + +child: + async cyclecheck_Grandchild(); + async __delete__(); +}; + diff --git a/ipc/ipdl/test/ipdl/error/cyclecheck_Grandchild.ipdl b/ipc/ipdl/test/ipdl/error/cyclecheck_Grandchild.ipdl new file mode 100644 index 0000000000..9152a56a16 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/cyclecheck_Grandchild.ipdl @@ -0,0 +1,15 @@ +//error: cycle(s) detected in manager/manages hierarchy: `cyclecheck_Parent -> cyclecheck_Child -> cyclecheck_Grandchild -> cyclecheck_Parent' +//error: |manages| declaration in protocol `cyclecheck_Grandchild' does not match any |manager| declaration in protocol `cyclecheck_Parent' + +include protocol cyclecheck_Child; +include protocol cyclecheck_Parent; + +protocol cyclecheck_Grandchild { + manager cyclecheck_Child; + manages cyclecheck_Parent; + +child: + async cyclecheck_Parent(); + async __delete__(); +}; + diff --git a/ipc/ipdl/test/ipdl/error/cyclecheck_Parent.ipdl b/ipc/ipdl/test/ipdl/error/cyclecheck_Parent.ipdl new file mode 100644 index 0000000000..f2eb060641 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/cyclecheck_Parent.ipdl @@ -0,0 +1,12 @@ +//error: cycle(s) detected in manager/manages hierarchy: `cyclecheck_Parent -> cyclecheck_Child -> cyclecheck_Grandchild -> cyclecheck_Parent' + +include protocol cyclecheck_Child; + +protocol cyclecheck_Parent { + manages cyclecheck_Child; + +child: + async cyclecheck_Child(); + async __delete__(); +}; + diff --git a/ipc/ipdl/test/ipdl/error/dtorReserved.ipdl b/ipc/ipdl/test/ipdl/error/dtorReserved.ipdl new file mode 100644 index 0000000000..51833489be --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/dtorReserved.ipdl @@ -0,0 +1,10 @@ +//error: lexically invalid characters `~SomeMsg(); + +protocol dtorReserved { + + // it's an error to use old-style dtor syntax + +child: + ~SomeMsg(); + +}; diff --git a/ipc/ipdl/test/ipdl/error/empty.ipdl b/ipc/ipdl/test/ipdl/error/empty.ipdl new file mode 100644 index 0000000000..c6249c280b --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/empty.ipdl @@ -0,0 +1 @@ +//error: bad syntax near `???'
\ No newline at end of file diff --git a/ipc/ipdl/test/ipdl/error/extra/PDouble.ipdl b/ipc/ipdl/test/ipdl/error/extra/PDouble.ipdl new file mode 100644 index 0000000000..2efae8fff4 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/extra/PDouble.ipdl @@ -0,0 +1,5 @@ +// This is a valid file. + +protocol PDouble { +child: async Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/error/inconsistentRC.ipdl b/ipc/ipdl/test/ipdl/error/inconsistentRC.ipdl new file mode 100644 index 0000000000..ee1375c312 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/inconsistentRC.ipdl @@ -0,0 +1,9 @@ +//error: inconsistent refcounted status of type `::mozilla::ipc::SomeRefcountedType` + +[RefCounted] using class mozilla::ipc::SomeRefcountedType from "SomeFile.h"; +using class mozilla::ipc::SomeRefcountedType from "SomeFile.h"; + +protocol inconsistentRC { +child: + async SomeMessage(); +}; diff --git a/ipc/ipdl/test/ipdl/error/intrMessageCompress.ipdl b/ipc/ipdl/test/ipdl/error/intrMessageCompress.ipdl new file mode 100644 index 0000000000..49bd5d4721 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/intrMessageCompress.ipdl @@ -0,0 +1,9 @@ +//error: message `foo' in protocol `intrMessageCompress' requests compression but is not async +//error: message `bar' in protocol `intrMessageCompress' requests compression but is not async + +intr protocol intrMessageCompress { +parent: + [Compress, LegacyIntr] intr foo(); +child: + [Compress, LegacyIntr] intr bar(); +}; diff --git a/ipc/ipdl/test/ipdl/error/lex1.ipdl b/ipc/ipdl/test/ipdl/error/lex1.ipdl new file mode 100644 index 0000000000..a00629cbca --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/lex1.ipdl @@ -0,0 +1,2 @@ +//error: bad syntax near `slkdjfl' +slkdjfl*&^* diff --git a/ipc/ipdl/test/ipdl/error/manageSelfToplevel.ipdl b/ipc/ipdl/test/ipdl/error/manageSelfToplevel.ipdl new file mode 100644 index 0000000000..4540c2c2f6 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/manageSelfToplevel.ipdl @@ -0,0 +1,10 @@ +//error: top-level protocol `manageSelfToplevel' cannot manage itself + +protocol manageSelfToplevel { + manager manageSelfToplevel; + manages manageSelfToplevel; + +child: + async manageSelfToplevel(); + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/managedNoDtor.ipdl b/ipc/ipdl/test/ipdl/error/managedNoDtor.ipdl new file mode 100644 index 0000000000..41bbd76689 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/managedNoDtor.ipdl @@ -0,0 +1,7 @@ +//error: destructor declaration `__delete__(...)' required for managed protocol `managedNoDtor' +include protocol managerNoDtor; + +protocol managedNoDtor { + manager managerNoDtor; + // empty +}; diff --git a/ipc/ipdl/test/ipdl/error/managerNoDtor.ipdl b/ipc/ipdl/test/ipdl/error/managerNoDtor.ipdl new file mode 100644 index 0000000000..0417cc5ace --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/managerNoDtor.ipdl @@ -0,0 +1,11 @@ +//error: destructor declaration `__delete__(...)' required for managed protocol `managedNoDtor' + +include protocol managedNoDtor; + +protocol managerNoDtor { + manages managedNoDtor; + +parent: + async managedNoDtor(); + // error: no ctor defined +}; diff --git a/ipc/ipdl/test/ipdl/error/maybe_Recursive.ipdl b/ipc/ipdl/test/ipdl/error/maybe_Recursive.ipdl new file mode 100644 index 0000000000..b039924199 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/maybe_Recursive.ipdl @@ -0,0 +1,5 @@ +//error: bad syntax near `?' + +protocol maybe_Recursive { +child: async Msg(int?? aa); +}; diff --git a/ipc/ipdl/test/ipdl/error/maybe_SelfRecStruct.ipdl b/ipc/ipdl/test/ipdl/error/maybe_SelfRecStruct.ipdl new file mode 100644 index 0000000000..a13cdc3647 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/maybe_SelfRecStruct.ipdl @@ -0,0 +1,9 @@ +//error: struct `X' is only partially defined + +struct X { + X? x; +}; + +protocol maybe_SelfRecStruct { +child: async Msg(X? aa); +}; diff --git a/ipc/ipdl/test/ipdl/error/maybe_SelfRecUnion.ipdl b/ipc/ipdl/test/ipdl/error/maybe_SelfRecUnion.ipdl new file mode 100644 index 0000000000..3cd5cde22a --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/maybe_SelfRecUnion.ipdl @@ -0,0 +1,7 @@ +//error: union `X' is only partially defined + +union X { X?; }; + +protocol maybe_SelfRecUnion { +child: async Msg(X? aa); +}; diff --git a/ipc/ipdl/test/ipdl/error/messageNoDirection.ipdl b/ipc/ipdl/test/ipdl/error/messageNoDirection.ipdl new file mode 100644 index 0000000000..199bb089c3 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/messageNoDirection.ipdl @@ -0,0 +1,5 @@ +//error: missing message direction + +protocol messageNoDirection { + async NoDirection(); +}; diff --git a/ipc/ipdl/test/ipdl/error/multimanDupMgrs.ipdl b/ipc/ipdl/test/ipdl/error/multimanDupMgrs.ipdl new file mode 100644 index 0000000000..805a9f905e --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/multimanDupMgrs.ipdl @@ -0,0 +1,10 @@ +//error: manager `multimanDupMgrsMgr' appears multiple times + +include protocol multimanDupMgrsMgr; + +protocol multimanDupMgrs { + manager multimanDupMgrsMgr or multimanDupMgrsMgr; + +child: + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/multimanDupMgrsMgr.ipdl b/ipc/ipdl/test/ipdl/error/multimanDupMgrsMgr.ipdl new file mode 100644 index 0000000000..ff96fe91a3 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/multimanDupMgrsMgr.ipdl @@ -0,0 +1,11 @@ +//error: manager `multimanDupMgrsMgr' appears multiple times + +include protocol multimanDupMgrs; + +protocol multimanDupMgrsMgr { + manages multimanDupMgrs; + +child: + async multimanDupMgrs(); + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/multimanNonexistentMgrs.ipdl b/ipc/ipdl/test/ipdl/error/multimanNonexistentMgrs.ipdl new file mode 100644 index 0000000000..24dafb8163 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/multimanNonexistentMgrs.ipdl @@ -0,0 +1,10 @@ +//error: protocol `Starsky' referenced as |manager| of `multimanNonexistentMgrs' has not been declared +//error: protocol `Hutch' referenced as |manager| of `multimanNonexistentMgrs' has not been declared + +protocol multimanNonexistentMgrs { + manager Starsky or Hutch; + +child: + async Dummy(); + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/mutualRecStruct.ipdl b/ipc/ipdl/test/ipdl/error/mutualRecStruct.ipdl new file mode 100644 index 0000000000..9df16543bf --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/mutualRecStruct.ipdl @@ -0,0 +1,24 @@ +//error: struct `X' is only partially defined +//error: struct `Y' is only partially defined +//error: struct `Z' is only partially defined + +struct X { + int i; + Y[] y; +}; + +struct Y { + X x; + Z z; +}; + +struct Z { + double d; + X x; +}; + +protocol mutualRecStruct { +child: + async Test(X x, Y y, Z z); + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/mutualRecStructUnion.ipdl b/ipc/ipdl/test/ipdl/error/mutualRecStructUnion.ipdl new file mode 100644 index 0000000000..cd193f9f29 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/mutualRecStructUnion.ipdl @@ -0,0 +1,24 @@ +//error: struct `X' is only partially defined +//error: union `Y' is only partially defined +//error: struct `Z' is only partially defined + +struct X { + int i; + Y[] y; +}; + +union Y { + X; + Z; +}; + +struct Z { + double d; + X x; +}; + +protocol mutualRecStructUnion { +child: + async Test(X x, Y y, Z z); + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/noEmptyToplevel.ipdl b/ipc/ipdl/test/ipdl/error/noEmptyToplevel.ipdl new file mode 100644 index 0000000000..bc36cead42 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/noEmptyToplevel.ipdl @@ -0,0 +1,7 @@ +//error: top-level protocol `noEmptyToplevel' cannot be empty + +protocol noEmptyToplevel { + + // it's an error for top-level protocols to be empty + +}; diff --git a/ipc/ipdl/test/ipdl/error/noProtocolInHeader.ipdlh b/ipc/ipdl/test/ipdl/error/noProtocolInHeader.ipdlh new file mode 100644 index 0000000000..8351adf4cc --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/noProtocolInHeader.ipdlh @@ -0,0 +1,6 @@ +//error: can't define a protocol in a header. Do it in a protocol spec instead. + +protocol noProtocolInHeader { +child: + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/oldIncludeSyntax.ipdl b/ipc/ipdl/test/ipdl/error/oldIncludeSyntax.ipdl new file mode 100644 index 0000000000..d13659a8e7 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/oldIncludeSyntax.ipdl @@ -0,0 +1,7 @@ +//error: bad syntax near `"Foo.ipdl"' + +include protocol "Foo.ipdl"; + +protocol oldIncludeSyntax { +child: async __delete__(X x); +}; diff --git a/ipc/ipdl/test/ipdl/error/parser.ipdl b/ipc/ipdl/test/ipdl/error/parser.ipdl new file mode 100644 index 0000000000..8bfd63546a --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/parser.ipdl @@ -0,0 +1,3 @@ +//error: bad syntax near `???' + +protocol parser { diff --git a/ipc/ipdl/test/ipdl/error/redeclMessage.ipdl b/ipc/ipdl/test/ipdl/error/redeclMessage.ipdl new file mode 100644 index 0000000000..1247aa41cf --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/redeclMessage.ipdl @@ -0,0 +1,12 @@ +//error: message name `Msg' already declared as `MessageType' +//error: redeclaration of symbol `Msg', first declared at + +protocol redeclMessage { + + // can't declare two messages with the same name + +child: + async Msg(); + async Msg(); + +}; diff --git a/ipc/ipdl/test/ipdl/error/redeclParamReturn.ipdl b/ipc/ipdl/test/ipdl/error/redeclParamReturn.ipdl new file mode 100644 index 0000000000..ccdcbbe0d1 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/redeclParamReturn.ipdl @@ -0,0 +1,9 @@ +//error: redeclaration of symbol `f', first declared at + +sync protocol redeclParamReturn { + + // it's an error to name a parameter with the same id as a return + +parent: async Msg(int f) returns (bool f); + +}; diff --git a/ipc/ipdl/test/ipdl/error/redeclScope.ipdlh b/ipc/ipdl/test/ipdl/error/redeclScope.ipdlh new file mode 100644 index 0000000000..a64439b599 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/redeclScope.ipdlh @@ -0,0 +1,12 @@ +//error: redeclaration of symbol `Foo', first declared at + +struct Foo { + bool b; +}; + +struct Bar { + // This should produce an error saying that Foo is a redeclaration, + // even though the initial declaration was in a different frame of + // the symbol table. + bool Foo; +}; diff --git a/ipc/ipdl/test/ipdl/error/shmem.ipdl b/ipc/ipdl/test/ipdl/error/shmem.ipdl new file mode 100644 index 0000000000..7f4326ccf5 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/shmem.ipdl @@ -0,0 +1,8 @@ +//error: redeclaration of symbol `Shmem', first declared at +//error: redeclaration of symbol `::mozilla::ipc::Shmem', first declared at + +using class mozilla::ipc::Shmem from "mozilla/ipc/Shmem.h"; // redeclaration + +protocol shmem { +child: async Msg(Shmem s); +}; diff --git a/ipc/ipdl/test/ipdl/error/structRedecl.ipdl b/ipc/ipdl/test/ipdl/error/structRedecl.ipdl new file mode 100644 index 0000000000..03a5557877 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/structRedecl.ipdl @@ -0,0 +1,10 @@ +//error: redeclaration of symbol `a', first declared at + +struct Redecl { + int a; + double a; +}; + +protocol structRedecl { +child: async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/error/structUnknownField.ipdl b/ipc/ipdl/test/ipdl/error/structUnknownField.ipdl new file mode 100644 index 0000000000..ac92edee23 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/structUnknownField.ipdl @@ -0,0 +1,9 @@ +//error: field `i' of struct `S' has unknown type `Foobers' + +struct S { + Foobers i; +}; + +protocol structUnknownField { +child: async __delete__(S s); +}; diff --git a/ipc/ipdl/test/ipdl/error/syncMessageCompress.ipdl b/ipc/ipdl/test/ipdl/error/syncMessageCompress.ipdl new file mode 100644 index 0000000000..376f2ae2dd --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/syncMessageCompress.ipdl @@ -0,0 +1,6 @@ +//error: message `foo' in protocol `syncMessageCompress' requests compression but is not async + +sync protocol syncMessageCompress { +parent: + [Compress] sync foo(); +}; diff --git a/ipc/ipdl/test/ipdl/error/syncParentToChild.ipdl b/ipc/ipdl/test/ipdl/error/syncParentToChild.ipdl new file mode 100644 index 0000000000..bd6c8bc5b1 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/syncParentToChild.ipdl @@ -0,0 +1,8 @@ +//error: sync parent-to-child messages are verboten (here, message `Msg' in protocol `syncParentToChild') + +intr protocol syncParentToChild { + + // can't declare sync parent-to-child messages +child: sync Msg(); + +}; diff --git a/ipc/ipdl/test/ipdl/error/tooWeakIntrAsync.ipdl b/ipc/ipdl/test/ipdl/error/tooWeakIntrAsync.ipdl new file mode 100644 index 0000000000..220e4c3c28 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/tooWeakIntrAsync.ipdl @@ -0,0 +1,9 @@ +//error: message `Msg' requires more powerful send semantics than its protocol `tooWeakIntrAsync' provides + +protocol tooWeakIntrAsync { + + // it's an error to declare an async protocol with an intr message + +parent: [LegacyIntr] intr Msg(); + +}; diff --git a/ipc/ipdl/test/ipdl/error/tooWeakIntrSync.ipdl b/ipc/ipdl/test/ipdl/error/tooWeakIntrSync.ipdl new file mode 100644 index 0000000000..4c3b61d0d9 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/tooWeakIntrSync.ipdl @@ -0,0 +1,8 @@ +//error: message `Msg' requires more powerful send semantics than its protocol `tooWeakIntrSync' provides + +sync protocol tooWeakIntrSync { + + // it's an error to declare a sync protocol with an interrupt message +parent: + [LegacyIntr] intr Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/error/tooWeakSyncAsync.ipdl b/ipc/ipdl/test/ipdl/error/tooWeakSyncAsync.ipdl new file mode 100644 index 0000000000..49e4d3b75b --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/tooWeakSyncAsync.ipdl @@ -0,0 +1,9 @@ +//error: message `Msg' requires more powerful send semantics than its protocol `tooWeakSyncAsync' provides + +protocol tooWeakSyncAsync { + + // it's an error to declare an async protocol with a sync message + +parent: sync Msg(); + +}; diff --git a/ipc/ipdl/test/ipdl/error/twoprotocols.ipdl b/ipc/ipdl/test/ipdl/error/twoprotocols.ipdl new file mode 100644 index 0000000000..7e2a51a61a --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/twoprotocols.ipdl @@ -0,0 +1,11 @@ +// it's an error to define two protocols in the same file + +//error: only one protocol definition per file + +protocol p1 { +child: async Msg(); +}; + +protocol p2 { +child: async Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/error/undeclParamType.ipdl b/ipc/ipdl/test/ipdl/error/undeclParamType.ipdl new file mode 100644 index 0000000000..3f93e747e1 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/undeclParamType.ipdl @@ -0,0 +1,7 @@ +//error: argument typename `FARGLEGARGLE' of message `Msg' has not been declared + +protocol undeclParamType { + +child: async Msg(FARGLEGARGLE p); + +}; diff --git a/ipc/ipdl/test/ipdl/error/undeclProtocol.ipdl b/ipc/ipdl/test/ipdl/error/undeclProtocol.ipdl new file mode 100644 index 0000000000..cddf57267f --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/undeclProtocol.ipdl @@ -0,0 +1,8 @@ +//error: protocol `undeclared', managed by `undeclProtocol', has not been declared + +protocol undeclProtocol { + manages undeclared; + +child: + async undeclared(); +}; diff --git a/ipc/ipdl/test/ipdl/error/undeclReturnType.ipdl b/ipc/ipdl/test/ipdl/error/undeclReturnType.ipdl new file mode 100644 index 0000000000..2f353a1957 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/undeclReturnType.ipdl @@ -0,0 +1,7 @@ +//error: argument typename `FARGLEGARGLE' of message `Msg' has not been declared + +sync protocol undeclReturnType { + +child: sync Msg() returns (FARGLEGARGLE r); + +}; diff --git a/ipc/ipdl/test/ipdl/error/undefMutualRecStruct.ipdl b/ipc/ipdl/test/ipdl/error/undefMutualRecStruct.ipdl new file mode 100644 index 0000000000..5ede1d8299 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/undefMutualRecStruct.ipdl @@ -0,0 +1,11 @@ +//error: struct `X' is only partially defined +//error: struct `Y' is only partially defined +//error: struct `Z' is only partially defined + +struct X { Y y; }; +struct Y { Z z; }; +struct Z { X x; }; + +protocol undefMutualRecStruct { +child: async __delete__(X x); +}; diff --git a/ipc/ipdl/test/ipdl/error/undefMutualRecStructUnion.ipdl b/ipc/ipdl/test/ipdl/error/undefMutualRecStructUnion.ipdl new file mode 100644 index 0000000000..0e88c126e0 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/undefMutualRecStructUnion.ipdl @@ -0,0 +1,11 @@ +//error: struct `X' is only partially defined +//error: union `Y' is only partially defined +//error: struct `Z' is only partially defined + +struct X { Y y; }; +union Y { Z; }; +struct Z { X x; }; + +protocol undefMutualRecStructUnion { +child: async __delete__(X x); +}; diff --git a/ipc/ipdl/test/ipdl/error/undefMutualRecUnion.ipdl b/ipc/ipdl/test/ipdl/error/undefMutualRecUnion.ipdl new file mode 100644 index 0000000000..3fc99bbb95 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/undefMutualRecUnion.ipdl @@ -0,0 +1,11 @@ +//error: union `X' is only partially defined +//error: union `Y' is only partially defined +//error: union `Z' is only partially defined + +union X { Y; }; +union Y { Z; }; +union Z { X; }; + +protocol undefMutualRecUnion { +child: async __delete__(X x); +}; diff --git a/ipc/ipdl/test/ipdl/error/undefSelfRecStruct.ipdl b/ipc/ipdl/test/ipdl/error/undefSelfRecStruct.ipdl new file mode 100644 index 0000000000..15fc45abcd --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/undefSelfRecStruct.ipdl @@ -0,0 +1,7 @@ +//error: struct `X' is only partially defined + +struct X { X x; }; + +protocol undefSelfRecStruct { +child: async __delete__(X x); +}; diff --git a/ipc/ipdl/test/ipdl/error/undefSelfRecUnion.ipdl b/ipc/ipdl/test/ipdl/error/undefSelfRecUnion.ipdl new file mode 100644 index 0000000000..951f916c57 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/undefSelfRecUnion.ipdl @@ -0,0 +1,7 @@ +//error: union `X' is only partially defined + +union X { X; }; + +protocol undefSelfRecUnion { +child: async __delete__(X x); +}; diff --git a/ipc/ipdl/test/ipdl/error/unknownIntrMessage.ipdl b/ipc/ipdl/test/ipdl/error/unknownIntrMessage.ipdl new file mode 100644 index 0000000000..4790232685 --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/unknownIntrMessage.ipdl @@ -0,0 +1,6 @@ +//error: Unknown sync IPC message unknownIntrMessage::Msg + +intr protocol unknownIntrMessage { +parent: + [LegacyIntr] intr Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/error/unknownSyncMessage.ipdl b/ipc/ipdl/test/ipdl/error/unknownSyncMessage.ipdl new file mode 100644 index 0000000000..be2cfad0cc --- /dev/null +++ b/ipc/ipdl/test/ipdl/error/unknownSyncMessage.ipdl @@ -0,0 +1,6 @@ +//error: Unknown sync IPC message unknownSyncMessage::Msg + +sync protocol unknownSyncMessage { +parent: + sync Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/message-metadata.ini b/ipc/ipdl/test/ipdl/message-metadata.ini new file mode 100644 index 0000000000..02ec1a992f --- /dev/null +++ b/ipc/ipdl/test/ipdl/message-metadata.ini @@ -0,0 +1,2 @@ +# The IPDL parser expects this file to exist, even though we don't +# need it for parsing tests, so here's an empty file.
\ No newline at end of file diff --git a/ipc/ipdl/test/ipdl/moz.build b/ipc/ipdl/test/ipdl/moz.build new file mode 100644 index 0000000000..572682b594 --- /dev/null +++ b/ipc/ipdl/test/ipdl/moz.build @@ -0,0 +1,8 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +# Copy test files so that they can be referenced in the docs. +SPHINX_TREES["/ipc/_static"] = "ok" diff --git a/ipc/ipdl/test/ipdl/ok/MutRecHeader1.ipdlh b/ipc/ipdl/test/ipdl/ok/MutRecHeader1.ipdlh new file mode 100644 index 0000000000..56135b2197 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/MutRecHeader1.ipdlh @@ -0,0 +1,34 @@ +include MutRecHeader2; + +/* MutRecHeader1 (H1) includes MutRecHeader2 (H2), and uses a struct from H2. + H2 includes MutRecHeader3 (H3). + H3 includes H1. + +When type checking H1, GatherDecls::visitInclude will recursively +cause us to first check H2, which in turn will cause us to first check +H3. + +H3 only includes H1, so when we check it, we do not have any +declarations from H2 in the context. There used to be code in +GatherDecls::visitTranslationUnit that would, as part of the "second +pass", check the validity of all included structures. This would check +Struct1, and fail, because Struct2 is not declared. + +Fundamentally, it doesn't make sense to check anything declared in an +included file in the context of the file that included it. + +Note that this error did not show up when either H2 or H3 was +checked. This is because in those cases we are not in the middle of +checking H1 when we check H3, so we end up fully checking H1 before we +get to the end of checking H3. This means the "visited" tag gets put +on Struct1 before we get to the end of that troublesome block of code +in visitTranslationUnit, and visitStructDecl doesn't do anything if +that tag is set, so we don't end up actually checking H1 in the +context of H3. + +*/ + +struct Struct1 +{ + Struct2 b; +}; diff --git a/ipc/ipdl/test/ipdl/ok/MutRecHeader2.ipdlh b/ipc/ipdl/test/ipdl/ok/MutRecHeader2.ipdlh new file mode 100644 index 0000000000..142979dc45 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/MutRecHeader2.ipdlh @@ -0,0 +1,8 @@ +include MutRecHeader3; + +// See MutRecHeader1.ipdlh for explanation. + +struct Struct2 +{ + bool b; +}; diff --git a/ipc/ipdl/test/ipdl/ok/MutRecHeader3.ipdlh b/ipc/ipdl/test/ipdl/ok/MutRecHeader3.ipdlh new file mode 100644 index 0000000000..2e303ae444 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/MutRecHeader3.ipdlh @@ -0,0 +1,8 @@ +include MutRecHeader1; + +// See MutRecHeader1.ipdlh for explanation. + +struct Struct3 +{ + bool b; +}; diff --git a/ipc/ipdl/test/ipdl/ok/MyTypes.ipdlh b/ipc/ipdl/test/ipdl/ok/MyTypes.ipdlh new file mode 100644 index 0000000000..9e33d71c51 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/MyTypes.ipdlh @@ -0,0 +1,6 @@ +include protocol PMyManaged; + +struct MyActorPair { + PMyManaged actor1; + nullable PMyManaged actor2; +}; diff --git a/ipc/ipdl/test/ipdl/ok/PAsyncReturn.ipdl b/ipc/ipdl/test/ipdl/ok/PAsyncReturn.ipdl new file mode 100644 index 0000000000..89c2a7c647 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PAsyncReturn.ipdl @@ -0,0 +1,8 @@ +// Async messages are not allowed to return values. + +//error: asynchronous message `Msg' declares return values + +protocol PAsyncReturn { +child: + async Msg() returns(int32_t aNumber); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PDelete.ipdl b/ipc/ipdl/test/ipdl/ok/PDelete.ipdl new file mode 100644 index 0000000000..f2f85fd7fa --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PDelete.ipdl @@ -0,0 +1,8 @@ +include protocol PDeleteSub; + +sync protocol PDelete { + manages PDeleteSub; + +child: + async PDeleteSub(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PDeleteSub.ipdl b/ipc/ipdl/test/ipdl/ok/PDeleteSub.ipdl new file mode 100644 index 0000000000..12b4c677eb --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PDeleteSub.ipdl @@ -0,0 +1,10 @@ +include protocol PDelete; + +sync protocol PDeleteSub { + manager PDelete; + +parent: + sync __delete__(int x) returns (double d); + +}; + diff --git a/ipc/ipdl/test/ipdl/ok/PEndpointDecl.ipdl b/ipc/ipdl/test/ipdl/ok/PEndpointDecl.ipdl new file mode 100644 index 0000000000..25f58f3f63 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PEndpointDecl.ipdl @@ -0,0 +1,18 @@ + +// Basic test that Endpoint types are declared for protocols, within +// that protocol. + +struct Whatever { + Endpoint<PEndpointDeclParent> par; + Endpoint<PEndpointDeclChild> chi; +}; + +namespace mozilla { + +protocol PEndpointDecl { + child: + async Message(Endpoint<PEndpointDeclParent> aEndpointParent, + Endpoint<PEndpointDeclChild> aEndpointChild); +}; + +} diff --git a/ipc/ipdl/test/ipdl/ok/PEndpointUse.ipdl b/ipc/ipdl/test/ipdl/ok/PEndpointUse.ipdl new file mode 100644 index 0000000000..0c776b359c --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PEndpointUse.ipdl @@ -0,0 +1,9 @@ +include protocol PEndpointDecl; + +// Basic test that Endpoint types are declared for included protocols. + +protocol PEndpointUse { + child: + async Message(Endpoint<PEndpointDeclParent> aEndpointParent, + Endpoint<PEndpointDeclChild> aEndpointChild); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PExtendedAttrMultipleAttributes.ipdl b/ipc/ipdl/test/ipdl/ok/PExtendedAttrMultipleAttributes.ipdl new file mode 100644 index 0000000000..4b1c9085b7 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PExtendedAttrMultipleAttributes.ipdl @@ -0,0 +1,4 @@ +[NestedUpTo=inside_sync, NeedsOtherPid] async protocol PExtendedAttrMultipleAttributes { +parent: + async Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PLazySend.ipdl b/ipc/ipdl/test/ipdl/ok/PLazySend.ipdl new file mode 100644 index 0000000000..1bc56a3c6f --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PLazySend.ipdl @@ -0,0 +1,6 @@ +protocol PLazySend { +child: + [LazySend] async foo(); +parent: + [LazySend] async bar() returns (bool baz); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PManagedEndpointDecl.ipdl b/ipc/ipdl/test/ipdl/ok/PManagedEndpointDecl.ipdl new file mode 100644 index 0000000000..13eb10fcc4 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PManagedEndpointDecl.ipdl @@ -0,0 +1,23 @@ +include protocol PManagedEndpointManager; + +// Basic test that ManagedEndpoint types are declared for protocols, within +// that protocol. + +struct ManagedWhatever { + ManagedEndpoint<PManagedEndpointDeclParent> par; + ManagedEndpoint<PManagedEndpointDeclChild> chi; +}; + +namespace mozilla { + +protocol PManagedEndpointDecl { + manager PManagedEndpointManager; + +child: + async Message(ManagedEndpoint<PManagedEndpointDeclParent> aEndpointParent, + ManagedEndpoint<PManagedEndpointDeclChild> aEndpointChild); + + async __delete__(); +}; + +} diff --git a/ipc/ipdl/test/ipdl/ok/PManagedEndpointManager.ipdl b/ipc/ipdl/test/ipdl/ok/PManagedEndpointManager.ipdl new file mode 100644 index 0000000000..4c95b7880c --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PManagedEndpointManager.ipdl @@ -0,0 +1,14 @@ +include protocol PManagedEndpointDecl; + +namespace mozilla { + +protocol PManagedEndpointManager { + manages PManagedEndpointDecl; + +child: + async Message(ManagedEndpoint<PManagedEndpointDeclParent> aEndpointParent, + ManagedEndpoint<PManagedEndpointDeclChild> aEndpointChild); +}; + +} + diff --git a/ipc/ipdl/test/ipdl/ok/PManualDealloc.ipdl b/ipc/ipdl/test/ipdl/ok/PManualDealloc.ipdl new file mode 100644 index 0000000000..53660762ad --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PManualDealloc.ipdl @@ -0,0 +1,8 @@ +include protocol PManualDealloc_manager; + +[ManualDealloc] async protocol PManualDealloc { + manager PManualDealloc_manager; + +child: + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PManualDealloc_manager.ipdl b/ipc/ipdl/test/ipdl/ok/PManualDealloc_manager.ipdl new file mode 100644 index 0000000000..224c7a5e46 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PManualDealloc_manager.ipdl @@ -0,0 +1,7 @@ +include protocol PManualDealloc; + +// [ManualDealloc] types must have a manager, as all toplevel protocols are +// refcounted. +async protocol PManualDealloc_manager { + manages PManualDealloc; +}; diff --git a/ipc/ipdl/test/ipdl/ok/PMessageTainted.ipdl b/ipc/ipdl/test/ipdl/ok/PMessageTainted.ipdl new file mode 100644 index 0000000000..0733117be9 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PMessageTainted.ipdl @@ -0,0 +1,4 @@ +intr protocol PMessageTainted { +child: + [Tainted] async foo(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PMessageTaintedWithPassback.ipdl b/ipc/ipdl/test/ipdl/ok/PMessageTaintedWithPassback.ipdl new file mode 100644 index 0000000000..1d03e97e73 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PMessageTaintedWithPassback.ipdl @@ -0,0 +1,4 @@ +intr protocol PMessageTaintedWithPassback { +child: + [Tainted] async foo([NoTaint=passback] int id); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PMyManaged.ipdl b/ipc/ipdl/test/ipdl/ok/PMyManaged.ipdl new file mode 100644 index 0000000000..d6ddb34bf8 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PMyManaged.ipdl @@ -0,0 +1,13 @@ +include protocol PMyManager; + +namespace mozilla { +namespace myns { + +protocol PMyManaged { + manager PMyManager; + child: + async __delete__(Shmem aShmem); +}; + +} // namespace myns +} // namespace mozilla diff --git a/ipc/ipdl/test/ipdl/ok/PMyManager.ipdl b/ipc/ipdl/test/ipdl/ok/PMyManager.ipdl new file mode 100644 index 0000000000..5e82f53cca --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PMyManager.ipdl @@ -0,0 +1,30 @@ +include protocol PMyManaged; +include MyTypes; // for MyActorPair + +using MyActorEnum from "mozilla/myns/MyActorUtils.h"; +using mozilla::myns::MyData from "mozilla/MyDataTypes.h"; +[MoveOnly] using class mozilla::myns::MyOtherData from "mozilla/MyDataTypes.h"; +[RefCounted] using class mozilla::myns::MyThirdData from "mozilla/MyDataTypes.h"; + +namespace mozilla { +namespace myns { + +[Comparable] union MyUnion { + float; + MyOtherData; +}; + +sync protocol PMyManager { + manages PMyManaged; + parent: + async __delete__(nsString aNote); + sync SomeMsg(MyActorPair? aActors, MyData[] aMyData) + returns (int32_t x, int32_t y, MyUnion aUnion); + async PMyManaged(); + both: + [Tainted] async AnotherMsg(MyActorEnum aEnum, int32_t aNumber) + returns (MyOtherData aOtherData); +}; + +} // namespace myns +} // namespace mozilla diff --git a/ipc/ipdl/test/ipdl/ok/PNested.ipdl b/ipc/ipdl/test/ipdl/ok/PNested.ipdl new file mode 100644 index 0000000000..4368f2a1ab --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PNested.ipdl @@ -0,0 +1,7 @@ +[NestedUpTo=inside_cpow] sync protocol PNested { +parent: + [Nested=not] async NotNested(); + [Nested=inside_sync] sync InsideSync(); + [Nested=inside_cpow] async InsideCpow(); + [Nested=inside_cpow] sync InsideCpowSync(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PNullable.ipdl b/ipc/ipdl/test/ipdl/ok/PNullable.ipdl new file mode 100644 index 0000000000..51601d59f1 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PNullable.ipdl @@ -0,0 +1,21 @@ +[RefCounted] using class nsIURI from "nsIURI.h"; + +union Union { + nullable PNullable; + nullable PNullable[]; + nullable PNullable?; + nullable nsIURI; + nullable nsIURI[]; + nullable nsIURI?; +}; + +protocol PNullable { +child: + async Msg(nullable PNullable n); + async Msg2(nullable PNullable[] N); + async Msg3(nullable PNullable? n); + async Msg4(nullable nsIURI u); + async Msg5(nullable nsIURI[] u); + async Msg6(nullable nsIURI? u); + async Msg7(Union u); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PSideImpl.ipdl b/ipc/ipdl/test/ipdl/ok/PSideImpl.ipdl new file mode 100644 index 0000000000..0858734c74 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PSideImpl.ipdl @@ -0,0 +1,5 @@ +[ParentImpl=virtual, ChildImpl="mozilla::FooBarImpl"] +async protocol PSideImpl { + parent: + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PStruct.ipdl b/ipc/ipdl/test/ipdl/ok/PStruct.ipdl new file mode 100644 index 0000000000..c670a83b0b --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PStruct.ipdl @@ -0,0 +1,10 @@ +struct S { + int i; + double d; +}; + +sync protocol PStruct { +parent: + sync test(S s) returns (S ss); + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PStructComparable.ipdl b/ipc/ipdl/test/ipdl/ok/PStructComparable.ipdl new file mode 100644 index 0000000000..ca1f654bb5 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PStructComparable.ipdl @@ -0,0 +1,10 @@ +[Comparable] struct S { + int i; + double d; +}; + +sync protocol PStructComparable { +parent: + sync test(S s) returns (S ss); + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PSyncSyncManagee.ipdl b/ipc/ipdl/test/ipdl/ok/PSyncSyncManagee.ipdl new file mode 100644 index 0000000000..458f0c83c0 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PSyncSyncManagee.ipdl @@ -0,0 +1,7 @@ +include protocol PSyncSyncManager; + +sync protocol PSyncSyncManagee { + manager PSyncSyncManager; +child: + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PSyncSyncManager.ipdl b/ipc/ipdl/test/ipdl/ok/PSyncSyncManager.ipdl new file mode 100644 index 0000000000..00f9dead47 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PSyncSyncManager.ipdl @@ -0,0 +1,11 @@ +include protocol PSyncSyncManagee; + +/* The main reason for this test is that it would have caught a bug + * in the Rust IPDL parser that was treating "sync" like "async" in the + * nested case. + */ +[NestedUpTo=not] sync protocol PSyncSyncManager { + manages PSyncSyncManagee; +parent: + async PSyncSyncManagee(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PUniquePtrBasic.ipdl b/ipc/ipdl/test/ipdl/ok/PUniquePtrBasic.ipdl new file mode 100644 index 0000000000..caffd0dbbc --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PUniquePtrBasic.ipdl @@ -0,0 +1,4 @@ +protocol PUniquePtrBasic { +child: + async Msg(UniquePtr<int> maybe); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PUniquePtrOfActors.ipdl b/ipc/ipdl/test/ipdl/ok/PUniquePtrOfActors.ipdl new file mode 100644 index 0000000000..ce67f23c68 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PUniquePtrOfActors.ipdl @@ -0,0 +1,10 @@ +include protocol PUniquePtrOfActorsSub; + +protocol PUniquePtrOfActors { + manages PUniquePtrOfActorsSub; + +child: + async Msg(UniquePtr<PUniquePtrOfActorsSub> p); + + async PUniquePtrOfActorsSub(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PUniquePtrOfActorsSub.ipdl b/ipc/ipdl/test/ipdl/ok/PUniquePtrOfActorsSub.ipdl new file mode 100644 index 0000000000..a60c8e475f --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PUniquePtrOfActorsSub.ipdl @@ -0,0 +1,7 @@ +include protocol PUniquePtrOfActors; + +protocol PUniquePtrOfActorsSub { + manager PUniquePtrOfActors; + +child: async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PUniquePtrRecUnion.ipdl b/ipc/ipdl/test/ipdl/ok/PUniquePtrRecUnion.ipdl new file mode 100644 index 0000000000..4a0b9e7299 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PUniquePtrRecUnion.ipdl @@ -0,0 +1,21 @@ +union X { + int; + Y[]; + UniquePtr<Y>; +}; + +union Y { + X; + Z; +}; + +union Z { + double; + X; +}; + +protocol PUniquePtrRecUnion { +child: + async Test(X x, Y y, Z z); + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PUniquePtrUnion.ipdl b/ipc/ipdl/test/ipdl/ok/PUniquePtrUnion.ipdl new file mode 100644 index 0000000000..d756339980 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PUniquePtrUnion.ipdl @@ -0,0 +1,10 @@ +union UniquePtrUnion { + int[]; + int; + double; +}; + +sync protocol PUniquePtrUnion { +parent: + async Msg(UniquePtrUnion u, UniquePtr<UniquePtrUnion> au) returns (UniquePtrUnion r); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PVirtualSendImpl.ipdl b/ipc/ipdl/test/ipdl/ok/PVirtualSendImpl.ipdl new file mode 100644 index 0000000000..042e762475 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PVirtualSendImpl.ipdl @@ -0,0 +1,5 @@ +async protocol PVirtualSendImpl +{ +child: + [VirtualSendImpl] async MockableFoo(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/Pactorparam.ipdl b/ipc/ipdl/test/ipdl/ok/Pactorparam.ipdl new file mode 100644 index 0000000000..0cba299cf5 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Pactorparam.ipdl @@ -0,0 +1,5 @@ +protocol Pactorparam { + +child: async Msg(Pactorparam p); + +}; diff --git a/ipc/ipdl/test/ipdl/ok/Pactorreturn.ipdl b/ipc/ipdl/test/ipdl/ok/Pactorreturn.ipdl new file mode 100644 index 0000000000..99d0d36225 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Pactorreturn.ipdl @@ -0,0 +1,6 @@ +sync protocol Pactorreturn { + +parent: + sync Msg(Pactorreturn p) returns (Pactorreturn r); + +}; diff --git a/ipc/ipdl/test/ipdl/ok/Parray_Basic.ipdl b/ipc/ipdl/test/ipdl/ok/Parray_Basic.ipdl new file mode 100644 index 0000000000..811c01f324 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Parray_Basic.ipdl @@ -0,0 +1,4 @@ +protocol Parray_Basic { +child: + async Msg(int[] array); +}; diff --git a/ipc/ipdl/test/ipdl/ok/Parray_OfActors.ipdl b/ipc/ipdl/test/ipdl/ok/Parray_OfActors.ipdl new file mode 100644 index 0000000000..b084c2c464 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Parray_OfActors.ipdl @@ -0,0 +1,10 @@ +include protocol Parray_OfActorsSub; + +protocol Parray_OfActors { + manages Parray_OfActorsSub; + +child: + async Msg(Parray_OfActorsSub[] p); + + async Parray_OfActorsSub(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/Parray_OfActorsSub.ipdl b/ipc/ipdl/test/ipdl/ok/Parray_OfActorsSub.ipdl new file mode 100644 index 0000000000..6769e98abb --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Parray_OfActorsSub.ipdl @@ -0,0 +1,7 @@ +include protocol Parray_OfActors; + +protocol Parray_OfActorsSub { + manager Parray_OfActors; + +child: async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/Parray_Union.ipdl b/ipc/ipdl/test/ipdl/ok/Parray_Union.ipdl new file mode 100644 index 0000000000..33c94e7ae5 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Parray_Union.ipdl @@ -0,0 +1,10 @@ +union Union { + int[]; + int; + double; +}; + +sync protocol Parray_Union { +parent: + sync Msg(Union u, Union[] au) returns (Union r); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PbasicUsing.ipdl b/ipc/ipdl/test/ipdl/ok/PbasicUsing.ipdl new file mode 100644 index 0000000000..ac4df3a258 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PbasicUsing.ipdl @@ -0,0 +1,50 @@ +using SomeType from "SomeFile.h"; +using class SomeClass from "SomeFile.h"; +using struct SomeStruct from "SomeFile.h"; + +[RefCounted] using SomeRefcountedType from "SomeFile.h"; +[RefCounted] using class SomeRefcountedClass from "SomeFile.h"; +[RefCounted] using struct SomeRefcountedStruct from "SomeFile.h"; + +[MoveOnly] using SomeMoveonlyType from "SomeFile.h"; +[MoveOnly] using class SomeMoveonlyClass from "SomeFile.h"; +[MoveOnly] using struct SomeMoveonlyStruct from "SomeFile.h"; + +[RefCounted, MoveOnly] using SomeRefcountedMoveonlyType from "SomeFile.h"; +[RefCounted, MoveOnly] using class SomeRefcountedMoveonlyClass from "SomeFile.h"; +[RefCounted, MoveOnly] using struct SomeRefcountedMoveonlyStruct from "SomeFile.h"; + +[MoveOnly=data] using SomeMoveonlyDataType from "SomeFile.h"; +[MoveOnly=data] using class SomeMoveonlyDataClass from "SomeFile.h"; +[MoveOnly=data] using struct SomeMoveonlyDataStruct from "SomeFile.h"; + +[MoveOnly=send] using SomeMoveonlySendType from "SomeFile.h"; +[MoveOnly=send] using class SomeMoveonlySendClass from "SomeFile.h"; +[MoveOnly=send] using struct SomeMoveonlySendStruct from "SomeFile.h"; + +union SomeUnion +{ + SomeType; + SomeClass; + SomeStruct; + SomeRefcountedType; + SomeRefcountedClass; + SomeRefcountedStruct; + SomeMoveonlyType; + SomeMoveonlyClass; + SomeMoveonlyStruct; + SomeRefcountedMoveonlyType; + SomeRefcountedMoveonlyClass; + SomeRefcountedMoveonlyStruct; + SomeMoveonlyDataType; + SomeMoveonlyDataClass; + SomeMoveonlyDataStruct; + SomeMoveonlySendType; + SomeMoveonlySendClass; + SomeMoveonlySendStruct; +}; + +protocol PbasicUsing { +child: + async Msg(SomeUnion foo); +}; diff --git a/ipc/ipdl/test/ipdl/ok/Pbuiltins.ipdl b/ipc/ipdl/test/ipdl/ok/Pbuiltins.ipdl new file mode 100644 index 0000000000..4085dc6d95 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Pbuiltins.ipdl @@ -0,0 +1,21 @@ +protocol Pbuiltins { + + // sanity-check that "essential" builtins are being declared + +child: async Msg(bool b, + char c, + int i, + long l, + + float f, + double d, + + int8_t i8t, + uint8_t u8t, + int16_t i16t, + uint16_t u16t, + int32_t i32t, + uint32_t u32t, + int64_t i64t, + uint64_t u64t); +}; diff --git a/ipc/ipdl/test/ipdl/ok/Pbytebuf.ipdl b/ipc/ipdl/test/ipdl/ok/Pbytebuf.ipdl new file mode 100644 index 0000000000..016702053e --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Pbytebuf.ipdl @@ -0,0 +1,13 @@ +union Foo { + int; + ByteBuf; +}; + +intr protocol Pbytebuf { +parent: + async Msg(ByteBuf s, Foo f); + sync SyncMsg(ByteBuf s, Foo f) + returns (ByteBuf t, Foo g); + [LegacyIntr] intr InterruptMsg(ByteBuf s, Foo f) + returns (ByteBuf t, Foo g); +}; diff --git a/ipc/ipdl/test/ipdl/ok/Pempty.ipdl b/ipc/ipdl/test/ipdl/ok/Pempty.ipdl new file mode 100644 index 0000000000..61b511bec6 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Pempty.ipdl @@ -0,0 +1,3 @@ +protocol Pempty { +child: async Msg(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PemptyStruct.ipdl b/ipc/ipdl/test/ipdl/ok/PemptyStruct.ipdl new file mode 100644 index 0000000000..a067bf71da --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PemptyStruct.ipdl @@ -0,0 +1,3 @@ +struct empty { }; + +protocol PemptyStruct { child: async __delete__(); }; diff --git a/ipc/ipdl/test/ipdl/ok/PheaderProto.ipdl b/ipc/ipdl/test/ipdl/ok/PheaderProto.ipdl new file mode 100644 index 0000000000..7ae285042e --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PheaderProto.ipdl @@ -0,0 +1,10 @@ +include header; + +namespace c { + +protocol PheaderProto { +child: + async __delete__(foo a, baz b, Inner1 c, Inner2 d, X x); +}; + +} diff --git a/ipc/ipdl/test/ipdl/ok/PintrProtocol.ipdl b/ipc/ipdl/test/ipdl/ok/PintrProtocol.ipdl new file mode 100644 index 0000000000..a931a7eb9a --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PintrProtocol.ipdl @@ -0,0 +1,13 @@ +intr protocol PintrProtocol { + + // sanity check of Interrupt protocols +child: + async AsyncMsg(); + +parent: + sync SyncMsg(int i) returns (int r); + +both: + [LegacyIntr] intr InterruptMsg(int x) returns (int y); + +}; diff --git a/ipc/ipdl/test/ipdl/ok/Pjetpack.ipdl b/ipc/ipdl/test/ipdl/ok/Pjetpack.ipdl new file mode 100644 index 0000000000..190732485d --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Pjetpack.ipdl @@ -0,0 +1,5 @@ +sync protocol Pjetpack { +child: + async __delete__(); + +}; diff --git a/ipc/ipdl/test/ipdl/ok/PmanageSelf.ipdl b/ipc/ipdl/test/ipdl/ok/PmanageSelf.ipdl new file mode 100644 index 0000000000..992f374a58 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PmanageSelf.ipdl @@ -0,0 +1,10 @@ +include protocol PmanageSelf_Toplevel; + +protocol PmanageSelf { + manager PmanageSelf_Toplevel or PmanageSelf; + manages PmanageSelf; + +child: + async PmanageSelf(); + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PmanageSelf_Toplevel.ipdl b/ipc/ipdl/test/ipdl/ok/PmanageSelf_Toplevel.ipdl new file mode 100644 index 0000000000..70b5fe8a9b --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PmanageSelf_Toplevel.ipdl @@ -0,0 +1,9 @@ +include protocol PmanageSelf; + +protocol PmanageSelf_Toplevel { + manages PmanageSelf; + +child: + async PmanageSelf(); + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PmanagedProtocol.ipdl b/ipc/ipdl/test/ipdl/ok/PmanagedProtocol.ipdl new file mode 100644 index 0000000000..cab07d06e7 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PmanagedProtocol.ipdl @@ -0,0 +1,8 @@ +include protocol PmanagerProtocol; + +protocol PmanagedProtocol { + manager PmanagerProtocol; + +child: + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PmanagerProtocol.ipdl b/ipc/ipdl/test/ipdl/ok/PmanagerProtocol.ipdl new file mode 100644 index 0000000000..40d1de31b0 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PmanagerProtocol.ipdl @@ -0,0 +1,11 @@ +include protocol PmanagedProtocol; + +// sanity check of managed/manager protocols + +protocol PmanagerProtocol { + manages PmanagedProtocol; + +parent: + async PmanagedProtocol(int i); + +}; diff --git a/ipc/ipdl/test/ipdl/ok/Pmaybe_Basic.ipdl b/ipc/ipdl/test/ipdl/ok/Pmaybe_Basic.ipdl new file mode 100644 index 0000000000..a0bbccd874 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Pmaybe_Basic.ipdl @@ -0,0 +1,4 @@ +protocol Pmaybe_Basic { +child: + async Msg(int? maybe); +}; diff --git a/ipc/ipdl/test/ipdl/ok/Pmaybe_OfActors.ipdl b/ipc/ipdl/test/ipdl/ok/Pmaybe_OfActors.ipdl new file mode 100644 index 0000000000..430faa9ae7 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Pmaybe_OfActors.ipdl @@ -0,0 +1,10 @@ +include protocol Pmaybe_OfActorsSub; + +protocol Pmaybe_OfActors { + manages Pmaybe_OfActorsSub; + +child: + async Msg(Pmaybe_OfActorsSub? p); + + async Pmaybe_OfActorsSub(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/Pmaybe_OfActorsSub.ipdl b/ipc/ipdl/test/ipdl/ok/Pmaybe_OfActorsSub.ipdl new file mode 100644 index 0000000000..6a4759953f --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Pmaybe_OfActorsSub.ipdl @@ -0,0 +1,7 @@ +include protocol Pmaybe_OfActors; + +protocol Pmaybe_OfActorsSub { + manager Pmaybe_OfActors; + +child: async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/Pmaybe_Union.ipdl b/ipc/ipdl/test/ipdl/ok/Pmaybe_Union.ipdl new file mode 100644 index 0000000000..67f24cb794 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Pmaybe_Union.ipdl @@ -0,0 +1,10 @@ +union MaybeUnion { + int[]; + int; + double; +}; + +sync protocol Pmaybe_Union { +parent: + async Msg(MaybeUnion u, MaybeUnion? au) returns (MaybeUnion r); +}; diff --git a/ipc/ipdl/test/ipdl/ok/Pmedia.ipdl b/ipc/ipdl/test/ipdl/ok/Pmedia.ipdl new file mode 100644 index 0000000000..0a8076a433 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Pmedia.ipdl @@ -0,0 +1,5 @@ +sync protocol Pmedia { +child: + async __delete__(); + +}; diff --git a/ipc/ipdl/test/ipdl/ok/PmessageCompress.ipdl b/ipc/ipdl/test/ipdl/ok/PmessageCompress.ipdl new file mode 100644 index 0000000000..53dfe9098f --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PmessageCompress.ipdl @@ -0,0 +1,5 @@ +intr protocol PmessageCompress { +child: + [Compress] async foo(); + [Compress=all] async bar(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PmultiManaged.ipdl b/ipc/ipdl/test/ipdl/ok/PmultiManaged.ipdl new file mode 100644 index 0000000000..d8c47fc9e3 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PmultiManaged.ipdl @@ -0,0 +1,9 @@ +include protocol PmultiManager1; +include protocol PmultiManager2; + +protocol PmultiManaged { + manager PmultiManager1 or PmultiManager2; + +child: + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PmultiManager1.ipdl b/ipc/ipdl/test/ipdl/ok/PmultiManager1.ipdl new file mode 100644 index 0000000000..b0654a8c4f --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PmultiManager1.ipdl @@ -0,0 +1,8 @@ +include protocol PmultiManaged; + +protocol PmultiManager1 { + manages PmultiManaged; + +child: + async PmultiManaged(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PmultiManager2.ipdl b/ipc/ipdl/test/ipdl/ok/PmultiManager2.ipdl new file mode 100644 index 0000000000..c03f05a173 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PmultiManager2.ipdl @@ -0,0 +1,8 @@ +include protocol PmultiManaged; + +protocol PmultiManager2 { + manages PmultiManaged; + +child: + async PmultiManaged(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PmultipleUsingCxxTypes.ipdl b/ipc/ipdl/test/ipdl/ok/PmultipleUsingCxxTypes.ipdl new file mode 100644 index 0000000000..fa63b5b4b0 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PmultipleUsingCxxTypes.ipdl @@ -0,0 +1,7 @@ +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; + +protocol PmultipleUsingCxxTypes { +child: + async Msg(void_t foo); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PmutualRecStructUnion.ipdl b/ipc/ipdl/test/ipdl/ok/PmutualRecStructUnion.ipdl new file mode 100644 index 0000000000..0fe5501a09 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PmutualRecStructUnion.ipdl @@ -0,0 +1,21 @@ +struct X { + int i; + Y[] y; +}; + +union Y { + double; + X; + Z; +}; + +struct Z { + X x; + Y y; +}; + +protocol PmutualRecStructUnion { +child: + async Test(X x, Y y, Z z); + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PmutualRecUnion.ipdl b/ipc/ipdl/test/ipdl/ok/PmutualRecUnion.ipdl new file mode 100644 index 0000000000..304847e003 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PmutualRecUnion.ipdl @@ -0,0 +1,21 @@ +union X { + int; + Y[]; + Y?; +}; + +union Y { + X; + Z; +}; + +union Z { + double; + X; +}; + +protocol PmutualRecUnion { +child: + async Test(X x, Y y, Z z); + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/Pnamespace_Basic.ipdl b/ipc/ipdl/test/ipdl/ok/Pnamespace_Basic.ipdl new file mode 100644 index 0000000000..4f996d9744 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Pnamespace_Basic.ipdl @@ -0,0 +1,12 @@ +namespace basic { + +// sanity check of namespaced protocols + +protocol Pnamespace_Basic { + +child: + async Msg(); + +}; + +} // namespace basic diff --git a/ipc/ipdl/test/ipdl/ok/PnoRedeclCrossMessage.ipdl b/ipc/ipdl/test/ipdl/ok/PnoRedeclCrossMessage.ipdl new file mode 100644 index 0000000000..d353a8c07c --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PnoRedeclCrossMessage.ipdl @@ -0,0 +1,9 @@ +protocol PnoRedeclCrossMessage { + + // each message has its own scope for param/return names + +child: + async Msg1(int f); + async Msg2(int f); + +}; diff --git a/ipc/ipdl/test/ipdl/ok/Pplugin.ipdl b/ipc/ipdl/test/ipdl/ok/Pplugin.ipdl new file mode 100644 index 0000000000..29c001e67b --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Pplugin.ipdl @@ -0,0 +1,5 @@ +intr protocol Pplugin { +child: + async __delete__(); + +}; diff --git a/ipc/ipdl/test/ipdl/ok/Prio.ipdl b/ipc/ipdl/test/ipdl/ok/Prio.ipdl new file mode 100644 index 0000000000..515b3fd68d --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Prio.ipdl @@ -0,0 +1,11 @@ +async protocol Prio +{ +child: + [Priority=normal] async NormalPrio(); + [Priority=vsync] async VsyncPrio(); + [Priority=input] async InputPrio(); + [Priority=mediumhigh] async MediumHighPrio(); + [Priority=control] async ControlPrio(); + [ReplyPriority=control] async ControlPrioReturns() returns (bool aValue); + [Priority=normal, ReplyPriority=control] async NormalControlPrioReturns() returns (bool aValue); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PselfRecUnion.ipdl b/ipc/ipdl/test/ipdl/ok/PselfRecUnion.ipdl new file mode 100644 index 0000000000..7d76c34542 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PselfRecUnion.ipdl @@ -0,0 +1,11 @@ +union R { + int; + double; + R; +}; + +protocol PselfRecUnion { +child: + async Test(R r); + async __delete__(); +}; diff --git a/ipc/ipdl/test/ipdl/ok/Pshmem.ipdl b/ipc/ipdl/test/ipdl/ok/Pshmem.ipdl new file mode 100644 index 0000000000..0894e95652 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Pshmem.ipdl @@ -0,0 +1,13 @@ +union Foo { + int; + Shmem; +}; + +intr protocol Pshmem { +parent: + async Msg(Shmem s, Foo f); + sync SyncMsg(Shmem s, Foo f) + returns (Shmem t, Foo g); + [LegacyIntr] intr InterruptMsg(Shmem s, Foo f) + returns (Shmem t, Foo g); +}; diff --git a/ipc/ipdl/test/ipdl/ok/PsyncProtocol.ipdl b/ipc/ipdl/test/ipdl/ok/PsyncProtocol.ipdl new file mode 100644 index 0000000000..377671b2d6 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PsyncProtocol.ipdl @@ -0,0 +1,11 @@ +sync protocol PsyncProtocol { + + // sanity check of sync protocols + +child: + async AsyncMsg(); + +parent: + sync SyncMsg() returns (int i); + +}; diff --git a/ipc/ipdl/test/ipdl/ok/PthreeDirections.ipdl b/ipc/ipdl/test/ipdl/ok/PthreeDirections.ipdl new file mode 100644 index 0000000000..81c8cf6041 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/PthreeDirections.ipdl @@ -0,0 +1,13 @@ +protocol PthreeDirections { + + // sanity check that the three direction specifiers are being accepted +child: + async ChildMsg(); + +parent: + async ParentMsg(); + +both: + async BothMsg(); + +}; diff --git a/ipc/ipdl/test/ipdl/ok/Punion_Basic.ipdl b/ipc/ipdl/test/ipdl/ok/Punion_Basic.ipdl new file mode 100644 index 0000000000..697b86e088 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Punion_Basic.ipdl @@ -0,0 +1,11 @@ +union Basic { + int; + double; +}; + +sync protocol Punion_Basic { + +parent: + sync Msg(Basic p) returns (Basic r); + +}; diff --git a/ipc/ipdl/test/ipdl/ok/Punion_Comparable.ipdl b/ipc/ipdl/test/ipdl/ok/Punion_Comparable.ipdl new file mode 100644 index 0000000000..c69ac07cc2 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Punion_Comparable.ipdl @@ -0,0 +1,11 @@ +[Comparable] union Basic { + int; + double; +}; + +sync protocol Punion_Comparable { + +parent: + sync Msg(Basic p) returns (Basic r); + +}; diff --git a/ipc/ipdl/test/ipdl/ok/Punion_Namespaced.ipdl b/ipc/ipdl/test/ipdl/ok/Punion_Namespaced.ipdl new file mode 100644 index 0000000000..9453a561c8 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/Punion_Namespaced.ipdl @@ -0,0 +1,18 @@ +namespace kitties { + +union Socks { + int; + double; +}; + +} // namespace kitties + + +namespace puppies { + +protocol Punion_Namespaced { +child: + async Msg(Socks s); +}; + +} // namespace puppies diff --git a/ipc/ipdl/test/ipdl/ok/header.ipdlh b/ipc/ipdl/test/ipdl/ok/header.ipdlh new file mode 100644 index 0000000000..fc3f8c8279 --- /dev/null +++ b/ipc/ipdl/test/ipdl/ok/header.ipdlh @@ -0,0 +1,15 @@ +using foo from "foo.h"; +using bar::baz from "foo.h"; + +struct Outer { }; + +namespace a { struct Inner1 { }; } + +namespace b { struct Inner2 { }; } + +namespace c { +union X { + int32_t; + float; +}; +} diff --git a/ipc/ipdl/test/ipdl/runtests.py b/ipc/ipdl/test/ipdl/runtests.py new file mode 100644 index 0000000000..4296eab65c --- /dev/null +++ b/ipc/ipdl/test/ipdl/runtests.py @@ -0,0 +1,119 @@ +import os +import unittest + +from IPDLCompile import IPDLCompile + + +class IPDLTestCase(unittest.TestCase): + def __init__(self, ipdlargv, filename): + unittest.TestCase.__init__(self, "test") + self.filename = filename + self.compile = IPDLCompile(filename, ipdlargv) + + def test(self): + self.compile.run() + self.assertFalse(self.compile.exception(), self.mkFailMsg()) + self.checkPassed() + + def mkCustomMsg(self, msg): + return """ +### Command: %s +### %s +### stderr: +%s""" % ( + " ".join(self.compile.argv), + msg, + self.compile.stderr, + ) + + def mkFailMsg(self): + return """ +### Command: %s +### stderr: +%s""" % ( + " ".join(self.compile.argv), + self.compile.stderr, + ) + + def shortDescription(self): + return '%s test of "%s"' % (self.__class__.__name__, self.filename) + + +class OkTestCase(IPDLTestCase): + """An invocation of the IPDL compiler on a valid specification. + The IPDL compiler should not produce errors or exceptions.""" + + def __init__(self, ipdlargv, filename): + IPDLTestCase.__init__(self, ipdlargv, filename) + + def checkPassed(self): + self.assertTrue(self.compile.ok(), self.mkFailMsg()) + + +class ErrorTestCase(IPDLTestCase): + """An invocation of the IPDL compiler on an *invalid* specification. + The IPDL compiler *should* produce errors but not exceptions.""" + + def __init__(self, ipdlargv, filename): + IPDLTestCase.__init__(self, ipdlargv, filename) + + # Look for expected errors in the input file. + f = open(filename, "r") + self.expectedErrorMessage = [] + for l in f: + if l.startswith("//error:"): + self.expectedErrorMessage.append(l[2:-1]) + f.close() + + def checkPassed(self): + self.assertNotEqual( + self.expectedErrorMessage, + [], + self.mkCustomMsg( + "Error test should contain at least " + + "one line starting with //error: " + + "that indicates the expected failure." + ), + ) + + for e in self.expectedErrorMessage: + self.assertTrue( + self.compile.error(e), + self.mkCustomMsg('Did not see expected error "' + e + '"'), + ) + + +if __name__ == "__main__": + import sys + + okdir = sys.argv[1] + assert os.path.isdir(okdir) + errordir = sys.argv[2] + assert os.path.isdir(errordir) + + ipdlargv = [] + oksuite = unittest.TestSuite() + errorsuite = unittest.TestSuite() + + oktests, errortests = False, False + for arg in sys.argv[3:]: + if errortests: + # The extra subdirectory is used for non-failing files we want + # to include from failing files. + errorIncludes = ["-I", os.path.join(errordir, "extra"), "-I", errordir] + errorsuite.addTest(ErrorTestCase(ipdlargv + errorIncludes, arg)) + elif oktests: + if "ERRORTESTS" == arg: + errortests = True + continue + oksuite.addTest(OkTestCase(ipdlargv + ["-I", okdir], arg)) + else: + if "OKTESTS" == arg: + oktests = True + continue + ipdlargv.append(arg) + + test_result = (unittest.TextTestRunner()).run( + unittest.TestSuite([oksuite, errorsuite]) + ) + sys.exit(not test_result.wasSuccessful()) diff --git a/ipc/ipdl/test/ipdl/sync-messages.ini b/ipc/ipdl/test/ipdl/sync-messages.ini new file mode 100644 index 0000000000..989964f0dd --- /dev/null +++ b/ipc/ipdl/test/ipdl/sync-messages.ini @@ -0,0 +1,50 @@ +[Pactorreturn::Msg] +description = test only +[Parray_Union::Msg] +description = test only +[Punion_Basic::Msg] +description = test only +[PStruct::test] +description = test only +[PStructComparable::test] +description = test only +[Punion_Comparable::Msg] +description = test only +[PintrProtocol::SyncMsg] +description = test only +[PintrProtocol::InterruptMsg] +description = test only +[Pshmem::SyncMsg] +description = test only +[Pshmem::InterruptMsg] +description = test only +[Pbytebuf::SyncMsg] +description = test only +[Pbytebuf::InterruptMsg] +description = test only +[PsyncProtocol::SyncMsg] +description = test only +[PDeleteSub::__delete__] +description = test only +[PintrMessageCompress::foo] +description = test only +[PintrMessageCompress::bar] +description = test only +[PsyncMessageCompress::foo] +description = test only +[PsyncParentToChild::Msg] +description = test only +[PtooWeakIntrSync::Msg] +description = test only +[PtooWeakSyncAsync::Msg] +description = test only +[PundeclReturnType::Msg] +description = test only +[PasyncMessageListed::Msg] +description = test only +[PNested::InsideSync] +description = test only +[PNested::InsideCpowSync] +description = test only +[PMyManager::SomeMsg] +description = test and docs only diff --git a/ipc/ipdl/test/moz.build b/ipc/ipdl/test/moz.build new file mode 100644 index 0000000000..a323b4a2eb --- /dev/null +++ b/ipc/ipdl/test/moz.build @@ -0,0 +1,9 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +DIRS += ["ipdl"] + +TEST_DIRS += ["gtest"] |