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