Number fixes (#1053)

* cleaning up the logic for parsing numbers

* Add Testcases for new Reader in jsontestrunner
This commit is contained in:
Jordan Bayles 2019-11-08 19:49:16 -08:00 committed by dota17
parent ff58fdcc75
commit 645cd0412c
115 changed files with 123 additions and 75 deletions

View File

@ -15,7 +15,7 @@ project(
'cpp_std=c++11', 'cpp_std=c++11',
'warning_level=1'], 'warning_level=1'],
license : 'Public Domain', license : 'Public Domain',
meson_version : '>= 0.50.0') meson_version : '>= 0.49.0')
jsoncpp_headers = [ jsoncpp_headers = [

View File

@ -15,7 +15,9 @@
#include <algorithm> // sort #include <algorithm> // sort
#include <cstdio> #include <cstdio>
#include <iostream>
#include <json/json.h> #include <json/json.h>
#include <memory>
#include <sstream> #include <sstream>
struct Options { struct Options {
@ -126,19 +128,45 @@ static int parseAndSaveValueTree(const Json::String& input,
const Json::String& actual, const Json::String& actual,
const Json::String& kind, const Json::String& kind,
const Json::Features& features, bool parseOnly, const Json::Features& features, bool parseOnly,
Json::Value* root) { Json::Value* root, bool use_legacy) {
Json::Reader reader(features); if (!use_legacy) {
bool parsingSuccessful = Json::CharReaderBuilder builder;
reader.parse(input.data(), input.data() + input.size(), *root);
if (!parsingSuccessful) { builder.settings_["allowComments"] = features.allowComments_;
printf("Failed to parse %s file: \n%s\n", kind.c_str(), builder.settings_["strictRoot"] = features.strictRoot_;
reader.getFormattedErrorMessages().c_str()); builder.settings_["allowDroppedNullPlaceholders"] =
return 1; features.allowDroppedNullPlaceholders_;
builder.settings_["allowNumericKeys"] = features.allowNumericKeys_;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
Json::String errors;
const bool parsingSuccessful =
reader->parse(input.data(), input.data() + input.size(), root, &errors);
if (!parsingSuccessful) {
std::cerr << "Failed to parse " << kind << " file: " << std::endl
<< errors << std::endl;
return 1;
}
// We may instead check the legacy implementation (to ensure it doesn't
// randomly get broken).
} else {
Json::Reader reader(features);
const bool parsingSuccessful =
reader.parse(input.data(), input.data() + input.size(), *root);
if (!parsingSuccessful) {
std::cerr << "Failed to parse " << kind << " file: " << std::endl
<< reader.getFormatedErrorMessages() << std::endl;
return 1;
}
} }
if (!parseOnly) { if (!parseOnly) {
FILE* factual = fopen(actual.c_str(), "wt"); FILE* factual = fopen(actual.c_str(), "wt");
if (!factual) { if (!factual) {
printf("Failed to create %s actual file.\n", kind.c_str()); std::cerr << "Failed to create '" << kind << "' actual file."
<< std::endl;
return 2; return 2;
} }
printValueTree(factual, *root); printValueTree(factual, *root);
@ -172,7 +200,7 @@ static int rewriteValueTree(const Json::String& rewritePath,
*rewrite = write(root); *rewrite = write(root);
FILE* fout = fopen(rewritePath.c_str(), "wt"); FILE* fout = fopen(rewritePath.c_str(), "wt");
if (!fout) { if (!fout) {
printf("Failed to create rewrite file: %s\n", rewritePath.c_str()); std::cerr << "Failed to create rewrite file: " << rewritePath << std::endl;
return 2; return 2;
} }
fprintf(fout, "%s\n", rewrite->c_str()); fprintf(fout, "%s\n", rewrite->c_str());
@ -193,14 +221,15 @@ static Json::String removeSuffix(const Json::String& path,
static void printConfig() { static void printConfig() {
// Print the configuration used to compile JsonCpp // Print the configuration used to compile JsonCpp
#if defined(JSON_NO_INT64) #if defined(JSON_NO_INT64)
printf("JSON_NO_INT64=1\n"); std::cout << "JSON_NO_INT64=1" << std::endl;
#else #else
printf("JSON_NO_INT64=0\n"); std::cout << "JSON_NO_INT64=0" << std::endl;
#endif #endif
} }
static int printUsage(const char* argv[]) { static int printUsage(const char* argv[]) {
printf("Usage: %s [--strict] input-json-file", argv[0]); std::cout << "Usage: " << argv[0] << " [--strict] input-json-file"
<< std::endl;
return 3; return 3;
} }
@ -230,7 +259,7 @@ static int parseCommandLine(int argc, const char* argv[], Options* opts) {
} else if (writerName == "BuiltStyledStreamWriter") { } else if (writerName == "BuiltStyledStreamWriter") {
opts->write = &useBuiltStyledStreamWriter; opts->write = &useBuiltStyledStreamWriter;
} else { } else {
printf("Unknown '--json-writer %s'\n", writerName.c_str()); std::cerr << "Unknown '--json-writer' " << writerName << std::endl;
return 4; return 4;
} }
} }
@ -240,19 +269,20 @@ static int parseCommandLine(int argc, const char* argv[], Options* opts) {
opts->path = argv[index]; opts->path = argv[index];
return 0; return 0;
} }
static int runTest(Options const& opts) {
static int runTest(Options const& opts, bool use_legacy) {
int exitCode = 0; int exitCode = 0;
Json::String input = readInputTestFile(opts.path.c_str()); Json::String input = readInputTestFile(opts.path.c_str());
if (input.empty()) { if (input.empty()) {
printf("Failed to read input or empty input: %s\n", opts.path.c_str()); std::cerr << "Invalid input file: " << opts.path << std::endl;
return 3; return 3;
} }
Json::String basePath = removeSuffix(opts.path, ".json"); Json::String basePath = removeSuffix(opts.path, ".json");
if (!opts.parseOnly && basePath.empty()) { if (!opts.parseOnly && basePath.empty()) {
printf("Bad input path. Path does not end with '.expected':\n%s\n", std::cerr << "Bad input path '" << opts.path
opts.path.c_str()); << "'. Must end with '.expected'" << std::endl;
return 3; return 3;
} }
@ -262,34 +292,47 @@ static int runTest(Options const& opts) {
Json::Value root; Json::Value root;
exitCode = parseAndSaveValueTree(input, actualPath, "input", opts.features, exitCode = parseAndSaveValueTree(input, actualPath, "input", opts.features,
opts.parseOnly, &root); opts.parseOnly, &root, use_legacy);
if (exitCode || opts.parseOnly) { if (exitCode || opts.parseOnly) {
return exitCode; return exitCode;
} }
Json::String rewrite; Json::String rewrite;
exitCode = rewriteValueTree(rewritePath, root, opts.write, &rewrite); exitCode = rewriteValueTree(rewritePath, root, opts.write, &rewrite);
if (exitCode) { if (exitCode) {
return exitCode; return exitCode;
} }
Json::Value rewriteRoot; Json::Value rewriteRoot;
exitCode = parseAndSaveValueTree(rewrite, rewriteActualPath, "rewrite", exitCode = parseAndSaveValueTree(rewrite, rewriteActualPath, "rewrite",
opts.features, opts.parseOnly, &rewriteRoot); opts.features, opts.parseOnly, &rewriteRoot,
if (exitCode) { use_legacy);
return exitCode;
} return exitCode;
return 0;
} }
int main(int argc, const char* argv[]) { int main(int argc, const char* argv[]) {
Options opts; Options opts;
try { try {
int exitCode = parseCommandLine(argc, argv, &opts); int exitCode = parseCommandLine(argc, argv, &opts);
if (exitCode != 0) { if (exitCode != 0) {
printf("Failed to parse command-line."); std::cerr << "Failed to parse command-line." << std::endl;
return exitCode; return exitCode;
} }
return runTest(opts);
const int modern_return_code = runTest(opts, false);
if (modern_return_code) {
return modern_return_code;
}
const std::string filename =
opts.path.substr(opts.path.find_last_of("\\/") + 1);
const bool should_run_legacy = (filename.rfind("legacy_", 0) == 0);
if (should_run_legacy) {
return runTest(opts, true);
}
} catch (const std::exception& e) { } catch (const std::exception& e) {
printf("Unhandled exception:\n%s\n", e.what()); std::cerr << "Unhandled exception:" << std::endl << e.what() << std::endl;
return 1; return 1;
} }
} }

View File

@ -1540,19 +1540,45 @@ bool OurReader::decodeNumber(Token& token, Value& decoded) {
// larger than the maximum supported value of an integer then // larger than the maximum supported value of an integer then
// we decode the number as a double. // we decode the number as a double.
Location current = token.start_; Location current = token.start_;
bool isNegative = *current == '-'; const bool isNegative = *current == '-';
if (isNegative) if (isNegative) {
++current; ++current;
}
static constexpr auto positive_threshold = Value::maxLargestUInt / 10; // We assume we can represent the largest and smallest integer types as
static constexpr auto positive_last_digit = Value::maxLargestUInt % 10; // unsigned integers with separate sign. This is only true if they can fit
static constexpr auto negative_threshold = // into an unsigned integer.
Value::LargestUInt(Value::minLargestInt) / 10; static_assert(Value::maxLargestInt <= Value::maxLargestUInt,
static constexpr auto negative_last_digit = "Int must be smaller than UInt");
Value::LargestUInt(Value::minLargestInt) % 10;
const auto threshold = isNegative ? negative_threshold : positive_threshold; // We need to convert minLargestInt into a positive number. The easiest way
const auto last_digit = // to do this conversion is to assume our "threshold" value of minLargestInt
// divided by 10 can fit in maxLargestInt when absolute valued. This should
// be a safe assumption.
static_assert(Value::minLargestInt <= -Value::maxLargestInt,
"The absolute value of minLargestInt must be greater than or "
"equal to maxLargestInt");
static_assert(Value::minLargestInt / 10 >= -Value::maxLargestInt,
"The absolute value of minLargestInt must be only 1 magnitude "
"larger than maxLargest Int");
static constexpr Value::LargestUInt positive_threshold =
Value::maxLargestUInt / 10;
static constexpr Value::UInt positive_last_digit = Value::maxLargestUInt % 10;
// For the negative values, we have to be more careful. Since typically
// -Value::minLargestInt will cause an overflow, we first divide by 10 and
// then take the inverse. This assumes that minLargestInt is only a single
// power of 10 different in magnitude, which we check above. For the last
// digit, we take the modulus before negating for the same reason.
static constexpr Value::LargestUInt negative_threshold =
Value::LargestUInt(-(Value::minLargestInt / 10));
static constexpr Value::UInt negative_last_digit =
Value::UInt(-(Value::minLargestInt % 10));
const Value::LargestUInt threshold =
isNegative ? negative_threshold : positive_threshold;
const Value::UInt max_last_digit =
isNegative ? negative_last_digit : positive_last_digit; isNegative ? negative_last_digit : positive_last_digit;
Value::LargestUInt value = 0; Value::LargestUInt value = 0;
@ -1561,26 +1587,30 @@ bool OurReader::decodeNumber(Token& token, Value& decoded) {
if (c < '0' || c > '9') if (c < '0' || c > '9')
return decodeDouble(token, decoded); return decodeDouble(token, decoded);
const auto digit(static_cast<Value::UInt>(c - '0')); const Value::UInt digit(static_cast<Value::UInt>(c - '0'));
if (value >= threshold) { if (value >= threshold) {
// We've hit or exceeded the max value divided by 10 (rounded down). If // We've hit or exceeded the max value divided by 10 (rounded down). If
// a) we've only just touched the limit, meaing value == threshold, // a) we've only just touched the limit, meaing value == threshold,
// b) this is the last digit, or // b) this is the last digit, or
// c) it's small enough to fit in that rounding delta, we're okay. // c) it's small enough to fit in that rounding delta, we're okay.
// Otherwise treat this number as a double to avoid overflow. // Otherwise treat this number as a double to avoid overflow.
if (value > threshold || current != token.end_ || digit > last_digit) { if (value > threshold || current != token.end_ ||
digit > max_last_digit) {
return decodeDouble(token, decoded); return decodeDouble(token, decoded);
} }
} }
value = value * 10 + digit; value = value * 10 + digit;
} }
if (isNegative) if (isNegative) {
decoded = -Value::LargestInt(value); // We use the same magnitude assumption here, just in case.
else if (value <= Value::LargestUInt(Value::maxLargestInt)) const Value::UInt last_digit = static_cast<Value::UInt>(value % 10);
decoded = -Value::LargestInt(value / 10) * 10 - last_digit;
} else if (value <= Value::LargestUInt(Value::maxLargestInt)) {
decoded = Value::LargestInt(value); decoded = Value::LargestInt(value);
else } else {
decoded = value; decoded = value;
}
return true; return true;
} }
@ -1597,37 +1627,12 @@ bool OurReader::decodeDouble(Token& token) {
bool OurReader::decodeDouble(Token& token, Value& decoded) { bool OurReader::decodeDouble(Token& token, Value& decoded) {
double value = 0; double value = 0;
const int bufferSize = 32; const String buffer(token.start_, token.end_);
int count; IStringStream is(buffer);
ptrdiff_t const length = token.end_ - token.start_; if (!(is >> value)) {
// Sanity check to avoid buffer overflow exploits.
if (length < 0) {
return addError("Unable to parse token length", token);
}
auto const ulength = static_cast<size_t>(length);
// Avoid using a string constant for the format control string given to
// sscanf, as this can cause hard to debug crashes on OS X. See here for more
// info:
//
// http://developer.apple.com/library/mac/#DOCUMENTATION/DeveloperTools/gcc-4.0.1/gcc/Incompatibilities.html
char format[] = "%lf";
if (length <= bufferSize) {
Char buffer[bufferSize + 1];
memcpy(buffer, token.start_, ulength);
buffer[length] = 0;
fixNumericLocaleInput(buffer, buffer + length);
count = sscanf(buffer, format, &value);
} else {
String buffer(token.start_, token.end_);
count = sscanf(buffer.c_str(), format, &value);
}
if (count != 1)
return addError( return addError(
"'" + String(token.start_, token.end_) + "' is not a number.", token); "'" + String(token.start_, token.end_) + "' is not a number.", token);
}
decoded = value; decoded = value;
return true; return true;
} }
@ -1649,9 +1654,9 @@ bool OurReader::decodeString(Token& token, String& decoded) {
Location end = token.end_ - 1; // do not include '"' Location end = token.end_ - 1; // do not include '"'
while (current != end) { while (current != end) {
Char c = *current++; Char c = *current++;
if (c == '"') if (c == '"') {
break; break;
else if (c == '\\') { } else if (c == '\\') {
if (current == end) if (current == end)
return addError("Empty escape sequence in string", token, current); return addError("Empty escape sequence in string", token, current);
Char escape = *current++; Char escape = *current++;

Some files were not shown because too many files have changed in this diff Show More