Skip to content

Commit 1d2ace2

Browse files
committed
Fix for bug 908824
1 parent 50f4669 commit 1d2ace2

File tree

2 files changed

+95
-53
lines changed

2 files changed

+95
-53
lines changed

Release/src/json/json_parsing.cpp

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
****/
2727

2828
#include "stdafx.h"
29+
#include <vector>
2930

3031
#pragma warning(disable : 4127) // allow expressions like while(true) pass
3132
using namespace web;
@@ -367,6 +368,34 @@ inline bool JSON_Parser<CharType>::ParseInt64(CharType first, uint64_t& value)
367368
return true;
368369
}
369370

371+
// This namespace hides the x-plat helper functions
372+
namespace
373+
{
374+
#ifdef _MS_WINDOWS
375+
static int print_llu(char* ptr, size_t n, uint64_t val64)
376+
{
377+
return _snprintf_s(ptr, n, _TRUNCATE, "%I64u", val64);
378+
}
379+
380+
static int print_llu(wchar_t* ptr, size_t n, uint64_t val64)
381+
{
382+
return _snwprintf_s(ptr, n, _TRUNCATE, L"%I64u", val64);
383+
}
384+
#else
385+
static int print_llu(char* ptr, size_t n, unsigned long long val64)
386+
{
387+
return snprintf(ptr, n, "%llu", val64);
388+
}
389+
static int print_llu(char* ptr, size_t n, unsigned long val64)
390+
{
391+
return snprintf(ptr, n, "%lu", val64);
392+
}
393+
#endif
394+
395+
static double anystod(const char* str) { return strtod(str, nullptr); }
396+
static double anystod(const wchar_t* str) { return wcstod(str, nullptr); }
397+
}
398+
370399
template <typename CharType>
371400
bool JSON_Parser<CharType>::CompleteNumberLiteral(CharType first, Token &token)
372401
{
@@ -424,25 +453,22 @@ bool JSON_Parser<CharType>::CompleteNumberLiteral(CharType first, Token &token)
424453
return true;
425454
}
426455

427-
double value = static_cast<double>(val64);
428-
bool decimal = false;
429-
int after_decimal = 0; // counts digits after the decimal
456+
// Magic number 5 leaves room for decimal point, null terminator, etc (in most cases)
457+
::std::vector<CharType> buf(::std::numeric_limits<uint64_t>::digits10 + 5);
458+
int count = print_llu(buf.data(), buf.size(), val64);
459+
_ASSERTE(count >= 0);
460+
_ASSERTE((size_t)count < buf.size());
461+
// Resize to cut off the null terminator
462+
buf.resize(count);
430463

431-
// Exponent and related flags
432-
int exponent = 0;
433-
bool has_exponent = false;
434-
bool exponent_minus = false;
464+
bool decimal = false;
435465

436466
while (ch != this->m_eof)
437467
{
438468
// Digit encountered?
439469
if (ch >= '0' && ch <= '9')
440470
{
441-
value *= 10;
442-
value += ch - '0';
443-
if (decimal)
444-
after_decimal++;
445-
471+
buf.push_back(ch);
446472
NextCharacter();
447473
ch = PeekCharacter();
448474
}
@@ -454,6 +480,7 @@ bool JSON_Parser<CharType>::CompleteNumberLiteral(CharType first, Token &token)
454480
return false;
455481

456482
decimal = true;
483+
buf.push_back(ch);
457484

458485
NextCharacter();
459486
ch = PeekCharacter();
@@ -462,40 +489,36 @@ bool JSON_Parser<CharType>::CompleteNumberLiteral(CharType first, Token &token)
462489
if (ch < '0' || ch > '9')
463490
return false;
464491

465-
// Parse it
466-
value *= 10;
467-
value += ch - '0';
468-
after_decimal = 1;
469-
492+
buf.push_back(ch);
470493
NextCharacter();
471494
ch = PeekCharacter();
472495
}
473496

474497
// Exponent?
475498
else if (ch == 'E' || ch == 'e')
476499
{
477-
has_exponent = true;
500+
buf.push_back(ch);
478501
NextCharacter();
479502
ch = PeekCharacter();
480503

481504
// Check for the exponent sign
482505
if (ch == '+')
483506
{
507+
buf.push_back(ch);
484508
NextCharacter();
485509
ch = PeekCharacter();
486510
}
487511
else if (ch == '-')
488512
{
489-
exponent_minus = true;
513+
buf.push_back(ch);
490514
NextCharacter();
491515
ch = PeekCharacter();
492516
}
493517

494518
// First number of the exponent
495519
if (ch >= '0' && ch <= '9')
496520
{
497-
exponent = ch - '0';
498-
521+
buf.push_back(ch);
499522
NextCharacter();
500523
ch = PeekCharacter();
501524
}
@@ -504,9 +527,7 @@ bool JSON_Parser<CharType>::CompleteNumberLiteral(CharType first, Token &token)
504527
// The rest of the exponent
505528
while (ch >= '0' && ch <= '9')
506529
{
507-
exponent *= 10;
508-
exponent += ch - '0';
509-
530+
buf.push_back(ch);
510531
NextCharacter();
511532
ch = PeekCharacter();
512533
}
@@ -521,24 +542,12 @@ bool JSON_Parser<CharType>::CompleteNumberLiteral(CharType first, Token &token)
521542
}
522543
};
523544

