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