mirror of
https://github.com/open-source-parsers/jsoncpp.git
synced 2024-12-14 02:35:09 +01:00
Number fixes (#1053)
* cleaning up the logic for parsing numbers * Add Testcases for new Reader in jsontestrunner
This commit is contained in:
parent
ff58fdcc75
commit
645cd0412c
@ -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 = [
|
||||||
|
@ -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);
|
|
||||||
|
builder.settings_["allowComments"] = features.allowComments_;
|
||||||
|
builder.settings_["strictRoot"] = features.strictRoot_;
|
||||||
|
builder.settings_["allowDroppedNullPlaceholders"] =
|
||||||
|
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) {
|
if (!parsingSuccessful) {
|
||||||
printf("Failed to parse %s file: \n%s\n", kind.c_str(),
|
std::cerr << "Failed to parse " << kind << " file: " << std::endl
|
||||||
reader.getFormattedErrorMessages().c_str());
|
<< errors << std::endl;
|
||||||
return 1;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
Loading…
Reference in New Issue
Block a user