schemagen: parse integer fallback exactly for decimal/exponent forms
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-arm64, ubuntu-latest-arm64, true) (pull_request) Successful in 1m0s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-arm64, ubuntu-latest-arm64, false) (pull_request) Successful in 58s
CI / pre-commit (pull_request) Successful in 51s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-amd64, ubuntu-latest-amd64, true) (pull_request) Successful in 1m30s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-amd64, ubuntu-latest-amd64, false) (pull_request) Successful in 1m24s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-arm64, ubuntu-latest-arm64, true) (pull_request) Successful in 1m0s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-arm64, ubuntu-latest-arm64, false) (pull_request) Successful in 58s
CI / pre-commit (pull_request) Successful in 51s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-amd64, ubuntu-latest-amd64, true) (pull_request) Successful in 1m30s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-amd64, ubuntu-latest-amd64, false) (pull_request) Successful in 1m24s
Replace the double-based fallback in generated integer slots with a string-to-int64 parser that handles decimal points and exponents without losing precision near the int64 boundaries. The old path used std::from_chars<double> and compared against ±9223372036854775808.0, which rounds the int64 max and min so that valid values are rejected and out-of-range negatives are accepted. The new helper: - Parses sign, integer part, optional fraction, and optional exponent. - Strips trailing zeros to cancel fractional places. - Rejects non-integral values and overflow using exact uint64_t arithmetic. Adds regression tests covering root integer and object-field integer boundary values, including the cases from issue #19.
This commit is contained in:
@@ -929,6 +929,90 @@ private:
|
||||
|
||||
void reject() {{ error_ = true; }}
|
||||
|
||||
// Parse a JSON number text as int64_t. Accepts optional decimal point and
|
||||
// exponent only when the mathematical value is an integer that fits in a
|
||||
// signed 64-bit range.
|
||||
static bool parseJsonInt64(const char *b, const char *e, int64_t &out) {{
|
||||
const char *p = b;
|
||||
bool neg = false;
|
||||
if (p < e) {{
|
||||
if (*p == '-') {{ neg = true; ++p; }}
|
||||
else if (*p == '+') return false;
|
||||
}}
|
||||
const char *intStart = p;
|
||||
while (p < e && *p >= '0' && *p <= '9') ++p;
|
||||
const char *intEnd = p;
|
||||
int fracDigits = 0;
|
||||
const char *fracStart = p;
|
||||
if (p < e && *p == '.') {{
|
||||
++p;
|
||||
fracStart = p;
|
||||
while (p < e && *p >= '0' && *p <= '9') {{ ++p; ++fracDigits; }}
|
||||
if (fracStart == p) return false;
|
||||
}}
|
||||
int64_t exp = 0;
|
||||
bool expNeg = false;
|
||||
if (p < e && (*p == 'e' || *p == 'E')) {{
|
||||
++p;
|
||||
if (p < e && (*p == '-' || *p == '+')) {{ expNeg = (*p == '-'); ++p; }}
|
||||
if (p == e || *p < '0' || *p > '9') return false;
|
||||
while (p < e && *p >= '0' && *p <= '9') {{
|
||||
int digit = *p - '0';
|
||||
if (exp <= (INT64_MAX - digit) / 10) exp = exp * 10 + digit;
|
||||
else exp = INT64_MAX;
|
||||
++p;
|
||||
}}
|
||||
if (expNeg) exp = -exp;
|
||||
}}
|
||||
if (p != e) return false;
|
||||
if (intStart == intEnd) return false;
|
||||
|
||||
std::string digits;
|
||||
digits.reserve((intEnd - intStart) + fracDigits);
|
||||
for (const char *q = intStart; q < intEnd; ++q) digits.push_back(*q);
|
||||
for (int i = 0; i < fracDigits; ++i) digits.push_back(fracStart[i]);
|
||||
int64_t trim = 0;
|
||||
while (!digits.empty() && digits.back() == '0') {{
|
||||
digits.pop_back();
|
||||
++trim;
|
||||
}}
|
||||
int64_t finalExp = exp - fracDigits + trim;
|
||||
if (finalExp < 0) return false;
|
||||
while (!digits.empty() && digits.front() == '0') digits.erase(digits.begin());
|
||||
if (digits.empty()) {{ out = 0; return true; }}
|
||||
|
||||
constexpr uint64_t kMaxNeg = 9223372036854775808ULL;
|
||||
constexpr uint64_t kMaxPos = 9223372036854775807ULL;
|
||||
const uint64_t limit = neg ? kMaxNeg : kMaxPos;
|
||||
|
||||
if (finalExp > 19) return false;
|
||||
int64_t maxSig = 19 - finalExp;
|
||||
uint64_t mag = 0;
|
||||
int64_t sigDigits = 0;
|
||||
for (char ch : digits) {{
|
||||
uint64_t d = static_cast<uint64_t>(ch - '0');
|
||||
if (sigDigits >= maxSig) return false;
|
||||
if (mag > (limit - d) / 10) return false;
|
||||
mag = mag * 10 + d;
|
||||
++sigDigits;
|
||||
}}
|
||||
for (int64_t i = 0; i < finalExp; ++i) {{
|
||||
if (mag > limit / 10) return false;
|
||||
mag *= 10;
|
||||
}}
|
||||
if (mag > limit) return false;
|
||||
if (neg) {{
|
||||
if (mag == kMaxNeg) {{
|
||||
out = INT64_MIN;
|
||||
}} else {{
|
||||
out = -static_cast<int64_t>(mag);
|
||||
}}
|
||||
}} else {{
|
||||
out = static_cast<int64_t>(mag);
|
||||
}}
|
||||
return true;
|
||||
}}
|
||||
|
||||
// Wrap the generated slotInfo() with the generic key state.
|
||||
SlotInfo slotInfoG(const Frame &f) {{
|
||||
if (isObjectKind(f.kind)) {{
|
||||
@@ -1038,16 +1122,11 @@ private:
|
||||
*(int64_t *)p = v; // plain integer literal: parsed exactly
|
||||
}} else {{
|
||||
// JSON Schema "integer" accepts any number with no fractional part,
|
||||
// including exponent/decimal forms like 1e3 or 2.0. Parse those as a
|
||||
// double and require an integral value within int64 range.
|
||||
double d = 0;
|
||||
auto rd = std::from_chars(b, e, d);
|
||||
if (rd.ec != std::errc() || rd.ptr != e || d != std::trunc(d) ||
|
||||
d < -9223372036854775808.0 || d >= 9223372036854775808.0) {{
|
||||
reject();
|
||||
return;
|
||||
}}
|
||||
*(int64_t *)p = (int64_t)d;
|
||||
// including exponent/decimal forms like 1e3 or 2.0. Parse those
|
||||
// exactly as int64 when the value is integral and in range.
|
||||
int64_t v2 = 0;
|
||||
if (!parseJsonInt64(b, e, v2)) {{ reject(); return; }}
|
||||
*(int64_t *)p = v2;
|
||||
}}
|
||||
}} else {{
|
||||
double v = 0;
|
||||
|
||||
Reference in New Issue
Block a user