[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Qemu-devel] [PATCH RFC 3/6] qapi: rewrite string-input-visitor
From: |
David Hildenbrand |
Subject: |
Re: [Qemu-devel] [PATCH RFC 3/6] qapi: rewrite string-input-visitor |
Date: |
Wed, 14 Nov 2018 20:56:30 +0100 |
User-agent: |
Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.3.0 |
On 14.11.18 18:38, Markus Armbruster wrote:
> David Hildenbrand <address@hidden> writes:
>
>> The input visitor has some problems right now, especially
>> - unsigned type "Range" is used to process signed ranges, resulting in
>> inconsistent behavior and ugly/magical code
>> - uint64_t are parsed like int64_t, so big uint64_t values are not
>> supported and error messages are misleading
>> - lists/ranges of int64_t are accepted although no list is parsed and
>> we should rather report an error
>> - lists/ranges are preparsed using int64_t, making it hard to
>> implement uint64_t values or uint64_t lists
>> - types that don't support lists don't bail out
>
> Known weirdness: empty list is invalid (test-string-input-visitor.c
> demonstates). Your patch doesn't change that (or else it would update
> the test). Should it be changed?
>
I don't change the test, so the old behavior still works.
(empty string -> error)
>>
>> So let's rewrite it by getting rid of usage of the type "Range" and
>> properly supporting lists of int64_t and uint64_t (including ranges of
>> both types), fixing the above mentioned issues.
>>
>> Lists of other types are not supported and will properly report an
>> error. Virtual walks are now supported.
>>
>> Tests have to be fixed up:
>> - Two BUGs were hardcoded that are fixed now
>> - The string-input-visitor now actually returns a parsed list and not
>> an ordered set.
>>
>> While at it, do some smaller style changes (e.g. use g_assert).
>
> Please don't replace assert() by g_assert() in files that consistently
> use assert() now.
Alright, will use assert() and don't replace. (I thought g_assert() was
the new fancy thing to do it in QEMU).
>
> In my opinion, g_assert() is one of the cases where GLib pointlessly
> reinvents wheels that have been carrying load since forever.
>
>> Signed-off-by: David Hildenbrand <address@hidden>
>> ---
>> include/qapi/string-input-visitor.h | 3 +-
>> qapi/string-input-visitor.c | 436 +++++++++++++++++-----------
>> tests/test-string-input-visitor.c | 18 +-
>> 3 files changed, 264 insertions(+), 193 deletions(-)
>>
>> diff --git a/include/qapi/string-input-visitor.h
>> b/include/qapi/string-input-visitor.h
>> index 33551340e3..2c40f7f5a6 100644
>> --- a/include/qapi/string-input-visitor.h
>> +++ b/include/qapi/string-input-visitor.h
>> @@ -19,8 +19,7 @@ typedef struct StringInputVisitor StringInputVisitor;
>>
>> /*
>> * The string input visitor does not implement support for visiting
>> - * QAPI structs, alternates, null, or arbitrary QTypes. It also
>> - * requires a non-null list argument to visit_start_list().
>> + * QAPI structs, alternates, null, or arbitrary QTypes.
>
> Preexisting: neglects to spell out the list limitations, i.e. can do
> only flat lists of integers. Care do fix that, too?
Added "Only flat lists of integers (int64/uint64) are supported."
>
>> */
>> Visitor *string_input_visitor_new(const char *str);
>>
>> diff --git a/qapi/string-input-visitor.c b/qapi/string-input-visitor.c
>> index dee708d384..16da47409e 100644
>> --- a/qapi/string-input-visitor.c
>> +++ b/qapi/string-input-visitor.c
>> @@ -4,10 +4,10 @@
>> * Copyright Red Hat, Inc. 2012-2016
>> *
>> * Author: Paolo Bonzini <address@hidden>
>> + * David Hildenbrand <address@hidden>
>> *
>> * This work is licensed under the terms of the GNU LGPL, version 2.1 or
>> later.
>> * See the COPYING.LIB file in the top-level directory.
>> - *
>> */
>>
>> #include "qemu/osdep.h"
>> @@ -18,21 +18,39 @@
>> #include "qapi/qmp/qerror.h"
>> #include "qapi/qmp/qnull.h"
>> #include "qemu/option.h"
>> -#include "qemu/queue.h"
>> -#include "qemu/range.h"
>> #include "qemu/cutils.h"
>>
>> +typedef enum ListMode {
>> + /* no list parsing active / no list expected */
>> + LM_NONE,
>> + /* we have an unparsed string remaining */
>> + LM_UNPARSED,
>> + /* we have an unfinished int64 range */
>> + LM_INT64_RANGE,
>> + /* we have an unfinished uint64 range */
>> + LM_UINT64_RANGE,
>> + /* we have parsed the string completely and no range is remaining */
>> + LM_END,
>> +} ListMode;
>> +
>> +typedef union RangeLimit {
>> + int64_t i64;
>> + uint64_t u64;
>> +} RangeLimit;
>>
>> struct StringInputVisitor
>> {
>> Visitor visitor;
>>
>> - GList *ranges;
>> - GList *cur_range;
>> - int64_t cur;
>> + /* Porperties related to list processing */
>
> "Properties"
>
> Suggest
>
> /* List parsing state */
Yes, will use that.
>
>> + ListMode lm;
>> + RangeLimit rangeNext;
>> + RangeLimit rangeEnd;
>
> RangeLimit is a good name for rangeEnd, but not so hot for rangeNext.
> Naming is hard.
Initially I had two unnamed unions to not have to give a name ;)
RangeElement ? Will use that for now.
>
>> + const char *unparsed_string;
>> + void *list;
>
> You drop /* Only needed for sanity checking the caller */.
> Double-checking: is that not true anymore?
I consider such comments bad as they can easily become outdated (and
IMHO don't help anybody). But I can leave it here, don't really care.
[...]
>> + error_setg(errp, "Already processing a list.");
>
> Drop the period, please. error_setg()'s contract stipulates:
>
> * The resulting message should be a single phrase, with no newline or
> * trailing punctuation.
>
> More of the same below.
All dots dropped :)
[...]
>> static GenericList *next_list(Visitor *v, GenericList *tail, size_t size)
>> {
>> StringInputVisitor *siv = to_siv(v);
>> - Range *r;
>> -
>> - if (!siv->ranges || !siv->cur_range) {
>> - return NULL;
>> - }
>>
>> - r = siv->cur_range->data;
>> - if (!r) {
>> + switch (siv->lm) {
>> + case LM_NONE:
>> + case LM_END:
>> + /* we have reached the end of the list already or have no list */
>> return NULL;
>
> Hmm. Is there a way to reach case LM_NONE other than a programming
> error? If no, we should abort then. To do so, fold LM_NONE into the
> default case below.
Alright, all programming errors will be encoded as asserts.
>
>> - }
>> -
>> - if (!range_contains(r, siv->cur)) {
>> - siv->cur_range = g_list_next(siv->cur_range);
>> - if (!siv->cur_range) {
>> - return NULL;
>> - }
>> - r = siv->cur_range->data;
>> - if (!r) {
>> - return NULL;
>> - }
>> - siv->cur = range_lob(r);
>> + case LM_INT64_RANGE:
>> + case LM_UINT64_RANGE:
>> + case LM_UNPARSED:
>> + /* we have an unparsed string or something left in a range */
>> + break;
>> + default:
>> + g_assert_not_reached();
>
> abort() suffices, and is more consistent with the rest of qapi/.
Right, qapi is special ;)
Will use s/g_assert/assert/
s/g_assert_not_reached/abort/
>
>> }
>>
>> tail->next = g_malloc0(size);
>> @@ -179,88 +109,216 @@ static GenericList *next_list(Visitor *v, GenericList
>> *tail, size_t size)
>> static void check_list(Visitor *v, Error **errp)
>> {
>> const StringInputVisitor *siv = to_siv(v);
>> - Range *r;
>> - GList *cur_range;
>>
>> - if (!siv->ranges || !siv->cur_range) {
>> + switch (siv->lm) {
>> + case LM_NONE:
>> + error_setg(errp, "Not processing a list.");
>
> Same question as for next_list().
abort() it is :) -> default case
>
>> + case LM_INT64_RANGE:
>> + case LM_UINT64_RANGE:
>> + case LM_UNPARSED:
>> + error_setg(errp, "There are elements remaining in the list.");
>
> Hmm. qobject_input_check_list() reports "Only %u list elements expected
> in %s" here. Looks unintuitive, until you remember how it's normally
> used: to parse user input. The error gets reported when the parsing
> didn't consume the whole list. Can only happen with a virtual walk.
> And when it happens, the issue to report is "you provided a longer list
> than I can accept". qobject_input_check_list() does exactly that. Your
> message is less clear.
We don't keep track of element count (and I don't like adding it just to
print a error message).
What about "Less list elements expected"? That at least matches the
other error.
>
>> return;
>> - }
>> -
>> - r = siv->cur_range->data;
>> - if (!r) {
>> + case LM_END:
>> return;
>> + default:
>> + g_assert_not_reached();
>> }
>> -
>> - if (!range_contains(r, siv->cur)) {
>> - cur_range = g_list_next(siv->cur_range);
>> - if (!cur_range) {
>> - return;
>> - }
>> - r = cur_range->data;
>> - if (!r) {
>> - return;
>> - }
>> - }
>> -
>> - error_setg(errp, "Range contains too many values");
>> }
>>
>> static void end_list(Visitor *v, void **obj)
>> {
>> StringInputVisitor *siv = to_siv(v);
>>
>> - assert(siv->list == obj);
>
> I suspect state LM_NONE is also a programming error here. If that's
> correct, let's assert(siv->lm != LM_NONE).
Indeed, added.
[...]
>> + if (siv->rangeNext.i64 > siv->rangeEnd.i64 || *obj == INT64_MAX) {
>> + /* end of range, check if there is more to parse */
>> + if (siv->unparsed_string[0]) {
>> + siv->lm = LM_UNPARSED;
>> + } else {
>> + siv->lm = LM_END;
>> + }
>> }
>
> Are you sure this is kosher?
>
> if siv->rangeNext.i64 and siv->rangeEnd are both initially INT64_MAX,
> siv->rangeNext++ wraps around to INT64_MIN. Except when the compiler
> exploits undefined behavior.
*obj == INT64_MAX checks exactly that
(it is the old value of siv->rangeNext.i64 before a possible wraparound).
BTW I added a test case for exactly that as I had the BUG you described
in the code ;)
[...]
>> + return;
>> + case LM_UNPARSED:
>> + if (try_parse_int64_list_entry(siv, obj)) {
>> + error_setg(errp, QERR_INVALID_PARAMETER_VALUE, name ? name :
>> "null",
>> + "list of int64 values or ranges");
>> + }
>
> I figure I'd make try_parse_int64_list_entry() just parse, and on
> success fall through to case LM_INT64_RANGE. But your solution works,
> too.
Then we would have to represent even single values as ranges, which is
something I'd like to avoid.
>
>> + return;
>> + case LM_END:
>> + error_setg(errp, "No more elements in the list.");
>
> This is the buddy of check_list() case LM_UNPARSED etc. There, input
> provides more list members than we expect. Here, input provides less.
> Again, the error is less clear than the one the QObject input visitor
> reports: "Parameter '%s' is missing".
>
> Same for the unsigned copy below.
Will also use "Less list elements expected".
"Parameter '%s' is missing" does not really apply to list elements
without a name. We can discuss when I resend.
>
>> + return;
>> + default:
>> + error_setg(errp, "Lists don't support mixed types.");
>
> Is this a programming error?
>
> Same for the unsigned copy below.
abort() it is.
[...]
>> +
>> + if (siv->rangeNext.u64 > siv->rangeEnd.u64 || *obj == UINT64_MAX) {
>> + /* end of range, check if there is more to parse */
>> + if (siv->unparsed_string[0]) {
>> + siv->lm = LM_UNPARSED;
>> + } else {
>> + siv->lm = LM_END;
>> + }
>> + }
>
> Unsigned wraps around fine. But when you fix the signed code, you
> should probably keep this unsigned code as similar as practical.
Again, *obj == UINT64_MAX checks this.
>
>> + return;
>> + case LM_UNPARSED:
>> + if (try_parse_uint64_list_entry(siv, obj)) {
>> + error_setg(errp, QERR_INVALID_PARAMETER_VALUE, name ? name :
>> "null",
>> + "list of uint64 values or ranges");
>> + }
>> + return;
>> + case LM_END:
>> + error_setg(errp, "No more elements in the list.");
>> + return;
>> + default:
>> + error_setg(errp, "Lists don't support mixed types.");
>> + return;
>> }
>> }
>>
>> @@ -271,6 +329,11 @@ static void parse_type_size(Visitor *v, const char
>> *name, uint64_t *obj,
>> Error *err = NULL;
>> uint64_t val;
>>
>> + if (siv->lm != LM_NONE) {
>> + error_setg(errp, "Lists not supported for type \"size\"");
>> + return;
>> + }
>> +
>
> Programming error? More of the same below.
assert() it is.
[...]
>> visit_type_uint64List(v, NULL, &res, &error_abort);
>> tail = res;
>> for (i = 0; i < n; i++) {
>> @@ -117,10 +107,10 @@ static void check_ulist(Visitor *v, uint64_t
>> *expected, size_t n)
>> static void test_visitor_in_intList(TestInputVisitorData *data,
>> const void *unused)
>> {
>> - /* Note: the visitor *sorts* ranges *unsigned* */
>> - int64_t expect1[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 20 };
>> + int64_t expect1[] = { 1, 2, 0, 2, 3, 4, 20, 5, 6, 7, 8, 9, 1, 2, 3, 4,
>> 5,
>
> Please wrap this line a bit earlier.
Will do.
>
>> + 6, 7, 8 };
>> int64_t expect2[] = { 32767, -32768, -32767 };
>> - int64_t expect3[] = { INT64_MAX, INT64_MIN };
>> + int64_t expect3[] = { INT64_MIN, INT64_MAX };
>
> Did you mean to change this line?
>
Yes, we don't sort the list anymore.
>> uint64_t expect4[] = { UINT64_MAX };
>> Error *err = NULL;
>> int64List *res = NULL;
>> @@ -189,7 +179,7 @@ static void test_visitor_in_intList(TestInputVisitorData
>> *data,
>> visit_type_int64(v, NULL, &tail->value, &err);
>> g_assert_cmpint(tail->value, ==, 0);
>> visit_type_int64(v, NULL, &val, &err);
>> - g_assert_cmpint(val, ==, 1); /* BUG */
>> + error_free_or_abort(&err);
>
> Which of the problems listed in commit message is this?
I guess the not mentioned "visiting beyond the end of a list is not
handled properly" bug (most probably due to old range handling). Will
add it to the description,
>
>> visit_check_list(v, &error_abort);
>> visit_end_list(v, (void **)&res);
>
> Please don't let my review comments discourage you! This is so much
> better than the code we have now, and feels pretty close to ready.
> Thanks a lot!
>
I appreciate people reviewing my code :) So don't worry, I'll do
requested changes and resend, then we'll see what we are still missing.
BTW: I'll add support for single-element ranges (1-1) as the previous
code seemed to accepted that.
--
Thanks,
David / dhildenb