qapi: Reserve 'q_*' and 'has_*' member names

c_name() produces names starting with 'q_' when protecting a
dictionary member name that would fail to directly compile, but
in doing so can cause clashes with any member name already
beginning with 'q-' or 'q_'. Likewise, we create a C name 'has_'
for any optional member that can clash with any member name
beginning with 'has-' or 'has_'.

Technically, rather than blindly reserving the namespace,
we could try to complain about user names only when an actual
collision occurs, or even teach c_name() how to munge names
to avoid collisions. But it is not trivial, especially when
collisions can occur across multiple types (such as via
inheritance or flat unions). Besides, no existing .json
files are trying to use these names. So it's easier to just
outright forbid the potential for collision. We can always
relax things in the future if a real need arises for QMP to
express member names that have been forbidden here.

'has_' only has to be reserved for struct/union member names,
while 'q_' is reserved everywhere (matching the fact that
only members can be optional, while we use c_name() for munging
both members and entities). Note that we could relax 'q_'
restrictions on entities independently from member names; for
example, c_name('qmp_' + 'unix') would result in a different
function name than our current 'qmp_' + c_name('unix').

Update and add tests to cover the new error messages.

Backports commit 9fb081e0b98409556d023c7193eeb68947cd1211 from qemu
This commit is contained in:
Eric Blake 2018-02-19 19:09:15 -05:00 committed by Lioncash
parent b9f93dd0e8
commit 1f11f92a80
No known key found for this signature in database
GPG Key ID: 4E3C3CC1031BA9C7

View File

@ -377,7 +377,9 @@ def check_name(expr_info, source, name, allow_optional=False,
# code always prefixes it with the enum name # code always prefixes it with the enum name
if enum_member: if enum_member:
membername = '_' + membername membername = '_' + membername
if not valid_name.match(membername): # Reserve the entire 'q_' namespace for c_name()
if not valid_name.match(membername) or \
c_name(membername, False).startswith('q_'):
raise QAPIExprError(expr_info, raise QAPIExprError(expr_info,
"%s uses invalid name '%s'" % (source, name)) "%s uses invalid name '%s'" % (source, name))
@ -488,6 +490,10 @@ def check_type(expr_info, source, value, allow_array=False,
for (key, arg) in value.items(): for (key, arg) in value.items():
check_name(expr_info, "Member of %s" % source, key, check_name(expr_info, "Member of %s" % source, key,
allow_optional=allow_optional) allow_optional=allow_optional)
if c_name(key, False).startswith('has_'):
raise QAPIExprError(expr_info,
"Member of %s uses reserved name '%s'"
% (source, key))
# Todo: allow dictionaries to represent default values of # Todo: allow dictionaries to represent default values of
# an optional argument. # an optional argument.
check_type(expr_info, "Member '%s' of %s" % (key, source), arg, check_type(expr_info, "Member '%s' of %s" % (key, source), arg,