Den mån 7 juli 2025 kl 17:15 skrev Branko Čibej <br...@apache.org>:
On 7. 7. 25 16:07, Daniel Sahlberg wrote:
> Den mån 7 juli 2025 kl 12:08 skrev Branko Čibej <br...@apache.org>:
>
> On 7. 7. 25 11:08, Branko Čibej wrote:
>> As you've probably noticed, I've been going through compilation
>> warnings, especially implicit narrowing conversions, and trying
>> to fix them in a sane way. Many of them, by the way, I
don't see
>> with gcc because it doesn't have a -Wshorten-64-to-32 flag, so
>> I'm using clang. This time I found a gem:
>>
>>
>> .../protocols/http2_stream.c:162:48: warning: implicit
>> conversion loses integer precision:
>> 'apr_size_t' (aka 'unsigned long') to 'apr_uint32_t'
>> (aka 'unsigned int') [-Wshorten-64-to-32]
>> 146 | max_payload_size,
>> | ^~~~~~~~~~~~~~~~
>>
>>
>> This is in a call to serf__bucket_http2_frame_create(), which
>> expects max_payload_size as an apr_uint32_t.
>>
>>
>> But then it goes on to allocate a serf_http2_frame_context_t
>> (see: http2_frame_buckets.h line 603) which has a member
>> max_payload_size that is an apr_size_t. And if that's not funny
>> enough, here's how the struct initialisation actually happens:
>>
>>
>> serf__bucket_http2_frame_create(serf_bucket_t *stream,
>> unsigned char frame_type,
>> unsigned char flags,
>> apr_int32_t *stream_id,
>> void(*stream_id_alloc)(
>> void *baton,
>> apr_int32_t *stream_id),
>> void *stream_id_baton,
>> apr_uint32_t max_payload_size,
>> serf_bucket_alloc_t *alloc)
>> {
>> serf_http2_frame_context_t *ctx =
>> serf_bucket_mem_alloc(alloc,
>> sizeof(*ctx));
>>
>> ctx->alloc = alloc;
>> ctx->stream = stream;
>> ctx->chunk = serf_bucket_aggregate_create(alloc);
>> ctx->max_payload_size = max_payload_size;
>> ctx->frametype = frame_type;
>> ctx->flags = flags;
>>
>> if (max_payload_size > 0xFFFFFF)
>> max_payload_size = 0xFFFFFF;
>> ...
>>
>>
>>
>> From this point onward, max_payload_size (the function
argument,
>> an apr_uint32_t) is not used. Um. The size adjustment is a
no-op?
>>
>>
>> So to silence the warning, it would be enough to change the
type
>> of the function argument. But this bit of code is actually
>> nonsense. Should the max size be limited to 16 MiB or not? If
>> yes, and it isn't, how does the HTTP/2 code even work
correctly?
>> Or maybe we just haven't encountered a case where it breaks?
>>
>>
>> I don't know anything about HTTP/2 framing. RFC 9113/4.2 says
>> that 16 MiB is the upper limit. My gut feeling is to move the
>> size adjustment before 'ctx' initialisation, and to change the
>> type of the parameter -- the latter because Serf operates with
>> apr_size_t all the time.
>
> Not sure how much I can help out here and I'm sure you read the
logs
> as well as I do, but it seems there are only a few revisions of
that
> particular part of the code. Initially, it seems
max_payload_size was
> indeed apr_size_t but r1712557 changed to the current
apr_uint32_t: "
> (serf__bucket_http2_frame_create): Remove some arguments. Limits
> max_payload_size to a uint32, as that is how http2 configures
it and
> it has to fit in 24 bits anyway."
>
> In the case you quoted above, stream_send_headers(), the
> max_payload_size is apr_size_t. Checking some places this is called
> find http2_stream_enque_response which gets the argument from
> serf_http2__max_payload_size which return apr_size_t BUT it get the
> actual returned number from an serf_http2_protocol_t struct member
> lr_max_framesize which in turn is (*TADA*) apr_uint32_t.
>
> It seems Bert's observation "it has to fit in 24 bits anyway" is
> correct and from that point of view it makes sense to use
> apr_uint32_t. But I don't see any real reason why it would be
> unreasonable to use apr_size_t - personally I think that type is
more
> logical.
The observation is correct, but changing that in only one place
and then
immediately converting it to apr_size_t is just ... I dunno,
incomplete?
I've noticed that the implementation (not just the http2 part) plays
fast and loose with int/apr_size_t/apr_uint32_t, creating problems
and
requiring conversions. It doesn't help that APR itself is
inconsistent
about that (apr_base64, /me rolls eyes).
This goes all the way to the public API in some places. Specifically,
the source of that apr_size_t is in Serf's public bucket API, so
non-negotiable.
> Is there a platform where apr_size_t is < 24 bits?
Theoretically, 16-bit platforms could have 16-bit apr_size_t, but I'm
pretty sure we don't support those for other reasons. All platforms
should have sizeof(apr_size_t) >= sizeof(int) but not necessarily >=
sizeof(apr_uint32_t). I'm pretty sure APR would fall on its face
on such
platforms, so I wouldn't worry about them.
> Specifically, this silences the warning and all tests still
pass,
> including a checkout with Subversion over https (though I've no
> idea how to verify if that actually uses HTTP/2; any pointers
> would be welcome).
>
>
> Index: buckets/http2_frame_buckets.c
> ===================================================================
> --- buckets/http2_frame_buckets.c (revision 1927021)
> +++ buckets/http2_frame_buckets.c (working copy)
> @@ -630,12 +630,17 @@
> void *baton,
> apr_int32_t *stream_id),
> void *stream_id_baton,
> - apr_uint32_t max_payload_size,
> + apr_size_t max_payload_size,
> serf_bucket_alloc_t *alloc)
> {
> serf_http2_frame_context_t *ctx =
serf_bucket_mem_alloc(alloc,
> sizeof(*ctx));
>
> + /* The upper limit for HTTP/2 MAX_FRAME_SIZE is 16 M1B.
> +https://www.rfc-editor.org/rfc/rfc9113.html#section-4.2 */
> + if (max_payload_size > 0xFFFFFF)
> + max_payload_size = 0xFFFFFF;
> +
>
>
> +1 to moving.
>
> ctx->alloc = alloc;
> ctx->stream = stream;
> ctx->chunk = serf_bucket_aggregate_create(alloc);
> @@ -643,9 +648,6 @@
> ctx->frametype = frame_type;
> ctx->flags = flags;
>
> - if (max_payload_size > 0xFFFFFF)
> - max_payload_size = 0xFFFFFF;
> -
> if (!stream_id_alloc || (stream_id && *stream_id >= 0))
> {
> /* Avoid all alloc handling; we know the final id */
> @@ -980,4 +982,3 @@
> serf_http2_frame_get_remaining,
> serf_http2_frame_set_config
> };
> -
> Index: protocols/http2_buckets.h
> ===================================================================
> --- protocols/http2_buckets.h (revision 1927021)
> +++ protocols/http2_buckets.h (working copy)
> @@ -202,7 +202,7 @@
> void *baton,
> apr_int32_t *stream_id),
> void *stream_id_baton,
> - apr_uint32_t max_payload_size,
> + apr_size_t max_payload_size,
> serf_bucket_alloc_t *alloc);
>
> /*
====================================================================
*/
>
>
> Makes sense to me, but it would probably make sense to update the
> other instances where we work on the size - as mentioned above the
> serf_http2_protocol_t is riddled with apr_uint32_t:s.
Yes ... but we're either not compiling those bits, or they're
converted
_to_ apr_size_t. Which tends to be safe.
It would be lovely to make the code consistent, but that's a *lot* of
changes for dubious benefit (and somewhat less dubious additional
source
of bugs). I'm not in the mood to tackle that, especially given
that I'm
not the author of the code and would have to understand[1] every
change
before making it.
-- Brane
[1] Right now I'm looking at some similar warnings in the use of
hpack_int() and, for now, I can't prove to myself that the
conversions
are safe -- so I'll leave the warnings in. I just have to find the
right
warning flags to make gcc yammer about them, too; seeing the warnings
only on macOS isn't exactly useful, and while they're present on
Windows, most of the MSVC warnings are red herrings and just
obscure the
real issues.
What I'm suggesting is to change the apr_uint32_t:s to apr_size_t.
That would match the public API and eliminate conversions.
I'm probably not experienced enough in C to see if there are potential
bugs hiding in such a change. (Of course, if there are places in the
standard where the maximum value is limited to 2^32-1 we could
potentially send too large values - but these should be taken care of
anyway to avoid a rollover, much like the existing max_payload_size
check above).