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