diff mbox

[v7,10/15] qapi-event: Reduce chance of collision with event data

Message ID 1463784024-17242-11-git-send-email-eblake@redhat.com
State New
Headers show

Commit Message

Eric Blake May 20, 2016, 10:40 p.m. UTC
When an event has data that is not boxed, we are exposing all of
its members alongside our local variables.  So far, we haven't
hit a collision, but it may be a matter of time before someone
wants to name a QMP data element 'err' or similar.  We can separate
the names by making the public function a shell that creates a
simple wrapper, then calls a worker that operates on only the
boxed version and thus has no user-supplied names to worry about
in naming its local variables.  For boxed events, we don't need
the wrapper.

There is still a chance for collision with 'errp' (if that happens,
tweak c_name() to rename a QMP member 'errp' into the C member
'q_errp'), and with 'param' (if that happens, tweak gen_event_send()
and gen_param_var() to name the temporary variable 'q_param').  But
with the division done here, the real worker function no longer has
to worry about collisions.

The generated file changes as follows:

|-void qapi_event_send_migration(MigrationStatus status, Error **errp)
|+static void do_qapi_event_send_migration(q_obj_MIGRATION_arg *arg, Error **errp)
| {
|     QDict *qmp;
|     Error *err = NULL;
|     QMPEventFuncEmit emit;
|     QObject *obj;
|     Visitor *v;
|-    q_obj_MIGRATION_arg param = {
|-        status
|-    };
|
|     emit = qmp_event_get_func_emit();
|     if (!emit) {
...
|     if (err) {
|         goto out;
|     }
|-    visit_type_q_obj_MIGRATION_arg_members(v, &param, &err);
|+    visit_type_q_obj_MIGRATION_arg_members(v, arg, &err);
|     if (!err) {
|         visit_check_struct(v, &err);
...
|+void qapi_event_send_migration(MigrationStatus status, Error **errp)
|+{
|+    q_obj_MIGRATION_arg param = {
|+        status
|+    };
|+    do_qapi_event_send_migration(&param, errp);
|+}
|+

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v7: new patch
---
 scripts/qapi.py       |  1 -
 scripts/qapi-event.py | 47 ++++++++++++++++++++++++++---------------------
 2 files changed, 26 insertions(+), 22 deletions(-)

Comments

Markus Armbruster June 14, 2016, 4:28 p.m. UTC | #1
Eric Blake <eblake@redhat.com> writes:

> When an event has data that is not boxed, we are exposing all of
> its members alongside our local variables.  So far, we haven't
> hit a collision, but it may be a matter of time before someone
> wants to name a QMP data element 'err' or similar.  We can separate
> the names by making the public function a shell that creates a
> simple wrapper, then calls a worker that operates on only the
> boxed version and thus has no user-supplied names to worry about
> in naming its local variables.  For boxed events, we don't need
> the wrapper.
>
> There is still a chance for collision with 'errp' (if that happens,
> tweak c_name() to rename a QMP member 'errp' into the C member
> 'q_errp'), and with 'param' (if that happens, tweak gen_event_send()
> and gen_param_var() to name the temporary variable 'q_param').  But
> with the division done here, the real worker function no longer has
> to worry about collisions.
>
> The generated file changes as follows:
>
> |-void qapi_event_send_migration(MigrationStatus status, Error **errp)
> |+static void do_qapi_event_send_migration(q_obj_MIGRATION_arg *arg, Error **errp)
> | {
> |     QDict *qmp;
> |     Error *err = NULL;
> |     QMPEventFuncEmit emit;
> |     QObject *obj;
> |     Visitor *v;
> |-    q_obj_MIGRATION_arg param = {
> |-        status
> |-    };
> |
> |     emit = qmp_event_get_func_emit();
> |     if (!emit) {
> ...
> |     if (err) {
> |         goto out;
> |     }
> |-    visit_type_q_obj_MIGRATION_arg_members(v, &param, &err);
> |+    visit_type_q_obj_MIGRATION_arg_members(v, arg, &err);
> |     if (!err) {
> |         visit_check_struct(v, &err);
> ...
> |+void qapi_event_send_migration(MigrationStatus status, Error **errp)
> |+{
> |+    q_obj_MIGRATION_arg param = {
> |+        status
> |+    };
> |+    do_qapi_event_send_migration(&param, errp);
> |+}
> |+
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> v7: new patch
> ---
>  scripts/qapi.py       |  1 -
>  scripts/qapi-event.py | 47 ++++++++++++++++++++++++++---------------------
>  2 files changed, 26 insertions(+), 22 deletions(-)
>
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index 6742e7a..7d568d9 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
> @@ -1016,7 +1016,6 @@ class QAPISchemaObjectType(QAPISchemaType):
>          return QAPISchemaType.c_name(self)
>
>      def c_type(self):
> -        assert not self.is_implicit()

Huh?

>          return c_name(self.name) + pointer_suffix
>
>      def c_unboxed_type(self):
> diff --git a/scripts/qapi-event.py b/scripts/qapi-event.py
> index b8ca8c8..fe4e50d 100644
> --- a/scripts/qapi-event.py
> +++ b/scripts/qapi-event.py
> @@ -14,8 +14,13 @@
>  from qapi import *
>
>
> -def gen_event_send_proto(name, arg_type, box):
> -    return 'void qapi_event_send_%(c_name)s(%(param)s)' % {
> +def gen_event_send_proto(name, arg_type, box, impl=False):
> +    intro='void '
> +    if impl and arg_type and not box:
> +        box = True
> +        intro='static void do_'
> +    return '%(intro)sqapi_event_send_%(c_name)s(%(param)s)' % {
> +        'intro': intro,
>          'c_name': c_name(name.lower()),
>          'param': gen_params(arg_type, box, 'Error **errp')}
>

I'd call it prefix rather than intro.

> @@ -53,12 +58,8 @@ def gen_param_var(typ):
>
>
>  def gen_event_send(name, arg_type, box):
> -    # FIXME: Our declaration of local variables (and of 'errp' in the
> -    # parameter list) can collide with exploded members of the event's
> -    # data type passed in as parameters.  If this collision ever hits in
> -    # practice, we can rename our local variables with a leading _ prefix,
> -    # or split the code into a wrapper function that creates a boxed
> -    # 'param' object then calls another to do the real work.
> +    if not arg_type or arg_type.is_empty():
> +        box = False
>      ret = mcgen('''
>
>  %(proto)s
> @@ -67,15 +68,13 @@ def gen_event_send(name, arg_type, box):
>      Error *err = NULL;
>      QMPEventFuncEmit emit;
>  ''',
> -                proto=gen_event_send_proto(name, arg_type, box))
> +                proto=gen_event_send_proto(name, arg_type, box, True))
>
>      if arg_type and not arg_type.is_empty():
>          ret += mcgen('''
>      QObject *obj;
>      Visitor *v;
>  ''')
> -        if not box:
> -            ret += gen_param_var(arg_type)

Because the not box case moves to the wrapper function (last hunk).

>
>      ret += mcgen('''
>
> @@ -93,20 +92,11 @@ def gen_event_send(name, arg_type, box):
>          ret += mcgen('''
>      v = qmp_output_visitor_new(&obj);
>
> -''')
> -
> -        if box:
> -            ret += mcgen('''
> -    visit_type_%(c_name)s(v, NULL, &arg, &err);
> -''',
> -                         c_name=arg_type.c_name(), name=arg_type.name)
> -        else:
> -            ret += mcgen('''
>      visit_start_struct(v, "%(name)s", NULL, 0, &err);
>      if (err) {
>          goto out;
>      }
> -    visit_type_%(c_name)s_members(v, &param, &err);
> +    visit_type_%(c_name)s_members(v, arg, &err);
>      if (!err) {
>          visit_check_struct(v, &err);
>      }

Getting confused...  why are we getting rid of the box case here?

Too many conditionals...  gen_event_send() has three cases: empty
arg_type, non-empty arg_type and box, non-empty arg_type and not box.
The commit message shows the change to generated code for the second
case.  It doesn't show visit_type_%(c_name)s(v, NULL, &arg, &err) going
away.

> @@ -136,6 +126,21 @@ out:
>      QDECREF(qmp);
>  }
>  ''')
> +
> +    if arg_type and not box:
> +        ret += mcgen('''
> +
> +%(proto)s
> +{
> +''',
> +                     proto=gen_event_send_proto(name, arg_type, box))
> +        ret += gen_param_var(arg_type)
> +        ret += mcgen('''
> +    do_qapi_event_send_%(c_name)s(&param, errp);
> +}
> +''',
> +                     c_name=c_name(name.lower()))
> +
>      return ret
Markus Armbruster June 16, 2016, 12:25 p.m. UTC | #2
Markus Armbruster <armbru@redhat.com> writes:

> Eric Blake <eblake@redhat.com> writes:
>
>> When an event has data that is not boxed, we are exposing all of
>> its members alongside our local variables.  So far, we haven't
>> hit a collision, but it may be a matter of time before someone
>> wants to name a QMP data element 'err' or similar.  We can separate
>> the names by making the public function a shell that creates a
>> simple wrapper, then calls a worker that operates on only the
>> boxed version and thus has no user-supplied names to worry about
>> in naming its local variables.  For boxed events, we don't need
>> the wrapper.
>>
>> There is still a chance for collision with 'errp' (if that happens,
>> tweak c_name() to rename a QMP member 'errp' into the C member
>> 'q_errp'), and with 'param' (if that happens, tweak gen_event_send()
>> and gen_param_var() to name the temporary variable 'q_param').  But
>> with the division done here, the real worker function no longer has
>> to worry about collisions.
>>
>> The generated file changes as follows:
>>
>> |-void qapi_event_send_migration(MigrationStatus status, Error **errp)
>> |+static void do_qapi_event_send_migration(q_obj_MIGRATION_arg *arg, Error **errp)
>> | {
>> |     QDict *qmp;
>> |     Error *err = NULL;
>> |     QMPEventFuncEmit emit;
>> |     QObject *obj;
>> |     Visitor *v;
>> |-    q_obj_MIGRATION_arg param = {
>> |-        status
>> |-    };
>> |
>> |     emit = qmp_event_get_func_emit();
>> |     if (!emit) {
>> ...
>> |     if (err) {
>> |         goto out;
>> |     }
>> |-    visit_type_q_obj_MIGRATION_arg_members(v, &param, &err);
>> |+    visit_type_q_obj_MIGRATION_arg_members(v, arg, &err);
>> |     if (!err) {
>> |         visit_check_struct(v, &err);
>> ...
>> |+void qapi_event_send_migration(MigrationStatus status, Error **errp)
>> |+{
>> |+    q_obj_MIGRATION_arg param = {
>> |+        status
>> |+    };
>> |+    do_qapi_event_send_migration(&param, errp);
>> |+}
>> |+
>>
>> Signed-off-by: Eric Blake <eblake@redhat.com>
>>
>> ---
>> v7: new patch
>> ---
>>  scripts/qapi.py       |  1 -
>>  scripts/qapi-event.py | 47 ++++++++++++++++++++++++++---------------------
>>  2 files changed, 26 insertions(+), 22 deletions(-)
>>
>> diff --git a/scripts/qapi.py b/scripts/qapi.py
>> index 6742e7a..7d568d9 100644
>> --- a/scripts/qapi.py
>> +++ b/scripts/qapi.py
>> @@ -1016,7 +1016,6 @@ class QAPISchemaObjectType(QAPISchemaType):
>>          return QAPISchemaType.c_name(self)
>>
>>      def c_type(self):
>> -        assert not self.is_implicit()
>
> Huh?
>
>>          return c_name(self.name) + pointer_suffix
>>
>>      def c_unboxed_type(self):
>> diff --git a/scripts/qapi-event.py b/scripts/qapi-event.py
>> index b8ca8c8..fe4e50d 100644
>> --- a/scripts/qapi-event.py
>> +++ b/scripts/qapi-event.py
>> @@ -14,8 +14,13 @@
>>  from qapi import *
>>
>>
>> -def gen_event_send_proto(name, arg_type, box):
>> -    return 'void qapi_event_send_%(c_name)s(%(param)s)' % {
>> +def gen_event_send_proto(name, arg_type, box, impl=False):
>> +    intro='void '
>> +    if impl and arg_type and not box:
>> +        box = True
>> +        intro='static void do_'
>> +    return '%(intro)sqapi_event_send_%(c_name)s(%(param)s)' % {
>> +        'intro': intro,
>>          'c_name': c_name(name.lower()),
>>          'param': gen_params(arg_type, box, 'Error **errp')}
>>
>
> I'd call it prefix rather than intro.
>
>> @@ -53,12 +58,8 @@ def gen_param_var(typ):
>>
>>
>>  def gen_event_send(name, arg_type, box):
>> -    # FIXME: Our declaration of local variables (and of 'errp' in the
>> -    # parameter list) can collide with exploded members of the event's
>> -    # data type passed in as parameters.  If this collision ever hits in
>> -    # practice, we can rename our local variables with a leading _ prefix,
>> -    # or split the code into a wrapper function that creates a boxed
>> -    # 'param' object then calls another to do the real work.
>> +    if not arg_type or arg_type.is_empty():
>> +        box = False
>>      ret = mcgen('''
>>
>>  %(proto)s
>> @@ -67,15 +68,13 @@ def gen_event_send(name, arg_type, box):
>>      Error *err = NULL;
>>      QMPEventFuncEmit emit;
>>  ''',
>> -                proto=gen_event_send_proto(name, arg_type, box))
>> +                proto=gen_event_send_proto(name, arg_type, box, True))
>>
>>      if arg_type and not arg_type.is_empty():
>>          ret += mcgen('''
>>      QObject *obj;
>>      Visitor *v;
>>  ''')
>> -        if not box:
>> -            ret += gen_param_var(arg_type)
>
> Because the not box case moves to the wrapper function (last hunk).
>
>>
>>      ret += mcgen('''
>>
>> @@ -93,20 +92,11 @@ def gen_event_send(name, arg_type, box):
>>          ret += mcgen('''
>>      v = qmp_output_visitor_new(&obj);
>>
>> -''')
>> -
>> -        if box:
>> -            ret += mcgen('''
>> -    visit_type_%(c_name)s(v, NULL, &arg, &err);
>> -''',
>> -                         c_name=arg_type.c_name(), name=arg_type.name)
>> -        else:
>> -            ret += mcgen('''
>>      visit_start_struct(v, "%(name)s", NULL, 0, &err);
>>      if (err) {
>>          goto out;
>>      }
>> -    visit_type_%(c_name)s_members(v, &param, &err);
>> +    visit_type_%(c_name)s_members(v, arg, &err);
>>      if (!err) {
>>          visit_check_struct(v, &err);
>>      }
>
> Getting confused...  why are we getting rid of the box case here?
>
> Too many conditionals...  gen_event_send() has three cases: empty
> arg_type, non-empty arg_type and box, non-empty arg_type and not box.
> The commit message shows the change to generated code for the second
> case.  It doesn't show visit_type_%(c_name)s(v, NULL, &arg, &err) going
> away.

Case empty arg_type: no change
Example: POWERDOWN

Case non-empty arg_type and box: visit gets open-coded
Example: EVENT_E

     void qapi_event_send_event_e(UserDefZero *arg, Error **errp)
     {
         QDict *qmp;
         Error *err = NULL;
         QMPEventFuncEmit emit;
         QObject *obj;
         Visitor *v;

         emit = qmp_event_get_func_emit();
         if (!emit) {
             return;
         }

         qmp = qmp_event_build_dict("EVENT_E");

         v = qmp_output_visitor_new(&obj);

    -    visit_type_UserDefZero(v, NULL, &arg, &err);
    +    visit_start_struct(v, "EVENT_E", NULL, 0, &err);
    +    if (err) {
    +        goto out;
    +    }
    +    visit_type_UserDefZero_members(v, arg, &err);
    +    if (!err) {
    +        visit_check_struct(v, &err);
    +    }
    +    visit_end_struct(v, NULL);
         if (err) {
             goto out;
         }

         visit_complete(v, &obj);
         qdict_put_obj(qmp, "data", obj);
         emit(TEST_QAPI_EVENT_EVENT_E, qmp, &err);

     out:
         visit_free(v);
         error_propagate(errp, err);
         QDECREF(qmp);
     }

Compare:

     void visit_type_UserDefZero(Visitor *v, const char *name, UserDefZero **obj, Error **errp)
     {
         Error *err = NULL;

         visit_start_struct(v, name, (void **)obj, sizeof(UserDefZero), &err);
         if (err) {
             goto out;
         }
--->     if (!*obj) {
--->         goto out_obj;
--->     }
         visit_type_UserDefZero_members(v, *obj, &err);
--->     if (err) {
--->         goto out_obj;
--->     }
--->     visit_check_struct(v, &err);
     out_obj:
         visit_end_struct(v, (void **)obj);
--->     if (err && visit_is_input(v)) {
--->         qapi_free_UserDefZero(*obj);
--->         *obj = NULL;
--->     }
     out:
         error_propagate(errp, err);
     }

The open-coded visit drops the !*obj check (okay, @arg isn't going
anywhere), skips the visit_check_struct() differently, and drops the
qapi_free_FOO() (okay, condition is always false here).

So this isn't wrong.  But why open-code?

Case non-empty arg_type and not box:
Example: ACPI_DEVICE_OST

    -void qapi_event_send_acpi_device_ost(ACPIOSTInfo *info, Error **errp)
    +static void do_qapi_event_send_acpi_device_ost(q_obj_ACPI_DEVICE_OST_arg *arg, Error **errp)
     {
         QDict *qmp;
         Error *err = NULL;
         QMPEventFuncEmit emit;
         QObject *obj;
         Visitor *v;
    -    q_obj_ACPI_DEVICE_OST_arg param = {
    -        info
    -    };

         emit = qmp_event_get_func_emit();
         if (!emit) {
             return;
         }

         qmp = qmp_event_build_dict("ACPI_DEVICE_OST");

         v = qmp_output_visitor_new(&obj);

         visit_start_struct(v, "ACPI_DEVICE_OST", NULL, 0, &err);
         if (err) {
             goto out;
         }
    -    visit_type_q_obj_ACPI_DEVICE_OST_arg_members(v, &param, &err);
    +    visit_type_q_obj_ACPI_DEVICE_OST_arg_members(v, arg, &err);
         if (!err) {
             visit_check_struct(v, &err);
         }
         visit_end_struct(v, NULL);
         if (err) {
             goto out;
         }

         visit_complete(v, &obj);
         qdict_put_obj(qmp, "data", obj);
         emit(QAPI_EVENT_ACPI_DEVICE_OST, qmp, &err);

     out:
         visit_free(v);
         error_propagate(errp, err);
         QDECREF(qmp);
     }

    -void qapi_event_send_balloon_change(int64_t actual, Error **errp)
    +void qapi_event_send_acpi_device_ost(ACPIOSTInfo *info, Error **errp)
    +{
    +    q_obj_ACPI_DEVICE_OST_arg param = {
    +        info
    +    };
    +    do_qapi_event_send_acpi_device_ost(&param, errp);
    +}
    +

This is the case the commit message advertises.

There is no visit_type_FOO() we could compare too, since FOO is an
implicit type

>> @@ -136,6 +126,21 @@ out:
>>      QDECREF(qmp);
>>  }
>>  ''')
>> +
>> +    if arg_type and not box:
>> +        ret += mcgen('''
>> +
>> +%(proto)s
>> +{
>> +''',
>> +                     proto=gen_event_send_proto(name, arg_type, box))
>> +        ret += gen_param_var(arg_type)
>> +        ret += mcgen('''
>> +    do_qapi_event_send_%(c_name)s(&param, errp);
>> +}
>> +''',
>> +                     c_name=c_name(name.lower()))
>> +
>>      return ret
Eric Blake June 28, 2016, 3:20 a.m. UTC | #3
On 06/16/2016 06:25 AM, Markus Armbruster wrote:
> Markus Armbruster <armbru@redhat.com> writes:
> 
>> Eric Blake <eblake@redhat.com> writes:
>>
>>> When an event has data that is not boxed, we are exposing all of
>>> its members alongside our local variables.  So far, we haven't
>>> hit a collision, but it may be a matter of time before someone
>>> wants to name a QMP data element 'err' or similar.  We can separate
>>> the names by making the public function a shell that creates a
>>> simple wrapper, then calls a worker that operates on only the
>>> boxed version and thus has no user-supplied names to worry about
>>> in naming its local variables.  For boxed events, we don't need
>>> the wrapper.
>>>
>>> There is still a chance for collision with 'errp' (if that happens,
>>> tweak c_name() to rename a QMP member 'errp' into the C member
>>> 'q_errp'), and with 'param' (if that happens, tweak gen_event_send()
>>> and gen_param_var() to name the temporary variable 'q_param').  But
>>> with the division done here, the real worker function no longer has
>>> to worry about collisions.
>>>

>>> +++ b/scripts/qapi.py
>>> @@ -1016,7 +1016,6 @@ class QAPISchemaObjectType(QAPISchemaType):
>>>          return QAPISchemaType.c_name(self)
>>>
>>>      def c_type(self):
>>> -        assert not self.is_implicit()
>>
>> Huh?

Required, because we now pass a pointer to an implicit type from
qapi_event_send_FOO() to do_qapi_event_send_FOO(), so the c_type() of
that implicit type is required for generating the C type for that
parameter.  Will document it better in the commit message.


>>> @@ -93,20 +92,11 @@ def gen_event_send(name, arg_type, box):
>>>          ret += mcgen('''
>>>      v = qmp_output_visitor_new(&obj);
>>>
>>> -''')
>>> -
>>> -        if box:
>>> -            ret += mcgen('''
>>> -    visit_type_%(c_name)s(v, NULL, &arg, &err);
>>> -''',
>>> -                         c_name=arg_type.c_name(), name=arg_type.name)
>>> -        else:
>>> -            ret += mcgen('''
>>>      visit_start_struct(v, "%(name)s", NULL, 0, &err);
>>>      if (err) {
>>>          goto out;
>>>      }
>>> -    visit_type_%(c_name)s_members(v, &param, &err);
>>> +    visit_type_%(c_name)s_members(v, arg, &err);
>>>      if (!err) {
>>>          visit_check_struct(v, &err);
>>>      }
>>
>> Getting confused...  why are we getting rid of the box case here?

No good reason. The visit_type_FOO() is more compact than
visit_type_FOO_members(), but only when we have a non-implicit type.  So
for v8, I'm switching the conditional from 'if box:' to 'if
arg_type.is_implicit():', more or less.

>>
>> Too many conditionals...  gen_event_send() has three cases: empty
>> arg_type, non-empty arg_type and box, non-empty arg_type and not box.
>> The commit message shows the change to generated code for the second
>> case.  It doesn't show visit_type_%(c_name)s(v, NULL, &arg, &err) going
>> away.
> 
> Case empty arg_type: no change
> Example: POWERDOWN

Good.

> 
> Case non-empty arg_type and box: visit gets open-coded
> Example: EVENT_E

Fixed in v8 so that it no longer changes.

> The open-coded visit drops the !*obj check (okay, @arg isn't going
> anywhere), skips the visit_check_struct() differently, and drops the
> qapi_free_FOO() (okay, condition is always false here).
> 
> So this isn't wrong.  But why open-code?

No need to add new open-coding, but we already had existing open-coding
for anonymous non-boxed 'data' (in part because commit 7ce106a9
intentionally chose not to create visit_type_FOO() for implicit types).

> 
> Case non-empty arg_type and not box:
> Example: ACPI_DEVICE_OST
> 

> 
> This is the case the commit message advertises.
> 
> There is no visit_type_FOO() we could compare too, since FOO is an
> implicit type

And in reviewing your message, I realize we have NO testsuite coverage of:

{ 'event': 'EVENT', 'data': 'NamedStruct' }

Guess I get to add that first.  Such a usage will then be improved by
using visit_type_NamedStruct() rather than open-coding around
visit_type_NamedStruct_members().
Markus Armbruster June 28, 2016, 8:06 a.m. UTC | #4
Eric Blake <eblake@redhat.com> writes:

> On 06/16/2016 06:25 AM, Markus Armbruster wrote:
>> Markus Armbruster <armbru@redhat.com> writes:
>> 
>>> Eric Blake <eblake@redhat.com> writes:
>>>
>>>> When an event has data that is not boxed, we are exposing all of
>>>> its members alongside our local variables.  So far, we haven't
>>>> hit a collision, but it may be a matter of time before someone
>>>> wants to name a QMP data element 'err' or similar.  We can separate
>>>> the names by making the public function a shell that creates a
>>>> simple wrapper, then calls a worker that operates on only the
>>>> boxed version and thus has no user-supplied names to worry about
>>>> in naming its local variables.  For boxed events, we don't need
>>>> the wrapper.
>>>>
>>>> There is still a chance for collision with 'errp' (if that happens,
>>>> tweak c_name() to rename a QMP member 'errp' into the C member
>>>> 'q_errp'), and with 'param' (if that happens, tweak gen_event_send()
>>>> and gen_param_var() to name the temporary variable 'q_param').  But
>>>> with the division done here, the real worker function no longer has
>>>> to worry about collisions.
>>>>
>
>>>> +++ b/scripts/qapi.py
>>>> @@ -1016,7 +1016,6 @@ class QAPISchemaObjectType(QAPISchemaType):
>>>>          return QAPISchemaType.c_name(self)
>>>>
>>>>      def c_type(self):
>>>> -        assert not self.is_implicit()
>>>
>>> Huh?
>
> Required, because we now pass a pointer to an implicit type from
> qapi_event_send_FOO() to do_qapi_event_send_FOO(), so the c_type() of
> that implicit type is required for generating the C type for that
> parameter.  Will document it better in the commit message.

Hmm.  The real assertion here is "we generate a C type for this QAPI
object type."  Can we express that in code?

>>>> @@ -93,20 +92,11 @@ def gen_event_send(name, arg_type, box):
>>>>          ret += mcgen('''
>>>>      v = qmp_output_visitor_new(&obj);
>>>>
>>>> -''')
>>>> -
>>>> -        if box:
>>>> -            ret += mcgen('''
>>>> -    visit_type_%(c_name)s(v, NULL, &arg, &err);
>>>> -''',
>>>> -                         c_name=arg_type.c_name(), name=arg_type.name)
>>>> -        else:
>>>> -            ret += mcgen('''
>>>>      visit_start_struct(v, "%(name)s", NULL, 0, &err);
>>>>      if (err) {
>>>>          goto out;
>>>>      }
>>>> -    visit_type_%(c_name)s_members(v, &param, &err);
>>>> +    visit_type_%(c_name)s_members(v, arg, &err);
>>>>      if (!err) {
>>>>          visit_check_struct(v, &err);
>>>>      }
>>>
>>> Getting confused...  why are we getting rid of the box case here?
>
> No good reason. The visit_type_FOO() is more compact than
> visit_type_FOO_members(), but only when we have a non-implicit type.  So
> for v8, I'm switching the conditional from 'if box:' to 'if
> arg_type.is_implicit():', more or less.
>
>>>
>>> Too many conditionals...  gen_event_send() has three cases: empty
>>> arg_type, non-empty arg_type and box, non-empty arg_type and not box.
>>> The commit message shows the change to generated code for the second
>>> case.  It doesn't show visit_type_%(c_name)s(v, NULL, &arg, &err) going
>>> away.
>> 
>> Case empty arg_type: no change
>> Example: POWERDOWN
>
> Good.
>
>> 
>> Case non-empty arg_type and box: visit gets open-coded
>> Example: EVENT_E
>
> Fixed in v8 so that it no longer changes.
>
>> The open-coded visit drops the !*obj check (okay, @arg isn't going
>> anywhere), skips the visit_check_struct() differently, and drops the
>> qapi_free_FOO() (okay, condition is always false here).
>> 
>> So this isn't wrong.  But why open-code?
>
> No need to add new open-coding, but we already had existing open-coding
> for anonymous non-boxed 'data' (in part because commit 7ce106a9
> intentionally chose not to create visit_type_FOO() for implicit types).
>
>> 
>> Case non-empty arg_type and not box:
>> Example: ACPI_DEVICE_OST
>> 
>
>> 
>> This is the case the commit message advertises.
>> 
>> There is no visit_type_FOO() we could compare too, since FOO is an
>> implicit type
>
> And in reviewing your message, I realize we have NO testsuite coverage of:
>
> { 'event': 'EVENT', 'data': 'NamedStruct' }
>
> Guess I get to add that first.  Such a usage will then be improved by
> using visit_type_NamedStruct() rather than open-coding around
> visit_type_NamedStruct_members().

Makes sense.
diff mbox

Patch

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 6742e7a..7d568d9 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -1016,7 +1016,6 @@  class QAPISchemaObjectType(QAPISchemaType):
         return QAPISchemaType.c_name(self)

     def c_type(self):
-        assert not self.is_implicit()
         return c_name(self.name) + pointer_suffix

     def c_unboxed_type(self):
diff --git a/scripts/qapi-event.py b/scripts/qapi-event.py
index b8ca8c8..fe4e50d 100644
--- a/scripts/qapi-event.py
+++ b/scripts/qapi-event.py
@@ -14,8 +14,13 @@ 
 from qapi import *


-def gen_event_send_proto(name, arg_type, box):
-    return 'void qapi_event_send_%(c_name)s(%(param)s)' % {
+def gen_event_send_proto(name, arg_type, box, impl=False):
+    intro='void '
+    if impl and arg_type and not box:
+        box = True
+        intro='static void do_'
+    return '%(intro)sqapi_event_send_%(c_name)s(%(param)s)' % {
+        'intro': intro,
         'c_name': c_name(name.lower()),
         'param': gen_params(arg_type, box, 'Error **errp')}

@@ -53,12 +58,8 @@  def gen_param_var(typ):


 def gen_event_send(name, arg_type, box):
-    # FIXME: Our declaration of local variables (and of 'errp' in the
-    # parameter list) can collide with exploded members of the event's
-    # data type passed in as parameters.  If this collision ever hits in
-    # practice, we can rename our local variables with a leading _ prefix,
-    # or split the code into a wrapper function that creates a boxed
-    # 'param' object then calls another to do the real work.
+    if not arg_type or arg_type.is_empty():
+        box = False
     ret = mcgen('''

 %(proto)s
@@ -67,15 +68,13 @@  def gen_event_send(name, arg_type, box):
     Error *err = NULL;
     QMPEventFuncEmit emit;
 ''',
-                proto=gen_event_send_proto(name, arg_type, box))
+                proto=gen_event_send_proto(name, arg_type, box, True))

     if arg_type and not arg_type.is_empty():
         ret += mcgen('''
     QObject *obj;
     Visitor *v;
 ''')
-        if not box:
-            ret += gen_param_var(arg_type)

     ret += mcgen('''

@@ -93,20 +92,11 @@  def gen_event_send(name, arg_type, box):
         ret += mcgen('''
     v = qmp_output_visitor_new(&obj);

-''')
-
-        if box:
-            ret += mcgen('''
-    visit_type_%(c_name)s(v, NULL, &arg, &err);
-''',
-                         c_name=arg_type.c_name(), name=arg_type.name)
-        else:
-            ret += mcgen('''
     visit_start_struct(v, "%(name)s", NULL, 0, &err);
     if (err) {
         goto out;
     }
-    visit_type_%(c_name)s_members(v, &param, &err);
+    visit_type_%(c_name)s_members(v, arg, &err);
     if (!err) {
         visit_check_struct(v, &err);
     }
@@ -136,6 +126,21 @@  out:
     QDECREF(qmp);
 }
 ''')
+
+    if arg_type and not box:
+        ret += mcgen('''
+
+%(proto)s
+{
+''',
+                     proto=gen_event_send_proto(name, arg_type, box))
+        ret += gen_param_var(arg_type)
+        ret += mcgen('''
+    do_qapi_event_send_%(c_name)s(&param, errp);
+}
+''',
+                     c_name=c_name(name.lower()))
+
     return ret