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