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