xref: /qemu/scripts/qapi/visit.py (revision 91fa93e516d080d440ead2ad4f88960545bd5b2c)
1"""
2QAPI visitor generator
3
4Copyright IBM, Corp. 2011
5Copyright (C) 2014-2018 Red Hat, Inc.
6
7Authors:
8 Anthony Liguori <aliguori@us.ibm.com>
9 Michael Roth    <mdroth@linux.vnet.ibm.com>
10 Markus Armbruster <armbru@redhat.com>
11
12This work is licensed under the terms of the GNU GPL, version 2.
13See the COPYING file in the top-level directory.
14"""
15
16from typing import List, Optional, Sequence
17
18from .common import (
19    c_enum_const,
20    c_name,
21    gen_endif,
22    gen_if,
23    indent,
24    mcgen,
25)
26from .gen import QAPISchemaModularCVisitor, ifcontext
27from .schema import (
28    QAPISchema,
29    QAPISchemaEnumMember,
30    QAPISchemaEnumType,
31    QAPISchemaFeature,
32    QAPISchemaObjectType,
33    QAPISchemaObjectTypeMember,
34    QAPISchemaType,
35    QAPISchemaVariants,
36)
37from .source import QAPISourceInfo
38
39
40def gen_visit_decl(name: str, scalar: bool = False) -> str:
41    c_type = c_name(name) + ' *'
42    if not scalar:
43        c_type += '*'
44    return mcgen('''
45
46bool visit_type_%(c_name)s(Visitor *v, const char *name,
47                 %(c_type)sobj, Error **errp);
48''',
49                 c_name=c_name(name), c_type=c_type)
50
51
52def gen_visit_members_decl(name: str) -> str:
53    return mcgen('''
54
55bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp);
56''',
57                 c_name=c_name(name))
58
59
60def gen_visit_object_members(name: str,
61                             base: Optional[QAPISchemaObjectType],
62                             members: List[QAPISchemaObjectTypeMember],
63                             variants: Optional[QAPISchemaVariants]) -> str:
64    ret = mcgen('''
65
66bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp)
67{
68''',
69                c_name=c_name(name))
70
71    if base:
72        ret += mcgen('''
73    if (!visit_type_%(c_type)s_members(v, (%(c_type)s *)obj, errp)) {
74        return false;
75    }
76''',
77                     c_type=base.c_name())
78
79    for memb in members:
80        deprecated = 'deprecated' in [f.name for f in memb.features]
81        ret += gen_if(memb.ifcond)
82        if memb.optional:
83            ret += mcgen('''
84    if (visit_optional(v, "%(name)s", &obj->has_%(c_name)s)) {
85''',
86                         name=memb.name, c_name=c_name(memb.name))
87            indent.increase()
88        if deprecated:
89            ret += mcgen('''
90    if (visit_deprecated(v, "%(name)s")) {
91''',
92                         name=memb.name)
93            indent.increase()
94        ret += mcgen('''
95    if (!visit_type_%(c_type)s(v, "%(name)s", &obj->%(c_name)s, errp)) {
96        return false;
97    }
98''',
99                     c_type=memb.type.c_name(), name=memb.name,
100                     c_name=c_name(memb.name))
101        if deprecated:
102            indent.decrease()
103            ret += mcgen('''
104    }
105''')
106        if memb.optional:
107            indent.decrease()
108            ret += mcgen('''
109    }
110''')
111        ret += gen_endif(memb.ifcond)
112
113    if variants:
114        tag_member = variants.tag_member
115        assert isinstance(tag_member.type, QAPISchemaEnumType)
116
117        ret += mcgen('''
118    switch (obj->%(c_name)s) {
119''',
120                     c_name=c_name(tag_member.name))
121
122        for var in variants.variants:
123            case_str = c_enum_const(tag_member.type.name, var.name,
124                                    tag_member.type.prefix)
125            ret += gen_if(var.ifcond)
126            if var.type.name == 'q_empty':
127                # valid variant and nothing to do
128                ret += mcgen('''
129    case %(case)s:
130        break;
131''',
132                             case=case_str)
133            else:
134                ret += mcgen('''
135    case %(case)s:
136        return visit_type_%(c_type)s_members(v, &obj->u.%(c_name)s, errp);
137''',
138                             case=case_str,
139                             c_type=var.type.c_name(), c_name=c_name(var.name))
140
141            ret += gen_endif(var.ifcond)
142        ret += mcgen('''
143    default:
144        abort();
145    }
146''')
147
148    ret += mcgen('''
149    return true;
150}
151''')
152    return ret
153
154
155def gen_visit_list(name: str, element_type: QAPISchemaType) -> str:
156    return mcgen('''
157
158bool visit_type_%(c_name)s(Visitor *v, const char *name,
159                 %(c_name)s **obj, Error **errp)
160{
161    bool ok = false;
162    %(c_name)s *tail;
163    size_t size = sizeof(**obj);
164
165    if (!visit_start_list(v, name, (GenericList **)obj, size, errp)) {
166        return false;
167    }
168
169    for (tail = *obj; tail;
170         tail = (%(c_name)s *)visit_next_list(v, (GenericList *)tail, size)) {
171        if (!visit_type_%(c_elt_type)s(v, NULL, &tail->value, errp)) {
172            goto out_obj;
173        }
174    }
175
176    ok = visit_check_list(v, errp);
177out_obj:
178    visit_end_list(v, (void **)obj);
179    if (!ok && visit_is_input(v)) {
180        qapi_free_%(c_name)s(*obj);
181        *obj = NULL;
182    }
183    return ok;
184}
185''',
186                 c_name=c_name(name), c_elt_type=element_type.c_name())
187
188
189def gen_visit_enum(name: str) -> str:
190    return mcgen('''
191
192bool visit_type_%(c_name)s(Visitor *v, const char *name,
193                 %(c_name)s *obj, Error **errp)
194{
195    int value = *obj;
196    bool ok = visit_type_enum(v, name, &value, &%(c_name)s_lookup, errp);
197    *obj = value;
198    return ok;
199}
200''',
201                 c_name=c_name(name))
202
203
204def gen_visit_alternate(name: str, variants: QAPISchemaVariants) -> str:
205    ret = mcgen('''
206
207bool visit_type_%(c_name)s(Visitor *v, const char *name,
208                 %(c_name)s **obj, Error **errp)
209{
210    bool ok = false;
211
212    if (!visit_start_alternate(v, name, (GenericAlternate **)obj,
213                               sizeof(**obj), errp)) {
214        return false;
215    }
216    if (!*obj) {
217        /* incomplete */
218        assert(visit_is_dealloc(v));
219        ok = true;
220        goto out_obj;
221    }
222    switch ((*obj)->type) {
223''',
224                c_name=c_name(name))
225
226    for var in variants.variants:
227        ret += gen_if(var.ifcond)
228        ret += mcgen('''
229    case %(case)s:
230''',
231                     case=var.type.alternate_qtype())
232        if isinstance(var.type, QAPISchemaObjectType):
233            ret += mcgen('''
234        if (!visit_start_struct(v, name, NULL, 0, errp)) {
235            break;
236        }
237        if (visit_type_%(c_type)s_members(v, &(*obj)->u.%(c_name)s, errp)) {
238            ok = visit_check_struct(v, errp);
239        }
240        visit_end_struct(v, NULL);
241''',
242                         c_type=var.type.c_name(),
243                         c_name=c_name(var.name))
244        else:
245            ret += mcgen('''
246        ok = visit_type_%(c_type)s(v, name, &(*obj)->u.%(c_name)s, errp);
247''',
248                         c_type=var.type.c_name(),
249                         c_name=c_name(var.name))
250        ret += mcgen('''
251        break;
252''')
253        ret += gen_endif(var.ifcond)
254
255    ret += mcgen('''
256    case QTYPE_NONE:
257        abort();
258    default:
259        assert(visit_is_input(v));
260        error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
261                   "%(name)s");
262        /* Avoid passing invalid *obj to qapi_free_%(c_name)s() */
263        g_free(*obj);
264        *obj = NULL;
265    }
266out_obj:
267    visit_end_alternate(v, (void **)obj);
268    if (!ok && visit_is_input(v)) {
269        qapi_free_%(c_name)s(*obj);
270        *obj = NULL;
271    }
272    return ok;
273}
274''',
275                 name=name, c_name=c_name(name))
276
277    return ret
278
279
280def gen_visit_object(name: str) -> str:
281    return mcgen('''
282
283bool visit_type_%(c_name)s(Visitor *v, const char *name,
284                 %(c_name)s **obj, Error **errp)
285{
286    bool ok = false;
287
288    if (!visit_start_struct(v, name, (void **)obj, sizeof(%(c_name)s), errp)) {
289        return false;
290    }
291    if (!*obj) {
292        /* incomplete */
293        assert(visit_is_dealloc(v));
294        ok = true;
295        goto out_obj;
296    }
297    if (!visit_type_%(c_name)s_members(v, *obj, errp)) {
298        goto out_obj;
299    }
300    ok = visit_check_struct(v, errp);
301out_obj:
302    visit_end_struct(v, (void **)obj);
303    if (!ok && visit_is_input(v)) {
304        qapi_free_%(c_name)s(*obj);
305        *obj = NULL;
306    }
307    return ok;
308}
309''',
310                 c_name=c_name(name))
311
312
313class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor):
314
315    def __init__(self, prefix: str):
316        super().__init__(
317            prefix, 'qapi-visit', ' * Schema-defined QAPI visitors',
318            ' * Built-in QAPI visitors', __doc__)
319
320    def _begin_builtin_module(self) -> None:
321        self._genc.preamble_add(mcgen('''
322#include "qemu/osdep.h"
323#include "qapi/error.h"
324#include "qapi/qapi-builtin-visit.h"
325'''))
326        self._genh.preamble_add(mcgen('''
327#include "qapi/visitor.h"
328#include "qapi/qapi-builtin-types.h"
329
330'''))
331
332    def _begin_user_module(self, name: str) -> None:
333        types = self._module_basename('qapi-types', name)
334        visit = self._module_basename('qapi-visit', name)
335        self._genc.preamble_add(mcgen('''
336#include "qemu/osdep.h"
337#include "qapi/error.h"
338#include "qapi/qmp/qerror.h"
339#include "%(visit)s.h"
340''',
341                                      visit=visit))
342        self._genh.preamble_add(mcgen('''
343#include "qapi/qapi-builtin-visit.h"
344#include "%(types)s.h"
345
346''',
347                                      types=types))
348
349    def visit_enum_type(self,
350                        name: str,
351                        info: Optional[QAPISourceInfo],
352                        ifcond: Sequence[str],
353                        features: List[QAPISchemaFeature],
354                        members: List[QAPISchemaEnumMember],
355                        prefix: Optional[str]) -> None:
356        with ifcontext(ifcond, self._genh, self._genc):
357            self._genh.add(gen_visit_decl(name, scalar=True))
358            self._genc.add(gen_visit_enum(name))
359
360    def visit_array_type(self,
361                         name: str,
362                         info: Optional[QAPISourceInfo],
363                         ifcond: Sequence[str],
364                         element_type: QAPISchemaType) -> None:
365        with ifcontext(ifcond, self._genh, self._genc):
366            self._genh.add(gen_visit_decl(name))
367            self._genc.add(gen_visit_list(name, element_type))
368
369    def visit_object_type(self,
370                          name: str,
371                          info: Optional[QAPISourceInfo],
372                          ifcond: Sequence[str],
373                          features: List[QAPISchemaFeature],
374                          base: Optional[QAPISchemaObjectType],
375                          members: List[QAPISchemaObjectTypeMember],
376                          variants: Optional[QAPISchemaVariants]) -> None:
377        # Nothing to do for the special empty builtin
378        if name == 'q_empty':
379            return
380        with ifcontext(ifcond, self._genh, self._genc):
381            self._genh.add(gen_visit_members_decl(name))
382            self._genc.add(gen_visit_object_members(name, base,
383                                                    members, variants))
384            # TODO Worth changing the visitor signature, so we could
385            # directly use rather than repeat type.is_implicit()?
386            if not name.startswith('q_'):
387                # only explicit types need an allocating visit
388                self._genh.add(gen_visit_decl(name))
389                self._genc.add(gen_visit_object(name))
390
391    def visit_alternate_type(self,
392                             name: str,
393                             info: Optional[QAPISourceInfo],
394                             ifcond: Sequence[str],
395                             features: List[QAPISchemaFeature],
396                             variants: QAPISchemaVariants) -> None:
397        with ifcontext(ifcond, self._genh, self._genc):
398            self._genh.add(gen_visit_decl(name))
399            self._genc.add(gen_visit_alternate(name, variants))
400
401
402def gen_visit(schema: QAPISchema,
403              output_dir: str,
404              prefix: str,
405              opt_builtins: bool) -> None:
406    vis = QAPISchemaGenVisitVisitor(prefix)
407    schema.visit(vis)
408    vis.write(output_dir, opt_builtins)
409