-
Notifications
You must be signed in to change notification settings - Fork 1.8k
reject out-of-range integers in JsonReader::ParseInt #16240
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ | |
| #ifndef FIRESTORE_CORE_SRC_UTIL_JSON_READER_H_ | ||
| #define FIRESTORE_CORE_SRC_UTIL_JSON_READER_H_ | ||
|
|
||
| #include <limits> | ||
| #include <string> | ||
| #include <vector> | ||
|
|
||
|
|
@@ -99,6 +100,40 @@ class JsonReader : public util::ReadContext { | |
| template <typename IntType> | ||
| IntType ParseInt(const nlohmann::json& value, JsonReader& reader) { | ||
| if (value.is_number_integer()) { | ||
| // nlohmann stores integers as int64_t or uint64_t, so `get<IntType>()` | ||
| // silently wraps a value that does not fit `IntType`. Reject it instead, | ||
| // matching the range checking the string branch below gets from | ||
| // `absl::SimpleAtoi`. | ||
| bool out_of_range = false; | ||
| if (value.is_number_unsigned()) { | ||
| out_of_range = | ||
| value.get<uint64_t>() > | ||
| static_cast<uint64_t>(std::numeric_limits<IntType>::max()); | ||
| } else { | ||
| // A non-negative value may still be stored as a signed `int64_t`, so | ||
| // accept it when it fits an unsigned `IntType` rather than rejecting | ||
| // every signed representation outright. Compute the bounds as | ||
| // `int64_t`/`uint64_t` so the comparisons never narrow `IntType::max()` | ||
| // into a signed type, which would warn for unsigned instantiations. | ||
| const int64_t val = value.get<int64_t>(); | ||
| const int64_t min_limit = | ||
| std::numeric_limits<IntType>::is_signed | ||
| ? static_cast<int64_t>(std::numeric_limits<IntType>::min()) | ||
| : 0; | ||
| const uint64_t max_limit = | ||
| static_cast<uint64_t>(std::numeric_limits<IntType>::max()); | ||
| if (std::numeric_limits<IntType>::is_signed) { | ||
| out_of_range = val < min_limit || | ||
| (val > 0 && static_cast<uint64_t>(val) > max_limit); | ||
| } else { | ||
| out_of_range = val < 0 || static_cast<uint64_t>(val) > max_limit; | ||
| } | ||
| } | ||
|
Comment on lines
+108
to
+131
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current implementation of bounds checking can trigger compiler warnings (such as We can avoid these warnings and simplify the code by calculating the limits safely as bool out_of_range = false;
if (value.is_number_unsigned()) {
out_of_range = value.get<uint64_t>() >
static_cast<uint64_t>(std::numeric_limits<IntType>::max());
} else {
// A non-negative value may still be stored as a signed int64_t, so
// accept it when it fits an unsigned IntType rather than rejecting
// every signed representation outright.
const int64_t val = value.get<int64_t>();
const int64_t min_limit =
std::numeric_limits<IntType>::is_signed
? static_cast<int64_t>(std::numeric_limits<IntType>::min())
: 0;
const uint64_t max_limit =
static_cast<uint64_t>(std::numeric_limits<IntType>::max());
if (std::numeric_limits<IntType>::is_signed) {
out_of_range = val < min_limit ||
(val > 0 && static_cast<uint64_t>(val) > max_limit);
} else {
out_of_range = val < 0 || static_cast<uint64_t>(val) > max_limit;
}
}
if (out_of_range) {
reader.Fail("Integer value out of range: " + value.dump());
return 0;
}
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. The bounds are now computed up front as |
||
|
|
||
| if (out_of_range) { | ||
| reader.Fail("Integer value out of range: " + value.dump()); | ||
| return 0; | ||
| } | ||
|
Comment on lines
+108
to
+131
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current implementation unconditionally rejects parsing any signed integer representation into an unsigned Instead of failing immediately when if (value.is_number_unsigned()) {
if (value.get<uint64_t>() >
static_cast<uint64_t>(std::numeric_limits<IntType>::max())) {
reader.Fail("Integer value out of range: " + value.dump());
return 0;
}
} else {
int64_t val = value.get<int64_t>();
if (std::numeric_limits<IntType>::is_signed) {
if (val < static_cast<int64_t>(std::numeric_limits<IntType>::min()) ||
val > static_cast<int64_t>(std::numeric_limits<IntType>::max())) {
reader.Fail("Integer value out of range: " + value.dump());
return 0;
}
} else {
if (val < 0 ||
static_cast<uint64_t>(val) >
static_cast<uint64_t>(std::numeric_limits<IntType>::max())) {
reader.Fail("Integer value out of range: " + value.dump());
return 0;
}
}
}
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. Reworked the signed branch so a non-negative value stored as |
||
| return value.get<IntType>(); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current implementation duplicates the error handling logic (
reader.Fail(...)andreturn 0;) three times across different branches. SinceParseIntis a template function, this duplication is repeated for every instantiation of the template (e.g., forint32_t,uint32_t,int64_t,uint64_t), leading to unnecessary template code bloat and larger binary sizes.We can refactor this by using a boolean flag
out_of_rangeto track the validation status and perform a single error handling call at the end. This improves maintainability and reduces the compiled code size.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Folded into the same change, error handling is now a single
out_of_rangeflag checked once instead of the three duplicatedFail/returnblocks.