-
-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ImVec2 [] operator aliasing #6272
Comments
Could you confirm the result of that last reduced sample code with the Intel compiler? I am not familiar with aliasing rules well enough to tell if this is actually UB, and what would be an adequate and efficient fix (aka not turning the class into some nightmarish union). |
Yes the reduced example code (basically A save replacement could be:
An efficent replacement could be:
EDIT: |
I last ran into a production problem with this kind of thing on macOS in the mid 00s with GCC, where in a very niche case my Z component became invalid in a In my case it was in a very particular type of leaf function where the compiler had statically determined that the only two components it needed to keep around on the stack were X and Y as none of the code had touched Z enough, so OpenGL read unrelated stack contents for the final component. The standards reading I did at the time which should still hold today is that through a pointer to a member the only subobject reachable is the subobject itself. There is a related rule that you may cast between a pointer to an object and a pointer to the first subobject in a POD type (which I guess is called something else in modern C++) and there's some wording around common initial subsequences, but I do not believe they help here. In my case when calling an external function with a pointer argument, it sufficed to cast the whole object's storage into a Implementation-wise, safe approaches include:
The approach of reinterpreting the underlying storage for the whole struct as a float pointer and indexing that is slightly more sketchy depending on how you read the aliasing rules but should be "better" than forming a pointer to a member. |
EDIT Sorry I forgot to thank you both for your detailed answers. Thank you! My main concern is getting this fixed in whichever compiler is not happy about this, rather than respecting some theoretical ruling. We probably a lots more low-level stuff in the codebase which are practical and effectively functional everywhere. I am concerned with Intel compiler having an issue here. EDIT.2 For avoidance of doubt, performances under typical "Debug" builds condition are as important to us as performances in more Optimized build conditions. To be realistic, in this very case, the stuff dragged by the assert macro is likely most costly but I'm interested in this as the operator is very convenient and I'm tempted to use it more. I quickly compared those 3 versions: float operator[] (size_t idx) const { IM_ASSERT(idx == 0 || idx == 1); return (&x)[idx]; } // We very rarely use this [] operator, the assert overhead is fine.
float& operator[] (size_t idx) { IM_ASSERT(idx == 0 || idx == 1); return (&x)[idx]; } // We very rarely use this [] operator, the assert overhead is fine. (2) float operator[] (size_t idx) const { IM_ASSERT(idx == 0 || idx == 1); return idx ? y : x; } // We very rarely use this [] operator, the assert overhead is fine.
float& operator[] (size_t idx) { IM_ASSERT(idx == 0 || idx == 1); return idx ? y : x; } // We very rarely use this [] operator, the assert overhead is fine. (3) float operator[] (size_t idx) const { IM_ASSERT(idx == 0 || idx == 1); return *(const float*)((const unsigned char*)(&x) + idx * sizeof(float)); } // We very rarely use this [] operator, the assert overhead is fine.
float& operator[] (size_t idx) { IM_ASSERT(idx == 0 || idx == 1); return *(float*)((unsigned char*)(&x) + idx * sizeof(float)); } // We very rarely use this [] operator, the assert overhead is fine.
Could you confirm that (3) written as-is fixes the issue for Intel Compiler? |
I haven't looked at the details here, but isn't an class ImVec2 {
union {
float a[2];
float x,y;
};
...
}; |
That may be cargo-culting but I'm really worried about the side-effect of an union on compilers especially for such a commonly and massively used type (and in contrast the |
@ocornut The unsigned vs. char thingy i must have hallucinated, i could have sworn i saw something about it and then reading about it in this Article had me convinced, but goining over it again its likely a noissue.
And now x and y would share the same memory. not ideal ;) |
Thank you for confirming. float& operator[] (size_t idx) { IM_ASSERT(idx == 0 || idx == 1); return ((float*)(char*)&x)[idx]; }
float operator[] (size_t idx) const { IM_ASSERT(idx == 0 || idx == 1); return ((const float*)(const char*)&x)[idx]; } |
Yes it also works in the limited set (imgui master and reduced code example) I tested BUT the compiler's optimization behavior is unfortunately not as derterministic as one would like. Completely unrelated changes (like in my reduced example on the Intel community forum just adding more output) can throw it off and you will not observe the same behavior. And my guess would still be that all the pointer cast variants here are technically UB. |
Do you mean WITH those solutions, or with the current code in master? |
The solutions here worked in all tested configurations. |
Doh, typo of course should have been a
Fair enough, I have mostly used them in C |
@emoon |
I'm feeling a bit uneasy about using a pointer to the first member rather than the object as a whole here as that's what got us into this mess in the first place, but I have nothing concrete here to back this up except my experience so consider this more of an observation. |
…ntel C++ compiler. (#6272) Note that this is not BayesBug's exact intended solution, so issues would be my responsibility ;)
…ntel C++ compiler. (#6272) Note that this is not BayesBug's exact intended solution, so issues would be my responsibility ;) Amended.
Right. I tested with I agree this is all a bit uneasy but I currently have to prioritize:
So going for that "simpler" solution for now, and in the event we do ever find evidence that there are further real issues we'll react to them, but I'd rather do the strict minimum to solve an existing problem now. Pushed a38e3c2 Thanks a lot for all bringing your expertise with this! |
This usage of unions is valid in C, but (technically) undefined behaviour in C++ (but still typically works as 'non-standard language extension' - personally I haven't seen it break so far in C++). From https://en.cppreference.com/w/cpp/language/union: |
Version/Branch of Dear ImGui:
Version: 1.60+
Branch: all
Back-end/Renderer/Compiler/OS
Back-ends: all
Compiler: all
Operating System: all
My Issue/Question:
ImVec2 got its operator[] in ad09396 accessing the float via pointer access to x. This was syntactically changed later on but basically still works the same way now.
Unfotunately this is wrong as:
Screenshots/Video
N/A
Standalone, minimal, complete and verifiable example:
See the linked Intel community forum thread.
The text was updated successfully, but these errors were encountered: