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