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 = tag_member 631 self.variants = variants 632 633 def set_defined_in(self, name): 634 for v in self.variants: 635 v.set_defined_in(name) 636 637 def check(self, schema, seen): 638 if self._tag_name: # union 639 self.tag_member = seen.get(c_name(self._tag_name)) 640 base = "'base'" 641 # Pointing to the base type when not implicit would be 642 # nice, but we don't know it here 643 if not self.tag_member or self._tag_name != self.tag_member.name: 644 raise QAPISemError( 645 self.info, 646 "discriminator '%s' is not a member of %s" 647 % (self._tag_name, base)) 648 # Here we do: 649 base_type = schema.lookup_type(self.tag_member.defined_in) 650 assert base_type 651 if not base_type.is_implicit(): 652 base = "base type '%s'" % self.tag_member.defined_in 653 if not isinstance(self.tag_member.type, QAPISchemaEnumType): 654 raise QAPISemError( 655 self.info, 656 "discriminator member '%s' of %s must be of enum type" 657 % (self._tag_name, base)) 658 if self.tag_member.optional: 659 raise QAPISemError( 660 self.info, 661 "discriminator member '%s' of %s must not be optional" 662 % (self._tag_name, base)) 663 if self.tag_member.ifcond.is_present(): 664 raise QAPISemError( 665 self.info, 666 "discriminator member '%s' of %s must not be conditional" 667 % (self._tag_name, base)) 668 else: # alternate 669 assert isinstance(self.tag_member.type, QAPISchemaEnumType) 670 assert not self.tag_member.optional 671 assert not self.tag_member.ifcond.is_present() 672 if self._tag_name: # union 673 # branches that are not explicitly covered get an empty type 674 cases = {v.name for v in self.variants} 675 for m in self.tag_member.type.members: 676 if m.name not in cases: 677 v = QAPISchemaVariant(m.name, self.info, 678 'q_empty', m.ifcond) 679 v.set_defined_in(self.tag_member.defined_in) 680 self.variants.append(v) 681 if not self.variants: 682 raise QAPISemError(self.info, "union has no branches") 683 for v in self.variants: 684 v.check(schema) 685 # Union names must match enum values; alternate names are 686 # checked separately. Use 'seen' to tell the two apart. 687 if seen: 688 if v.name not in self.tag_member.type.member_names(): 689 raise QAPISemError( 690 self.info, 691 "branch '%s' is not a value of %s" 692 % (v.name, self.tag_member.type.describe())) 693 if not isinstance(v.type, QAPISchemaObjectType): 694 raise QAPISemError( 695 self.info, 696 "%s cannot use %s" 697 % (v.describe(self.info), v.type.describe())) 698 v.type.check(schema) 699 700 def check_clash(self, info, seen): 701 for v in self.variants: 702 # Reset seen map for each variant, since qapi names from one 703 # branch do not affect another branch 704 v.type.check_clash(info, dict(seen)) 705 706 707class QAPISchemaMember: 708 """ Represents object members, enum members and features """ 709 role = 'member' 710 711 def __init__(self, name, info, ifcond=None): 712 assert isinstance(name, str) 713 self.name = name 714 self.info = info 715 self.ifcond = ifcond or QAPISchemaIfCond() 716 self.defined_in = None 717 718 def set_defined_in(self, name): 719 assert not self.defined_in 720 self.defined_in = name 721 722 def check_clash(self, info, seen): 723 cname = c_name(self.name) 724 if cname in seen: 725 raise QAPISemError( 726 info, 727 "%s collides with %s" 728 % (self.describe(info), seen[cname].describe(info))) 729 seen[cname] = self 730 731 def connect_doc(self, doc): 732 if doc: 733 doc.connect_member(self) 734 735 def describe(self, info): 736 role = self.role 737 meta = 'type' 738 defined_in = self.defined_in 739 assert defined_in 740 741 if defined_in.startswith('q_obj_'): 742 # See QAPISchema._make_implicit_object_type() - reverse the 743 # mapping there to create a nice human-readable description 744 defined_in = defined_in[6:] 745 if defined_in.endswith('-arg'): 746 # Implicit type created for a command's dict 'data' 747 assert role == 'member' 748 role = 'parameter' 749 meta = 'command' 750 defined_in = defined_in[:-4] 751 elif defined_in.endswith('-base'): 752 # Implicit type created for a union's dict 'base' 753 role = 'base ' + role 754 defined_in = defined_in[:-5] 755 else: 756 assert False 757 758 assert info is not None 759 if defined_in != info.defn_name: 760 return "%s '%s' of %s '%s'" % (role, self.name, meta, defined_in) 761 return "%s '%s'" % (role, self.name) 762 763 764class QAPISchemaEnumMember(QAPISchemaMember): 765 role = 'value' 766 767 def __init__(self, name, info, ifcond=None, features=None): 768 super().__init__(name, info, ifcond) 769 for f in features or []: 770 assert isinstance(f, QAPISchemaFeature) 771 f.set_defined_in(name) 772 self.features = features or [] 773 774 def connect_doc(self, doc): 775 super().connect_doc(doc) 776 if doc: 777 for f in self.features: 778 doc.connect_feature(f) 779 780 781class QAPISchemaFeature(QAPISchemaMember): 782 role = 'feature' 783 784 def is_special(self): 785 return self.name in ('deprecated', 'unstable') 786 787 788class QAPISchemaObjectTypeMember(QAPISchemaMember): 789 def __init__(self, name, info, typ, optional, ifcond=None, features=None): 790 super().__init__(name, info, ifcond) 791 assert isinstance(typ, str) 792 assert isinstance(optional, bool) 793 for f in features or []: 794 assert isinstance(f, QAPISchemaFeature) 795 f.set_defined_in(name) 796 self._type_name = typ 797 self.type: QAPISchemaType # set during check() 798 self.optional = optional 799 self.features = features or [] 800 801 def need_has(self): 802 assert self.type 803 return self.optional and self.type.need_has_if_optional() 804 805 def check(self, schema): 806 assert self.defined_in 807 self.type = schema.resolve_type(self._type_name, self.info, 808 self.describe) 809 seen = {} 810 for f in self.features: 811 f.check_clash(self.info, seen) 812 813 def connect_doc(self, doc): 814 super().connect_doc(doc) 815 if doc: 816 for f in self.features: 817 doc.connect_feature(f) 818 819 820class QAPISchemaVariant(QAPISchemaObjectTypeMember): 821 role = 'branch' 822 823 def __init__(self, name, info, typ, ifcond=None): 824 super().__init__(name, info, typ, False, ifcond) 825 826 827class QAPISchemaCommand(QAPISchemaDefinition): 828 meta = 'command' 829 830 def __init__(self, name, info, doc, ifcond, features, 831 arg_type, ret_type, 832 gen, success_response, boxed, allow_oob, allow_preconfig, 833 coroutine): 834 super().__init__(name, info, doc, ifcond, features) 835 assert not arg_type or isinstance(arg_type, str) 836 assert not ret_type or isinstance(ret_type, str) 837 self._arg_type_name = arg_type 838 self.arg_type = None 839 self._ret_type_name = ret_type 840 self.ret_type = None 841 self.gen = gen 842 self.success_response = success_response 843 self.boxed = boxed 844 self.allow_oob = allow_oob 845 self.allow_preconfig = allow_preconfig 846 self.coroutine = coroutine 847 848 def check(self, schema): 849 assert self.info is not None 850 super().check(schema) 851 if self._arg_type_name: 852 arg_type = schema.resolve_type( 853 self._arg_type_name, self.info, "command's 'data'") 854 if not isinstance(arg_type, QAPISchemaObjectType): 855 raise QAPISemError( 856 self.info, 857 "command's 'data' cannot take %s" 858 % arg_type.describe()) 859 self.arg_type = arg_type 860 if self.arg_type.variants and not self.boxed: 861 raise QAPISemError( 862 self.info, 863 "command's 'data' can take %s only with 'boxed': true" 864 % self.arg_type.describe()) 865 self.arg_type.check(schema) 866 if self.arg_type.has_conditional_members() and not self.boxed: 867 raise QAPISemError( 868 self.info, 869 "conditional command arguments require 'boxed': true") 870 if self._ret_type_name: 871 self.ret_type = schema.resolve_type( 872 self._ret_type_name, self.info, "command's 'returns'") 873 if self.name not in self.info.pragma.command_returns_exceptions: 874 typ = self.ret_type 875 if isinstance(typ, QAPISchemaArrayType): 876 assert typ 877 typ = typ.element_type 878 if not isinstance(typ, QAPISchemaObjectType): 879 raise QAPISemError( 880 self.info, 881 "command's 'returns' cannot take %s" 882 % self.ret_type.describe()) 883 884 def connect_doc(self, doc=None): 885 super().connect_doc(doc) 886 doc = doc or self.doc 887 if doc: 888 if self.arg_type and self.arg_type.is_implicit(): 889 self.arg_type.connect_doc(doc) 890 891 def visit(self, visitor): 892 super().visit(visitor) 893 visitor.visit_command( 894 self.name, self.info, self.ifcond, self.features, 895 self.arg_type, self.ret_type, self.gen, self.success_response, 896 self.boxed, self.allow_oob, self.allow_preconfig, 897 self.coroutine) 898 899 900class QAPISchemaEvent(QAPISchemaDefinition): 901 meta = 'event' 902 903 def __init__(self, name, info, doc, ifcond, features, arg_type, boxed): 904 super().__init__(name, info, doc, ifcond, features) 905 assert not arg_type or isinstance(arg_type, str) 906 self._arg_type_name = arg_type 907 self.arg_type = None 908 self.boxed = boxed 909 910 def check(self, schema): 911 super().check(schema) 912 if self._arg_type_name: 913 typ = schema.resolve_type( 914 self._arg_type_name, self.info, "event's 'data'") 915 if not isinstance(typ, QAPISchemaObjectType): 916 raise QAPISemError( 917 self.info, 918 "event's 'data' cannot take %s" 919 % typ.describe()) 920 self.arg_type = typ 921 if self.arg_type.variants and not self.boxed: 922 raise QAPISemError( 923 self.info, 924 "event's 'data' can take %s only with 'boxed': true" 925 % self.arg_type.describe()) 926 self.arg_type.check(schema) 927 if self.arg_type.has_conditional_members() and not self.boxed: 928 raise QAPISemError( 929 self.info, 930 "conditional event arguments require 'boxed': true") 931 932 def connect_doc(self, doc=None): 933 super().connect_doc(doc) 934 doc = doc or self.doc 935 if doc: 936 if self.arg_type and self.arg_type.is_implicit(): 937 self.arg_type.connect_doc(doc) 938 939 def visit(self, visitor): 940 super().visit(visitor) 941 visitor.visit_event( 942 self.name, self.info, self.ifcond, self.features, 943 self.arg_type, self.boxed) 944 945 946class QAPISchema: 947 def __init__(self, fname): 948 self.fname = fname 949 950 try: 951 parser = QAPISchemaParser(fname) 952 except OSError as err: 953 raise QAPIError( 954 f"can't read schema file '{fname}': {err.strerror}" 955 ) from err 956 957 exprs = check_exprs(parser.exprs) 958 self.docs = parser.docs 959 self._entity_list = [] 960 self._entity_dict = {} 961 self._module_dict = OrderedDict() 962 self._schema_dir = os.path.dirname(fname) 963 self._make_module(QAPISchemaModule.BUILTIN_MODULE_NAME) 964 self._make_module(fname) 965 self._predefining = True 966 self._def_predefineds() 967 self._predefining = False 968 self._def_exprs(exprs) 969 self.check() 970 971 def _def_entity(self, ent): 972 self._entity_list.append(ent) 973 974 def _def_definition(self, defn): 975 # Only the predefined types are allowed to not have info 976 assert defn.info or self._predefining 977 self._def_entity(defn) 978 # TODO reject names that differ only in '_' vs. '.' vs. '-', 979 # because they're liable to clash in generated C. 980 other_defn = self._entity_dict.get(defn.name) 981 if other_defn: 982 if other_defn.info: 983 where = QAPISourceError(other_defn.info, "previous definition") 984 raise QAPISemError( 985 defn.info, 986 "'%s' is already defined\n%s" % (defn.name, where)) 987 raise QAPISemError( 988 defn.info, "%s is already defined" % other_defn.describe()) 989 self._entity_dict[defn.name] = defn 990 991 def lookup_entity(self, name, typ=None): 992 ent = self._entity_dict.get(name) 993 if typ and not isinstance(ent, typ): 994 return None 995 return ent 996 997 def lookup_type(self, name): 998 typ = self.lookup_entity(name, QAPISchemaType) 999 assert typ is None or isinstance(typ, QAPISchemaType) 1000 return typ 1001 1002 def resolve_type(self, name, info, what): 1003 typ = self.lookup_type(name) 1004 if not typ: 1005 assert info and what # built-in types must not fail lookup 1006 if callable(what): 1007 what = what(info) 1008 raise QAPISemError( 1009 info, "%s uses unknown type '%s'" % (what, name)) 1010 return typ 1011 1012 def _module_name(self, fname: str) -> str: 1013 if QAPISchemaModule.is_system_module(fname): 1014 return fname 1015 return os.path.relpath(fname, self._schema_dir) 1016 1017 def _make_module(self, fname): 1018 name = self._module_name(fname) 1019 if name not in self._module_dict: 1020 self._module_dict[name] = QAPISchemaModule(name) 1021 return self._module_dict[name] 1022 1023 def module_by_fname(self, fname): 1024 name = self._module_name(fname) 1025 return self._module_dict[name] 1026 1027 def _def_include(self, expr: QAPIExpression): 1028 include = expr['include'] 1029 assert expr.doc is None 1030 self._def_entity( 1031 QAPISchemaInclude(self._make_module(include), expr.info)) 1032 1033 def _def_builtin_type(self, name, json_type, c_type): 1034 self._def_definition(QAPISchemaBuiltinType(name, json_type, c_type)) 1035 # Instantiating only the arrays that are actually used would 1036 # be nice, but we can't as long as their generated code 1037 # (qapi-builtin-types.[ch]) may be shared by some other 1038 # schema. 1039 self._make_array_type(name, None) 1040 1041 def _def_predefineds(self): 1042 for t in [('str', 'string', 'char' + POINTER_SUFFIX), 1043 ('number', 'number', 'double'), 1044 ('int', 'int', 'int64_t'), 1045 ('int8', 'int', 'int8_t'), 1046 ('int16', 'int', 'int16_t'), 1047 ('int32', 'int', 'int32_t'), 1048 ('int64', 'int', 'int64_t'), 1049 ('uint8', 'int', 'uint8_t'), 1050 ('uint16', 'int', 'uint16_t'), 1051 ('uint32', 'int', 'uint32_t'), 1052 ('uint64', 'int', 'uint64_t'), 1053 ('size', 'int', 'uint64_t'), 1054 ('bool', 'boolean', 'bool'), 1055 ('any', 'value', 'QObject' + POINTER_SUFFIX), 1056 ('null', 'null', 'QNull' + POINTER_SUFFIX)]: 1057 self._def_builtin_type(*t) 1058 self.the_empty_object_type = QAPISchemaObjectType( 1059 'q_empty', None, None, None, None, None, [], None) 1060 self._def_definition(self.the_empty_object_type) 1061 1062 qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 1063 'qbool'] 1064 qtype_values = self._make_enum_members( 1065 [{'name': n} for n in qtypes], None) 1066 1067 self._def_definition(QAPISchemaEnumType( 1068 'QType', None, None, None, None, qtype_values, 'QTYPE')) 1069 1070 def _make_features(self, features, info): 1071 if features is None: 1072 return [] 1073 return [QAPISchemaFeature(f['name'], info, 1074 QAPISchemaIfCond(f.get('if'))) 1075 for f in features] 1076 1077 def _make_enum_member(self, name, ifcond, features, info): 1078 return QAPISchemaEnumMember(name, info, 1079 QAPISchemaIfCond(ifcond), 1080 self._make_features(features, info)) 1081 1082 def _make_enum_members(self, values, info): 1083 return [self._make_enum_member(v['name'], v.get('if'), 1084 v.get('features'), info) 1085 for v in values] 1086 1087 def _make_array_type(self, element_type, info): 1088 name = element_type + 'List' # reserved by check_defn_name_str() 1089 if not self.lookup_type(name): 1090 self._def_definition(QAPISchemaArrayType( 1091 name, info, element_type)) 1092 return name 1093 1094 def _make_implicit_object_type(self, name, info, ifcond, role, members): 1095 if not members: 1096 return None 1097 # See also QAPISchemaObjectTypeMember.describe() 1098 name = 'q_obj_%s-%s' % (name, role) 1099 typ = self.lookup_entity(name, QAPISchemaObjectType) 1100 if typ: 1101 # The implicit object type has multiple users. This can 1102 # only be a duplicate definition, which will be flagged 1103 # later. 1104 pass 1105 else: 1106 self._def_definition(QAPISchemaObjectType( 1107 name, info, None, ifcond, None, None, members, None)) 1108 return name 1109 1110 def _def_enum_type(self, expr: QAPIExpression): 1111 name = expr['enum'] 1112 data = expr['data'] 1113 prefix = expr.get('prefix') 1114 ifcond = QAPISchemaIfCond(expr.get('if')) 1115 info = expr.info 1116 features = self._make_features(expr.get('features'), info) 1117 self._def_definition(QAPISchemaEnumType( 1118 name, info, expr.doc, ifcond, features, 1119 self._make_enum_members(data, info), prefix)) 1120 1121 def _make_member(self, name, typ, ifcond, features, info): 1122 optional = False 1123 if name.startswith('*'): 1124 name = name[1:] 1125 optional = True 1126 if isinstance(typ, list): 1127 assert len(typ) == 1 1128 typ = self._make_array_type(typ[0], info) 1129 return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond, 1130 self._make_features(features, info)) 1131 1132 def _make_members(self, data, info): 1133 return [self._make_member(key, value['type'], 1134 QAPISchemaIfCond(value.get('if')), 1135 value.get('features'), info) 1136 for (key, value) in data.items()] 1137 1138 def _def_struct_type(self, expr: QAPIExpression): 1139 name = expr['struct'] 1140 base = expr.get('base') 1141 data = expr['data'] 1142 info = expr.info 1143 ifcond = QAPISchemaIfCond(expr.get('if')) 1144 features = self._make_features(expr.get('features'), info) 1145 self._def_definition(QAPISchemaObjectType( 1146 name, info, expr.doc, ifcond, features, base, 1147 self._make_members(data, info), 1148 None)) 1149 1150 def _make_variant(self, case, typ, ifcond, info): 1151 if isinstance(typ, list): 1152 assert len(typ) == 1 1153 typ = self._make_array_type(typ[0], info) 1154 return QAPISchemaVariant(case, info, typ, ifcond) 1155 1156 def _def_union_type(self, expr: QAPIExpression): 1157 name = expr['union'] 1158 base = expr['base'] 1159 tag_name = expr['discriminator'] 1160 data = expr['data'] 1161 assert isinstance(data, dict) 1162 info = expr.info 1163 ifcond = QAPISchemaIfCond(expr.get('if')) 1164 features = self._make_features(expr.get('features'), info) 1165 if isinstance(base, dict): 1166 base = self._make_implicit_object_type( 1167 name, info, ifcond, 1168 'base', self._make_members(base, info)) 1169 variants = [ 1170 self._make_variant(key, value['type'], 1171 QAPISchemaIfCond(value.get('if')), 1172 info) 1173 for (key, value) in data.items()] 1174 members: List[QAPISchemaObjectTypeMember] = [] 1175 self._def_definition( 1176 QAPISchemaObjectType(name, info, expr.doc, ifcond, features, 1177 base, members, 1178 QAPISchemaVariants( 1179 tag_name, info, None, variants))) 1180 1181 def _def_alternate_type(self, expr: QAPIExpression): 1182 name = expr['alternate'] 1183 data = expr['data'] 1184 assert isinstance(data, dict) 1185 ifcond = QAPISchemaIfCond(expr.get('if')) 1186 info = expr.info 1187 features = self._make_features(expr.get('features'), info) 1188 variants = [ 1189 self._make_variant(key, value['type'], 1190 QAPISchemaIfCond(value.get('if')), 1191 info) 1192 for (key, value) in data.items()] 1193 tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False) 1194 self._def_definition( 1195 QAPISchemaAlternateType( 1196 name, info, expr.doc, ifcond, features, 1197 QAPISchemaVariants(None, info, tag_member, variants))) 1198 1199 def _def_command(self, expr: QAPIExpression): 1200 name = expr['command'] 1201 data = expr.get('data') 1202 rets = expr.get('returns') 1203 gen = expr.get('gen', True) 1204 success_response = expr.get('success-response', True) 1205 boxed = expr.get('boxed', False) 1206 allow_oob = expr.get('allow-oob', False) 1207 allow_preconfig = expr.get('allow-preconfig', False) 1208 coroutine = expr.get('coroutine', False) 1209 ifcond = QAPISchemaIfCond(expr.get('if')) 1210 info = expr.info 1211 features = self._make_features(expr.get('features'), info) 1212 if isinstance(data, OrderedDict): 1213 data = self._make_implicit_object_type( 1214 name, info, ifcond, 1215 'arg', self._make_members(data, info)) 1216 if isinstance(rets, list): 1217 assert len(rets) == 1 1218 rets = self._make_array_type(rets[0], info) 1219 self._def_definition( 1220 QAPISchemaCommand(name, info, expr.doc, ifcond, features, data, 1221 rets, gen, success_response, boxed, allow_oob, 1222 allow_preconfig, coroutine)) 1223 1224 def _def_event(self, expr: QAPIExpression): 1225 name = expr['event'] 1226 data = expr.get('data') 1227 boxed = expr.get('boxed', False) 1228 ifcond = QAPISchemaIfCond(expr.get('if')) 1229 info = expr.info 1230 features = self._make_features(expr.get('features'), info) 1231 if isinstance(data, OrderedDict): 1232 data = self._make_implicit_object_type( 1233 name, info, ifcond, 1234 'arg', self._make_members(data, info)) 1235 self._def_definition(QAPISchemaEvent(name, info, expr.doc, ifcond, 1236 features, data, boxed)) 1237 1238 def _def_exprs(self, exprs): 1239 for expr in exprs: 1240 if 'enum' in expr: 1241 self._def_enum_type(expr) 1242 elif 'struct' in expr: 1243 self._def_struct_type(expr) 1244 elif 'union' in expr: 1245 self._def_union_type(expr) 1246 elif 'alternate' in expr: 1247 self._def_alternate_type(expr) 1248 elif 'command' in expr: 1249 self._def_command(expr) 1250 elif 'event' in expr: 1251 self._def_event(expr) 1252 elif 'include' in expr: 1253 self._def_include(expr) 1254 else: 1255 assert False 1256 1257 def check(self): 1258 for ent in self._entity_list: 1259 ent.check(self) 1260 ent.connect_doc() 1261 for ent in self._entity_list: 1262 ent.set_module(self) 1263 for doc in self.docs: 1264 doc.check() 1265 1266 def visit(self, visitor): 1267 visitor.visit_begin(self) 1268 for mod in self._module_dict.values(): 1269 mod.visit(visitor) 1270 visitor.visit_end() 1271