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