Strict weak ordering revisited#186
Conversation
|
Thank you for looking into this again. I'd like to merge the code fixing the issue but as of right now there seems to be a lot of debugging / error checking code in there that I'm not sure we really need. I'd argue once it's fixed properly it's unlikely to easily break again. It just seems kinda over engineered to have a 200 line change to fix a bug that, from what I can tell, is a one-liner If we were to keep all the debugging code, I think it would be good to just have it enabled at all times in debug builds to ensure correctness, if it's gated behind a macro it will get forgotten. On another note, please try to follow the general code style / quality of the rest of the code. For example, don't use |
|
Cool thanks! Could you fix the code style issues I raised before there? Then it should be good for merging |
|
That use of |
|
There should not be any |
|
Sorry man. It's been a long time since I've programmed in C++. C++11 era, when it was new. That's one or the reason why I find ImHex interesting (apart from the highlighting, that was a revelation). I had to Google "snake_case". I think I've addressed the style issues. I am unsure on the capitalisation of Note that these changes are in this PR, not this one. |
|
No problem, thanks for the work! |
|
Like this? Again, in the other leaner PR. |
Overview
Some time ago I fixed a crash related to sorting in the pattern drawer. The cause was a sort predicate that did not implement strict weak ordering correctly. I have revisited this and found (and fixed) another violation (transitivity of incomparability).
The code I used to find this is included in this PR (disabled).
The issue is here.
A PR without the sort checking code.
A bad sort predicate. So what?
I made this repo to demonstrate some of the potential consequences of incorrect strict weak ordering. I've made it easy. It's simple and can be built with CMake. I've mainly ran it on mingw64 (it exhibits the most severe consequences, and the ImHex I use is built with it). It uses a deliberatly poorly coded sort predicate which attempts to simulate the bug I referred to above. It shuffles a vector; sorts it with a bad predicate; check for gargabe; repeats. Strap in and check it out. Here's an example run I'll kick off right now.
The problem
Incomparability for us just means equality. If
x<yisfalseandy<xisfalse(xandyare incomparable) we sayx==y.In the code path were I found this issue (sorting by the "Value" column) the code in
std::strong_ordering Token::Literal::operator<=>does the comparison. Iflhsandrhsare the same type the implementation is pretty straight forward. It handles some cases wherelhsandrhsand different numeric types.Then we get this
If
lhsandrhsare different types we consider them equal. This is the most sensibe value we could unconditionally return, but in our case it wont do the trick.Say we have a
u128x, astd::stringyand anotheru128z.The code the above gives
x==y(different types).Similarly
y==z.Transitivity of incomparability (equality) mandates that in this case
x==z.But
xandzare the same type,u128. The comparison is numerical. Ifxandzhave different values thenx==zis NOTtrueas is required. We have violated the strict weak ordering.The solution in the PR
I have altered the code quoted above to look like this:
hlp::variant_type_index_vis a utility I wrote the get the index of a type in astd::variant.The
std::variantwe use looks like this:So
char<bool,bool<std::shared_ptr<ptrn::Pattern>etc... The comparison is based on the type index in thestd::variant.In the example I gave above now
x<yandy>z, so transitivity does not apply.Conslusion
A sort predicate that does not implement a strict weak ordering correctly is dangerous. Elements can be deleted. Element that never existed in the sequence to be sorted are sometimes conjured. In the example program I linked above I get heap corruption. MingW64 seems particularly vulnarable. I'm not judging here. Perhaps when the predicate is well formed this is a strength. It's not a strength when it's malformed however.