On Tue, Nov 04, 2025 at 05:42:25AM +0100, Stefano Brivio wrote:
On Mon, 3 Nov 2025 20:32:14 +1100 David Gibson
wrote: On Mon, Nov 03, 2025 at 10:57:27AM +0800, Yumei Huang wrote:
On Mon, Nov 3, 2025 at 9:38 AM David Gibson
wrote: On Fri, Oct 31, 2025 at 01:42:41PM +0800, Yumei Huang wrote:
[snip]
@@ -589,12 +585,10 @@ static void tcp_timer_ctl(const struct ctx *c, struct tcp_tap_conn *conn) if (conn->flags & ACK_TO_TAP_DUE) { it.it_value.tv_nsec = (long)ACK_INTERVAL * 1000 * 1000; } else if (conn->flags & ACK_FROM_TAP_DUE) { - if (!(conn->events & ESTABLISHED)) { - int exp = conn->retries - c->tcp.syn_linear_timeouts;
I didn't spot it in the previous patch, but this is (theoretically) buggy. conn->retries is unsigned, so the subtraction will be performed unsigned and only then cast to signed. I think that will probably do the right thing in practice, but I don't think that's guaranteed by the C standard (and might even be UB).
I'm not sure, but I just googled it. IIUC, the uint8_t (conn->retries and c->tcp.syn_linear_timeouts) will go through integer promotion before subtraction. So the line is like:
int exp = (int) conn->retries - (int) c->tcp.syn_linear_timeouts;
Please correct me if I'm wrong.
Huh, I thought it would only be promoted if one of the operands was an int.
I thought and I think so too, yes.
But C promotion rules are really confusing, so I could well be wrong.
Some references linked at:
https://archives.passt.top/passt-dev/20240103080834.24fa0a7a@elisabeth/
and here, it's about paragraph 6.3.1.8, "Usual arithmetic conversions" of C11 (that's what passt uses right now).
No double or float here, so this is the relevant text:
Otherwise, the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands:
If both operands have the same type, then no further conversion is needed
now, are they really the same type? The standard doesn't define uint8_t. If we see it as an unsigned int, with storage limited to 8 bits, then I would call them the same type.
So, I was a bit confused in the first place - I had thought both operands were literally uint8_t typed. But that's not the case, 'retries' is an unsigned int that's bit limited.
If we see c->tcp.syn_linear_timeouts as a character type instead (6.3.1.1, "Boolean, characters, and integers"), then the integer conversion rank of unsigned int (conn->retries) is greater than the rank of char, and the same text applies (promotion of char to unsigned int).
But I'm fairly sure that's not the case. We used uint8_t, not short, and not char.
All in all I don't think there's any possibility of promotion to a signed int: for that, you would need one of the operands to be signed. Or two unsigned chars/shorts (see examples below).
Right. I'm fairly confident the arguments will be promoted to unsigned int, not signed.
If the operation is performed unsigned, and that's what gcc appears to do here, promoting both operands to unsigned int (32-bit):
--- $ cat pro_uh_oh_motion.c #include
#include int main() { uint8_t a = 0; struct { unsigned b:3; } x = { 7 };
printf("%u %i\n", a - x.b, a - x.b); } $ gcc -o pro_uh_oh_motion{,.c} $ ./pro_uh_oh_motion 4294967289 -7 ---
...do we really have a problem with that? To me it looks like the behaviour is defined, and it's what we want.
So, this is a second step, where we cast the result of the subtraction into a signed int of the same size. I'm not sure if that behaviour is defined for values outside the positive range the signed type can represent. In practice the obvious result on a two's complement machine will do what we need, but I'm not sure that C guarantees that.
These pages:
https://wiki.sei.cmu.edu/confluence/display/c/INT02-C.+Understand+integer+co... https://wiki.sei.cmu.edu/confluence/display/c/EXP14-C.+Beware+of+integer+pro...
show some problematic examples. This one is close to our case:
unsigned short x = 45000, y = 50000; unsigned int z = x * y;
but there we have implicit promotion of the unsigned shorts to int, which doesn't happen in our case, because we already have an unsigned int.
Also multiplication which is a whole other thing.
See also the examples with two unsigned shorts from:
https://cryptoservices.github.io/fde/2018/11/30/undefined-behavior.html
and there the operation would be performed as signed int.
On top of that, you have the fact that the original types can't store the result of the operation. Here it's not the case.
They can't though - the result is negative which can't be stored in an unsigned type. Now, wraparound / mod 2^n behaviour *is* defined for unsigned (but not signed) integer types. The second part where we assign an unsigned "negative" value into a signed variable might not be. -- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson