Use of C++ undefined behavior in various `IteratorComparators` #19496

issue cculianu opened this issue on July 11, 2020
  1. cculianu commented at 8:24 PM on July 11, 2020: none

    Expected behavior

    All C++ code to be 100% using legal C++ and not UB which can lead to subtle bugs if not now, then perhaps in the future.

    Actual behavior

    Use of undefined behavior in at least two places:

    Background

    Please see this part of the C++ specification: https://en.cppreference.com/w/cpp/language/operator_comparison

    In particular I will quote the relevant section here:

    If two pointers are not specified to compare greater or compare equal, the result of the comparison is unspecified. The result may be nondeterministic, and need not be consistent even for multiple evaluations of the same expression with the same operands in the same execution of the program:

    int x, y;
     
    bool f(int* p, int* q) { return p < q; }
     
    assert(f(&x, &y) == f(&x, &y)); // may fire in a conforming implementation
    

    Note that in the above-linked section (which I admit is difficult to decipher) -- pointers are only specified to have a legal < or > comparison operations if the objects they point to either belong to the same array, or if the objects they point to live basically as members in the same object (i.e. as class members). This is unlike in C. The code will compile.. it will run -- but like the above section states, and the above sample program illustrates -- the results may be non-deterministic!

    In practice it appears the code works in bitcoin for all known compilers and platforms -- but is not guaranteed to do so forever and may lead to subtle bugs in the future as compilers evolve or as libstdc++ evolves.

  2. cculianu added the label Bug on Jul 11, 2020
  3. practicalswift commented at 9:53 PM on July 11, 2020: contributor

    Is this one of those cases where std::less is more? :)

  4. cculianu commented at 11:36 PM on July 11, 2020: none

    Ha ha ha 😄

    No, I don't believe so. :) (The iterators in question don't define operator< .. one would have to actually examine the underlying .first perhaps and do < on those...)

  5. sipa commented at 4:09 PM on July 12, 2020: member

    I am not convinced this is UB.

    Pointer comparison of objects that are not in the same array (or one past it) is unspecified in C++, not undefined. This means the compiler can choose whatever it likes about the order of the two objects, but it's one or the other, and cannot do anything else.

    However, there is no guaranteed total ordering on unrelated pointers. This means that while not UB, the code in IteratorComparator may in theory not do what it's intended to do. E.g. a<b and b<a might both be simultaneously true.

    I'm not sure if this is what @practicalswift was alluding to, but std::less (and friends) when specialized to any given pointer type is a well defined total ordering, even when < etc are not.

    So it seems that to be fully compliant, it is sufficient to replace < with std::less in these sorts of operations.

  6. practicalswift commented at 9:07 PM on July 12, 2020: contributor

    @sipa

    This means that while not UB, the code in IteratorComparator may in theory not do what it's intended to do. E.g. a<b and b<a might both be simultaneously true.

    I'm not sure if this is what @practicalswift was alluding to, but std::less (and friends) when specialized to any given pointer type is a well defined total ordering, even when < etc are not.

    So it seems that to be fully compliant, it is sufficient to replace < with std::less in these sorts of operations.

    Exactly! That's why std::less is more :)

  7. cculianu commented at 8:48 AM on July 13, 2020: none

    Hmm. In theory std::less is supposed to be a demarcation point where the compiler's optimizer "lays off" with the assumptions -- there are known bugs in GCC for example (many gcc stdlibcc++'s just default to < anyway without any magic given to the optimizer). The problem is not all compilers do the right thing with std::less.. it's not easy. Here is a relevant blog post about this from 2016 that is quite informative: https://quuxplusone.github.io/blog/2019/01/20/std-less-nightmare/

    But yes.. it does appear that std::less is more.. as @practicalswift originally asserted. At least in theory. :/

    There be dragons here...

  8. practicalswift commented at 1:28 PM on July 13, 2020: contributor

    @cculianu

    Nice find BTW and thanks for reporting!

    May I ask how you found this case? Static analysis?


    Nit: Perhaps "undefined behaviour" in the title could be replaced with "unspecified behaviour" in order to not confuse readers?

    The subtle differences between undefined, unspecified and implementation-defined in the C++ standard:

    • undefined behavior: "behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements."
    • unspecified behavior: "use of an unspecified value, or other behavior where this International Standard provides two or more possibilities and imposes no further requirements on which is chosen in any instance"
    • implementation-defined behavior: "unspecified behavior where each implementation documents how the choice is made"
  9. cculianu commented at 7:57 PM on July 13, 2020: none

    Nice find BTW and thanks for reporting! @practicalswift -- My pleasure. You guys have a lot of influence on what happens in to the Bitcoins* (what BTC people refer to as altcoins such as Bitcoin Cash, Bitcoin SV, Bitcoin Gold, etc) -- so Core software being top notch impacts us all.

    May I ask how you found this case? Static analysis?

    Ha -- strangely the free static analyzers didn't catch this. I do have a PVS Studio license -- I should try that and see if it catches it. No.. I used "Eyeball analysis". I'm maintaining a downstream version of your code -- I work for the Bitcoin Cash Node project. So -- we have a lot of similarities in terms of large parts of the system. I initially found this in our software then went to the source (you guys) to discuss it.


    As for the delineation between UB vs. unspecified -- I'll leave it up to you. I am not exactly sure either here without reading the "standardese" more carefully. If you like I can modify the title of this issue or you can as well without objection.

  10. maflcko commented at 10:17 AM on August 4, 2022: member

    The problem is not all compilers do the right thing with std::less

    Not sure what to do here then. For now the current code happens to work for some reason and when changing to std::less doesn't give additional checks, then it might be best to leave the code as is? Or just change to std::less and treat it as upstream bug if something goes wrong?

  11. willcl-ark referenced this in commit c1569d3e10 on Feb 20, 2023
  12. willcl-ark referenced this in commit 8c60024518 on Feb 20, 2023
  13. willcl-ark referenced this in commit 930cd7ed8c on Feb 20, 2023
  14. willcl-ark referenced this in commit c98d75329f on Feb 20, 2023
  15. willcl-ark commented at 2:13 PM on October 21, 2025: member

    This issue hasn’t attracted much interest from other contributors in quite some time.

    Given that, it doesn’t seem important enough to keep open indefinitely. I’m going to close it for now due to lack of activity, but pull requests or renewed discussion are always welcome.

    Comment here if you think this shoudl be re-opened.

  16. willcl-ark closed this on Oct 21, 2025

  17. maflcko commented at 2:14 PM on October 21, 2025: member

    This was fixed in e009bf681c0e38a6451afa594ba3c7c8861f61c3 , no?


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-05-01 00:14 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me