Den mån 7 juli 2025 kl 21:43 skrev Branko Čibej <br...@apache.org>:
On 7. 7. 25 21:30, Daniel Sahlberg wrote:
> 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).
By "the standard", I expect you mean the HTTP(/2) RFCs? I haven't
read
them all, but surely there are some "SHOULD" and "MUST" kinds of
limits.
At least the MAX_FRAME_SIZE has both a lower and an upper bound
defined,
but I don't know whether the actual value is controlled by the
server or
somehow negotiated between server and client.
Yes, I meant the RFCs.
In the http2_protocol_t struct, for each setting always two there are,
no more, no less. An "lr" (local->remote) and a "rl" (remote->local),
each controlled by either the server or the client.
About all those apr_uint32_t's ... I'm not seeing any warnings from
there. But, again, I haven't investigated.
For the lr_max_framesize, there is code to parse it from the SETTIGS
frame (ie, sent from the server), this code seems to read the binary
data to an apr_uint32_t so obviously no warning there.
rl_max_framesize is set to a default value (16384) and there is no way
to change it. There should probably be a public API to set it, in
which case we'd have to decide if we should use apr_size_t or
apr_uint32_t. In either case we'd have to check the min and max value
according to the bounds in the RFC.