diff --git a/src/google/protobuf/compiler/parser.cc b/src/google/protobuf/compiler/parser.cc index e5f42c4506..4381307624 100644 --- a/src/google/protobuf/compiler/parser.cc +++ b/src/google/protobuf/compiler/parser.cc @@ -310,9 +310,14 @@ bool Parser::ConsumeNumber(double* output, const char* error) { std::numeric_limits::max(), &value)) { *output = value; - } else { - // out of int range, treat as double literal - *output = io::Tokenizer::ParseFloat(input_->current().text); + } else if (input_->current().text[0] == '0') { + // octal or hexadecimal; don't bother parsing as float + AddError("Integer out of range."); + // We still return true because we did, in fact, parse a number. + } else if (!io::Tokenizer::TryParseFloat(input_->current().text, output)) { + // out of int range, and not valid float? 🤷 + AddError("Integer out of range."); + // We still return true because we did, in fact, parse a number. } input_->Next(); return true; diff --git a/src/google/protobuf/compiler/parser_unittest.cc b/src/google/protobuf/compiler/parser_unittest.cc index 7685371c14..39d5b60744 100644 --- a/src/google/protobuf/compiler/parser_unittest.cc +++ b/src/google/protobuf/compiler/parser_unittest.cc @@ -1941,6 +1941,22 @@ TEST_F(ParserValidationErrorTest, FieldDefaultValueError) { "2:32: Enum type \"Baz\" has no value named \"NO_SUCH_VALUE\".\n"); } +TEST_F(ParserValidationErrorTest, FieldDefaultIntegerOutOfRange) { + ExpectHasErrors( + "message Foo {\n" + " optional double bar = 1 [default = 0x10000000000000000];\n" + "}\n", + "1:37: Integer out of range.\n"); +} + +TEST_F(ParserValidationErrorTest, FieldOptionOutOfRange) { + ExpectHasErrors( + "message Foo {\n" + " optional double bar = 1 [foo = 0x10000000000000000];\n" + "}\n", + "1:33: Integer out of range.\n"); +} + TEST_F(ParserValidationErrorTest, FileOptionNameError) { ExpectHasValidationErrors( "option foo = 5;", diff --git a/src/google/protobuf/io/tokenizer.cc b/src/google/protobuf/io/tokenizer.cc index 7bc0820dd8..127fb5a402 100644 --- a/src/google/protobuf/io/tokenizer.cc +++ b/src/google/protobuf/io/tokenizer.cc @@ -1002,9 +1002,19 @@ bool Tokenizer::ParseInteger(const std::string& text, uint64_t max_value, } double Tokenizer::ParseFloat(const std::string& text) { + double result; + GOOGLE_LOG_IF(DFATAL, + !TryParseFloat(text, &result)) + << " Tokenizer::ParseFloat() passed text that could not have been" + " tokenized as a float: " + << absl::CEscape(text); + return result; +} + +bool Tokenizer::TryParseFloat(const std::string& text, double* result) { const char* start = text.c_str(); char* end; - double result = NoLocaleStrtod(start, &end); + *result = NoLocaleStrtod(start, &end); // "1e" is not a valid float, but if the tokenizer reads it, it will // report an error but still return it as a valid token. We need to @@ -1020,12 +1030,7 @@ double Tokenizer::ParseFloat(const std::string& text) { ++end; } - GOOGLE_LOG_IF(DFATAL, - static_cast(end - start) != text.size() || *start == '-') - << " Tokenizer::ParseFloat() passed text that could not have been" - " tokenized as a float: " - << absl::CEscape(text); - return result; + return static_cast(end - start) == text.size() && *start != '-'; } // Helper to append a Unicode code point to a string as UTF8, without bringing diff --git a/src/google/protobuf/io/tokenizer.h b/src/google/protobuf/io/tokenizer.h index cab1faf917..73877ccc24 100644 --- a/src/google/protobuf/io/tokenizer.h +++ b/src/google/protobuf/io/tokenizer.h @@ -214,6 +214,10 @@ class PROTOBUF_EXPORT Tokenizer { // result is undefined (possibly an assert failure). static double ParseFloat(const std::string& text); + // Parses given text as if it were a TYPE_FLOAT token. Returns false if the + // given text is not actually a valid float literal. + static bool TryParseFloat(const std::string& text, double* result); + // Parses a TYPE_STRING token. This never fails, so long as the text actually // comes from a TYPE_STRING token parsed by Tokenizer. If it doesn't, the // result is undefined (possibly an assert failure).