| 1 | """ |
| 2 | QAPI visitor generator |
| 3 | |
| 4 | Copyright IBM, Corp. 2011 |
| 5 | Copyright (C) 2014-2018 Red Hat, Inc. |
| 6 | |
| 7 | Authors: |
| 8 | Anthony Liguori <aliguori@us.ibm.com> |
| 9 | Michael Roth <mdroth@linux.vnet.ibm.com> |
| 10 | Markus Armbruster <armbru@redhat.com> |
| 11 | |
| 12 | This work is licensed under the terms of the GNU GPL, version 2. |
| 13 | See the COPYING file in the top-level directory. |
| 14 | """ |
| 15 | |
| 16 | from typing import List, Optional |
| 17 | |
| 18 | from .common import ( |
| 19 | c_enum_const, |
| 20 | c_name, |
| 21 | indent, |
| 22 | mcgen, |
| 23 | ) |
| 24 | from .gen import QAPISchemaModularCVisitor, gen_features, ifcontext |
| 25 | from .schema import ( |
| 26 | QAPISchema, |
| 27 | QAPISchemaAlternatives, |
| 28 | QAPISchemaBranches, |
| 29 | QAPISchemaEnumMember, |
| 30 | QAPISchemaEnumType, |
| 31 | QAPISchemaFeature, |
| 32 | QAPISchemaIfCond, |
| 33 | QAPISchemaObjectType, |
| 34 | QAPISchemaObjectTypeMember, |
| 35 | QAPISchemaType, |
| 36 | ) |
| 37 | from .source import QAPISourceInfo |
| 38 | |
| 39 | |
| 40 | def 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 | |
| 46 | bool 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 | |
| 52 | def gen_visit_members_decl(name: str) -> str: |
| 53 | return mcgen(''' |
| 54 | |
| 55 | bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp); |
| 56 | ''', |
| 57 | c_name=c_name(name)) |
| 58 | |
| 59 | |
| 60 | def gen_visit_object_members(name: str, |
| 61 | base: Optional[QAPISchemaObjectType], |
| 62 | members: List[QAPISchemaObjectTypeMember], |
| 63 | branches: Optional[QAPISchemaBranches]) -> str: |
| 64 | ret = mcgen(''' |
| 65 | |
| 66 | bool 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 | |
| 173 | def gen_visit_list(name: str, element_type: QAPISchemaType) -> str: |
| 174 | return mcgen(''' |
| 175 | |
| 176 | bool 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); |
| 195 | out_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 | |
| 207 | def gen_visit_enum(name: str) -> str: |
| 208 | return mcgen(''' |
| 209 | |
| 210 | bool 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 | |
| 222 | def gen_visit_alternate(name: str, |
| 223 | alternatives: QAPISchemaAlternatives) -> str: |
| 224 | ret = mcgen(''' |
| 225 | |
| 226 | bool 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 | } |
| 286 | out_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 | |
| 300 | def gen_visit_object(name: str) -> str: |
| 301 | return mcgen(''' |
| 302 | |
| 303 | bool 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); |
| 321 | out_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 | |
| 333 | class 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 | |
| 422 | def 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) |