545+
buf.push_back('\0');
546+
token.double_val = anystod(buf.data());
524547
if (minus_sign)
525-
value = -value;
526-
527-
if (has_exponent)
528548
{
529-
if (exponent_minus)
530-
exponent = -exponent;
531-
532-
if (decimal)
533-
exponent -= after_decimal;
534-
535-
token.double_val = value * pow(double(10), exponent);
549+
token.double_val = -token.double_val;
536550
}
537-
else
538-
{
539-
token.double_val = value / pow(double(10), after_decimal);
540-
}
541-
542551
token.kind = (JSON_Parser<CharType>::Token::TKN_NumberLiteral);
543552

544553
return true;

Release/tests/Functional/json/json_numbers_tests.cpp

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
#include "stdafx.h"
2727
#include <iomanip>
28+
#include <clocale>
2829

2930
using namespace web; using namespace utility;
3031

@@ -124,12 +125,12 @@ TEST(parse_uint64)
124125
test_int64(uint64_t(ULLONG_MAX)-1);
125126
}
126127

127-
void test_double(double number)
128+
const int DOUBLE_DIGITS = std::numeric_limits<double>::digits10 + 7; //7 = length of "1." and "e+123" which is the begining and the end of the double representation
129+
130+
void test_double(double number, string_t str_rep)
128131
{
129-
int precision = std::numeric_limits<double>::digits10 + 7; //7 = length of "1." and "e+123" which is the begining and the end of the double representation
130132
stringstream_t ss;
131-
ss << std::setprecision(precision);
132-
ss << number;
133+
ss << str_rep;
133134

134135
json::value num = json::value::parse(ss);
135136
VERIFY_ARE_EQUAL(number, num.as_double());
@@ -140,41 +141,73 @@ void test_double(double number)
140141

141142
// If it is outside the range, these methods should return false.
142143
// Note that at this point there is no guarantee that the number was stored as double.
143-
144+
144145
if (number < INT_MIN || number > INT_MAX || number!=floor(number))
145146
VERIFY_IS_FALSE(num.as_number().is_int32());
146-
147+
147148
if (number < 0 || number > UINT_MAX || number!=floor(number))
148149
VERIFY_IS_FALSE(num.as_number().is_uint32());
149-
150+
150151
if (number < LLONG_MIN || number > LLONG_MAX || number!=floor(number))
151152
VERIFY_IS_FALSE(num.as_number().is_int64());
152153

153154
if (number < 0 || number > ULLONG_MAX || number!=floor(number))
154155
VERIFY_IS_FALSE(num.as_number().is_uint64());
155156
}
156157

157-
TEST(parsing_doubles)
158+
void test_double(double d)
159+
{
160+
::std::basic_stringstream<string_t::value_type> ss;
161+
ss << ::std::setprecision(DOUBLE_DIGITS);
162+
ss << d;
163+
test_double(d, ss.str());
164+
}
165+
166+
TEST(parsing_doubles_into_longs)
158167
{
159168
test_double(2.0);
169+
test_double(pow(2.0, 10.0));
170+
test_double(pow(2.0, 20.0));
171+
test_double(pow(2.0, 60.0));
172+
test_double(pow(2.0, 63.0));
173+
}
174+
175+
TEST(parsing_doubles)
176+
{
160177
test_double(3.14);
161178
test_double(-9.81);
179+
180+
// Note: this should not parse to a ullong because of rounding
162181
test_double(static_cast<double>(ULLONG_MAX));
182+
163183
test_double(0 - static_cast<double>(ULLONG_MAX));
164184
test_double(static_cast<double>(ULLONG_MAX)+(2<<(64-52))); // the lowest number that will be represented as double due to overflowing unsigned int64 (52bits fraction in double-precision)
165-
test_double(pow(2.0, 10.0));
166-
test_double(pow(2.0, 20.0));
167-
test_double(pow(2.0, 60.0));
168-
test_double(pow(2.0, 63.0));
169185
test_double(0 - pow(2.0, 63.0) * 1.5); // between 0-ULLONG_MAX and LLONGMIN
170186
}
171187

172-
TEST(parsing_very_large_doubles, "Ignore:Linux", "Bug 908824")
188+
TEST(parsing_doubles_setlocale, "Ignore", "Bug 912803")
189+
{
190+
// JSON uses the C locale always and should therefore not be impacted by the process locale
191+
setlocale(LC_ALL, "fr-FR");
192+
test_double(1.91563, U("1.91563"));
193+
test_double(2.0e93, U("2.0e93"));
194+
setlocale(LC_ALL, "C");
195+
}
196+
197+
TEST(parsing_very_large_doubles)
173198
{
174199
test_double(pow(2.0, 64.0));
175200
test_double(pow(2.0, 70.0));
176201
test_double(pow(2.0, 80.0));
177202
test_double(pow(2.0, 120.0));
203+
test_double(pow(2.0, 240.0));
204+
test_double(pow(2.0, 300.0));
205+
}
206+
207+
TEST(parsing_very_small_doubles)
208+
{
209+
test_double(2.34e-308);
210+
test_double(1e-308);
178211
}
179212

180213
void test_integral(int number)
@@ -267,4 +300,4 @@ TEST(compare_numbers)
267300

268301
} // SUITE(json_numbers_tests)
269302

270-
}}}
303+
}}}

0 commit comments

Comments
 (0)