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