Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork32.1k
gh-96821: Fix undefined behaviour in_ctypes/cfield.c
#96925
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
base:main
Are you sure you want to change the base?
gh-96821: Fix undefined behaviour in_ctypes/cfield.c
#96925
Uh oh!
There was an error while loading.Please reload this page.
Conversation
@kumaraditya303@mdickinson Would you please have a look? Thanks! |
Uh oh!
There was an error while loading.Please reload this page.
cfield.c
_ctypes/cfield.c
I'm not super-keen on this fix - I agree that it would be good to fix the undefined behaviour, but I wonder whether there's a better way. The multiplication bugs me on both conceptual grounds and technical grounds: conceptually, the intent really is just to shift bits around (unlike the similar changes in It feels as though the right solution is to ensure that we always do the bitwise operations (or at the very least, the shifts) on unsigned types rather than signed types, so that everything's well-defined, and then do sign-extension as necessary. So for staticPyObject*h_get(void*ptr,Py_ssize_tsize){shortval;memcpy(&val,ptr,sizeof(val));GET_BITFIELD(val,size);returnPyLong_FromLong((long)val);} Could we arrange for the staticPyObject*h_get(void*ptr,Py_ssize_tsize){unsigned shortval,sign_bit;memcpy(&val,ptr,sizeof(val));if (NUM_BITS(size)) {// Assumes that 0 < NUM_BITS(size) <= width of shortassert(0<NUM_BITS(size));assert(NUM_BITS(size) <=16);sign_bit= (unsigned short)1 <<NUM_BITS(size)-1;// Assumes that 0 <= LOW_BIT(size) < width of shortassert(0 <=LOW_BIT(size));// assert(LOW_BIT(size) < 16);val=val >>LOW_BIT(size)& (sign_bit |sign_bit-1U);val= (val ^sign_bit)-sign_bit; }returnPyLong_FromLong((long)(short)val);} N.B. The |
Thanks for having a look. I actually had a similar idea at first: work in unsigned for the left shift, then convert to signed for the right shift. But I had some bugs in my implementation and things were getting complicated. So instead of thinking too hard, I went with the multiplication instead, which was much simpler for me to wrap my head around. Your code looks good. Probably better than what I was trying to do. If it's similar enough to other fix, then our conpilers can probably see through it just as well, and compile it to the same code we used to have. Btw, I am running benchmarks on main vs a version with -fstrict-overflow enabled, and so far it seems like I'm getting about 1% speedup on (geometric) average across the board for all benchmarks that have a 'significant' difference. It's nothing too exciting, but considering that this is a fairly trivial compiler flag change, I guess we shouldn't complain. (I'm rerunning the benchmarks with slightly different settings, I don't really have much experience with pyperformance.) |
Drive-by comment: there's a use of the cpython/Lib/test/test_ctypes/test_bitfields.py Lines 43 to 44 inc8c0afc
|
I'm thinking something along the lines of this: staticPyObject*h_get(void*ptr,Py_ssize_tsize){unsigned shortval;memcpy(&val,ptr,sizeof(val));GET_SIGNED_BITFIELD(unsigned short,val,size);returnPyLong_FromLong((long)(short)val);} where: #defineGET_SIGNED_BITFIELD(unsigned_type,v,size) \ if (NUM_BITS(size)) { \ unsigned_type sign_bit = (unsigned_type)1 << NUM_BITS(size) - 1; \ v = v >> LOW_BIT(size) & (sign_bit | sign_bit - 1U); \ v = (v ^ sign_bit) - sign_bit; \ } |
I'm a bit worried that there's a deeper bug here. The relevant test case arises from testing this structure:
It seems possible that we're trying to cram the bitfield for In any case, if there is a bug there it would be orthogonal to the fixes in this PR. |
Many bugs.#73939,#84039,#86098,#59324 (which apparently I opened, a long time ago), and more ... In the case of that
and none of those make much sense. But discussion should be deferred to the other issues mentioned above. @matthiasgoergens I don't suppose you'd be interested in becoming a ctypes bitfield expert / champion? There's a definite maintenance hole here. (I'm ruling myself out, I'm afraid: I do have a small portion of the necessary expertise, but unfortunately close to zero of the necessary energy and time.) |
@mdickinson I have a bit of time until mid October, as I am waiting out a non-compete before starting my next job. I'll have a look at the bitfield bugs already mentioned here, let's see how much I can learn. |
This comment was marked as outdated.
This comment was marked as outdated.
Throwing an error may be a backwards incompatible change. Having assignments to bit fields wrap modulo the appropriate power of two wouldn't be unreasonable behaviour; after all, that's what most compilers would do for direct assignments to an integer type that's too small (and is defined behaviour in the case of an unsigned type, and implementation-defined but typical behaviour for signed types). But one of the things that makes ctypes development hard is that we don't just need something reasonable in a self-contained sense; we need something that also matches the behaviour of commonly-used C compilers for the relevant system. So it may be worth a test at C level: if you assign |
mdickinson commentedSep 27, 2022 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Here's what happens with Clang 14.0.0. Using this test code: #include<stdio.h>structbitfields {unsignedintsome_val:2;signedintother_val:2;};intmain(void) {structbitfieldsx;x.some_val=7;x.other_val=7;printf("%u %d\n",x.some_val,x.other_val);return0;} when I compile and run on my laptop, I get |
@mdickinson Yes, the modulo behaviour is exactly what you get on the C level. (Not sure if that's what the standard says, but it's definitely what GCC and clang give you.) I figured that out after I wrote that comment, that's why I hid it afterwards. However, I found some other problems. See the new issue and failing-test PR that I made. |
Uh oh!
There was an error while loading.Please reload this page.
Left-shifting negative numbers is undefined behaviour.
Fortunately, multiplication works just as well, is (implementation) defined behaviour, and gets compiled to the same machine code as before by optimizing compilers.
-fstrict-overflow
#96821