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