Fix integer comparison bug#5211
Conversation
🔴 Amalgamation check failed! 🔴The source code has not been amalgamated. @ljcjclljc |
There was a problem hiding this comment.
This seems reasonable at a low level. There may be some cases that should be documented where a < b and json(a) < json(b) will provide different results. Those are the same cases where the compiler should have been warning you not to do that a < b, so that's probably okay, but good to call it out explicitly.
| return lhs.m_data.m_value.number_float op static_cast<number_float_t>(rhs.m_data.m_value.number_unsigned); \ | ||
| } \ | ||
| else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) \ | ||
| else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) \ |
| { \ | ||
| return number_integer_t(1) op number_integer_t(0); \ | ||
| } \ | ||
| return lhs.m_data.m_value.number_unsigned op static_cast<number_unsigned_t>(rhs.m_data.m_value.number_integer); \ |
There was a problem hiding this comment.
This is a good change, aligns more with the C++ promotion rules.
Signed-off-by: ljccjlljc <939159710@qq.com>
…r std_fs::path (nlohmann#5209) * added explicit instantiations of 'has_from_json' and 'has_to_json' for std_fs::path; Signed-off-by: drcosmin <cosmin.dr@pm.me> * Fixed amalgamation Signed-off-by: drcosmin <cosmin.dr@pm.me> --------- Signed-off-by: drcosmin <cosmin.dr@pm.me> Signed-off-by: ljccjlljc <939159710@qq.com>
Signed-off-by: ljccjlljc <939159710@qq.com>
Signed-off-by: ljccjlljc <939159710@qq.com>
🔴 Amalgamation check failed! 🔴The source code has not been amalgamated. @ljcjclljc |
Signed-off-by: ljccjlljc <939159710@qq.com>
gregmarr
left a comment
There was a problem hiding this comment.
IMO, this needs a lot more documentation, both in the main documentation section, and somewhere that will show up in the changes section of the readme as a potentially breaking change, with the appropriate rationale, as described in the linked issue.
There was a problem hiding this comment.
Should this be part of this PR, or separate? I am leaning towards separate.
There was a problem hiding this comment.
Yes, please separate this from the original PR scope.
There was a problem hiding this comment.
This change is already on develop branch. Please rebase.
There was a problem hiding this comment.
Should this be part of this PR, or separate? I am leaning towards separate.
There was a problem hiding this comment.
Yes, please separate this from the original PR scope.
There was a problem hiding this comment.
This change is already on develop branch. Please rebase.
| return lhs.m_data.m_value.number_unsigned op static_cast<number_unsigned_t>(rhs.m_data.m_value.number_integer); \ | ||
| } \ | ||
| else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) \ | ||
| else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) /* NOLINT(readability/braces) */ \ |
There was a problem hiding this comment.
Rather than disabling the lint warning, why not just add the parens and make it clear what the precedence is?
| // Mixed signed/unsigned integer comparisons check for negative signed | ||
| // values before casting. This avoids wraparound when converting a negative | ||
| // integer to an unsigned type and preserves the rule that any negative | ||
| // integer is smaller than any unsigned integer. |
There was a problem hiding this comment.
Since we probably don't want to put a comment inside the macro explaining the actual op, should there be one here?
// This is done by checking for the signed value being less than zero.
// If it is, the comparison is performed using `0` for the signed value
// and `1` for the unsigned value. Since the negative signed value will
// always be less than the unsigned value, any two fixed values that
// have the same relationship will produce the right result.
// If the signed value is not less than zero, it is cast to unsigned
// before doing the comparison.
I'm wondering if -1 and 1 would be clearer than 0 and 1. That way you can explicitly see the negative sign.
| return lhs.m_data.m_value.number_float op static_cast<number_float_t>(rhs.m_data.m_value.number_unsigned); \ | ||
| } \ | ||
| else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) \ | ||
| else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) /* NOLINT(readability/braces) */ \ |
There was a problem hiding this comment.
Rather than disabling the lint warning, why not just add the parens and make it clear what the precedence is?
nlohmann
left a comment
There was a problem hiding this comment.
Some comments. Will review later.
There was a problem hiding this comment.
Yes, please separate this from the original PR scope.
There was a problem hiding this comment.
Yes, please separate this from the original PR scope.
| return lhs.m_data.m_value.number_float op static_cast<number_float_t>(rhs.m_data.m_value.number_unsigned); \ | ||
| } \ | ||
| else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) \ | ||
| else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) /* NOLINT(readability/braces) */ \ |
| return lhs.m_data.m_value.number_unsigned op static_cast<number_unsigned_t>(rhs.m_data.m_value.number_integer); \ | ||
| } \ | ||
| else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) \ | ||
| else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) /* NOLINT(readability/braces) */ \ |
Signed-off-by: ljccjlljc <939159710@qq.com>
Signed-off-by: ljccjlljc <939159710@qq.com>
| @@ -0,0 +1,19 @@ | |||
| #include <cstdint> | |||
There was a problem hiding this comment.
This file can now be removed given we have tests/src/unit-comparison.cpp.
There was a problem hiding this comment.
This change is already on develop branch. Please rebase.
There was a problem hiding this comment.
This change is already on develop branch. Please rebase.
| const json max_uint64 = (std::numeric_limits<std::uint64_t>::max)(); | ||
| const json negative_one = -1; | ||
|
|
||
| CHECK_FALSE(above_int64_max == negative_one); |
There was a problem hiding this comment.
The tests only check == and < but not <=, >, >=, !=, or operator<=>, and don't cover the non‑negative‑signed + large‑unsigned path. Please add these.
This PR provides a minimal implementation for #5210.
Fiex #5210
Background
When comparing
number_unsignedandnumber_integer, the current implementation converts the unsigned value tonumber_integer_tbefore applying the operator.This is problematic for values above
INT64_MAX, because converting suchuint64_tvalues tonumber_integer_tchanges them into the negative signed range. As a result, valid mixed signed/unsigned comparisons can return mathematically incorrect results.Problem
The affected logic is in the mixed comparison branches of
JSON_IMPLEMENT_OPERATORininclude/nlohmann/json.hpp.Before this change, comparisons such as these could fail:
json(-1) < json(18446744073709551615ULL)returnedfalsejson(18446744073709551615ULL) == json(-1)returnedtrueThis is not limited to one construction path. The same incorrect behavior can be observed after direct construction, object assignment, array insertion, initializer-list construction, nested access, and
json::parse().Solution
This PR keeps the existing macro shape and applies the smallest possible fix in the mixed
number_unsigned/number_integercomparison branches:number_unsigned_tand compare in the unsigned domain.This avoids the unsafe
uint64_t -> int64_tconversion while preserving the existing comparison structure.Scope
This PR is intentionally minimal:
It only fixes the signed/unsigned mixed comparison bug described in #5210.
https://github.com/nlohmann/json/issuesis referenced.https://coveralls.io/github/nlohmann/jsonremained at 100%. A test case for every new line of code.https://json.nlohmann.meis updated.make amalgamate.