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