diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn index a14592a304..5dce4bee54 100644 --- a/src/base/BUILD.gn +++ b/src/base/BUILD.gn @@ -381,6 +381,8 @@ component("base") { "hash/legacy_hash.cc", "hash/legacy_hash.h", "json/json_common.h", + "json/json_parser.cc", + "json/json_parser.h", "json/json_reader.cc", "json/json_reader.h", "json/json_string_value_serializer.cc", @@ -1126,11 +1128,6 @@ component("base") { # Used by metrics/crc32 deps += [ "//third_party/zlib" ] - deps += [ - "logging/rust_logger", - "//third_party/rust/serde_json_lenient/v0_2/wrapper", - ] - # `raw_ptr` cannot be made a component due to CRT symbol issues. # Its gateway to being a component is through `//base`, so we have # to provide the appropriate `#define` here. @@ -1166,15 +1163,6 @@ component("base") { "//third_party/abseil-cpp:absl", ] - sources += [ - "containers/span_rust.h", - "strings/string_view_rust.h", - ] - - # Base provides conversions between CXX types and base types (e.g. - # std::string_view). - public_deps += [ "//build/rust:cxx_cppdeps" ] - # Needed for if using newer C++ library than sysroot, except if # building inside the cros_sdk environment - use host_toolchain as a # more robust check for this. @@ -1620,8 +1608,6 @@ component("base") { "files/scoped_temp_file.h", "json/json_file_value_serializer.cc", "json/json_file_value_serializer.h", - "logging/rust_logger/print_rust_log_ffi.cc", - "logging/rust_logger/print_rust_log_ffi.h", "memory/discardable_memory.cc", "memory/discardable_memory.h", "memory/discardable_memory_allocator.cc", @@ -2630,110 +2616,6 @@ if (is_android) { } } -rust_static_library("scoped_refptr") { - sources = [ "memory/scoped_refptr.rs" ] - crate_root = "memory/scoped_refptr.rs" - allow_unsafe = true - - # This file uses cxx APIs but doesn't have its own bindings - deps = [ "//third_party/rust/cxx/v1:lib" ] -} - -rust_static_library("callback") { - sources = [ "functional/callback.rs" ] - crate_root = "functional/callback.rs" - cxx_bindings = [ "functional/callback.rs" ] - allow_unsafe = true - deps = [ ":scoped_refptr" ] -} - -rust_static_library("run_loop") { - sources = [ "run_loop.rs" ] - crate_root = "run_loop.rs" - cxx_bindings = [ "run_loop.rs" ] - allow_unsafe = true - deps = [ - ":run_loop_shim", - "//base", - ] -} - -rust_static_library("sequenced_task_runner") { - sources = [ "task/sequenced_task_runner.rs" ] - crate_root = "task/sequenced_task_runner.rs" - cxx_bindings = [ "task/sequenced_task_runner.rs" ] - allow_unsafe = true - deps = [ - ":callback", - ":run_loop", - ":scoped_refptr", - ":sequenced_task_runner_shim", - "//base", - ] -} - -rust_static_library("sequenced_task_runner_test_bridge") { - testonly = true - sources = [ "task/sequenced_task_runner_test_bridge.rs" ] - crate_root = "task/sequenced_task_runner_test_bridge.rs" - cxx_bindings = [ "task/sequenced_task_runner_test_bridge.rs" ] - allow_unsafe = true - deps = [ - ":base_rust", - ":scoped_refptr", - ":sequenced_task_runner_test_util", - "//base", - ] -} - -source_set("sequenced_task_runner_test_util") { - testonly = true - sources = [ - "task/sequenced_task_runner_test_util.cc", - "task/sequenced_task_runner_test_util.h", - ] - deps = [ - "test:test_support", - "//base", - ] -} - -source_set("run_loop_shim") { - sources = [ - "run_loop_rust_shim.cc", - "run_loop_rust_shim.h", - ] - - deps = [ "//base" ] -} - -source_set("sequenced_task_runner_shim") { - sources = [ - "task/sequenced_task_runner_rust_shim.cc", - "task/sequenced_task_runner_rust_shim.h", - ] - - deps = [ - ":callback", - "//base", - ] -} - -rust_static_library("rust_sequences_unittests") { - testonly = true - is_gtest_unittests = true - allow_unsafe = true - sources = [ "task/sequenced_task_runner_unittest.rs" ] - crate_root = "task/sequenced_task_runner_unittest.rs" - deps = [ - ":scoped_refptr", - ":sequenced_task_runner", - ":sequenced_task_runner_test_bridge", - "//base/test:task_environment", - "//testing/rust_gtest_interop", - ] -} - if (is_linux || is_chromeos) { # Split out as a separate target for two reasons: # - the line number reader is 2x slower in debug builds if not optimized, diff --git a/src/base/json/json_parser.cc b/src/base/json/json_parser.cc new file mode 100644 index 0000000000..1338c7f3a4 --- /dev/null +++ b/src/base/json/json_parser.cc @@ -0,0 +1,871 @@ +// Copyright 2012 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/json/json_parser.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "base/check_op.h" +#include "base/compiler_specific.h" +#include "base/feature_list.h" +#include "base/features.h" +#include "base/json/json_reader.h" +#include "base/notreached.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversion_utils.h" +#include "base/strings/utf_string_conversions.h" +#include "base/third_party/icu/icu_utf.h" +#include "base/types/pass_key.h" + +namespace base::internal { + +namespace { + +// Values 1000 and above are used by JSONFileValueSerializer::JsonFileError. +static_assert(JSONParser::JSON_PARSE_ERROR_COUNT < 1000, + "JSONParser error out of bounds"); + +std::string ErrorCodeToString(JSONParser::JsonParseError error_code) { + switch (error_code) { + case JSONParser::JSON_NO_ERROR: + return std::string(); + case JSONParser::JSON_SYNTAX_ERROR: + return JSONParser::kSyntaxError; + case JSONParser::JSON_INVALID_ESCAPE: + return JSONParser::kInvalidEscape; + case JSONParser::JSON_UNEXPECTED_TOKEN: + return JSONParser::kUnexpectedToken; + case JSONParser::JSON_TRAILING_COMMA: + return JSONParser::kTrailingComma; + case JSONParser::JSON_TOO_MUCH_NESTING: + return JSONParser::kTooMuchNesting; + case JSONParser::JSON_UNEXPECTED_DATA_AFTER_ROOT: + return JSONParser::kUnexpectedDataAfterRoot; + case JSONParser::JSON_UNSUPPORTED_ENCODING: + return JSONParser::kUnsupportedEncoding; + case JSONParser::JSON_UNQUOTED_DICTIONARY_KEY: + return JSONParser::kUnquotedDictionaryKey; + case JSONParser::JSON_UNREPRESENTABLE_NUMBER: + return JSONParser::kUnrepresentableNumber; + case JSONParser::JSON_PARSE_ERROR_COUNT: + NOTREACHED(); + } + NOTREACHED(); +} + +const int32_t kExtendedASCIIStart = 0x80; +constexpr base_icu::UChar32 kUnicodeReplacementPoint = 0xFFFD; + +// UnprefixedHexStringToInt acts like |HexStringToInt|, but enforces that the +// input consists purely of hex digits. I.e. no "0x" nor "OX" prefix is +// permitted. +bool UnprefixedHexStringToInt(std::string_view input, int* output) { + for (char i : input) { + if (!IsHexDigit(i)) { + return false; + } + } + return HexStringToInt(input, output); +} + +} // namespace + +// This is U+FFFD. +const char kUnicodeReplacementString[] = "\xEF\xBF\xBD"; + +const char JSONParser::kSyntaxError[] = "Syntax error."; +const char JSONParser::kInvalidEscape[] = "Invalid escape sequence."; +const char JSONParser::kUnexpectedToken[] = "Unexpected token."; +const char JSONParser::kTrailingComma[] = "Trailing comma not allowed."; +const char JSONParser::kTooMuchNesting[] = "Too much nesting."; +const char JSONParser::kUnexpectedDataAfterRoot[] = + "Unexpected data after root element."; +const char JSONParser::kUnsupportedEncoding[] = + "Unsupported encoding. JSON must be UTF-8."; +const char JSONParser::kUnquotedDictionaryKey[] = + "Dictionary keys must be quoted."; +const char JSONParser::kUnrepresentableNumber[] = + "Number cannot be represented."; + +JSONParser::JSONParser(int options, size_t max_depth) + : options_(options), + max_depth_(max_depth), + index_(0), + stack_depth_(0), + line_number_(0), + index_last_line_(0), + error_code_(JSON_NO_ERROR), + error_line_(0), + error_column_(0) { + CHECK_LE(max_depth, kAbsoluteMaxDepth); +} + +JSONParser::~JSONParser() = default; + +std::optional JSONParser::Parse(std::string_view input) { + input_ = input; + index_ = 0; + // Line and column counting is 1-based, but |index_| is 0-based. For example, + // if input is "Aaa\nB" then 'A' and 'B' are both in column 1 (at lines 1 and + // 2) and have indexes of 0 and 4. We track the line number explicitly (the + // |line_number_| field) and the column number implicitly (the difference + // between |index_| and |index_last_line_|). In calculating that difference, + // |index_last_line_| is the index of the '\r' or '\n', not the index of the + // first byte after the '\n'. For the 'B' in "Aaa\nB", its |index_| and + // |index_last_line_| would be 4 and 3: 'B' is in column (4 - 3) = 1. We + // initialize |index_last_line_| to -1, not 0, since -1 is the (out of range) + // index of the imaginary '\n' immediately before the start of the string: + // 'A' is in column (0 - -1) = 1. + line_number_ = 1; + index_last_line_ = static_cast(-1); + + error_code_ = JSON_NO_ERROR; + error_line_ = 0; + error_column_ = 0; + + // When the input JSON string starts with a UTF-8 Byte-Order-Mark, + // advance the start position to avoid the ParseNextToken function mis- + // treating a Unicode BOM as an invalid character and returning NULL. + ConsumeIfMatch("\xEF\xBB\xBF"); + + // Parse the first and any nested tokens. + std::optional root(ParseNextToken()); + if (!root) { + return std::nullopt; + } + + // Make sure the input stream is at an end. + if (GetNextToken() != T_END_OF_INPUT) { + ReportError(JSON_UNEXPECTED_DATA_AFTER_ROOT, 0); + return std::nullopt; + } + + return root; +} + +JSONParser::JsonParseError JSONParser::error_code() const { + return error_code_; +} + +std::string JSONParser::GetErrorMessage() const { + return FormatErrorMessage(error_line_, error_column_, + ErrorCodeToString(error_code_)); +} + +int JSONParser::error_line() const { + return error_line_; +} + +int JSONParser::error_column() const { + return error_column_; +} + +// JSONParser private ////////////////////////////////////////////////////////// + +std::optional JSONParser::PeekChars(size_t count) { + if (count > input_.length() - index_) { + return std::nullopt; + } + // Using string_view::substr() was historically significantly slower + // (according to base_perftests) than constructing a substring manually. + // + // TODO(crbug.com/40284755): Is this still the case? Ideally the bounds check + // performed by substr would be deleted by the optimizer for being redundant + // with the runtime check above. However, to do so, the compiler would need + // to know `index_ <= input_.length()` is a class invariant. If we + // restructured the code so that we only stored the remaining data, that + // would avoid this, but it would prevent rewinding (the places in this file + // which look at `input_[index_ - 1]`.) + return UNSAFE_BUFFERS(std::string_view(input_.data() + index_, count)); +} + +std::optional JSONParser::PeekChar() { + std::optional chars = PeekChars(1); + if (chars) { + return (*chars)[0]; + } + return std::nullopt; +} + +std::optional JSONParser::ConsumeChars(size_t count) { + std::optional chars = PeekChars(count); + if (chars) { + index_ += count; + } + return chars; +} + +std::optional JSONParser::ConsumeChar() { + std::optional chars = ConsumeChars(1); + if (chars) { + return (*chars)[0]; + } + return std::nullopt; +} + +const char* JSONParser::pos() { + CHECK_LE(index_, input_.length()); + // SAFETY: Checked above. + return UNSAFE_BUFFERS(input_.data() + index_); +} + +JSONParser::Token JSONParser::GetNextToken() { + EatWhitespaceAndComments(); + + std::optional c = PeekChar(); + if (!c) { + return T_END_OF_INPUT; + } + + switch (*c) { + case '{': + return T_OBJECT_BEGIN; + case '}': + return T_OBJECT_END; + case '[': + return T_ARRAY_BEGIN; + case ']': + return T_ARRAY_END; + case '"': + return T_STRING; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return T_NUMBER; + case 't': + return T_BOOL_TRUE; + case 'f': + return T_BOOL_FALSE; + case 'n': + return T_NULL; + case ',': + return T_LIST_SEPARATOR; + case ':': + return T_OBJECT_PAIR_SEPARATOR; + default: + return T_INVALID_TOKEN; + } +} + +void JSONParser::EatWhitespaceAndComments() { + while (std::optional c = PeekChar()) { + switch (*c) { + case '\r': + case '\n': + index_last_line_ = index_; + // Don't increment line_number_ twice for "\r\n". + if (!(c == '\n' && index_ > 0 && input_[index_ - 1] == '\r')) { + ++line_number_; + } + [[fallthrough]]; + case ' ': + case '\t': + ConsumeChar(); + break; + case '/': + if (!EatComment()) { + return; + } + break; + default: + return; + } + } +} + +bool JSONParser::EatComment() { + std::optional comment_start = PeekChars(2); + if (!comment_start) { + return false; + } + + const bool comments_allowed = options_ & JSON_ALLOW_COMMENTS; + + if (comment_start == "//") { + if (!comments_allowed) { + ReportError(JSON_UNEXPECTED_TOKEN, 0); + return false; + } + + ConsumeChars(2); + // Single line comment, read to newline. + while (std::optional c = PeekChar()) { + if (c == '\n' || c == '\r') { + return true; + } + ConsumeChar(); + } + } else if (comment_start == "/*") { + if (!comments_allowed) { + ReportError(JSON_UNEXPECTED_TOKEN, 0); + return false; + } + + ConsumeChars(2); + char previous_char = '\0'; + // Block comment, read until end marker. + while (std::optional c = PeekChar()) { + if (previous_char == '*' && c == '/') { + // EatWhitespaceAndComments will inspect pos(), which will still be on + // the last / of the comment, so advance once more (which may also be + // end of input). + ConsumeChar(); + return true; + } + previous_char = *ConsumeChar(); + } + + // If the comment is unterminated, GetNextToken will report T_END_OF_INPUT. + } + + return false; +} + +std::optional JSONParser::ParseNextToken() { + return ParseToken(GetNextToken()); +} + +std::optional JSONParser::ParseToken(Token token) { + switch (token) { + case T_OBJECT_BEGIN: + return ConsumeDictionary(); + case T_ARRAY_BEGIN: + return ConsumeList(); + case T_STRING: + return ConsumeString(); + case T_NUMBER: + return ConsumeNumber(); + case T_BOOL_TRUE: + case T_BOOL_FALSE: + case T_NULL: + return ConsumeLiteral(); + default: + ReportError(JSON_UNEXPECTED_TOKEN, 0); + return std::nullopt; + } +} + +std::optional JSONParser::ConsumeDictionary() { + if (ConsumeChar() != '{') { + ReportError(JSON_UNEXPECTED_TOKEN, 0); + return std::nullopt; + } + + StackMarker depth_check(max_depth_, &stack_depth_); + if (depth_check.IsTooDeep()) { + ReportError(JSON_TOO_MUCH_NESTING, -1); + return std::nullopt; + } + + std::vector> values; + + Token token = GetNextToken(); + while (token != T_OBJECT_END) { + if (token != T_STRING) { + ReportError(JSON_UNQUOTED_DICTIONARY_KEY, 0); + return std::nullopt; + } + + // First consume the key. + std::optional key = ConsumeStringRaw(); + if (!key) { + return std::nullopt; + } + + // Read the separator. + token = GetNextToken(); + if (token != T_OBJECT_PAIR_SEPARATOR) { + ReportError(JSON_SYNTAX_ERROR, 0); + return std::nullopt; + } + + // The next token is the value. Ownership transfers to |dict|. + ConsumeChar(); + std::optional value = ParseNextToken(); + if (!value) { + // ReportError from deeper level. + return std::nullopt; + } + + values.emplace_back(std::move(*key), std::move(*value)); + + token = GetNextToken(); + if (token == T_LIST_SEPARATOR) { + ConsumeChar(); + token = GetNextToken(); + if (token == T_OBJECT_END && !(options_ & JSON_ALLOW_TRAILING_COMMAS)) { + ReportError(JSON_TRAILING_COMMA, 0); + return std::nullopt; + } + } else if (token != T_OBJECT_END) { + ReportError(JSON_SYNTAX_ERROR, 0); + return std::nullopt; + } + } + + ConsumeChar(); // Closing '}'. + // Reverse |dict_storage| to keep the last of elements with the same key in + // the input. + std::ranges::reverse(values); + return Value(DictValue(std::make_move_iterator(values.begin()), + std::make_move_iterator(values.end()))); +} + +std::optional JSONParser::ConsumeList() { + if (ConsumeChar() != '[') { + ReportError(JSON_UNEXPECTED_TOKEN, 0); + return std::nullopt; + } + + StackMarker depth_check(max_depth_, &stack_depth_); + if (depth_check.IsTooDeep()) { + ReportError(JSON_TOO_MUCH_NESTING, -1); + return std::nullopt; + } + + ListValue list; + + Token token = GetNextToken(); + while (token != T_ARRAY_END) { + std::optional item = ParseToken(token); + if (!item) { + // ReportError from deeper level. + return std::nullopt; + } + + list.Append(std::move(*item)); + + token = GetNextToken(); + if (token == T_LIST_SEPARATOR) { + ConsumeChar(); + token = GetNextToken(); + if (token == T_ARRAY_END && !(options_ & JSON_ALLOW_TRAILING_COMMAS)) { + ReportError(JSON_TRAILING_COMMA, 0); + return std::nullopt; + } + } else if (token != T_ARRAY_END) { + ReportError(JSON_SYNTAX_ERROR, 0); + return std::nullopt; + } + } + + ConsumeChar(); // Closing ']'. + + return Value(std::move(list)); +} + +std::optional JSONParser::ConsumeString() { + std::optional string = ConsumeStringRaw(); + if (!string) { + return std::nullopt; + } + return Value(std::move(*string)); +} + +std::optional JSONParser::ConsumeStringRaw() { + if (ConsumeChar() != '"') { + ReportError(JSON_UNEXPECTED_TOKEN, 0); + return std::nullopt; + } + + std::string string; + for (;;) { + auto [result, consumed] = ConsumeStringPart(); + switch (result) { + case StringResult::kError: + return std::nullopt; + + case StringResult::kDone: + // This is the last time we're appending, so pre-reserve the desired + // size, to prevent `+=` from overallocating. (In other cases, the + // overallocating is desirable for amortization.) In particular, + // the common case is that `string` is empty and we return in one step. + string.reserve(string.size() + consumed.size()); + string += consumed; + return std::move(string); + + case StringResult::kReplacementCharacter: + string += consumed; + string += kUnicodeReplacementString; + break; // Keep parsing. + + case StringResult::kEscape: + string += consumed; + std::optional escape_char = ConsumeChar(); + if (!escape_char) { + ReportError(JSON_INVALID_ESCAPE, -1); + return std::nullopt; + } + + switch (*escape_char) { + // Allowed esape sequences: + case 'x': { // UTF-8 sequence. + // UTF-8 \x escape sequences are not allowed in the spec, but they + // are supported here for backwards-compatiblity with the old + // parser. + if (!(options_ & JSON_ALLOW_X_ESCAPES)) { + ReportError(JSON_INVALID_ESCAPE, -1); + return std::nullopt; + } + + std::optional escape_sequence = ConsumeChars(2); + if (!escape_sequence) { + ReportError(JSON_INVALID_ESCAPE, -3); + return std::nullopt; + } + + int hex_digit = 0; + if (!UnprefixedHexStringToInt(*escape_sequence, &hex_digit)) { + ReportError(JSON_INVALID_ESCAPE, -3); + return std::nullopt; + } + + // A two-character hex sequence is at most 0xff and all codepoints + // up to 0xff are valid. + DCHECK_LE(hex_digit, 0xff); + DCHECK(IsValidCharacter(hex_digit)); + WriteUnicodeCharacter(hex_digit, &string); + break; + } + case 'u': { // UTF-16 sequence. + // UTF units are of the form \uXXXX. + base_icu::UChar32 code_point; + if (!DecodeUTF16(&code_point)) { + ReportError(JSON_INVALID_ESCAPE, -1); + return std::nullopt; + } + WriteUnicodeCharacter(code_point, &string); + break; + } + case '"': + string.push_back('"'); + break; + case '\\': + string.push_back('\\'); + break; + case '/': + string.push_back('/'); + break; + case 'b': + string.push_back('\b'); + break; + case 'f': + string.push_back('\f'); + break; + case 'n': + string.push_back('\n'); + break; + case 'r': + string.push_back('\r'); + break; + case 't': + string.push_back('\t'); + break; + case 'v': // Not listed as valid escape sequence in the RFC. + if (!(options_ & JSON_ALLOW_VERT_TAB)) { + ReportError(JSON_INVALID_ESCAPE, -1); + return std::nullopt; + } + string.push_back('\v'); + break; + // All other escape squences are illegal. + default: + ReportError(JSON_INVALID_ESCAPE, -1); + return std::nullopt; + } + break; // Keep parsing. + } + } +} + +std::pair +JSONParser::ConsumeStringPart() { + const size_t start_index = index_; + while (std::optional c = PeekChar()) { + // Handle non-ASCII characters, which never trigger any special handling + // beyond needing to be valid UTF-8. ASCII characters will be handled + // separately below. + if (static_cast(*c) >= kExtendedASCIIStart) { + base_icu::UChar32 next_char = 0; + size_t last_index = index_; + if (!ReadUnicodeCharacter(input_, &index_, &next_char)) { + if ((options_ & JSON_REPLACE_INVALID_CHARACTERS) == 0) { + ReportError(JSON_UNSUPPORTED_ENCODING, 0); + // No need to return consumed data. + return {StringResult::kError, {}}; + } + ConsumeChar(); + return {StringResult::kReplacementCharacter, + input_.substr(start_index, last_index - start_index)}; + } + + // Valid UTF-8 will be copied as-is into the output, so keep processing. + DCHECK_GE(next_char, kExtendedASCIIStart); + ConsumeChar(); + continue; + } + + if (*c == '"') { + std::string_view ret = input_.substr(start_index, index_ - start_index); + ConsumeChar(); + return {StringResult::kDone, ret}; + } + if (*c == '\\') { + std::string_view ret = input_.substr(start_index, index_ - start_index); + ConsumeChar(); + return {StringResult::kEscape, ret}; + } + + // Per Section 7, "All Unicode characters may be placed within the + // quotation marks, except for the characters that MUST be escaped: + // quotation mark, reverse solidus, and the control characters (U+0000 + // through U+001F)". + if (*c == '\n' || *c == '\r') { + if (!(options_ & JSON_ALLOW_NEWLINES_IN_STRINGS)) { + ReportError(JSON_UNSUPPORTED_ENCODING, -1); + return {StringResult::kError, {}}; // No need to return consumed data. + } + } else if (*c <= 0x1F) { + ReportError(JSON_UNSUPPORTED_ENCODING, -1); + return {StringResult::kError, {}}; // No need to return consumed data. + } + + // If this character is not an escape sequence, track any line breaks and + // keep parsing. The JSON spec forbids unescaped ASCII control characters + // within a string, including '\r' and '\n', but this implementation is more + // lenient. + if (*c == '\r' || *c == '\n') { + index_last_line_ = index_; + // Don't increment line_number_ twice for "\r\n". We are guaranteed that + // (index_ > 0) because we are consuming a string, so we must have seen an + // opening '"' quote character. + if ((*c == '\r') || (input_[index_ - 1] != '\r')) { + ++line_number_; + } + } + ConsumeChar(); + } + + ReportError(JSON_SYNTAX_ERROR, -1); + return {StringResult::kError, {}}; // No need to return consumed data. +} + +// Entry is at the first X in \uXXXX. +bool JSONParser::DecodeUTF16(base_icu::UChar32* out_code_point) { + std::optional escape_sequence = ConsumeChars(4); + if (!escape_sequence) { + return false; + } + + // Consume the UTF-16 code unit, which may be a high surrogate. + int code_unit16_high = 0; + if (!UnprefixedHexStringToInt(*escape_sequence, &code_unit16_high)) { + return false; + } + + // If this is a high surrogate, consume the next code unit to get the + // low surrogate. + if (CBU16_IS_SURROGATE(code_unit16_high)) { + // Make sure this is the high surrogate. + if (!CBU16_IS_SURROGATE_LEAD(code_unit16_high)) { + if ((options_ & JSON_REPLACE_INVALID_CHARACTERS) == 0) { + return false; + } + *out_code_point = kUnicodeReplacementPoint; + return true; + } + + // Make sure that the token has more characters to consume the + // lower surrogate. + if (!ConsumeIfMatch("\\u")) { + if ((options_ & JSON_REPLACE_INVALID_CHARACTERS) == 0) { + return false; + } + *out_code_point = kUnicodeReplacementPoint; + return true; + } + + escape_sequence = ConsumeChars(4); + if (!escape_sequence) { + return false; + } + + int code_unit16_low = 0; + if (!UnprefixedHexStringToInt(*escape_sequence, &code_unit16_low)) { + return false; + } + + if (!CBU16_IS_TRAIL(code_unit16_low)) { + if ((options_ & JSON_REPLACE_INVALID_CHARACTERS) == 0) { + return false; + } + *out_code_point = kUnicodeReplacementPoint; + return true; + } + + base_icu::UChar32 code_point = + CBU16_GET_SUPPLEMENTARY(code_unit16_high, code_unit16_low); + + *out_code_point = code_point; + } else { + // Not a surrogate. + DCHECK(CBU16_IS_SINGLE(code_unit16_high)); + + *out_code_point = code_unit16_high; + } + + return true; +} + +std::optional JSONParser::ConsumeNumber() { + const char* num_start = pos(); + const size_t start_index = index_; + size_t end_index = start_index; + + if (PeekChar() == '-') { + ConsumeChar(); + } + + if (!ReadInt(false)) { + ReportError(JSON_SYNTAX_ERROR, 0); + return std::nullopt; + } + end_index = index_; + + // The optional fraction part. + if (PeekChar() == '.') { + ConsumeChar(); + if (!ReadInt(true)) { + ReportError(JSON_SYNTAX_ERROR, 0); + return std::nullopt; + } + end_index = index_; + } + + // Optional exponent part. + std::optional c = PeekChar(); + if (c == 'e' || c == 'E') { + ConsumeChar(); + if (PeekChar() == '-' || PeekChar() == '+') { + ConsumeChar(); + } + if (!ReadInt(true)) { + ReportError(JSON_SYNTAX_ERROR, 0); + return std::nullopt; + } + end_index = index_; + } + + std::string_view num_string(num_start, end_index - start_index); + + int num_int; + if (StringToInt(num_string, &num_int)) { + // StringToInt will treat `-0` as zero, losing the significance of the + // negation. + if (num_int == 0 && num_string.starts_with('-')) { + return Value(-0.0); + } + return Value(num_int); + } + + double num_double; + if (StringToDouble(num_string, &num_double) && std::isfinite(num_double)) { + return Value(num_double); + } + + ReportError(JSON_UNREPRESENTABLE_NUMBER, 0); + return std::nullopt; +} + +bool JSONParser::ReadInt(bool allow_leading_zeros) { + size_t len = 0; + char first = 0; + + while (std::optional c = PeekChar()) { + if (!IsAsciiDigit(*c)) { + break; + } + + if (len == 0) { + first = *c; + } + + ++len; + ConsumeChar(); + } + + if (len == 0) { + return false; + } + + if (!allow_leading_zeros && len > 1 && first == '0') { + return false; + } + + return true; +} + +std::optional JSONParser::ConsumeLiteral() { + if (ConsumeIfMatch("true")) { + return Value(true); + } + if (ConsumeIfMatch("false")) { + return Value(false); + } + if (ConsumeIfMatch("null")) { + return Value(Value::Type::NONE); + } + ReportError(JSON_SYNTAX_ERROR, 0); + return std::nullopt; +} + +bool JSONParser::ConsumeIfMatch(std::string_view match) { + if (match == PeekChars(match.size())) { + ConsumeChars(match.size()); + return true; + } + return false; +} + +void JSONParser::ReportError(JsonParseError code, int column_adjust) { + error_code_ = code; + error_line_ = line_number_; + error_column_ = static_cast(index_ - index_last_line_) + column_adjust; + + // For a final blank line ('\n' and then EOF), a negative column_adjust may + // put us below 1, which doesn't really make sense for 1-based columns. + if (error_column_ < 1) { + error_column_ = 1; + } +} + +// static +std::string JSONParser::FormatErrorMessage(int line, + int column, + const std::string& description) { + if (line || column) { + return StringPrintf("Line: %i, column: %i, %s", line, column, + description.c_str()); + } + return description; +} + +} // namespace base::internal diff --git a/src/base/json/json_parser.h b/src/base/json/json_parser.h new file mode 100644 index 0000000000..92e058640b --- /dev/null +++ b/src/base/json/json_parser.h @@ -0,0 +1,269 @@ +// Copyright 2012 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_JSON_JSON_PARSER_H_ +#define BASE_JSON_JSON_PARSER_H_ + +#include +#include + +#include +#include +#include +#include +#include + +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/json/json_common.h" +#include "base/third_party/icu/icu_utf.h" +#include "base/values.h" + +namespace base { + +class Value; + +namespace internal { + +class JSONParserTest; + +// The implementation behind the JSONReader interface. This class is not meant +// to be used directly; it encapsulates logic that need not be exposed publicly. +// +// This parser guarantees O(n) time through the input string. Iteration happens +// on the byte level, with the functions ConsumeChars() and ConsumeChar(). The +// conversion from byte to JSON token happens without advancing the parser in +// GetNextToken/ParseToken, that is tokenization operates on the current parser +// position without advancing. +// +// Built on top of these are a family of Consume functions that iterate +// internally. Invariant: on entry of a Consume function, the parser is wound +// to the first byte of a valid JSON token. On exit, it is on the first byte +// after the token that was just consumed, which would likely be the first byte +// of the next token. +class BASE_EXPORT JSONParser { + public: + // Error codes during parsing. + enum JsonParseError { + JSON_NO_ERROR = base::ValueDeserializer::kErrorCodeNoError, + JSON_SYNTAX_ERROR = base::ValueDeserializer::kErrorCodeInvalidFormat, + JSON_INVALID_ESCAPE, + JSON_UNEXPECTED_TOKEN, + JSON_TRAILING_COMMA, + JSON_TOO_MUCH_NESTING, + JSON_UNEXPECTED_DATA_AFTER_ROOT, + JSON_UNSUPPORTED_ENCODING, + JSON_UNQUOTED_DICTIONARY_KEY, + JSON_UNREPRESENTABLE_NUMBER, + JSON_PARSE_ERROR_COUNT + }; + + // String versions of parse error codes. + static const char kSyntaxError[]; + static const char kInvalidEscape[]; + static const char kUnexpectedToken[]; + static const char kTrailingComma[]; + static const char kTooMuchNesting[]; + static const char kUnexpectedDataAfterRoot[]; + static const char kUnsupportedEncoding[]; + static const char kUnquotedDictionaryKey[]; + static const char kUnrepresentableNumber[]; + + explicit JSONParser(int options, size_t max_depth = kAbsoluteMaxDepth); + + JSONParser(const JSONParser&) = delete; + JSONParser& operator=(const JSONParser&) = delete; + + ~JSONParser(); + + // Parses the input string according to the set options and returns the + // result as a Value. + // Wrap this in base::FooValue::From() to check the Value is of type Foo and + // convert to a FooValue at the same time. + std::optional Parse(std::string_view input); + + // Returns the error code. + JsonParseError error_code() const; + + // Returns the human-friendly error message. + std::string GetErrorMessage() const; + + // Returns the error line number if parse error happened. Otherwise always + // returns 0. + int error_line() const; + + // Returns the error column number if parse error happened. Otherwise always + // returns 0. + int error_column() const; + + private: + enum Token { + T_OBJECT_BEGIN, // { + T_OBJECT_END, // } + T_ARRAY_BEGIN, // [ + T_ARRAY_END, // ] + T_STRING, + T_NUMBER, + T_BOOL_TRUE, // true + T_BOOL_FALSE, // false + T_NULL, // null + T_LIST_SEPARATOR, // , + T_OBJECT_PAIR_SEPARATOR, // : + T_END_OF_INPUT, + T_INVALID_TOKEN, + }; + + // Returns the next |count| bytes of the input stream, or nullopt if fewer + // than |count| bytes remain. + std::optional PeekChars(size_t count); + + // Calls PeekChars() with a |count| of 1. + std::optional PeekChar(); + + // Returns the next |count| bytes of the input stream, or nullopt if fewer + // than |count| bytes remain, and advances the parser position by |count|. + std::optional ConsumeChars(size_t count); + + // Calls ConsumeChars() with a |count| of 1. + std::optional ConsumeChar(); + + // Returns a pointer to the current character position. + const char* pos(); + + // Skips over whitespace and comments to find the next token in the stream. + // This does not advance the parser for non-whitespace or comment chars. + Token GetNextToken(); + + // Consumes whitespace characters and comments until the next non-that is + // encountered. + void EatWhitespaceAndComments(); + // Helper function that consumes a comment, assuming that the parser is + // currently wound to a '/'. + bool EatComment(); + + // Calls GetNextToken() and then ParseToken(). + std::optional ParseNextToken(); + + // Takes a token that represents the start of a Value ("a structural token" + // in RFC terms) and consumes it, returning the result as a Value. + std::optional ParseToken(Token token); + + // Assuming that the parser is currently wound to '{', this parses a JSON + // object into a Value. + std::optional ConsumeDictionary(); + + // Assuming that the parser is wound to '[', this parses a JSON list into a + // Value. + std::optional ConsumeList(); + + // Calls through ConsumeStringRaw and wraps it in a value. + std::optional ConsumeString(); + + // Assuming that the parser is wound to a double quote, this parses a string, + // decoding any escape sequences and validating UTF-8. Returns the string on + // success or std::nullopt on error, with error information set. + std::optional ConsumeStringRaw(); + + enum class StringResult { + // Parsing stopped because of invalid input. Error information has been set. + // The caller should return failure. + kError, + // Parsing stopped because the string is finished. The parser is wound to + // just paste the closing quote. The caller should stop parsing the string. + kDone, + // Parsing stopped because of invalid Unicode which should be replaced with + // a replacement character. The parser is wound to just past the input that + // should be a replacement character. The caller should add a replacement + // character and continue parsing. + kReplacementCharacter, + // Parsing stopped because of an escape sequence. The parser is wound to + // just past the backslash. The caller should consume the escape sequence + // and continue parsing. + kEscape, + }; + + // Consumes the portion of a JavaScript string which may be copied to the + // input with no conversions, stopping at one of the events above. Returns the + // reason parsing stopped and the data that was consumed. This should be + // called in a loop, handling all the cases above until reaching kDone. + std::pair ConsumeStringPart(); + + // Helper function for ConsumeStringRaw() that consumes the next four or 10 + // bytes (parser is wound to the first character of a HEX sequence, with the + // potential for consuming another \uXXXX for a surrogate). Returns true on + // success and places the code point |out_code_point|, and false on failure. + bool DecodeUTF16(base_icu::UChar32* out_code_point); + + // Assuming that the parser is wound to the start of a valid JSON number, + // this parses and converts it to either an int or double value. + std::optional ConsumeNumber(); + // Helper that reads characters that are ints. Returns true if a number was + // read and false on error. + bool ReadInt(bool allow_leading_zeros); + + // Consumes the literal values of |true|, |false|, and |null|, assuming the + // parser is wound to the first character of any of those. + std::optional ConsumeLiteral(); + + // Helper function that returns true if the byte squence |match| can be + // consumed at the current parser position. Returns false if there are fewer + // than |match|-length bytes or if the sequence does not match, and the + // parser state is unchanged. + bool ConsumeIfMatch(std::string_view match); + + // Sets the error information to |code| at the current column, based on + // |index_| and |index_last_line_|, with an optional positive/negative + // adjustment by |column_adjust|. + void ReportError(JsonParseError code, int column_adjust); + + // Given the line and column number of an error, formats one of the error + // message contants from json_reader.h for human display. + static std::string FormatErrorMessage(int line, + int column, + const std::string& description); + + // base::JSONParserOptions that control parsing. + const int options_; + + // Maximum depth to parse. + const size_t max_depth_; + + // The input stream being parsed. Note: Not guaranteed to NUL-terminated. + std::string_view input_; + + // The index in the input stream to which the parser is wound. + size_t index_; + + // The number of times the parser has recursed (current stack depth). + size_t stack_depth_; + + // The line number that the parser is at currently. + int line_number_; + + // The last value of |index_| on the previous line. + size_t index_last_line_; + + // Error information. + JsonParseError error_code_; + int error_line_; + int error_column_; + + friend class JSONParserTest; + FRIEND_TEST_ALL_PREFIXES(JSONParserTest, NextChar); + FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ConsumeDictionary); + FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ConsumeList); + FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ConsumeString); + FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ConsumeLiterals); + FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ConsumeNumbers); + FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ErrorMessages); +}; + +// Used when decoding and an invalid utf-8 sequence is encountered. +BASE_EXPORT extern const char kUnicodeReplacementString[]; + +} // namespace internal +} // namespace base + +#endif // BASE_JSON_JSON_PARSER_H_ diff --git a/src/base/json/json_reader.cc b/src/base/json/json_reader.cc index 046198aa23..7c720300a2 100644 --- a/src/base/json/json_reader.cc +++ b/src/base/json/json_reader.cc @@ -7,115 +7,12 @@ #include #include +#include "base/json/json_parser.h" #include "base/logging.h" #include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" #include "build/build_config.h" -#include "base/strings/string_view_rust.h" -#include "third_party/rust/serde_json_lenient/v0_2/wrapper/functions.h" -#include "third_party/rust/serde_json_lenient/v0_2/wrapper/lib.rs.h" -// This namespace defines FFI-friendly functions that are be called from Rust in -// //third_party/rust/serde_json_lenient/v0_2/wrapper/. -namespace serde_json_lenient { - -base::ListValue& list_append_list(base::ListValue& ctx) { - ctx.Append(base::ListValue()); - return ctx.back().GetList(); -} - -base::DictValue& list_append_dict(base::ListValue& ctx) { - ctx.Append(base::DictValue()); - return ctx.back().GetDict(); -} - -void list_append_none(base::ListValue& ctx) { - ctx.Append(base::Value()); -} - -void list_append_bool(base::ListValue& ctx, bool val) { - ctx.Append(val); -} - -void list_append_i32(base::ListValue& ctx, int32_t val) { - ctx.Append(val); -} - -void list_append_f64(base::ListValue& ctx, double val) { - ctx.Append(val); -} - -void list_append_str(base::ListValue& ctx, rust::Str val) { - ctx.Append(std::string(val)); -} - -base::ListValue& dict_set_list(base::DictValue& ctx, rust::Str key) { - base::Value* value = - ctx.Set(base::RustStrToStringView(key), base::ListValue()); - return value->GetList(); -} - -base::DictValue& dict_set_dict(base::DictValue& ctx, rust::Str key) { - base::Value* value = - ctx.Set(base::RustStrToStringView(key), base::DictValue()); - return value->GetDict(); -} - -void dict_set_none(base::DictValue& ctx, rust::Str key) { - ctx.Set(base::RustStrToStringView(key), base::Value()); -} - -void dict_set_bool(base::DictValue& ctx, rust::Str key, bool val) { - ctx.Set(base::RustStrToStringView(key), val); -} - -void dict_set_i32(base::DictValue& ctx, rust::Str key, int32_t val) { - ctx.Set(base::RustStrToStringView(key), val); -} - -void dict_set_f64(base::DictValue& ctx, rust::Str key, double val) { - ctx.Set(base::RustStrToStringView(key), val); -} - -void dict_set_str(base::DictValue& ctx, rust::Str key, rust::Str val) { - ctx.Set(base::RustStrToStringView(key), std::string(val)); -} - -namespace { - -base::JSONReader::Result DecodeJSONInRust(std::string_view json, - int options, - size_t max_depth) { - const JsonOptions rust_options = { - .allow_trailing_commas = - (options & base::JSON_ALLOW_TRAILING_COMMAS) != 0, - .replace_invalid_characters = - (options & base::JSON_REPLACE_INVALID_CHARACTERS) != 0, - .allow_comments = (options & base::JSON_ALLOW_COMMENTS) != 0, - .allow_newlines = (options & base::JSON_ALLOW_NEWLINES_IN_STRINGS) != 0, - .allow_vert_tab = (options & base::JSON_ALLOW_VERT_TAB) != 0, - .allow_x_escapes = (options & base::JSON_ALLOW_X_ESCAPES) != 0, - .max_depth = max_depth, - }; - - base::ListValue list; - DecodeError error; - bool ok = - decode_json(base::StringViewToRustSlice(json), rust_options, list, error); - - if (!ok) { - return base::unexpected(base::JSONReader::Error{ - .message = std::string(error.message), - .line = error.line, - .column = error.column, - }); - } - - return std::move(list.back()); -} - -} // namespace -} // namespace serde_json_lenient namespace base { @@ -128,12 +25,8 @@ std::string JSONReader::Error::ToString() const { std::optional JSONReader::Read(std::string_view json, int options, size_t max_depth) { - JSONReader::Result result = - serde_json_lenient::DecodeJSONInRust(json, options, max_depth); - if (!result.has_value()) { - return std::nullopt; - } - return std::move(*result); + internal::JSONParser parser(options, max_depth); + return parser.Parse(json); } // static @@ -162,8 +55,17 @@ std::optional JSONReader::ReadList(std::string_view json, JSONReader::Result JSONReader::ReadAndReturnValueWithError( std::string_view json, int options) { - return serde_json_lenient::DecodeJSONInRust(json, options, - internal::kAbsoluteMaxDepth); + internal::JSONParser parser(options); + auto value = parser.Parse(json); + if (!value) { + Error error; + error.message = parser.GetErrorMessage(); + error.line = parser.error_line(); + error.column = parser.error_column(); + return base::unexpected(std::move(error)); + } + + return std::move(*value); } } // namespace base diff --git a/src/base/logging.cc b/src/base/logging.cc index 726c5cb002..9bd974be95 100644 --- a/src/base/logging.cc +++ b/src/base/logging.cc @@ -35,7 +35,6 @@ #include "base/functional/callback.h" #include "base/immediate_crash.h" #include "base/logging/logging_settings.h" -#include "base/logging/rust_logger/lib.rs.h" #include "base/no_destructor.h" #include "base/path_service.h" #include "base/pending_task.h" @@ -508,9 +507,6 @@ bool BaseInitLoggingImpl(const LoggingSettings& settings) { } #endif - // Connects Rust logging with the //base logging functionality. - internal::init_rust_logging(); - // Ignore file options unless logging to file is set. if ((g_logging_destination & LOG_TO_FILE) == 0) { return true; diff --git a/src/base/logging/BUILD.gn b/src/base/logging/BUILD.gn index e36f775142..c82eb416dc 100644 --- a/src/base/logging/BUILD.gn +++ b/src/base/logging/BUILD.gn @@ -9,10 +9,3 @@ source_set("log_severity") { deps = [ "//base:dcheck_is_on" ] visibility = [ "*" ] } - -rust_bindgen("log_severity_bindgen") { - header = "log_severity.h" - cpp = true - visibility = [ "rust_logger:*" ] - deps = [ ":log_severity" ] -}