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