diff mbox

[v2,01/17] qapi: fix error propagation

Message ID 1339575768-2557-2-git-send-email-lersek@redhat.com
State New
Headers show

Commit Message

Laszlo Ersek June 13, 2012, 8:22 a.m. UTC
From: Paolo Bonzini <pbonzini@redhat.com>

Don't overwrite / leak previously set errors.
Don't try to end a container that could not be started.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
---
 error.h                        |    4 +-
 error.c                        |    4 +-
 qapi/qapi-visit-core.c         |   10 +--
 tests/test-qmp-input-visitor.c |   24 +++++---
 docs/qapi-code-gen.txt         |    2 +
 scripts/qapi-visit.py          |  129 +++++++++++++++++++++++----------------
 6 files changed, 102 insertions(+), 71 deletions(-)

Comments

Luiz Capitulino July 13, 2012, 4:38 p.m. UTC | #1
On Wed, 13 Jun 2012 10:22:32 +0200
Laszlo Ersek <lersek@redhat.com> wrote:

> From: Paolo Bonzini <pbonzini@redhat.com>
> 
> Don't overwrite / leak previously set errors.

Can you elaborate a bit more? It's not clear to me where the bug is.

More comments below.

> Don't try to end a container that could not be started.
> 
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> Signed-off-by: Laszlo Ersek <lersek@redhat.com>
> ---
>  error.h                        |    4 +-
>  error.c                        |    4 +-
>  qapi/qapi-visit-core.c         |   10 +--
>  tests/test-qmp-input-visitor.c |   24 +++++---
>  docs/qapi-code-gen.txt         |    2 +
>  scripts/qapi-visit.py          |  129 +++++++++++++++++++++++----------------
>  6 files changed, 102 insertions(+), 71 deletions(-)
> 
> diff --git a/error.h b/error.h
> index 45ff6c1..6898f84 100644
> --- a/error.h
> +++ b/error.h
> @@ -24,7 +24,7 @@ typedef struct Error Error;
>  /**
>   * Set an indirect pointer to an error given a printf-style format parameter.
>   * Currently, qerror.h defines these error formats.  This function is not
> - * meant to be used outside of QEMU.
> + * meant to be used outside of QEMU.  Errors after the first are discarded.
>   */
>  void error_set(Error **err, const char *fmt, ...) GCC_FMT_ATTR(2, 3);
>  
> @@ -57,7 +57,7 @@ void error_set_field(Error *err, const char *field, const char *value);
>  /**
>   * Propagate an error to an indirect pointer to an error.  This function will
>   * always transfer ownership of the error reference and handles the case where
> - * dst_err is NULL correctly.
> + * dst_err is NULL correctly.  Errors after the first are discarded.
>   */
>  void error_propagate(Error **dst_err, Error *local_err);
>  
> diff --git a/error.c b/error.c
> index a52b771..0177972 100644
> --- a/error.c
> +++ b/error.c
> @@ -29,7 +29,7 @@ void error_set(Error **errp, const char *fmt, ...)
>      Error *err;
>      va_list ap;
>  
> -    if (errp == NULL) {
> +    if (errp == NULL || *errp != NULL) {

I think we should use assert() here.

If the error is already set, that most probably indicates a bug in the caller, as
it's the caller's responsibility to decide which error to return.

>          return;
>      }
>  
> @@ -132,7 +132,7 @@ bool error_is_type(Error *err, const char *fmt)
>  
>  void error_propagate(Error **dst_err, Error *local_err)
>  {
> -    if (dst_err) {
> +    if (dst_err && !*dst_err) {
>          *dst_err = local_err;
>      } else if (local_err) {
>          error_free(local_err);
> diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
> index ffffbf7..0a513d2 100644
> --- a/qapi/qapi-visit-core.c
> +++ b/qapi/qapi-visit-core.c
> @@ -39,9 +39,8 @@ void visit_start_struct(Visitor *v, void **obj, const char *kind,
>  
>  void visit_end_struct(Visitor *v, Error **errp)
>  {
> -    if (!error_is_set(errp)) {
> -        v->end_struct(v, errp);
> -    }

Is this the ending of a container that could not be started? But if it couldn't
be started, then errp be will be set and we won't try to end it, no?

> +    assert(!error_is_set(errp));
> +    v->end_struct(v, errp);
>  }
>  
>  void visit_start_list(Visitor *v, const char *name, Error **errp)
> @@ -62,9 +61,8 @@ GenericList *visit_next_list(Visitor *v, GenericList **list, Error **errp)
>  
>  void visit_end_list(Visitor *v, Error **errp)
>  {
> -    if (!error_is_set(errp)) {
> -        v->end_list(v, errp);
> -    }
> +    assert(!error_is_set(errp));
> +    v->end_list(v, errp);
>  }
>  
>  void visit_start_optional(Visitor *v, bool *present, const char *name,
> diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
> index c30fdc4..8f5a509 100644
> --- a/tests/test-qmp-input-visitor.c
> +++ b/tests/test-qmp-input-visitor.c
> @@ -151,14 +151,22 @@ typedef struct TestStruct
>  static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
>                                    const char *name, Error **errp)
>  {
> -    visit_start_struct(v, (void **)obj, "TestStruct", name, sizeof(TestStruct),
> -                       errp);
> -
> -    visit_type_int(v, &(*obj)->integer, "integer", errp);
> -    visit_type_bool(v, &(*obj)->boolean, "boolean", errp);
> -    visit_type_str(v, &(*obj)->string, "string", errp);
> -
> -    visit_end_struct(v, errp);
> +    Error *err = NULL;
> +    if (!error_is_set(errp)) {
> +        visit_start_struct(v, (void **)obj, "TestStruct", name, sizeof(TestStruct),
> +                           &err);
> +        if (!err) {
> +            visit_type_int(v, &(*obj)->integer, "integer", &err);
> +            visit_type_bool(v, &(*obj)->boolean, "boolean", &err);
> +            visit_type_str(v, &(*obj)->string, "string", &err);
> +
> +            /* Always call end_struct if start_struct succeeded.  */
> +            error_propagate(errp, err);
> +            err = NULL;
> +            visit_end_struct(v, &err);
> +        }
> +        error_propagate(errp, err);
> +    }
>  }
>  
>  static void test_visitor_in_struct(TestInputVisitorData *data,
> diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
> index ad11767..cccb11e 100644
> --- a/docs/qapi-code-gen.txt
> +++ b/docs/qapi-code-gen.txt
> @@ -220,6 +220,8 @@ Example:
>      #endif
>      mdroth@illuin:~/w/qemu2.git$
>  
> +(The actual structure of the visit_type_* functions is a bit more complex
> +in order to propagate errors correctly and avoid leaking memory).
>  
>  === scripts/qapi-commands.py ===
>  
> diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
> index 8d4e94a..61cf586 100644
> --- a/scripts/qapi-visit.py
> +++ b/scripts/qapi-visit.py
> @@ -17,14 +17,37 @@ import os
>  import getopt
>  import errno
>  
> -def generate_visit_struct_body(field_prefix, members):
> -    ret = ""
> +def generate_visit_struct_body(field_prefix, name, members):
> +    ret = mcgen('''
> +if (!error_is_set(errp)) {
> +''')
> +    push_indent()
> +
>      if len(field_prefix):
>          field_prefix = field_prefix + "."
> +        ret += mcgen('''
> +Error **errp = &err; /* from outer scope */
> +Error *err = NULL;
> +visit_start_struct(m, NULL, "", "%(name)s", 0, &err);
> +''',
> +                name=name)
> +    else:
> +        ret += mcgen('''
> +Error *err = NULL;
> +visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), &err);
> +''',
> +                name=name)
> +
> +    ret += mcgen('''
> +if (!err) {
> +    assert(!obj || *obj);
> +''')
> +
> +    push_indent()
>      for argname, argentry, optional, structured in parse_args(members):
>          if optional:
>              ret += mcgen('''
> -visit_start_optional(m, (obj && *obj) ? &(*obj)->%(c_prefix)shas_%(c_name)s : NULL, "%(name)s", errp);
> +visit_start_optional(m, obj ? &(*obj)->%(c_prefix)shas_%(c_name)s : NULL, "%(name)s", &err);
>  if ((*obj)->%(prefix)shas_%(c_name)s) {
>  ''',
>                           c_prefix=c_var(field_prefix), prefix=field_prefix,
> @@ -32,17 +55,10 @@ if ((*obj)->%(prefix)shas_%(c_name)s) {
>              push_indent()
>  
>          if structured:
> -            ret += mcgen('''
> -visit_start_struct(m, NULL, "", "%(name)s", 0, errp);
> -''',
> -                         name=argname)
> -            ret += generate_visit_struct_body(field_prefix + argname, argentry)
> -            ret += mcgen('''
> -visit_end_struct(m, errp);
> -''')
> +            ret += generate_visit_struct_body(field_prefix + argname, argname, argentry)
>          else:
>              ret += mcgen('''
> -visit_type_%(type)s(m, (obj && *obj) ? &(*obj)->%(c_prefix)s%(c_name)s : NULL, "%(name)s", errp);
> +visit_type_%(type)s(m, obj ? &(*obj)->%(c_prefix)s%(c_name)s : NULL, "%(name)s", &err);
>  ''',
>                           c_prefix=c_var(field_prefix), prefix=field_prefix,
>                           type=type_name(argentry), c_name=c_var(argname),
> @@ -52,7 +68,19 @@ visit_type_%(type)s(m, (obj && *obj) ? &(*obj)->%(c_prefix)s%(c_name)s : NULL, "
>              pop_indent()
>              ret += mcgen('''
>  }
> -visit_end_optional(m, errp);
> +visit_end_optional(m, &err);
> +''')
> +
> +    pop_indent()
> +    pop_indent()
> +    ret += mcgen('''
> +        /* Always call end_struct if start_struct succeeded.  */
> +        error_propagate(errp, err);
> +        err = NULL;
> +        visit_end_struct(m, &err);
> +    }
> +    error_propagate(errp, err);
> +}
>  ''')
>      return ret
>  
> @@ -61,22 +89,14 @@ def generate_visit_struct(name, members):
>  
>  void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **errp)
>  {
> -    if (error_is_set(errp)) {
> -        return;
> -    }
> -    visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), errp);
> -    if (obj && !*obj) {
> -        goto end;
> -    }
>  ''',
>                  name=name)
> +
>      push_indent()
> -    ret += generate_visit_struct_body("", members)
> +    ret += generate_visit_struct_body("", name, members)
>      pop_indent()
>  
>      ret += mcgen('''
> -end:
> -    visit_end_struct(m, errp);
>  }
>  ''')
>      return ret
> @@ -87,18 +107,22 @@ def generate_visit_list(name, members):
>  void visit_type_%(name)sList(Visitor *m, %(name)sList ** obj, const char *name, Error **errp)
>  {
>      GenericList *i, **prev = (GenericList **)obj;
> +    Error *err = NULL;
>  
> -    if (error_is_set(errp)) {
> -        return;
> -    }
> -    visit_start_list(m, name, errp);
> -
> -    for (; (i = visit_next_list(m, prev, errp)) != NULL; prev = &i) {
> -        %(name)sList *native_i = (%(name)sList *)i;
> -        visit_type_%(name)s(m, &native_i->value, NULL, errp);
> +    if (!error_is_set(errp)) {
> +        visit_start_list(m, name, &err);
> +        if (!err) {
> +            for (; (i = visit_next_list(m, prev, &err)) != NULL; prev = &i) {
> +                %(name)sList *native_i = (%(name)sList *)i;
> +                visit_type_%(name)s(m, &native_i->value, NULL, &err);
> +            }
> +            /* Always call end_list if start_list succeeded.  */
> +            error_propagate(errp, err);
> +            err = NULL;
> +            visit_end_list(m, &err);
> +        }
> +        error_propagate(errp, err);
>      }
> -
> -    visit_end_list(m, errp);
>  }
>  ''',
>                  name=name)
> @@ -122,27 +146,21 @@ void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **
>  {
>      Error *err = NULL;
>  
> -    if (error_is_set(errp)) {
> -        return;
> -    }
> -    visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), &err);
> -    if (obj && !*obj) {
> -        goto end;
> -    }
> -    visit_type_%(name)sKind(m, &(*obj)->kind, "type", &err);
> -    if (err) {
> -        error_propagate(errp, err);
> -        goto end;
> -    }
> -    switch ((*obj)->kind) {
> +    if (!error_is_set(errp)) {
> +        visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), &err);
> +        if (!err) {
> +            visit_type_%(name)sKind(m, &(*obj)->kind, "type", &err);
> +        }
> +        if (!err) {
> +            switch ((*obj)->kind) {
>  ''',
>                   name=name)
>  
>      for key in members:
>          ret += mcgen('''
> -    case %(abbrev)s_KIND_%(enum)s:
> -        visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "data", errp);
> -        break;
> +            case %(abbrev)s_KIND_%(enum)s:
> +                visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "data", &err);
> +                break;
>  ''',
>                  abbrev = de_camel_case(name).upper(),
>                  enum = c_fun(de_camel_case(key)).upper(),
> @@ -150,11 +168,16 @@ void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **
>                  c_name=c_fun(key))
>  
>      ret += mcgen('''
> -    default:
> -        abort();
> +            default:
> +                abort();
> +            }
> +        }
> +        /* Always call end_struct if start_struct succeeded.  */
> +        error_propagate(errp, err);
> +        err = NULL;
> +        visit_end_struct(m, &err);
>      }
> -end:
> -    visit_end_struct(m, errp);
> +    error_propagate(errp, err);
>  }
>  ''')
>
Laszlo Ersek July 13, 2012, 5:30 p.m. UTC | #2
On 07/13/12 18:38, Luiz Capitulino wrote:
> On Wed, 13 Jun 2012 10:22:32 +0200
> Laszlo Ersek <lersek@redhat.com> wrote:
> 
>> From: Paolo Bonzini <pbonzini@redhat.com>
>>
>> Don't overwrite / leak previously set errors.
> 
> Can you elaborate a bit more? It's not clear to me where the bug is.

Suppose you encounter the first error on the normal path, while a bunch
of objects is being constructed / composed. You set the error
accordingly and start to unwind the stack, tearing down objects
previously composed fully or partially. While doing this, you encounter
another error. If you call error_set() or error_propagate() now, the
first error is leaked. To avoid this, you have to check.

This change saves you the checks during stack unwinding, keeps the first
error stored (which is more important than any destructor errors, since
the latter could be the direct consequence of the first error and
aborting further processing). Second and later errors attempted to be
set via error_set() are simply not formatted, while second and later
errors attempted to be propagated with error_propagate() are released
(as there are two errors and we keep only one).

See "qapi: introduce OptsVisitor", function opts_end_struct(), comment
"we should have processed all (distinct) QemuOpt instances". If we abort
processing due to some error, there may be leftover options. We
shouldn't overwrite the first (real) error with this bogus one.

The stack is unwound in this case by the generated code -- if some
deeper part of OptsVisitor sets an error, the generated code will make
sure opts_end_struct() is called the right number of times.


> 
> More comments below.
> 
>> Don't try to end a container that could not be started.
>>
>> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
>> Signed-off-by: Laszlo Ersek <lersek@redhat.com>
>> ---
>>  error.h                        |    4 +-
>>  error.c                        |    4 +-
>>  qapi/qapi-visit-core.c         |   10 +--
>>  tests/test-qmp-input-visitor.c |   24 +++++---
>>  docs/qapi-code-gen.txt         |    2 +
>>  scripts/qapi-visit.py          |  129 +++++++++++++++++++++++----------------
>>  6 files changed, 102 insertions(+), 71 deletions(-)
>>
>> diff --git a/error.h b/error.h
>> index 45ff6c1..6898f84 100644
>> --- a/error.h
>> +++ b/error.h
>> @@ -24,7 +24,7 @@ typedef struct Error Error;
>>  /**
>>   * Set an indirect pointer to an error given a printf-style format parameter.
>>   * Currently, qerror.h defines these error formats.  This function is not
>> - * meant to be used outside of QEMU.
>> + * meant to be used outside of QEMU.  Errors after the first are discarded.
>>   */
>>  void error_set(Error **err, const char *fmt, ...) GCC_FMT_ATTR(2, 3);
>>  
>> @@ -57,7 +57,7 @@ void error_set_field(Error *err, const char *field, const char *value);
>>  /**
>>   * Propagate an error to an indirect pointer to an error.  This function will
>>   * always transfer ownership of the error reference and handles the case where
>> - * dst_err is NULL correctly.
>> + * dst_err is NULL correctly.  Errors after the first are discarded.
>>   */
>>  void error_propagate(Error **dst_err, Error *local_err);
>>  
>> diff --git a/error.c b/error.c
>> index a52b771..0177972 100644
>> --- a/error.c
>> +++ b/error.c
>> @@ -29,7 +29,7 @@ void error_set(Error **errp, const char *fmt, ...)
>>      Error *err;
>>      va_list ap;
>>  
>> -    if (errp == NULL) {
>> +    if (errp == NULL || *errp != NULL) {
> 
> I think we should use assert() here.
> 
> If the error is already set, that most probably indicates a bug in the caller, as
> it's the caller's responsibility to decide which error to return.

I believe we had a good argument against this, but I can't precisely
recall (or find) it now. Paolo, do you remember? Can you please both
search your respective mailboxen for Message-ID
<4FB21B71.7030804@redhat.com>? That's where we started to discuss this.

I believe I saw some paths in the code that tripped on this leak, and
generally keeping the first error seemed like a good idea.
opts_end_struct() originally checked for any pre-existent error
explicitly, but then the check was moved to the common code.


> 
>>          return;
>>      }
>>  
>> @@ -132,7 +132,7 @@ bool error_is_type(Error *err, const char *fmt)
>>  
>>  void error_propagate(Error **dst_err, Error *local_err)
>>  {
>> -    if (dst_err) {
>> +    if (dst_err && !*dst_err) {
>>          *dst_err = local_err;
>>      } else if (local_err) {
>>          error_free(local_err);
>> diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
>> index ffffbf7..0a513d2 100644
>> --- a/qapi/qapi-visit-core.c
>> +++ b/qapi/qapi-visit-core.c
>> @@ -39,9 +39,8 @@ void visit_start_struct(Visitor *v, void **obj, const char *kind,
>>  
>>  void visit_end_struct(Visitor *v, Error **errp)
>>  {
>> -    if (!error_is_set(errp)) {
>> -        v->end_struct(v, errp);
>> -    }
> 
> Is this the ending of a container that could not be started? But if it couldn't
> be started, then errp be will be set and we won't try to end it, no?
> 
>> +    assert(!error_is_set(errp));
>> +    v->end_struct(v, errp);
>>  }

(Perhaps I'm misunderstanding.)

Exactly as you say. That's why we replace the check with an assert.

(This seems to be your last question for this patch, so I'm cutting the
rest.)

Thanks,
Laszlo
Paolo Bonzini July 13, 2012, 7:11 p.m. UTC | #3
Il 13/07/2012 19:30, Laszlo Ersek ha scritto:
>>> >> -    if (errp == NULL) {
>>> >> +    if (errp == NULL || *errp != NULL) {
>> > 
>> > I think we should use assert() here.
>> > 
>> > If the error is already set, that most probably indicates a bug in the caller, as
>> > it's the caller's responsibility to decide which error to return.
> I believe we had a good argument against this, but I can't precisely
> recall (or find) it now. Paolo, do you remember? Can you please both
> search your respective mailboxen for Message-ID
> <4FB21B71.7030804@redhat.com>? That's where we started to discuss this.
> 
> I believe I saw some paths in the code that tripped on this leak, and
> generally keeping the first error seemed like a good idea.
> opts_end_struct() originally checked for any pre-existent error
> explicitly, but then the check was moved to the common code.

The reason to do this for error_propagate was to allow this idiom:

          /* Always call end_struct if start_struct succeeded.  */
          error_propagate(errp, err);
          err = NULL;
          visit_end_struct(v, &err);
          error_propagate(errp, err);

I think doing it for error_set was just for symmetry and to avoid
introducing excessive complexity.

Paolo
Laszlo Ersek July 13, 2012, 8:11 p.m. UTC | #4
On 07/13/12 21:11, Paolo Bonzini wrote:
> Il 13/07/2012 19:30, Laszlo Ersek ha scritto:
>>>>>> -    if (errp == NULL) {
>>>>>> +    if (errp == NULL || *errp != NULL) {
>>>>
>>>> I think we should use assert() here.
>>>>
>>>> If the error is already set, that most probably indicates a bug in the caller, as
>>>> it's the caller's responsibility to decide which error to return.
>> I believe we had a good argument against this, but I can't precisely
>> recall (or find) it now. Paolo, do you remember? Can you please both
>> search your respective mailboxen for Message-ID
>> <4FB21B71.7030804@redhat.com>? That's where we started to discuss this.
>>
>> I believe I saw some paths in the code that tripped on this leak, and
>> generally keeping the first error seemed like a good idea.
>> opts_end_struct() originally checked for any pre-existent error
>> explicitly, but then the check was moved to the common code.
> 
> The reason to do this for error_propagate was to allow this idiom:
> 
>           /* Always call end_struct if start_struct succeeded.  */
>           error_propagate(errp, err);
>           err = NULL;
>           visit_end_struct(v, &err);
>           error_propagate(errp, err);

Right!

> I think doing it for error_set was just for symmetry and to avoid
> introducing excessive complexity.

Correct again. IIRC it was even yours truly who humbly suggested that.

Thanks!
Laszlo
Luiz Capitulino July 16, 2012, 5:12 p.m. UTC | #5
On Fri, 13 Jul 2012 21:11:15 +0200
Paolo Bonzini <pbonzini@redhat.com> wrote:

> Il 13/07/2012 19:30, Laszlo Ersek ha scritto:
> >>> >> -    if (errp == NULL) {
> >>> >> +    if (errp == NULL || *errp != NULL) {
> >> > 
> >> > I think we should use assert() here.
> >> > 
> >> > If the error is already set, that most probably indicates a bug in the caller, as
> >> > it's the caller's responsibility to decide which error to return.
> > I believe we had a good argument against this, but I can't precisely
> > recall (or find) it now. Paolo, do you remember? Can you please both
> > search your respective mailboxen for Message-ID
> > <4FB21B71.7030804@redhat.com>? That's where we started to discuss this.
> > 
> > I believe I saw some paths in the code that tripped on this leak, and
> > generally keeping the first error seemed like a good idea.
> > opts_end_struct() originally checked for any pre-existent error
> > explicitly, but then the check was moved to the common code.
> 
> The reason to do this for error_propagate was to allow this idiom:
> 
>           /* Always call end_struct if start_struct succeeded.  */
>           error_propagate(errp, err);
>           err = NULL;
>           visit_end_struct(v, &err);
>           error_propagate(errp, err);

I agree with this change for error_propagate() because it encapsulates our
rules for error propagation.

> I think doing it for error_set was just for symmetry and to avoid
> introducing excessive complexity.

We already check if the error is set in several places, and I don't think
it will add much complexity. I still think that an assert() is better.
Laszlo Ersek July 16, 2012, 8:31 p.m. UTC | #6
On 07/16/12 19:12, Luiz Capitulino wrote:
> On Fri, 13 Jul 2012 21:11:15 +0200
> Paolo Bonzini <pbonzini@redhat.com> wrote:

> 
>> I think doing it for error_set was just for symmetry and to avoid
>> introducing excessive complexity.
> 
> We already check if the error is set in several places, and I don't think
> it will add much complexity. I still think that an assert() is better.

If that means that the generated traversal code takes responsibility to
call any visitor callback with a fresh error receptacle, IOW I can go
ahead and just use error_set() in OptsVisitor and any firing assert will
be blamed on the generator: fine :)

Thanks,
Laszlo
Luiz Capitulino July 16, 2012, 8:44 p.m. UTC | #7
On Mon, 16 Jul 2012 22:31:26 +0200
Laszlo Ersek <lersek@redhat.com> wrote:

> On 07/16/12 19:12, Luiz Capitulino wrote:
> > On Fri, 13 Jul 2012 21:11:15 +0200
> > Paolo Bonzini <pbonzini@redhat.com> wrote:
> 
> > 
> >> I think doing it for error_set was just for symmetry and to avoid
> >> introducing excessive complexity.
> > 
> > We already check if the error is set in several places, and I don't think
> > it will add much complexity. I still think that an assert() is better.
> 
> If that means that the generated traversal code takes responsibility to
> call any visitor callback with a fresh error receptacle, IOW I can go
> ahead and just use error_set() in OptsVisitor and any firing assert will
> be blamed on the generator: fine :)

If that means it's finding bugs then that's great. On the other hand, if it
shows only false positives and we end up having to re-work the code just to
avoid that, then I'd agree on not having an assert().
diff mbox

Patch

diff --git a/error.h b/error.h
index 45ff6c1..6898f84 100644
--- a/error.h
+++ b/error.h
@@ -24,7 +24,7 @@  typedef struct Error Error;
 /**
  * Set an indirect pointer to an error given a printf-style format parameter.
  * Currently, qerror.h defines these error formats.  This function is not
- * meant to be used outside of QEMU.
+ * meant to be used outside of QEMU.  Errors after the first are discarded.
  */
 void error_set(Error **err, const char *fmt, ...) GCC_FMT_ATTR(2, 3);
 
@@ -57,7 +57,7 @@  void error_set_field(Error *err, const char *field, const char *value);
 /**
  * Propagate an error to an indirect pointer to an error.  This function will
  * always transfer ownership of the error reference and handles the case where
- * dst_err is NULL correctly.
+ * dst_err is NULL correctly.  Errors after the first are discarded.
  */
 void error_propagate(Error **dst_err, Error *local_err);
 
diff --git a/error.c b/error.c
index a52b771..0177972 100644
--- a/error.c
+++ b/error.c
@@ -29,7 +29,7 @@  void error_set(Error **errp, const char *fmt, ...)
     Error *err;
     va_list ap;
 
-    if (errp == NULL) {
+    if (errp == NULL || *errp != NULL) {
         return;
     }
 
@@ -132,7 +132,7 @@  bool error_is_type(Error *err, const char *fmt)
 
 void error_propagate(Error **dst_err, Error *local_err)
 {
-    if (dst_err) {
+    if (dst_err && !*dst_err) {
         *dst_err = local_err;
     } else if (local_err) {
         error_free(local_err);
diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index ffffbf7..0a513d2 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -39,9 +39,8 @@  void visit_start_struct(Visitor *v, void **obj, const char *kind,
 
 void visit_end_struct(Visitor *v, Error **errp)
 {
-    if (!error_is_set(errp)) {
-        v->end_struct(v, errp);
-    }
+    assert(!error_is_set(errp));
+    v->end_struct(v, errp);
 }
 
 void visit_start_list(Visitor *v, const char *name, Error **errp)
@@ -62,9 +61,8 @@  GenericList *visit_next_list(Visitor *v, GenericList **list, Error **errp)
 
 void visit_end_list(Visitor *v, Error **errp)
 {
-    if (!error_is_set(errp)) {
-        v->end_list(v, errp);
-    }
+    assert(!error_is_set(errp));
+    v->end_list(v, errp);
 }
 
 void visit_start_optional(Visitor *v, bool *present, const char *name,
diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index c30fdc4..8f5a509 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -151,14 +151,22 @@  typedef struct TestStruct
 static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
                                   const char *name, Error **errp)
 {
-    visit_start_struct(v, (void **)obj, "TestStruct", name, sizeof(TestStruct),
-                       errp);
-
-    visit_type_int(v, &(*obj)->integer, "integer", errp);
-    visit_type_bool(v, &(*obj)->boolean, "boolean", errp);
-    visit_type_str(v, &(*obj)->string, "string", errp);
-
-    visit_end_struct(v, errp);
+    Error *err = NULL;
+    if (!error_is_set(errp)) {
+        visit_start_struct(v, (void **)obj, "TestStruct", name, sizeof(TestStruct),
+                           &err);
+        if (!err) {
+            visit_type_int(v, &(*obj)->integer, "integer", &err);
+            visit_type_bool(v, &(*obj)->boolean, "boolean", &err);
+            visit_type_str(v, &(*obj)->string, "string", &err);
+
+            /* Always call end_struct if start_struct succeeded.  */
+            error_propagate(errp, err);
+            err = NULL;
+            visit_end_struct(v, &err);
+        }
+        error_propagate(errp, err);
+    }
 }
 
 static void test_visitor_in_struct(TestInputVisitorData *data,
diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index ad11767..cccb11e 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -220,6 +220,8 @@  Example:
     #endif
     mdroth@illuin:~/w/qemu2.git$
 
+(The actual structure of the visit_type_* functions is a bit more complex
+in order to propagate errors correctly and avoid leaking memory).
 
 === scripts/qapi-commands.py ===
 
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 8d4e94a..61cf586 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -17,14 +17,37 @@  import os
 import getopt
 import errno
 
-def generate_visit_struct_body(field_prefix, members):
-    ret = ""
+def generate_visit_struct_body(field_prefix, name, members):
+    ret = mcgen('''
+if (!error_is_set(errp)) {
+''')
+    push_indent()
+
     if len(field_prefix):
         field_prefix = field_prefix + "."
+        ret += mcgen('''
+Error **errp = &err; /* from outer scope */
+Error *err = NULL;
+visit_start_struct(m, NULL, "", "%(name)s", 0, &err);
+''',
+                name=name)
+    else:
+        ret += mcgen('''
+Error *err = NULL;
+visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), &err);
+''',
+                name=name)
+
+    ret += mcgen('''
+if (!err) {
+    assert(!obj || *obj);
+''')
+
+    push_indent()
     for argname, argentry, optional, structured in parse_args(members):
         if optional:
             ret += mcgen('''
-visit_start_optional(m, (obj && *obj) ? &(*obj)->%(c_prefix)shas_%(c_name)s : NULL, "%(name)s", errp);
+visit_start_optional(m, obj ? &(*obj)->%(c_prefix)shas_%(c_name)s : NULL, "%(name)s", &err);
 if ((*obj)->%(prefix)shas_%(c_name)s) {
 ''',
                          c_prefix=c_var(field_prefix), prefix=field_prefix,
@@ -32,17 +55,10 @@  if ((*obj)->%(prefix)shas_%(c_name)s) {
             push_indent()
 
         if structured:
-            ret += mcgen('''
-visit_start_struct(m, NULL, "", "%(name)s", 0, errp);
-''',
-                         name=argname)
-            ret += generate_visit_struct_body(field_prefix + argname, argentry)
-            ret += mcgen('''
-visit_end_struct(m, errp);
-''')
+            ret += generate_visit_struct_body(field_prefix + argname, argname, argentry)
         else:
             ret += mcgen('''
-visit_type_%(type)s(m, (obj && *obj) ? &(*obj)->%(c_prefix)s%(c_name)s : NULL, "%(name)s", errp);
+visit_type_%(type)s(m, obj ? &(*obj)->%(c_prefix)s%(c_name)s : NULL, "%(name)s", &err);
 ''',
                          c_prefix=c_var(field_prefix), prefix=field_prefix,
                          type=type_name(argentry), c_name=c_var(argname),
@@ -52,7 +68,19 @@  visit_type_%(type)s(m, (obj && *obj) ? &(*obj)->%(c_prefix)s%(c_name)s : NULL, "
             pop_indent()
             ret += mcgen('''
 }
-visit_end_optional(m, errp);
+visit_end_optional(m, &err);
+''')
+
+    pop_indent()
+    pop_indent()
+    ret += mcgen('''
+        /* Always call end_struct if start_struct succeeded.  */
+        error_propagate(errp, err);
+        err = NULL;
+        visit_end_struct(m, &err);
+    }
+    error_propagate(errp, err);
+}
 ''')
     return ret
 
@@ -61,22 +89,14 @@  def generate_visit_struct(name, members):
 
 void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **errp)
 {
-    if (error_is_set(errp)) {
-        return;
-    }
-    visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), errp);
-    if (obj && !*obj) {
-        goto end;
-    }
 ''',
                 name=name)
