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