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