1# -*- coding: utf-8 -*- 2# 3# QAPI schema internal representation 4# 5# Copyright (c) 2015-2019 Red Hat Inc. 6# 7# Authors: 8# Markus Armbruster <armbru@redhat.com> 9# Eric Blake <eblake@redhat.com> 10# Marc-André Lureau <marcandre.lureau@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# TODO catching name collisions in generated code would be nice 16 17from collections import OrderedDict 18import os 19import re 20from typing import List, Optional 21 22from .common import ( 23 POINTER_SUFFIX, 24 c_name, 25 cgen_ifcond, 26 docgen_ifcond, 27 gen_endif, 28 gen_if, 29) 30from .error import QAPIError, QAPISemError, QAPISourceError 31from .expr import check_exprs 32from .parser import QAPIExpression, QAPISchemaParser 33 34 35class QAPISchemaIfCond: 36 def __init__(self, ifcond=None): 37 self.ifcond = ifcond 38 39 def _cgen(self): 40 return cgen_ifcond(self.ifcond) 41 42 def gen_if(self): 43 return gen_if(self._cgen()) 44 45 def gen_endif(self): 46 return gen_endif(self._cgen()) 47 48 def docgen(self): 49 return docgen_ifcond(self.ifcond) 50 51 def is_present(self): 52 return bool(self.ifcond) 53 54 55class QAPISchemaEntity: 56 meta: Optional[str] = None 57 58 def __init__(self, name: str, info, doc, ifcond=None, features=None): 59 assert name is None or isinstance(name, str) 60 for f in features or []: 61 assert isinstance(f, QAPISchemaFeature) 62 f.set_defined_in(name) 63 self.name = name 64 self._module = None 65 # For explicitly defined entities, info points to the (explicit) 66 # definition. For builtins (and their arrays), info is None. 67 # For implicitly defined entities, info points to a place that 68 # triggered the implicit definition (there may be more than one 69 # such place). 70 self.info = info 71 self.doc = doc 72 self._ifcond = ifcond or QAPISchemaIfCond() 73 self.features = features or [] 74 self._checked = False 75 76 def __repr__(self): 77 if self.name is None: 78 return "<%s at 0x%x>" % (type(self).__name__, id(self)) 79 return "<%s:%s at 0x%x>" % (type(self).__name__, self.name, 80 id(self)) 81 82 def c_name(self): 83 return c_name(self.name) 84 85 def check(self, schema): 86 assert not self._checked 87 seen = {} 88 for f in self.features: 89 f.check_clash(self.info, seen) 90 self._checked = True 91 92 def connect_doc(self, doc=None): 93 doc = doc or self.doc 94 if doc: 95 for f in self.features: 96 doc.connect_feature(f) 97 98 def _set_module(self, schema, info): 99 assert self._checked 100 fname = info.fname if info else QAPISchemaModule.BUILTIN_MODULE_NAME 101 self._module = schema.module_by_fname(fname) 102 self._module.add_entity(self) 103 104 def set_module(self, schema): 105 self._set_module(schema, self.info) 106 107 @property 108 def ifcond(self): 109 assert self._checked 110 return self._ifcond 111 112 def is_implicit(self): 113 return not self.info 114 115 def visit(self, visitor): 116 assert self._checked 117 118 def describe(self): 119 assert self.meta 120 return "%s '%s'" % (self.meta, self.name) 121 122 123class QAPISchemaVisitor: 124 def visit_begin(self, schema): 125 pass 126 127 def visit_end(self): 128 pass 129 130 def visit_module(self, name): 131 pass 132 133 def visit_needed(self, entity): 134 # Default to visiting everything 135 return True 136 137 def visit_include(self, name, info): 138 pass 139 140 def visit_builtin_type(self, name, info, json_type): 141 pass 142 143 def visit_enum_type(self, name, info, ifcond, features, members, prefix): 144 pass 145 146 def visit_array_type(self, name, info, ifcond, element_type): 147 pass 148 149 def visit_object_type(self, name, info, ifcond, features, 150 base, members, variants): 151 pass 152 153 def visit_object_type_flat(self, name, info, ifcond, features, 154 members, variants): 155 pass 156 157 def visit_alternate_type(self, name, info, ifcond, features, variants): 158 pass 159 160 def visit_command(self, name, info, ifcond, features, 161 arg_type, ret_type, gen, success_response, boxed, 162 allow_oob, allow_preconfig, coroutine): 163 pass 164 165 def visit_event(self, name, info, ifcond, features, arg_type, boxed): 166 pass 167 168 169class QAPISchemaModule: 170 171 BUILTIN_MODULE_NAME = './builtin' 172 173 def __init__(self, name): 174 self.name = name 175 self._entity_list = [] 176 177 @staticmethod 178 def is_system_module(name: str) -> bool: 179 """ 180 System modules are internally defined modules. 181 182 Their names start with the "./" prefix. 183 """ 184 return name.startswith('./') 185 186 @classmethod 187 def is_user_module(cls, name: str) -> bool: 188 """ 189 User modules are those defined by the user in qapi JSON files. 190 191 They do not start with the "./" prefix. 192 """ 193 return not cls.is_system_module(name) 194 195 @classmethod 196 def is_builtin_module(cls, name: str) -> bool: 197 """ 198 The built-in module is a single System module for the built-in types. 199 200 It is always "./builtin". 201 """ 202 return name == cls.BUILTIN_MODULE_NAME 203 204 def add_entity(self, ent): 205 self._entity_list.append(ent) 206 207 def visit(self, visitor): 208 visitor.visit_module(self.name) 209 for entity in self._entity_list: 210 if visitor.visit_needed(entity): 211 entity.visit(visitor) 212 213 214class QAPISchemaInclude(QAPISchemaEntity): 215 def __init__(self, sub_module, info): 216 super().__init__(None, info, None) 217 self._sub_module = sub_module 218 219 def visit(self, visitor): 220 super().visit(visitor) 221 visitor.visit_include(self._sub_module.name, self.info) 222 223 224class QAPISchemaType(QAPISchemaEntity): 225 # Return the C type for common use. 226 # For the types we commonly box, this is a pointer type. 227 def c_type(self): 228 pass 229 230 # Return the C type to be used in a parameter list. 231 def c_param_type(self): 232 return self.c_type() 233 234 # Return the C type to be used where we suppress boxing. 235 def c_unboxed_type(self): 236 return self.c_type() 237 238 def json_type(self): 239 pass 240 241 def alternate_qtype(self): 242 json2qtype = { 243 'null': 'QTYPE_QNULL', 244 'string': 'QTYPE_QSTRING', 245 'number': 'QTYPE_QNUM', 246 'int': 'QTYPE_QNUM', 247 'boolean': 'QTYPE_QBOOL', 248 'array': 'QTYPE_QLIST', 249 'object': 'QTYPE_QDICT' 250 } 251 return json2qtype.get(self.json_type()) 252 253 def doc_type(self): 254 if self.is_implicit(): 255 return None 256 return self.name 257 258 def need_has_if_optional(self): 259 # When FOO is a pointer, has_FOO == !!FOO, i.e. has_FOO is redundant. 260 # Except for arrays; see QAPISchemaArrayType.need_has_if_optional(). 261 return not self.c_type().endswith(POINTER_SUFFIX) 262 263 def check(self, schema): 264 super().check(schema) 265 for feat in self.features: 266 if feat.is_special(): 267 raise QAPISemError( 268 self.info, 269 f"feature '{feat.name}' is not supported for types") 270 271 def describe(self): 272 assert self.meta 273 return "%s type '%s'" % (self.meta, self.name) 274 275 276class QAPISchemaBuiltinType(QAPISchemaType): 277 meta = 'built-in' 278 279 def __init__(self, name, json_type, c_type): 280 super().__init__(name, None, None) 281 assert not c_type or isinstance(c_type, str) 282 assert json_type in ('string', 'number', 'int', 'boolean', 'null', 283 'value') 284 self._json_type_name = json_type 285 self._c_type_name = c_type 286 287 def c_name(self): 288 return self.name 289 290 def c_type(self): 291 return self._c_type_name 292 293 def c_param_type(self): 294 if self.name == 'str': 295 return 'const ' + self._c_type_name 296 return self._c_type_name 297 298 def json_type(self): 299 return self._json_type_name 300 301 def doc_type(self): 302 return self.json_type() 303 304 def visit(self, visitor): 305 super().visit(visitor) 306 visitor.visit_builtin_type(self.name, self.info, self.json_type()) 307 308 309class QAPISchemaEnumType(QAPISchemaType): 310 meta = 'enum' 311 312 def __init__(self, name, info, doc, ifcond, features, members, prefix): 313 super().__init__(name, info, doc, ifcond, features) 314 for m in members: 315 assert isinstance(m, QAPISchemaEnumMember) 316 m.set_defined_in(name) 317 assert prefix is None or isinstance(prefix, str) 318 self.members = members 319 self.prefix = prefix 320 321 def check(self, schema): 322 super().check(schema) 323 seen = {} 324 for m in self.members: 325 m.check_clash(self.info, seen) 326 327 def connect_doc(self, doc=None): 328 super().connect_doc(doc) 329 doc = doc or self.doc 330 for m in self.members: 331 m.connect_doc(doc) 332 333 def is_implicit(self): 334 # See QAPISchema._def_predefineds() 335 return self.name == 'QType' 336 337 def c_type(self): 338 return c_name(self.name) 339 340 def member_names(self): 341 return [m.name for m in self.members] 342 343 def json_type(self): 344 return 'string' 345 346 def visit(self, visitor): 347 super().visit(visitor) 348 visitor.visit_enum_type( 349 self.name, self.info, self.ifcond, self.features, 350 self.members, self.prefix) 351 352 353class QAPISchemaArrayType(QAPISchemaType): 354 meta = 'array' 355 356 def __init__(self, name, info, element_type): 357 super().__init__(name, info, None) 358 assert isinstance(element_type, str) 359 self._element_type_name = element_type 360 self.element_type = None 361 362 def need_has_if_optional(self): 363 # When FOO is an array, we still need has_FOO to distinguish 364 # absent (!has_FOO) from present and empty (has_FOO && !FOO). 365 return True 366 367 def check(self, schema): 368 super().check(schema) 369 self.element_type = schema.resolve_type( 370 self._element_type_name, self.info, 371 self.info and self.info.defn_meta) 372 assert not isinstance(self.element_type, QAPISchemaArrayType) 373 374 def set_module(self, schema): 375 self._set_module(schema, self.element_type.info) 376 377 @property 378 def ifcond(self): 379 assert self._checked 380 return self.element_type.ifcond 381 382 def is_implicit(self): 383 return True 384 385 def c_type(self): 386 return c_name(self.name) + POINTER_SUFFIX 387 388 def json_type(self): 389 return 'array' 390 391 def doc_type(self): 392 elt_doc_type = self.element_type.doc_type() 393 if not elt_doc_type: 394 return None 395 return 'array of ' + elt_doc_type 396 397 def visit(self, visitor): 398 super().visit(visitor) 399 visitor.visit_array_type(self.name, self.info, self.ifcond, 400 self.element_type) 401 402 def describe(self): 403 assert self.meta 404 return "%s type ['%s']" % (self.meta, self._element_type_name) 405 406 407class QAPISchemaObjectType(QAPISchemaType): 408 def __init__(self, name, info, doc, ifcond, features, 409 base, local_members, variants): 410 # struct has local_members, optional base, and no variants 411 # union has base, variants, and no local_members 412 super().__init__(name, info, doc, ifcond, features) 413 self.meta = 'union' if variants else 'struct' 414 assert base is None or isinstance(base, str) 415 for m in local_members: 416 assert isinstance(m, QAPISchemaObjectTypeMember) 417 m.set_defined_in(name) 418 if variants is not None: 419 assert isinstance(variants, QAPISchemaVariants) 420 variants.set_defined_in(name) 421 self._base_name = base 422 self.base = None 423 self.local_members = local_members 424 self.variants = variants 425 self.members = None 426 427 def check(self, schema): 428 # This calls another type T's .check() exactly when the C 429 # struct emitted by gen_object() contains that T's C struct 430 # (pointers don't count). 431 if self.members is not None: 432 # A previous .check() completed: nothing to do 433 return 434 if self._checked: 435 # Recursed: C struct contains itself 436 raise QAPISemError(self.info, 437 "object %s contains itself" % self.name) 438 439 super().check(schema) 440 assert self._checked and self.members is None 441 442 seen = OrderedDict() 443 if self._base_name: 444 self.base = schema.resolve_type(self._base_name, self.info, 445 "'base'") 446 if (not isinstance(self.base, QAPISchemaObjectType) 447 or self.base.variants): 448 raise QAPISemError( 449 self.info, 450 "'base' requires a struct type, %s isn't" 451 % self.base.describe()) 452 self.base.check(schema) 453 self.base.check_clash(self.info, seen) 454 for m in self.local_members: 455 m.check(schema) 456 m.check_clash(self.info, seen) 457 members = seen.values() 458 459 if self.variants: 460 self.variants.check(schema, seen) 461 self.variants.check_clash(self.info, seen) 462 463 self.members = members # mark completed 464 465 # Check that the members of this type do not cause duplicate JSON members, 466 # and update seen to track the members seen so far. Report any errors 467 # on behalf of info, which is not necessarily self.info 468 def check_clash(self, info, seen): 469 assert self._checked 470 for m in self.members: 471 m.check_clash(info, seen) 472 if self.variants: 473 self.variants.check_clash(info, seen) 474 475 def connect_doc(self, doc=None): 476 super().connect_doc(doc) 477 doc = doc or self.doc 478 if self.base and self.base.is_implicit(): 479 self.base.connect_doc(doc) 480 for m in self.local_members: 481 m.connect_doc(doc) 482 483 def is_implicit(self): 484 # See QAPISchema._make_implicit_object_type(), as well as 485 # _def_predefineds() 486 return self.name.startswith('q_') 487 488 def is_empty(self): 489 assert self.members is not None 490 return not self.members and not self.variants 491 492 def has_conditional_members(self): 493 assert self.members is not None 494 return any(m.ifcond.is_present() for m in self.members) 495 496 def c_name(self): 497 assert self.name != 'q_empty' 498 return super().c_name() 499 500 def c_type(self): 501 assert not self.is_implicit() 502 return c_name(self.name) + POINTER_SUFFIX 503 504 def c_unboxed_type(self): 505 return c_name(self.name) 506 507 def json_type(self): 508 return 'object' 509 510 def visit(self, visitor): 511 super().visit(visitor) 512 visitor.visit_object_type( 513 self.name, self.info, self.ifcond, self.features, 514 self.base, self.local_members, self.variants) 515 visitor.visit_object_type_flat( 516 self.name, self.info, self.ifcond, self.features, 517 self.members, self.variants) 518 519 520class QAPISchemaAlternateType(QAPISchemaType): 521 meta = 'alternate' 522 523 def __init__(self, name, info, doc, ifcond, features, variants): 524 super().__init__(name, info, doc, ifcond, features) 525 assert isinstance(variants, QAPISchemaVariants) 526 assert variants.tag_member 527 variants.set_defined_in(name) 528 variants.tag_member.set_defined_in(self.name) 529 self.variants = variants 530 531 def check(self, schema): 532 super().check(schema) 533 self.variants.tag_member.check(schema) 534 # Not calling self.variants.check_clash(), because there's nothing 535 # to clash with 536 self.variants.check(schema, {}) 537 # Alternate branch names have no relation to the tag enum values; 538 # so we have to check for potential name collisions ourselves. 539 seen = {} 540 types_seen = {} 541 for v in self.variants.variants: 542 v.check_clash(self.info, seen) 543 qtype = v.type.alternate_qtype() 544 if not qtype: 545 raise QAPISemError( 546 self.info, 547 "%s cannot use %s" 548 % (v.describe(self.info), v.type.describe())) 549 conflicting = set([qtype]) 550 if qtype == 'QTYPE_QSTRING': 551 if isinstance(v.type, QAPISchemaEnumType): 552 for m in v.type.members: 553 if m.name in ['on', 'off']: 554 conflicting.add('QTYPE_QBOOL') 555 if re.match(r'[-+0-9.]', m.name): 556 # lazy, could be tightened 557 conflicting.add('QTYPE_QNUM') 558 else: 559 conflicting.add('QTYPE_QNUM') 560 conflicting.add('QTYPE_QBOOL') 561 for qt in conflicting: 562 if qt in types_seen: 563 raise QAPISemError( 564 self.info, 565 "%s can't be distinguished from '%s'" 566 % (v.describe(self.info), types_seen[qt])) 567 types_seen[qt] = v.name 568 569 def connect_doc(self, doc=None): 570 super().connect_doc(doc) 571 doc = doc or self.doc 572 for v in self.variants.variants: 573 v.connect_doc(doc) 574 575 def c_type(self): 576 return c_name(self.name) + POINTER_SUFFIX 577 578 def json_type(self): 579 return 'value' 580 581 def visit(self, visitor): 582 super().visit(visitor) 583 visitor.visit_alternate_type( 584 self.name, self.info, self.ifcond, self.features, self.variants) 585 586 587class QAPISchemaVariants: 588 def __init__(self, tag_name, info, tag_member, variants): 589 # Unions pass tag_name but not tag_member. 590 # Alternates pass tag_member but not tag_name. 591 # After check(), tag_member is always set. 592 assert bool(tag_member) != bool(tag_name) 593 assert (isinstance(tag_name, str) or 594 isinstance(tag_member, QAPISchemaObjectTypeMember)) 595 for v in variants: 596 assert isinstance(v, QAPISchemaVariant) 597 self._tag_name = tag_name 598 self.info = info 599 self.tag_member = tag_member 600 self.variants = variants 601 602 def set_defined_in(self, name): 603 for v in self.variants: 604 v.set_defined_in(name) 605 606 def check(self, schema, seen): 607 if self._tag_name: # union 608 self.tag_member = seen.get(c_name(self._tag_name)) 609 base = "'base'" 610 # Pointing to the base type when not implicit would be 611 # nice, but we don't know it here 612 if not self.tag_member or self._tag_name != self.tag_member.name: 613 raise QAPISemError( 614 self.info, 615 "discriminator '%s' is not a member of %s" 616 % (self._tag_name, base)) 617 # Here we do: 618 base_type = schema.lookup_type(self.tag_member.defined_in) 619 assert base_type 620 if not base_type.is_implicit(): 621 base = "base type '%s'" % self.tag_member.defined_in 622 if not isinstance(self.tag_member.type, QAPISchemaEnumType): 623 raise QAPISemError( 624 self.info, 625 "discriminator member '%s' of %s must be of enum type" 626 % (self._tag_name, base)) 627 if self.tag_member.optional: 628 raise QAPISemError( 629 self.info, 630 "discriminator member '%s' of %s must not be optional" 631 % (self._tag_name, base)) 632 if self.tag_member.ifcond.is_present(): 633 raise QAPISemError( 634 self.info, 635 "discriminator member '%s' of %s must not be conditional" 636 % (self._tag_name, base)) 637 else: # alternate 638 assert isinstance(self.tag_member.type, QAPISchemaEnumType) 639 assert not self.tag_member.optional 640 assert not self.tag_member.ifcond.is_present() 641 if self._tag_name: # union 642 # branches that are not explicitly covered get an empty type 643 cases = {v.name for v in self.variants} 644 for m in self.tag_member.type.members: 645 if m.name not in cases: 646 v = QAPISchemaVariant(m.name, self.info, 647 'q_empty', m.ifcond) 648 v.set_defined_in(self.tag_member.defined_in) 649 self.variants.append(v) 650 if not self.variants: 651 raise QAPISemError(self.info, "union has no branches") 652 for v in self.variants: 653 v.check(schema) 654 # Union names must match enum values; alternate names are 655 # checked separately. Use 'seen' to tell the two apart. 656 if seen: 657 if v.name not in self.tag_member.type.member_names(): 658 raise QAPISemError( 659 self.info, 660 "branch '%s' is not a value of %s" 661 % (v.name, self.tag_member.type.describe())) 662 if not isinstance(v.type, QAPISchemaObjectType): 663 raise QAPISemError( 664 self.info, 665 "%s cannot use %s" 666 % (v.describe(self.info), v.type.describe())) 667 v.type.check(schema) 668 669 def check_clash(self, info, seen): 670 for v in self.variants: 671 # Reset seen map for each variant, since qapi names from one 672 # branch do not affect another branch 673 v.type.check_clash(info, dict(seen)) 674 675 676class QAPISchemaMember: 677 """ Represents object members, enum members and features """ 678 role = 'member' 679 680 def __init__(self, name, info, ifcond=None): 681 assert isinstance(name, str) 682 self.name = name 683 self.info = info 684 self.ifcond = ifcond or QAPISchemaIfCond() 685 self.defined_in = None 686 687 def set_defined_in(self, name): 688 assert not self.defined_in 689 self.defined_in = name 690 691 def check_clash(self, info, seen): 692 cname = c_name(self.name) 693 if cname in seen: 694 raise QAPISemError( 695 info, 696 "%s collides with %s" 697 % (self.describe(info), seen[cname].describe(info))) 698 seen[cname] = self 699 700 def connect_doc(self, doc): 701 if doc: 702 doc.connect_member(self) 703 704 def describe(self, info): 705 role = self.role 706 meta = 'type' 707 defined_in = self.defined_in 708 assert defined_in 709 710 if defined_in.startswith('q_obj_'): 711 # See QAPISchema._make_implicit_object_type() - reverse the 712 # mapping there to create a nice human-readable description 713 defined_in = defined_in[6:] 714 if defined_in.endswith('-arg'): 715 # Implicit type created for a command's dict 'data' 716 assert role == 'member' 717 role = 'parameter' 718 meta = 'command' 719 defined_in = defined_in[:-4] 720 elif defined_in.endswith('-base'): 721 # Implicit type created for a union's dict 'base' 722 role = 'base ' + role 723 defined_in = defined_in[:-5] 724 else: 725 assert False 726 727 if defined_in != info.defn_name: 728 return "%s '%s' of %s '%s'" % (role, self.name, meta, defined_in) 729 return "%s '%s'" % (role, self.name) 730 731 732class QAPISchemaEnumMember(QAPISchemaMember): 733 role = 'value' 734 735 def __init__(self, name, info, ifcond=None, features=None): 736 super().__init__(name, info, ifcond) 737 for f in features or []: 738 assert isinstance(f, QAPISchemaFeature) 739 f.set_defined_in(name) 740 self.features = features or [] 741 742 def connect_doc(self, doc): 743 super().connect_doc(doc) 744 if doc: 745 for f in self.features: 746 doc.connect_feature(f) 747 748 749class QAPISchemaFeature(QAPISchemaMember): 750 role = 'feature' 751 752 def is_special(self): 753 return self.name in ('deprecated', 'unstable') 754 755 756class QAPISchemaObjectTypeMember(QAPISchemaMember): 757 def __init__(self, name, info, typ, optional, ifcond=None, features=None): 758 super().__init__(name, info, ifcond) 759 assert isinstance(typ, str) 760 assert isinstance(optional, bool) 761 for f in features or []: 762 assert isinstance(f, QAPISchemaFeature) 763 f.set_defined_in(name) 764 self._type_name = typ 765 self.type = None 766 self.optional = optional 767 self.features = features or [] 768 769 def need_has(self): 770 assert self.type 771 return self.optional and self.type.need_has_if_optional() 772 773 def check(self, schema): 774 assert self.defined_in 775 self.type = schema.resolve_type(self._type_name, self.info, 776 self.describe) 777 seen = {} 778 for f in self.features: 779 f.check_clash(self.info, seen) 780 781 def connect_doc(self, doc): 782 super().connect_doc(doc) 783 if doc: 784 for f in self.features: 785 doc.connect_feature(f) 786 787 788class QAPISchemaVariant(QAPISchemaObjectTypeMember): 789 role = 'branch' 790 791 def __init__(self, name, info, typ, ifcond=None): 792 super().__init__(name, info, typ, False, ifcond) 793 794 795class QAPISchemaCommand(QAPISchemaEntity): 796 meta = 'command' 797 798 def __init__(self, name, info, doc, ifcond, features, 799 arg_type, ret_type, 800 gen, success_response, boxed, allow_oob, allow_preconfig, 801 coroutine): 802 super().__init__(name, info, doc, ifcond, features) 803 assert not arg_type or isinstance(arg_type, str) 804 assert not ret_type or isinstance(ret_type, str) 805 self._arg_type_name = arg_type 806 self.arg_type = None 807 self._ret_type_name = ret_type 808 self.ret_type = None 809 self.gen = gen 810 self.success_response = success_response 811 self.boxed = boxed 812 self.allow_oob = allow_oob 813 self.allow_preconfig = allow_preconfig 814 self.coroutine = coroutine 815 816 def check(self, schema): 817 super().check(schema) 818 if self._arg_type_name: 819 self.arg_type = schema.resolve_type( 820 self._arg_type_name, self.info, "command's 'data'") 821 if not isinstance(self.arg_type, QAPISchemaObjectType): 822 raise QAPISemError( 823 self.info, 824 "command's 'data' cannot take %s" 825 % self.arg_type.describe()) 826 if self.arg_type.variants and not self.boxed: 827 raise QAPISemError( 828 self.info, 829 "command's 'data' can take %s only with 'boxed': true" 830 % self.arg_type.describe()) 831 self.arg_type.check(schema) 832 if self.arg_type.has_conditional_members() and not self.boxed: 833 raise QAPISemError( 834 self.info, 835 "conditional command arguments require 'boxed': true") 836 if self._ret_type_name: 837 self.ret_type = schema.resolve_type( 838 self._ret_type_name, self.info, "command's 'returns'") 839 if self.name not in self.info.pragma.command_returns_exceptions: 840 typ = self.ret_type 841 if isinstance(typ, QAPISchemaArrayType): 842 typ = self.ret_type.element_type 843 assert typ 844 if not isinstance(typ, QAPISchemaObjectType): 845 raise QAPISemError( 846 self.info, 847 "command's 'returns' cannot take %s" 848 % self.ret_type.describe()) 849 850 def connect_doc(self, doc=None): 851 super().connect_doc(doc) 852 doc = doc or self.doc 853 if doc: 854 if self.arg_type and self.arg_type.is_implicit(): 855 self.arg_type.connect_doc(doc) 856 857 def visit(self, visitor): 858 super().visit(visitor) 859 visitor.visit_command( 860 self.name, self.info, self.ifcond, self.features, 861 self.arg_type, self.ret_type, self.gen, self.success_response, 862 self.boxed, self.allow_oob, self.allow_preconfig, 863 self.coroutine) 864 865 866class QAPISchemaEvent(QAPISchemaEntity): 867 meta = 'event' 868 869 def __init__(self, name, info, doc, ifcond, features, arg_type, boxed): 870 super().__init__(name, info, doc, ifcond, features) 871 assert not arg_type or isinstance(arg_type, str) 872 self._arg_type_name = arg_type 873 self.arg_type = None 874 self.boxed = boxed 875 876 def check(self, schema): 877 super().check(schema) 878 if self._arg_type_name: 879 self.arg_type = schema.resolve_type( 880 self._arg_type_name, self.info, "event's 'data'") 881 if not isinstance(self.arg_type, QAPISchemaObjectType): 882 raise QAPISemError( 883 self.info, 884 "event's 'data' cannot take %s" 885 % self.arg_type.describe()) 886 if self.arg_type.variants and not self.boxed: 887 raise QAPISemError( 888 self.info, 889 "event's 'data' can take %s only with 'boxed': true" 890 % self.arg_type.describe()) 891 self.arg_type.check(schema) 892 if self.arg_type.has_conditional_members() and not self.boxed: 893 raise QAPISemError( 894 self.info, 895 "conditional event arguments require 'boxed': true") 896 897 def connect_doc(self, doc=None): 898 super().connect_doc(doc) 899 doc = doc or self.doc 900 if doc: 901 if self.arg_type and self.arg_type.is_implicit(): 902 self.arg_type.connect_doc(doc) 903 904 def visit(self, visitor): 905 super().visit(visitor) 906 visitor.visit_event( 907 self.name, self.info, self.ifcond, self.features, 908 self.arg_type, self.boxed) 909 910 911class QAPISchema: 912 def __init__(self, fname): 913 self.fname = fname 914 915 try: 916 parser = QAPISchemaParser(fname) 917 except OSError as err: 918 raise QAPIError( 919 f"can't read schema file '{fname}': {err.strerror}" 920 ) from err 921 922 exprs = check_exprs(parser.exprs) 923 self.docs = parser.docs 924 self._entity_list = [] 925 self._entity_dict = {} 926 self._module_dict = OrderedDict() 927 self._schema_dir = os.path.dirname(fname) 928 self._make_module(QAPISchemaModule.BUILTIN_MODULE_NAME) 929 self._make_module(fname) 930 self._predefining = True 931 self._def_predefineds() 932 self._predefining = False 933 self._def_exprs(exprs) 934 self.check() 935 936 def _def_entity(self, ent): 937 # Only the predefined types are allowed to not have info 938 assert ent.info or self._predefining 939 self._entity_list.append(ent) 940 if ent.name is None: 941 return 942 # TODO reject names that differ only in '_' vs. '.' vs. '-', 943 # because they're liable to clash in generated C. 944 other_ent = self._entity_dict.get(ent.name) 945 if other_ent: 946 if other_ent.info: 947 where = QAPISourceError(other_ent.info, "previous definition") 948 raise QAPISemError( 949 ent.info, 950 "'%s' is already defined\n%s" % (ent.name, where)) 951 raise QAPISemError( 952 ent.info, "%s is already defined" % other_ent.describe()) 953 self._entity_dict[ent.name] = ent 954 955 def lookup_entity(self, name, typ=None): 956 ent = self._entity_dict.get(name) 957 if typ and not isinstance(ent, typ): 958 return None 959 return ent 960 961 def lookup_type(self, name): 962 return self.lookup_entity(name, QAPISchemaType) 963 964 def resolve_type(self, name, info, what): 965 typ = self.lookup_type(name) 966 if not typ: 967 if callable(what): 968 what = what(info) 969 raise QAPISemError( 970 info, "%s uses unknown type '%s'" % (what, name)) 971 return typ 972 973 def _module_name(self, fname: str) -> str: 974 if QAPISchemaModule.is_system_module(fname): 975 return fname 976 return os.path.relpath(fname, self._schema_dir) 977 978 def _make_module(self, fname): 979 name = self._module_name(fname) 980 if name not in self._module_dict: 981 self._module_dict[name] = QAPISchemaModule(name) 982 return self._module_dict[name] 983 984 def module_by_fname(self, fname): 985 name = self._module_name(fname) 986 return self._module_dict[name] 987 988 def _def_include(self, expr: QAPIExpression): 989 include = expr['include'] 990 assert expr.doc is None 991 self._def_entity( 992 QAPISchemaInclude(self._make_module(include), expr.info)) 993 994 def _def_builtin_type(self, name, json_type, c_type): 995 self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type)) 996 # Instantiating only the arrays that are actually used would 997 # be nice, but we can't as long as their generated code 998 # (qapi-builtin-types.[ch]) may be shared by some other 999 # schema. 1000 self._make_array_type(name, None) 1001 1002 def _def_predefineds(self): 1003 for t in [('str', 'string', 'char' + POINTER_SUFFIX), 1004 ('number', 'number', 'double'), 1005 ('int', 'int', 'int64_t'), 1006 ('int8', 'int', 'int8_t'), 1007 ('int16', 'int', 'int16_t'), 1008 ('int32', 'int', 'int32_t'), 1009 ('int64', 'int', 'int64_t'), 1010 ('uint8', 'int', 'uint8_t'), 1011 ('uint16', 'int', 'uint16_t'), 1012 ('uint32', 'int', 'uint32_t'), 1013 ('uint64', 'int', 'uint64_t'), 1014 ('size', 'int', 'uint64_t'), 1015 ('bool', 'boolean', 'bool'), 1016 ('any', 'value', 'QObject' + POINTER_SUFFIX), 1017 ('null', 'null', 'QNull' + POINTER_SUFFIX)]: 1018 self._def_builtin_type(*t) 1019 self.the_empty_object_type = QAPISchemaObjectType( 1020 'q_empty', None, None, None, None, None, [], None) 1021 self._def_entity(self.the_empty_object_type) 1022 1023 qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 1024 'qbool'] 1025 qtype_values = self._make_enum_members( 1026 [{'name': n} for n in qtypes], None) 1027 1028 self._def_entity(QAPISchemaEnumType('QType', None, None, None, None, 1029 qtype_values, 'QTYPE')) 1030 1031 def _make_features(self, features, info): 1032 if features is None: 1033 return [] 1034 return [QAPISchemaFeature(f['name'], info, 1035 QAPISchemaIfCond(f.get('if'))) 1036 for f in features] 1037 1038 def _make_enum_member(self, name, ifcond, features, info): 1039 return QAPISchemaEnumMember(name, info, 1040 QAPISchemaIfCond(ifcond), 1041 self._make_features(features, info)) 1042 1043 def _make_enum_members(self, values, info): 1044 return [self._make_enum_member(v['name'], v.get('if'), 1045 v.get('features'), info) 1046 for v in values] 1047 1048 def _make_array_type(self, element_type, info): 1049 name = element_type + 'List' # reserved by check_defn_name_str() 1050 if not self.lookup_type(name): 1051 self._def_entity(QAPISchemaArrayType(name, info, element_type)) 1052 return name 1053 1054 def _make_implicit_object_type(self, name, info, ifcond, role, members): 1055 if not members: 1056 return None 1057 # See also QAPISchemaObjectTypeMember.describe() 1058 name = 'q_obj_%s-%s' % (name, role) 1059 typ = self.lookup_entity(name, QAPISchemaObjectType) 1060 if typ: 1061 # The implicit object type has multiple users. This can 1062 # only be a duplicate definition, which will be flagged 1063 # later. 1064 pass 1065 else: 1066 self._def_entity(QAPISchemaObjectType( 1067 name, info, None, ifcond, None, None, members, None)) 1068 return name 1069 1070 def _def_enum_type(self, expr: QAPIExpression): 1071 name = expr['enum'] 1072 data = expr['data'] 1073 prefix = expr.get('prefix') 1074 ifcond = QAPISchemaIfCond(expr.get('if')) 1075 info = expr.info 1076 features = self._make_features(expr.get('features'), info) 1077 self._def_entity(QAPISchemaEnumType( 1078 name, info, expr.doc, ifcond, features, 1079 self._make_enum_members(data, info), prefix)) 1080 1081 def _make_member(self, name, typ, ifcond, features, info): 1082 optional = False 1083 if name.startswith('*'): 1084 name = name[1:] 1085 optional = True 1086 if isinstance(typ, list): 1087 assert len(typ) == 1 1088 typ = self._make_array_type(typ[0], info) 1089 return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond, 1090 self._make_features(features, info)) 1091 1092 def _make_members(self, data, info): 1093 return [self._make_member(key, value['type'], 1094 QAPISchemaIfCond(value.get('if')), 1095 value.get('features'), info) 1096 for (key, value) in data.items()] 1097 1098 def _def_struct_type(self, expr: QAPIExpression): 1099 name = expr['struct'] 1100 base = expr.get('base') 1101 data = expr['data'] 1102 info = expr.info 1103 ifcond = QAPISchemaIfCond(expr.get('if')) 1104 features = self._make_features(expr.get('features'), info) 1105 self._def_entity(QAPISchemaObjectType( 1106 name, info, expr.doc, ifcond, features, base, 1107 self._make_members(data, info), 1108 None)) 1109 1110 def _make_variant(self, case, typ, ifcond, info): 1111 if isinstance(typ, list): 1112 assert len(typ) == 1 1113 typ = self._make_array_type(typ[0], info) 1114 return QAPISchemaVariant(case, info, typ, ifcond) 1115 1116 def _def_union_type(self, expr: QAPIExpression): 1117 name = expr['union'] 1118 base = expr['base'] 1119 tag_name = expr['discriminator'] 1120 data = expr['data'] 1121 assert isinstance(data, dict) 1122 info = expr.info 1123 ifcond = QAPISchemaIfCond(expr.get('if')) 1124 features = self._make_features(expr.get('features'), info) 1125 if isinstance(base, dict): 1126 base = self._make_implicit_object_type( 1127 name, info, ifcond, 1128 'base', self._make_members(base, info)) 1129 variants = [ 1130 self._make_variant(key, value['type'], 1131 QAPISchemaIfCond(value.get('if')), 1132 info) 1133 for (key, value) in data.items()] 1134 members: List[QAPISchemaObjectTypeMember] = [] 1135 self._def_entity( 1136 QAPISchemaObjectType(name, info, expr.doc, ifcond, features, 1137 base, members, 1138 QAPISchemaVariants( 1139 tag_name, info, None, variants))) 1140 1141 def _def_alternate_type(self, expr: QAPIExpression): 1142 name = expr['alternate'] 1143 data = expr['data'] 1144 assert isinstance(data, dict) 1145 ifcond = QAPISchemaIfCond(expr.get('if')) 1146 info = expr.info 1147 features = self._make_features(expr.get('features'), info) 1148 variants = [ 1149 self._make_variant(key, value['type'], 1150 QAPISchemaIfCond(value.get('if')), 1151 info) 1152 for (key, value) in data.items()] 1153 tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False) 1154 self._def_entity( 1155 QAPISchemaAlternateType( 1156 name, info, expr.doc, ifcond, features, 1157 QAPISchemaVariants(None, info, tag_member, variants))) 1158 1159 def _def_command(self, expr: QAPIExpression): 1160 name = expr['command'] 1161 data = expr.get('data') 1162 rets = expr.get('returns') 1163 gen = expr.get('gen', True) 1164 success_response = expr.get('success-response', True) 1165 boxed = expr.get('boxed', False) 1166 allow_oob = expr.get('allow-oob', False) 1167 allow_preconfig = expr.get('allow-preconfig', False) 1168 coroutine = expr.get('coroutine', False) 1169 ifcond = QAPISchemaIfCond(expr.get('if')) 1170 info = expr.info 1171 features = self._make_features(expr.get('features'), info) 1172 if isinstance(data, OrderedDict): 1173 data = self._make_implicit_object_type( 1174 name, info, ifcond, 1175 'arg', self._make_members(data, info)) 1176 if isinstance(rets, list): 1177 assert len(rets) == 1 1178 rets = self._make_array_type(rets[0], info) 1179 self._def_entity(QAPISchemaCommand(name, info, expr.doc, ifcond, 1180 features, data, rets, 1181 gen, success_response, 1182 boxed, allow_oob, allow_preconfig, 1183 coroutine)) 1184 1185 def _def_event(self, expr: QAPIExpression): 1186 name = expr['event'] 1187 data = expr.get('data') 1188 boxed = expr.get('boxed', False) 1189 ifcond = QAPISchemaIfCond(expr.get('if')) 1190 info = expr.info 1191 features = self._make_features(expr.get('features'), info) 1192 if isinstance(data, OrderedDict): 1193 data = self._make_implicit_object_type( 1194 name, info, ifcond, 1195 'arg', self._make_members(data, info)) 1196 self._def_entity(QAPISchemaEvent(name, info, expr.doc, ifcond, 1197 features, data, boxed)) 1198 1199 def _def_exprs(self, exprs): 1200 for expr in exprs: 1201 if 'enum' in expr: 1202 self._def_enum_type(expr) 1203 elif 'struct' in expr: 1204 self._def_struct_type(expr) 1205 elif 'union' in expr: 1206 self._def_union_type(expr) 1207 elif 'alternate' in expr: 1208 self._def_alternate_type(expr) 1209 elif 'command' in expr: 1210 self._def_command(expr) 1211 elif 'event' in expr: 1212 self._def_event(expr) 1213 elif 'include' in expr: 1214 self._def_include(expr) 1215 else: 1216 assert False 1217 1218 def check(self): 1219 for ent in self._entity_list: 1220 ent.check(self) 1221 ent.connect_doc() 1222 for ent in self._entity_list: 1223 ent.set_module(self) 1224 for doc in self.docs: 1225 doc.check() 1226 1227 def visit(self, visitor): 1228 visitor.visit_begin(self) 1229 for mod in self._module_dict.values(): 1230 mod.visit(visitor) 1231 visitor.visit_end() 1232