+
     push_indent()
-    ret += generate_visit_struct_body("", members)
+    ret += generate_visit_struct_body("", name, members)
     pop_indent()
 
     ret += mcgen('''
-end:
-    visit_end_struct(m, errp);
 }
 ''')
     return ret
@@ -87,18 +107,22 @@  def generate_visit_list(name, members):
 void visit_type_%(name)sList(Visitor *m, %(name)sList ** obj, const char *name, Error **errp)
 {
     GenericList *i, **prev = (GenericList **)obj;
+    Error *err = NULL;
 
-    if (error_is_set(errp)) {
-        return;
-    }
-    visit_start_list(m, name, errp);
-
-    for (; (i = visit_next_list(m, prev, errp)) != NULL; prev = &i) {
-        %(name)sList *native_i = (%(name)sList *)i;
-        visit_type_%(name)s(m, &native_i->value, NULL, errp);
+    if (!error_is_set(errp)) {
+        visit_start_list(m, name, &err);
+        if (!err) {
+            for (; (i = visit_next_list(m, prev, &err)) != NULL; prev = &i) {
+                %(name)sList *native_i = (%(name)sList *)i;
+                visit_type_%(name)s(m, &native_i->value, NULL, &err);
+            }
+            /* Always call end_list if start_list succeeded.  */
+            error_propagate(errp, err);
+            err = NULL;
+            visit_end_list(m, &err);
+        }
+        error_propagate(errp, err);
     }
-
-    visit_end_list(m, errp);
 }
 ''',
                 name=name)
@@ -122,27 +146,21 @@  void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **
 {
     Error *err = NULL;
 
-    if (error_is_set(errp)) {
-        return;
-    }
-    visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), &err);
-    if (obj && !*obj) {
-        goto end;
-    }
-    visit_type_%(name)sKind(m, &(*obj)->kind, "type", &err);
-    if (err) {
-        error_propagate(errp, err);
-        goto end;
-    }
-    switch ((*obj)->kind) {
+    if (!error_is_set(errp)) {
+        visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), &err);
+        if (!err) {
+            visit_type_%(name)sKind(m, &(*obj)->kind, "type", &err);
+        }
+        if (!err) {
+            switch ((*obj)->kind) {
 ''',
                  name=name)
 
     for key in members:
         ret += mcgen('''
-    case %(abbrev)s_KIND_%(enum)s:
-        visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "data", errp);
-        break;
+            case %(abbrev)s_KIND_%(enum)s:
+                visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "data", &err);
+                break;
 ''',
                 abbrev = de_camel_case(name).upper(),
                 enum = c_fun(de_camel_case(key)).upper(),
@@ -150,11 +168,16 @@  void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **
                 c_name=c_fun(key))
 
     ret += mcgen('''
-    default:
-        abort();
+            default:
+                abort();
+            }
+        }
+        /* Always call end_struct if start_struct succeeded.  */
+        error_propagate(errp, err);
+        err = NULL;
+        visit_end_struct(m, &err);
     }
-end:
-    visit_end_struct(m, errp);
+    error_propagate(errp, err);
 }
 ''')