def check(self, schema): QAPISchemaType.check(self, schema) self.variants.tag_member.check(schema) # Not calling self.variants.check_clash(), because there's nothing # to clash with self.variants.check(schema, {}) # Alternate branch names have no relation to the tag enum values; # so we have to check for potential name collisions ourselves. seen = {} types_seen = {} for v in self.variants.variants: v.check_clash(self.info, seen) qtype = v.type.alternate_qtype() if not qtype: raise QAPISemError( self.info, "%s cannot use %s" % (v.describe(self.info), v.type.describe())) conflicting = set([qtype]) if qtype == 'QTYPE_QSTRING': if isinstance(v.type, QAPISchemaEnumType): for m in v.type.members: if m.name in ['on', 'off']: conflicting.add('QTYPE_QBOOL') if re.match(r'[-+0-9.]', m.name): # lazy, could be tightened conflicting.add('QTYPE_QNUM') else: conflicting.add('QTYPE_QNUM') conflicting.add('QTYPE_QBOOL') for qt in conflicting: if qt in types_seen: raise QAPISemError( self.info, "%s can't be distinguished from '%s'" % (v.describe(self.info), types_seen[qt])) types_seen[qt] = v.name
def check(self, schema): QAPISchemaEntity.check(self, schema) if self._arg_type_name: self.arg_type = schema.resolve_type(self._arg_type_name, self.info, "command's 'data'") if not isinstance(self.arg_type, QAPISchemaObjectType): raise QAPISemError( self.info, "command's 'data' cannot take %s" % self.arg_type.describe()) if self.arg_type.variants and not self.boxed: raise QAPISemError( self.info, "command's 'data' can take %s only with 'boxed': true" % self.arg_type.describe()) if self._ret_type_name: self.ret_type = schema.resolve_type(self._ret_type_name, self.info, "command's 'returns'") if self.name not in self.info.pragma.returns_whitelist: if not (isinstance(self.ret_type, QAPISchemaObjectType) or (isinstance(self.ret_type, QAPISchemaArrayType) and isinstance(self.ret_type.element_type, QAPISchemaObjectType))): raise QAPISemError( self.info, "command's 'returns' cannot take %s" % self.ret_type.describe()) # Features are in a name space separate from members seen = {} for f in self.features: f.check_clash(self.info, seen)
def check(self, schema): super().check(schema) if self._arg_type_name: self.arg_type = schema.resolve_type( self._arg_type_name, self.info, "command's 'data'") if not isinstance(self.arg_type, QAPISchemaObjectType): raise QAPISemError( self.info, "command's 'data' cannot take %s" % self.arg_type.describe()) if self.arg_type.variants and not self.boxed: raise QAPISemError( self.info, "command's 'data' can take %s only with 'boxed': true" % self.arg_type.describe()) if self._ret_type_name: self.ret_type = schema.resolve_type( self._ret_type_name, self.info, "command's 'returns'") if self.name not in self.info.pragma.returns_whitelist: typ = self.ret_type if isinstance(typ, QAPISchemaArrayType): typ = self.ret_type.element_type assert typ if not isinstance(typ, QAPISchemaObjectType): raise QAPISemError( self.info, "command's 'returns' cannot take %s" % self.ret_type.describe())
def check_flags(expr, info): for key in ['gen', 'success-response']: if key in expr and expr[key] is not False: raise QAPISemError(info, "flag '%s' may only use false value" % key) for key in ['boxed', 'allow-oob', 'allow-preconfig']: if key in expr and expr[key] is not True: raise QAPISemError(info, "flag '%s' may only use true value" % key)
def check_if_str(ifcond, info): if not isinstance(ifcond, str): raise QAPISemError( info, "'if' condition of %s must be a string or a list of strings" % source) if ifcond.strip() == '': raise QAPISemError( info, "'if' condition '%s' of %s makes no sense" % (ifcond, source))
def check(self, schema): QAPISchemaEntity.check(self, schema) if self._arg_type_name: self.arg_type = schema.resolve_type(self._arg_type_name, self.info, "event's 'data'") if not isinstance(self.arg_type, QAPISchemaObjectType): raise QAPISemError( self.info, "event's 'data' cannot take %s" % self.arg_type.describe()) if self.arg_type.variants and not self.boxed: raise QAPISemError( self.info, "event's 'data' can take %s only with 'boxed': true" % self.arg_type.describe())
def check_clash(self, info, seen): cname = c_name(self.name) if cname in seen: raise QAPISemError( info, "%s collides with %s" % (self.describe(info), seen[cname].describe(info))) seen[cname] = self
def connect_feature(self, feature): if feature.name not in self.features: raise QAPISemError( feature.info, "feature '%s' lacks documentation" % feature.name) self.features[feature.name] = QAPIDoc.ArgSection(feature.name) self.features[feature.name].connect(feature)
def check_if(expr, info, source): def check_if_str(ifcond, info): if not isinstance(ifcond, str): raise QAPISemError( info, "'if' condition of %s must be a string or a list of strings" % source) if ifcond.strip() == '': raise QAPISemError( info, "'if' condition '%s' of %s makes no sense" % (ifcond, source)) ifcond = expr.get('if') if ifcond is None: return if isinstance(ifcond, list): if ifcond == []: raise QAPISemError( info, "'if' condition [] of %s is useless" % source) for elt in ifcond: check_if_str(elt, info) else: check_if_str(ifcond, info) expr['if'] = [ifcond]
def check_flags(expr, info): for key in ['gen', 'success-response']: if key in expr and expr[key] is not False: raise QAPISemError( info, "flag '%s' may only use false value" % key) for key in ['boxed', 'allow-oob', 'allow-preconfig', 'coroutine']: if key in expr and expr[key] is not True: raise QAPISemError( info, "flag '%s' may only use true value" % key) if 'allow-oob' in expr and 'coroutine' in expr: # This is not necessarily a fundamental incompatibility, but # we don't have a use case and the desired semantics isn't # obvious. The simplest solution is to forbid it until we get # a use case for it. raise QAPISemError(info, "flags 'allow-oob' and 'coroutine' " "are incompatible")
def check_event(expr, info): args = expr.get('data') boxed = expr.get('boxed', False) if boxed and args is None: raise QAPISemError(info, "'boxed': true requires 'data'") check_type(args, info, "'data'", allow_dict=not boxed)
def check_keys(value, info, source, required, optional): def pprint(elems): return ', '.join("'" + e + "'" for e in sorted(elems)) missing = set(required) - set(value) if missing: raise QAPISemError( info, "%s misses key%s %s" % (source, 's' if len(missing) > 1 else '', pprint(missing))) allowed = set(required + optional) unknown = set(value) - allowed if unknown: raise QAPISemError( info, "%s has unknown key%s %s\nValid keys are %s." % (source, 's' if len(unknown) > 1 else '', pprint(unknown), pprint(allowed)))
def check(self): bogus = [ name for name, section in self.args.items() if not section.member ] if bogus: raise QAPISemError( self.info, "the following documented members are not in " "the declaration: %s" % ", ".join(bogus))
def check(self, schema): # This calls another type T's .check() exactly when the C # struct emitted by gen_object() contains that T's C struct # (pointers don't count). if self.members is not None: # A previous .check() completed: nothing to do return if self._checked: # Recursed: C struct contains itself raise QAPISemError(self.info, "object %s contains itself" % self.name) QAPISchemaType.check(self, schema) assert self._checked and self.members is None seen = OrderedDict() if self._base_name: self.base = schema.resolve_type(self._base_name, self.info, "'base'") if (not isinstance(self.base, QAPISchemaObjectType) or self.base.variants): raise QAPISemError( self.info, "'base' requires a struct type, %s isn't" % self.base.describe()) self.base.check(schema) self.base.check_clash(self.info, seen) for m in self.local_members: m.check(schema) m.check_clash(self.info, seen) if self.doc: self.doc.connect_member(m) members = seen.values() if self.variants: self.variants.check(schema, seen) self.variants.check_clash(self.info, seen) # Features are in a name space separate from members seen = {} for f in self.features: f.check_clash(self.info, seen) if self.doc: self.doc.check() self.members = members # mark completed
def resolve_type(self, name, info, what): typ = self.lookup_type(name) if not typ: if callable(what): what = what(info) raise QAPISemError( info, "%s uses unknown type '%s'" % (what, name)) return typ
def check(self, schema, seen): if not self.tag_member: # flat union self.tag_member = seen.get(c_name(self._tag_name)) base = "'base'" # Pointing to the base type when not implicit would be # nice, but we don't know it here if not self.tag_member or self._tag_name != self.tag_member.name: raise QAPISemError( self.info, "discriminator '%s' is not a member of %s" % (self._tag_name, base)) # Here we do: base_type = schema.lookup_type(self.tag_member.defined_in) assert base_type if not base_type.is_implicit(): base = "base type '%s'" % self.tag_member.defined_in if not isinstance(self.tag_member.type, QAPISchemaEnumType): raise QAPISemError( self.info, "discriminator member '%s' of %s must be of enum type" % (self._tag_name, base)) if self.tag_member.optional: raise QAPISemError( self.info, "discriminator member '%s' of %s must not be optional" % (self._tag_name, base)) if self.tag_member.ifcond: raise QAPISemError( self.info, "discriminator member '%s' of %s must not be conditional" % (self._tag_name, base)) else: # simple union assert isinstance(self.tag_member.type, QAPISchemaEnumType) assert not self.tag_member.optional assert self.tag_member.ifcond == [] if self._tag_name: # flat union # branches that are not explicitly covered get an empty type cases = set([v.name for v in self.variants]) for m in self.tag_member.type.members: if m.name not in cases: v = QAPISchemaObjectTypeVariant(m.name, self.info, 'q_empty', m.ifcond) v.set_defined_in(self.tag_member.defined_in) self.variants.append(v) if not self.variants: raise QAPISemError(self.info, "union has no branches") for v in self.variants: v.check(schema) # Union names must match enum values; alternate names are # checked separately. Use 'seen' to tell the two apart. if seen: if v.name not in self.tag_member.type.member_names(): raise QAPISemError( self.info, "branch '%s' is not a value of %s" % (v.name, self.tag_member.type.describe())) if (not isinstance(v.type, QAPISchemaObjectType) or v.type.variants): raise QAPISemError( self.info, "%s cannot use %s" % (v.describe(self.info), v.type.describe())) v.type.check(schema)
def check_args_section(args, info, what): bogus = [ name for name, section in args.items() if not section.member ] if bogus: raise QAPISemError( self.info, "documented member%s '%s' %s not exist" % ("s" if len(bogus) > 1 else "", "', '".join(bogus), "do" if len(bogus) > 1 else "does"))
def _def_entity(self, ent): # Only the predefined types are allowed to not have info assert ent.info or self._predefining self._entity_list.append(ent) if ent.name is None: return # TODO reject names that differ only in '_' vs. '.' vs. '-', # because they're liable to clash in generated C. other_ent = self._entity_dict.get(ent.name) if other_ent: if other_ent.info: where = QAPIError(other_ent.info, None, "previous definition") raise QAPISemError( ent.info, "'%s' is already defined\n%s" % (ent.name, where)) raise QAPISemError( ent.info, "%s is already defined" % other_ent.describe()) self._entity_dict[ent.name] = ent
def check_command(expr, info): args = expr.get('data') rets = expr.get('returns') boxed = expr.get('boxed', False) if boxed and args is None: raise QAPISemError(info, "'boxed': true requires 'data'") check_type(args, info, "'data'", allow_dict=not boxed) check_type(rets, info, "'returns'", allow_array=True)
def check_type(value, info, source, allow_array=False, allow_dict=False): if value is None: return # Array type if isinstance(value, list): if not allow_array: raise QAPISemError(info, "%s cannot be an array" % source) if len(value) != 1 or not isinstance(value[0], str): raise QAPISemError( info, "%s: array type must contain single type name" % source) return # Type name if isinstance(value, str): return # Anonymous type if not allow_dict: raise QAPISemError(info, "%s should be a type name" % source) if not isinstance(value, OrderedDict): raise QAPISemError(info, "%s should be an object or type name" % source) permit_upper = allow_dict in info.pragma.name_case_whitelist # value is a dictionary, check that each member is okay for (key, arg) in value.items(): key_source = "%s member '%s'" % (source, key) check_name_str(key, info, key_source, allow_optional=True, permit_upper=permit_upper) if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'): raise QAPISemError(info, "%s uses reserved name" % key_source) check_keys(arg, info, key_source, ['type'], ['if', 'features']) check_if(arg, info, key_source) check_features(arg.get('features'), info) check_type(arg['type'], info, key_source, allow_array=True)
def check_alternate(expr, info): members = expr['data'] if not members: raise QAPISemError(info, "'data' must not be empty") for (key, value) in members.items(): source = "'data' member '%s'" % key check_name_str(key, info, source) check_keys(value, info, source, ['type'], ['if']) check_if(value, info, source) check_type(value['type'], info, source)
def check_name_str(name, info, source, allow_optional=False, enum_member=False, permit_upper=False): membername = name if allow_optional and name.startswith('*'): membername = name[1:] # Enum members can start with a digit, because the generated C # code always prefixes it with the enum name if enum_member and membername[0].isdigit(): membername = 'D' + membername # Reserve the entire 'q_' namespace for c_name(), and for 'q_empty' # and 'q_obj_*' implicit type names. if not valid_name.match(membername) or \ c_name(membername, False).startswith('q_'): raise QAPISemError(info, "%s has an invalid name" % source) if not permit_upper and name.lower() != name: raise QAPISemError( info, "%s uses uppercase in name" % source) assert not membername.startswith('*')
def check_union(expr, info): name = expr['union'] base = expr.get('base') discriminator = expr.get('discriminator') members = expr['data'] if discriminator is None: # simple union if base is not None: raise QAPISemError(info, "'base' requires 'discriminator'") else: # flat union check_type(base, info, "'base'", allow_dict=name) if not base: raise QAPISemError(info, "'discriminator' requires 'base'") check_name_is_str(discriminator, info, "'discriminator'") for (key, value) in members.items(): source = "'data' member '%s'" % key check_name_str(key, info, source) check_keys(value, info, source, ['type'], ['if']) check_if(value, info, source) check_type(value['type'], info, source, allow_array=not base)
def _pragma(self, name, value, info): if name == 'doc-required': if not isinstance(value, bool): raise QAPISemError(info, "pragma 'doc-required' must be boolean") info.pragma.doc_required = value elif name == 'returns-whitelist': if (not isinstance(value, list) or any([not isinstance(elt, str) for elt in value])): raise QAPISemError( info, "pragma returns-whitelist must be a list of strings") info.pragma.returns_whitelist = value elif name == 'name-case-whitelist': if (not isinstance(value, list) or any([not isinstance(elt, str) for elt in value])): raise QAPISemError( info, "pragma name-case-whitelist must be a list of strings") info.pragma.name_case_whitelist = value else: raise QAPISemError(info, "unknown pragma '%s'" % name)
def check_enum(expr, info): name = expr['enum'] members = expr['data'] prefix = expr.get('prefix') if not isinstance(members, list): raise QAPISemError(info, "'data' must be an array") if prefix is not None and not isinstance(prefix, str): raise QAPISemError(info, "'prefix' must be a string") permit_upper = name in info.pragma.name_case_whitelist members[:] = [m if isinstance(m, dict) else {'name': m} for m in members] for member in members: source = "'data' member" check_keys(member, info, source, ['name'], ['if']) check_name_is_str(member['name'], info, source) source = "%s '%s'" % (source, member['name']) check_name_str(member['name'], info, source, enum_member=True, permit_upper=permit_upper) check_if(member, info, source)
def check_features(features, info): if features is None: return if not isinstance(features, list): raise QAPISemError(info, "'features' must be an array") features[:] = [f if isinstance(f, dict) else {'name': f} for f in features] for f in features: source = "'features' member" assert isinstance(f, dict) check_keys(f, info, source, ['name'], ['if']) check_name_is_str(f['name'], info, source) source = "%s '%s'" % (source, f['name']) check_name_str(f['name'], info, source) check_if(f, info, source)
def _include(self, include, info, incl_fname, previously_included): incl_abs_fname = os.path.abspath(incl_fname) # catch inclusion cycle inf = info while inf: if incl_abs_fname == os.path.abspath(inf.fname): raise QAPISemError(info, "inclusion loop for %s" % include) inf = inf.parent # skip multiple include of the same file if incl_abs_fname in previously_included: return None return QAPISchemaParser(incl_fname, previously_included, info)
def _start_new_heading(self, heading, level): """Start a new heading at the specified heading level Create a new section whose title is 'heading' and which is placed in the docutils node tree as a child of the most recent level-1 heading. Subsequent document sections (commands, freeform doc chunks, etc) will be placed as children of this new heading section. """ if len(self._active_headings) < level: raise QAPISemError( self._cur_doc.info, 'Level %d subheading found outside a ' 'level %d heading' % (level, level - 1)) snode = self._make_section(heading) self._active_headings[level - 1] += snode self._active_headings = self._active_headings[:level] self._active_headings.append(snode)
def check_defn_name_str(name, info, meta): check_name_str(name, info, meta, permit_upper=True) if name.endswith('Kind') or name.endswith('List'): raise QAPISemError( info, "%s name should not end in '%s'" % (meta, name[-4:]))
def check_name_is_str(name, info, source): if not isinstance(name, str): raise QAPISemError(info, "%s requires a string name" % source)