1# This Source Code Form is subject to the terms of the Mozilla Public
2# License, v. 2.0. If a copy of the MPL was not distributed with this
3# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4from __future__ import print_function
5
6from io import StringIO
7import optparse
8import os
9import sys
10from configparser import RawConfigParser
11
12import ipdl
13
14
15def log(minv, fmt, *args):
16    if _verbosity >= minv:
17        print(fmt % args)
18
19
20# process command line
21
22
23op = optparse.OptionParser(usage="ipdl.py [options] IPDLfiles...")
24op.add_option(
25    "-I",
26    "--include",
27    dest="includedirs",
28    default=[],
29    action="append",
30    help="Additional directory to search for included protocol specifications",
31)
32op.add_option(
33    "-s",
34    "--sync-msg-list",
35    dest="syncMsgList",
36    default="sync-messages.ini",
37    help="Config file listing allowed sync messages",
38)
39op.add_option(
40    "-m",
41    "--msg-metadata",
42    dest="msgMetadata",
43    default="message-metadata.ini",
44    help="Predicted message sizes for reducing serialization malloc overhead.",
45)
46op.add_option(
47    "-v",
48    "--verbose",
49    dest="verbosity",
50    default=1,
51    action="count",
52    help="Verbose logging (specify -vv or -vvv for very verbose logging)",
53)
54op.add_option(
55    "-q",
56    "--quiet",
57    dest="verbosity",
58    action="store_const",
59    const=0,
60    help="Suppress logging output",
61)
62op.add_option(
63    "-d",
64    "--outheaders-dir",
65    dest="headersdir",
66    default=".",
67    help="""Directory into which C++ headers will be generated.
68A protocol Foo in the namespace bar will cause the headers
69  dir/bar/Foo.h, dir/bar/FooParent.h, and dir/bar/FooParent.h
70to be generated""",
71)
72op.add_option(
73    "-o",
74    "--outcpp-dir",
75    dest="cppdir",
76    default=".",
77    help="""Directory into which C++ sources will be generated
78A protocol Foo in the namespace bar will cause the sources
79  cppdir/FooParent.cpp, cppdir/FooChild.cpp
80to be generated""",
81)
82
83options, files = op.parse_args()
84_verbosity = options.verbosity
85syncMsgList = options.syncMsgList
86msgMetadata = options.msgMetadata
87headersdir = options.headersdir
88cppdir = options.cppdir
89includedirs = [os.path.abspath(incdir) for incdir in options.includedirs]
90
91if not len(files):
92    op.error("No IPDL files specified")
93
94ipcmessagestartpath = os.path.join(headersdir, "IPCMessageStart.h")
95ipc_msgtype_name_path = os.path.join(cppdir, "IPCMessageTypeName.cpp")
96
97log(2, 'Generated C++ headers will be generated relative to "%s"', headersdir)
98log(2, 'Generated C++ sources will be generated in "%s"', cppdir)
99
100allmessages = {}
101allmessageprognames = []
102allprotocols = []
103
104
105def normalizedFilename(f):
106    if f == "-":
107        return "<stdin>"
108    return f
109
110
111log(2, "Reading sync message list")
112parser = RawConfigParser()
113parser.read_file(open(options.syncMsgList))
114syncMsgList = parser.sections()
115
116for section in syncMsgList:
117    if not parser.get(section, "description"):
118        print("Error: Sync message %s lacks a description" % section, file=sys.stderr)
119        sys.exit(1)
120
121# Read message metadata. Right now we only have 'segment_capacity'
122# for the standard segment size used for serialization.
123log(2, "Reading message metadata...")
124msgMetadataConfig = RawConfigParser()
125msgMetadataConfig.read_file(open(options.msgMetadata))
126
127segmentCapacityDict = {}
128for msgName in msgMetadataConfig.sections():
129    if msgMetadataConfig.has_option(msgName, "segment_capacity"):
130        capacity = msgMetadataConfig.get(msgName, "segment_capacity")
131        segmentCapacityDict[msgName] = capacity
132
133# First pass: parse and type-check all protocols
134for f in files:
135    log(2, os.path.basename(f))
136    filename = normalizedFilename(f)
137    if f == "-":
138        fd = sys.stdin
139    else:
140        fd = open(f)
141
142    specstring = fd.read()
143    fd.close()
144
145    ast = ipdl.parse(specstring, filename, includedirs=includedirs)
146    if ast is None:
147        print("Specification could not be parsed.", file=sys.stderr)
148        sys.exit(1)
149
150    log(2, "checking types")
151    if not ipdl.typecheck(ast):
152        print("Specification is not well typed.", file=sys.stderr)
153        sys.exit(1)
154
155    if not ipdl.checkSyncMessage(ast, syncMsgList):
156        print(
157            "Error: New sync IPC messages must be reviewed by an IPC peer and recorded in %s"
158            % options.syncMsgList,
159            file=sys.stderr,
160        )  # NOQA: E501
161        sys.exit(1)
162
163if not ipdl.checkFixedSyncMessages(parser):
164    # Errors have alraedy been printed to stderr, just exit
165    sys.exit(1)
166
167# Second pass: generate code
168for f in files:
169    # Read from parser cache
170    filename = normalizedFilename(f)
171    ast = ipdl.parse(None, filename, includedirs=includedirs)
172    ipdl.gencxx(filename, ast, headersdir, cppdir, segmentCapacityDict)
173
174    if ast.protocol:
175        allmessages[ast.protocol.name] = ipdl.genmsgenum(ast)
176        allprotocols.append(ast.protocol.name)
177        # e.g. PContent::RequestMemoryReport (not prefixed or suffixed.)
178        for md in ast.protocol.messageDecls:
179            allmessageprognames.append("%s::%s" % (md.namespace, md.decl.progname))
180
181allprotocols.sort()
182
183# Check if we have undefined message names in segmentCapacityDict.
184# This is a fool-proof of the 'message-metadata.ini' file.
185undefinedMessages = set(segmentCapacityDict.keys()) - set(allmessageprognames)
186if len(undefinedMessages) > 0:
187    print("Error: Undefined message names in message-metadata.ini:", file=sys.stderr)
188    print(undefinedMessages, file=sys.stderr)
189    sys.exit(1)
190
191ipcmsgstart = StringIO()
192
193print(
194    """
195// CODE GENERATED by ipdl.py. Do not edit.
196
197#ifndef IPCMessageStart_h
198#define IPCMessageStart_h
199
200enum IPCMessageStart {
201""",
202    file=ipcmsgstart,
203)
204
205for name in allprotocols:
206    print("  %sMsgStart," % name, file=ipcmsgstart)
207
208print(
209    """
210  LastMsgIndex
211};
212
213static_assert(LastMsgIndex <= 65536, "need to update IPC_MESSAGE_MACRO");
214
215#endif // ifndef IPCMessageStart_h
216""",
217    file=ipcmsgstart,
218)
219
220ipc_msgtype_name = StringIO()
221print(
222    """
223// CODE GENERATED by ipdl.py. Do not edit.
224#include <cstdint>
225
226#include "mozilla/ipc/ProtocolUtils.h"
227#include "IPCMessageStart.h"
228
229using std::uint32_t;
230
231namespace {
232
233enum IPCMessages {
234""",
235    file=ipc_msgtype_name,
236)
237
238for protocol in sorted(allmessages.keys()):
239    for (msg, num) in allmessages[protocol].idnums:
240        if num:
241            print("  %s = %s," % (msg, num), file=ipc_msgtype_name)
242        elif not msg.endswith("End"):
243            print("  %s__%s," % (protocol, msg), file=ipc_msgtype_name)
244
245print(
246    """
247};
248
249} // anonymous namespace
250
251namespace IPC {
252
253const char* StringFromIPCMessageType(uint32_t aMessageType)
254{
255  switch (aMessageType) {
256""",
257    file=ipc_msgtype_name,
258)
259
260for protocol in sorted(allmessages.keys()):
261    for (msg, num) in allmessages[protocol].idnums:
262        if num or msg.endswith("End"):
263            continue
264        print(
265            """
266  case %s__%s:
267    return "%s::%s";"""
268            % (protocol, msg, protocol, msg),
269            file=ipc_msgtype_name,
270        )
271
272print(
273    """
274  case ACCEPT_INVITE_MESSAGE_TYPE:
275    return "ACCEPT_INVITE_MESSAGE";
276  case REQUEST_INTRODUCTION_MESSAGE_TYPE:
277    return "REQUEST_INTRODUCTION_MESSAGE";
278  case INTRODUCE_MESSAGE_TYPE:
279    return "INTRODUCE_MESSAGE";
280  case BROADCAST_MESSAGE_TYPE:
281    return "BROADCAST_MESSAGE";
282  case EVENT_MESSAGE_TYPE:
283    return "EVENT_MESSAGE";
284  case IMPENDING_SHUTDOWN_MESSAGE_TYPE:
285    return "IMPENDING_SHUTDOWN";
286  case BUILD_IDS_MATCH_MESSAGE_TYPE:
287    return "BUILD_IDS_MATCH_MESSAGE";
288  case BUILD_ID_MESSAGE_TYPE:
289    return "BUILD_ID_MESSAGE";
290  case CHANNEL_OPENED_MESSAGE_TYPE:
291    return "CHANNEL_OPENED_MESSAGE";
292  case SHMEM_DESTROYED_MESSAGE_TYPE:
293    return "SHMEM_DESTROYED_MESSAGE";
294  case SHMEM_CREATED_MESSAGE_TYPE:
295    return "SHMEM_CREATED_MESSAGE";
296  case GOODBYE_MESSAGE_TYPE:
297    return "GOODBYE_MESSAGE";
298  case CANCEL_MESSAGE_TYPE:
299    return "CANCEL_MESSAGE";
300  default:
301    return "<unknown IPC msg name>";
302  }
303}
304
305} // namespace IPC
306
307namespace mozilla {
308namespace ipc {
309
310const char* ProtocolIdToName(IPCMessageStart aId) {
311  switch (aId) {
312""",
313    file=ipc_msgtype_name,
314)
315
316for name in allprotocols:
317    print("    case %sMsgStart:" % name, file=ipc_msgtype_name)
318    print('      return "%s";' % name, file=ipc_msgtype_name)
319
320print(
321    """
322  default:
323    return "<unknown protocol id>";
324  }
325}
326
327} // namespace ipc
328} // namespace mozilla
329""",
330    file=ipc_msgtype_name,
331)
332
333ipdl.writeifmodified(ipcmsgstart.getvalue(), ipcmessagestartpath)
334ipdl.writeifmodified(ipc_msgtype_name.getvalue(), ipc_msgtype_name_path)
335