xref: /openbmc/qemu/scripts/qapi/visit.py (revision 4f7b1ecb)
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,
284                   "Invalid parameter type for '%%s', expected: %(name)s",
285                   name ? name : "null");
286        /* Avoid passing invalid *obj to qapi_free_%(c_name)s() */
287        g_free(*obj);
288        *obj = NULL;
289    }
290out_obj:
291    visit_end_alternate(v, (void **)obj);
292    if (!ok && visit_is_input(v)) {
293        qapi_free_%(c_name)s(*obj);
294        *obj = NULL;
295    }
296    return ok;
297}
298''',
299                 name=name, c_name=c_name(name))
300
301    return ret
302
303
304def gen_visit_object(name: str) -> str:
305    return mcgen('''
306
307bool visit_type_%(c_name)s(Visitor *v, const char *name,
308                 %(c_name)s **obj, Error **errp)
309{
310    bool ok = false;
311
312    if (!visit_start_struct(v, name, (void **)obj, sizeof(%(c_name)s), errp)) {
313        return false;
314    }
315    if (!*obj) {
316        /* incomplete */
317        assert(visit_is_dealloc(v));
318        ok = true;
319        goto out_obj;
320    }
321    if (!visit_type_%(c_name)s_members(v, *obj, errp)) {
322        goto out_obj;
323    }
324    ok = visit_check_struct(v, errp);
325out_obj:
326    visit_end_struct(v, (void **)obj);
327    if (!ok && visit_is_input(v)) {
328        qapi_free_%(c_name)s(*obj);
329        *obj = NULL;
330    }
331    return ok;
332}
333''',
334                 c_name=c_name(name))
335
336
337class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor):
338
339    def __init__(self, prefix: str):
340        super().__init__(
341            prefix, 'qapi-visit', ' * Schema-defined QAPI visitors',
342            ' * Built-in QAPI visitors', __doc__)
343
344    def _begin_builtin_module(self) -> None:
345        self._genc.preamble_add(mcgen('''
346#include "qemu/osdep.h"
347#include "qapi/error.h"
348#include "qapi/qapi-builtin-visit.h"
349'''))
350        self._genh.preamble_add(mcgen('''
351#include "qapi/visitor.h"
352#include "qapi/qapi-builtin-types.h"
353
354'''))
355
356    def _begin_user_module(self, name: str) -> None:
357        types = self._module_basename('qapi-types', name)
358        visit = self._module_basename('qapi-visit', name)
359        self._genc.preamble_add(mcgen('''
360#include "qemu/osdep.h"
361#include "qapi/error.h"
362#include "%(visit)s.h"
363''',
364                                      visit=visit))
365        self._genh.preamble_add(mcgen('''
366#include "qapi/qapi-builtin-visit.h"
367#include "%(types)s.h"
368
369''',
370                                      types=types))
371
372    def visit_enum_type(self,
373                        name: str,
374                        info: Optional[QAPISourceInfo],
375                        ifcond: QAPISchemaIfCond,
376                        features: List[QAPISchemaFeature],
377                        members: List[QAPISchemaEnumMember],
378                        prefix: Optional[str]) -> None:
379        with ifcontext(ifcond, self._genh, self._genc):
380            self._genh.add(gen_visit_decl(name, scalar=True))
381            self._genc.add(gen_visit_enum(name))
382
383    def visit_array_type(self,
384                         name: str,
385                         info: Optional[QAPISourceInfo],
386                         ifcond: QAPISchemaIfCond,
387                         element_type: QAPISchemaType) -> None:
388        with ifcontext(ifcond, self._genh, self._genc):
389            self._genh.add(gen_visit_decl(name))
390            self._genc.add(gen_visit_list(name, element_type))
391
392    def visit_object_type(self,
393                          name: str,
394                          info: Optional[QAPISourceInfo],
395                          ifcond: QAPISchemaIfCond,
396                          features: List[QAPISchemaFeature],
397                          base: Optional[QAPISchemaObjectType],
398                          members: List[QAPISchemaObjectTypeMember],
399                          branches: Optional[QAPISchemaBranches]) -> None:
400        # Nothing to do for the special empty builtin
401        if name == 'q_empty':
402            return
403        with ifcontext(ifcond, self._genh, self._genc):
404            self._genh.add(gen_visit_members_decl(name))
405            self._genc.add(gen_visit_object_members(name, base,
406                                                    members, branches))
407            # TODO Worth changing the visitor signature, so we could
408            # directly use rather than repeat type.is_implicit()?
409            if not name.startswith('q_'):
410                # only explicit types need an allocating visit
411                self._genh.add(gen_visit_decl(name))
412                self._genc.add(gen_visit_object(name))
413
414    def visit_alternate_type(self,
415                             name: str,
416                             info: Optional[QAPISourceInfo],
417                             ifcond: QAPISchemaIfCond,
418                             features: List[QAPISchemaFeature],
419                             alternatives: QAPISchemaAlternatives) -> None:
420        with ifcontext(ifcond, self._genh, self._genc):
421            self._genh.add(gen_visit_decl(name))
422            self._genc.add(gen_visit_alternate(name, alternatives))
423
424
425def gen_visit(schema: QAPISchema,
426              output_dir: str,
427              prefix: str,
428              opt_builtins: bool) -> None:
429    vis = QAPISchemaGenVisitVisitor(prefix)
430    schema.visit(vis)
431    vis.write(output_dir, opt_builtins)
432