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