summaryrefslogtreecommitdiff
path: root/deps/v8/third_party
diff options
context:
space:
mode:
authorMichaël Zasso <targos@protonmail.com>2019-08-01 08:38:30 +0200
committerMichaël Zasso <targos@protonmail.com>2019-08-01 12:53:56 +0200
commit2dcc3665abf57c3607cebffdeeca062f5894885d (patch)
tree4f560748132edcfb4c22d6f967a7e80d23d7ea2c /deps/v8/third_party
parent1ee47d550c6de132f06110aa13eceb7551d643b3 (diff)
downloadandroid-node-v8-2dcc3665abf57c3607cebffdeeca062f5894885d.tar.gz
android-node-v8-2dcc3665abf57c3607cebffdeeca062f5894885d.tar.bz2
android-node-v8-2dcc3665abf57c3607cebffdeeca062f5894885d.zip
deps: update V8 to 7.6.303.28
PR-URL: https://github.com/nodejs/node/pull/28016 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Refael Ackermann (רפאל פלחי) <refack@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com> Reviewed-By: Jiawen Geng <technicalcute@gmail.com>
Diffstat (limited to 'deps/v8/third_party')
-rw-r--r--deps/v8/third_party/inspector_protocol/.clang-format36
-rw-r--r--deps/v8/third_party/inspector_protocol/BUILD.gn34
-rw-r--r--deps/v8/third_party/inspector_protocol/README.v82
-rwxr-xr-xdeps/v8/third_party/inspector_protocol/code_generator.py32
-rwxr-xr-xdeps/v8/third_party/inspector_protocol/convert_protocol_to_json.py2
-rw-r--r--deps/v8/third_party/inspector_protocol/encoding/encoding.cc2190
-rw-r--r--deps/v8/third_party/inspector_protocol/encoding/encoding.h510
-rw-r--r--deps/v8/third_party/inspector_protocol/encoding/encoding_test.cc1878
-rw-r--r--deps/v8/third_party/inspector_protocol/encoding/encoding_test_helper.h33
-rw-r--r--deps/v8/third_party/inspector_protocol/lib/Values_cpp.template9
-rw-r--r--deps/v8/third_party/inspector_protocol/lib/base_string_adapter_cc.template69
-rw-r--r--deps/v8/third_party/inspector_protocol/lib/base_string_adapter_h.template6
-rw-r--r--deps/v8/third_party/inspector_protocol/lib/encoding_cpp.template395
-rw-r--r--deps/v8/third_party/inspector_protocol/lib/encoding_h.template60
-rw-r--r--deps/v8/third_party/inspector_protocol/pdl.py24
-rwxr-xr-xdeps/v8/third_party/inspector_protocol/roll.py47
-rw-r--r--deps/v8/third_party/inspector_protocol/templates/TypeBuilder_cpp.template4
-rw-r--r--deps/v8/third_party/inspector_protocol/templates/TypeBuilder_h.template4
-rw-r--r--deps/v8/third_party/v8/builtins/array-sort.tq395
19 files changed, 5285 insertions, 445 deletions
diff --git a/deps/v8/third_party/inspector_protocol/.clang-format b/deps/v8/third_party/inspector_protocol/.clang-format
new file mode 100644
index 0000000000..fcbc9c321a
--- /dev/null
+++ b/deps/v8/third_party/inspector_protocol/.clang-format
@@ -0,0 +1,36 @@
+# Defines the Chromium style for automatic reformatting.
+# http://clang.llvm.org/docs/ClangFormatStyleOptions.html
+BasedOnStyle: Chromium
+# This defaults to 'Auto'. Explicitly set it for a while, so that
+# 'vector<vector<int> >' in existing files gets formatted to
+# 'vector<vector<int>>'. ('Auto' means that clang-format will only use
+# 'int>>' if the file already contains at least one such instance.)
+Standard: Cpp11
+
+# Make sure code like:
+# IPC_BEGIN_MESSAGE_MAP()
+# IPC_MESSAGE_HANDLER(WidgetHostViewHost_Update, OnUpdate)
+# IPC_END_MESSAGE_MAP()
+# gets correctly indented.
+MacroBlockBegin: "^\
+BEGIN_MSG_MAP|\
+BEGIN_MSG_MAP_EX|\
+BEGIN_SAFE_MSG_MAP_EX|\
+CR_BEGIN_MSG_MAP_EX|\
+IPC_BEGIN_MESSAGE_MAP|\
+IPC_BEGIN_MESSAGE_MAP_WITH_PARAM|\
+IPC_PROTOBUF_MESSAGE_TRAITS_BEGIN|\
+IPC_STRUCT_BEGIN|\
+IPC_STRUCT_BEGIN_WITH_PARENT|\
+IPC_STRUCT_TRAITS_BEGIN|\
+POLPARAMS_BEGIN|\
+PPAPI_BEGIN_MESSAGE_MAP$"
+MacroBlockEnd: "^\
+CR_END_MSG_MAP|\
+END_MSG_MAP|\
+IPC_END_MESSAGE_MAP|\
+IPC_PROTOBUF_MESSAGE_TRAITS_END|\
+IPC_STRUCT_END|\
+IPC_STRUCT_TRAITS_END|\
+POLPARAMS_END|\
+PPAPI_END_MESSAGE_MAP$"
diff --git a/deps/v8/third_party/inspector_protocol/BUILD.gn b/deps/v8/third_party/inspector_protocol/BUILD.gn
new file mode 100644
index 0000000000..974471bf27
--- /dev/null
+++ b/deps/v8/third_party/inspector_protocol/BUILD.gn
@@ -0,0 +1,34 @@
+# Copyright 2019 the V8 project authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+static_library("encoding") {
+ sources = [
+ "encoding/encoding.cc",
+ "encoding/encoding.h",
+ ]
+}
+
+# encoding_test is part of the unittests, defined in
+# test/unittests/BUILD.gn.
+
+import("../../gni/v8.gni")
+
+v8_source_set("encoding_test") {
+ sources = [
+ "encoding/encoding_test.cc",
+ "encoding/encoding_test_helper.h",
+ ]
+ configs = [
+ "../..:external_config",
+ "../..:internal_config_base",
+ ]
+ deps = [
+ ":encoding",
+ "../..:v8_libbase",
+ "../../src/inspector:inspector_string_conversions",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+ testonly = true
+}
diff --git a/deps/v8/third_party/inspector_protocol/README.v8 b/deps/v8/third_party/inspector_protocol/README.v8
index 3c795662f4..ade9e8e52c 100644
--- a/deps/v8/third_party/inspector_protocol/README.v8
+++ b/deps/v8/third_party/inspector_protocol/README.v8
@@ -2,7 +2,7 @@ Name: inspector protocol
Short Name: inspector_protocol
URL: https://chromium.googlesource.com/deps/inspector_protocol/
Version: 0
-Revision: 16b370abe6f4b59efea00377473b5dddb438defb
+Revision: fe0467fd105a9ea90fbb091dc2a7b4cdbf539803
License: BSD
License File: LICENSE
Security Critical: no
diff --git a/deps/v8/third_party/inspector_protocol/code_generator.py b/deps/v8/third_party/inspector_protocol/code_generator.py
index 18777d0ff8..1e12343e05 100755
--- a/deps/v8/third_party/inspector_protocol/code_generator.py
+++ b/deps/v8/third_party/inspector_protocol/code_generator.py
@@ -5,7 +5,7 @@
import os.path
import sys
-import optparse
+import argparse
import collections
import functools
import re
@@ -17,6 +17,13 @@ except ImportError:
import pdl
+try:
+ unicode
+except NameError:
+ # Define unicode for Py3
+ def unicode(s, *_):
+ return s
+
# Path handling for libraries and templates
# Paths have to be normalized because Jinja uses the exact template path to
# determine the hash used in the cache filename, and we need a pre-caching step
@@ -53,28 +60,17 @@ def read_config():
return collections.namedtuple('X', keys)(*values)
try:
- cmdline_parser = optparse.OptionParser()
- cmdline_parser.add_option("--output_base")
- cmdline_parser.add_option("--jinja_dir")
- cmdline_parser.add_option("--config")
- cmdline_parser.add_option("--config_value", action="append", type="string")
- arg_options, _ = cmdline_parser.parse_args()
+ cmdline_parser = argparse.ArgumentParser()
+ cmdline_parser.add_argument("--output_base", type=unicode, required=True)
+ cmdline_parser.add_argument("--jinja_dir", type=unicode, required=True)
+ cmdline_parser.add_argument("--config", type=unicode, required=True)
+ cmdline_parser.add_argument("--config_value", default=[], action="append")
+ arg_options = cmdline_parser.parse_args()
jinja_dir = arg_options.jinja_dir
- if not jinja_dir:
- raise Exception("jinja directory must be specified")
- jinja_dir = jinja_dir.decode('utf8')
output_base = arg_options.output_base
- if not output_base:
- raise Exception("Base output directory must be specified")
- output_base = output_base.decode('utf8')
config_file = arg_options.config
- if not config_file:
- raise Exception("Config file name must be specified")
- config_file = config_file.decode('utf8')
config_base = os.path.dirname(config_file)
config_values = arg_options.config_value
- if not config_values:
- config_values = []
except Exception:
# Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html
exc = sys.exc_info()[1]
diff --git a/deps/v8/third_party/inspector_protocol/convert_protocol_to_json.py b/deps/v8/third_party/inspector_protocol/convert_protocol_to_json.py
index 96048f793d..f98bebcd5e 100755
--- a/deps/v8/third_party/inspector_protocol/convert_protocol_to_json.py
+++ b/deps/v8/third_party/inspector_protocol/convert_protocol_to_json.py
@@ -4,10 +4,8 @@
# found in the LICENSE file.
import argparse
-import collections
import json
import os.path
-import re
import sys
import pdl
diff --git a/deps/v8/third_party/inspector_protocol/encoding/encoding.cc b/deps/v8/third_party/inspector_protocol/encoding/encoding.cc
new file mode 100644
index 0000000000..649cc060f5
--- /dev/null
+++ b/deps/v8/third_party/inspector_protocol/encoding/encoding.cc
@@ -0,0 +1,2190 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "encoding.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstring>
+#include <limits>
+#include <stack>
+
+namespace v8_inspector_protocol_encoding {
+// =============================================================================
+// Status and Error codes
+// =============================================================================
+
+std::string Status::ToASCIIString() const {
+ switch (error) {
+ case Error::OK:
+ return "OK";
+ case Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS:
+ return ToASCIIString("JSON: unprocessed input remains");
+ case Error::JSON_PARSER_STACK_LIMIT_EXCEEDED:
+ return ToASCIIString("JSON: stack limit exceeded");
+ case Error::JSON_PARSER_NO_INPUT:
+ return ToASCIIString("JSON: no input");
+ case Error::JSON_PARSER_INVALID_TOKEN:
+ return ToASCIIString("JSON: invalid token");
+ case Error::JSON_PARSER_INVALID_NUMBER:
+ return ToASCIIString("JSON: invalid number");
+ case Error::JSON_PARSER_INVALID_STRING:
+ return ToASCIIString("JSON: invalid string");
+ case Error::JSON_PARSER_UNEXPECTED_ARRAY_END:
+ return ToASCIIString("JSON: unexpected array end");
+ case Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED:
+ return ToASCIIString("JSON: comma or array end expected");
+ case Error::JSON_PARSER_STRING_LITERAL_EXPECTED:
+ return ToASCIIString("JSON: string literal expected");
+ case Error::JSON_PARSER_COLON_EXPECTED:
+ return ToASCIIString("JSON: colon expected");
+ case Error::JSON_PARSER_UNEXPECTED_MAP_END:
+ return ToASCIIString("JSON: unexpected map end");
+ case Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED:
+ return ToASCIIString("JSON: comma or map end expected");
+ case Error::JSON_PARSER_VALUE_EXPECTED:
+ return ToASCIIString("JSON: value expected");
+
+ case Error::CBOR_INVALID_INT32:
+ return ToASCIIString("CBOR: invalid int32");
+ case Error::CBOR_INVALID_DOUBLE:
+ return ToASCIIString("CBOR: invalid double");
+ case Error::CBOR_INVALID_ENVELOPE:
+ return ToASCIIString("CBOR: invalid envelope");
+ case Error::CBOR_INVALID_STRING8:
+ return ToASCIIString("CBOR: invalid string8");
+ case Error::CBOR_INVALID_STRING16:
+ return ToASCIIString("CBOR: invalid string16");
+ case Error::CBOR_INVALID_BINARY:
+ return ToASCIIString("CBOR: invalid binary");
+ case Error::CBOR_UNSUPPORTED_VALUE:
+ return ToASCIIString("CBOR: unsupported value");
+ case Error::CBOR_NO_INPUT:
+ return ToASCIIString("CBOR: no input");
+ case Error::CBOR_INVALID_START_BYTE:
+ return ToASCIIString("CBOR: invalid start byte");
+ case Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE:
+ return ToASCIIString("CBOR: unexpected eof expected value");
+ case Error::CBOR_UNEXPECTED_EOF_IN_ARRAY:
+ return ToASCIIString("CBOR: unexpected eof in array");
+ case Error::CBOR_UNEXPECTED_EOF_IN_MAP:
+ return ToASCIIString("CBOR: unexpected eof in map");
+ case Error::CBOR_INVALID_MAP_KEY:
+ return ToASCIIString("CBOR: invalid map key");
+ case Error::CBOR_STACK_LIMIT_EXCEEDED:
+ return ToASCIIString("CBOR: stack limit exceeded");
+ case Error::CBOR_TRAILING_JUNK:
+ return ToASCIIString("CBOR: trailing junk");
+ case Error::CBOR_MAP_START_EXPECTED:
+ return ToASCIIString("CBOR: map start expected");
+ case Error::CBOR_MAP_STOP_EXPECTED:
+ return ToASCIIString("CBOR: map stop expected");
+ case Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED:
+ return ToASCIIString("CBOR: envelope size limit exceeded");
+ }
+ // Some compilers can't figure out that we can't get here.
+ return "INVALID ERROR CODE";
+}
+
+std::string Status::ToASCIIString(const char* msg) const {
+ return std::string(msg) + " at position " + std::to_string(pos);
+}
+
+namespace cbor {
+namespace {
+// Indicates the number of bits the "initial byte" needs to be shifted to the
+// right after applying |kMajorTypeMask| to produce the major type in the
+// lowermost bits.
+static constexpr uint8_t kMajorTypeBitShift = 5u;
+// Mask selecting the low-order 5 bits of the "initial byte", which is where
+// the additional information is encoded.
+static constexpr uint8_t kAdditionalInformationMask = 0x1f;
+// Mask selecting the high-order 3 bits of the "initial byte", which indicates
+// the major type of the encoded value.
+static constexpr uint8_t kMajorTypeMask = 0xe0;
+// Indicates the integer is in the following byte.
+static constexpr uint8_t kAdditionalInformation1Byte = 24u;
+// Indicates the integer is in the next 2 bytes.
+static constexpr uint8_t kAdditionalInformation2Bytes = 25u;
+// Indicates the integer is in the next 4 bytes.
+static constexpr uint8_t kAdditionalInformation4Bytes = 26u;
+// Indicates the integer is in the next 8 bytes.
+static constexpr uint8_t kAdditionalInformation8Bytes = 27u;
+
+// Encodes the initial byte, consisting of the |type| in the first 3 bits
+// followed by 5 bits of |additional_info|.
+constexpr uint8_t EncodeInitialByte(MajorType type, uint8_t additional_info) {
+ return (static_cast<uint8_t>(type) << kMajorTypeBitShift) |
+ (additional_info & kAdditionalInformationMask);
+}
+
+// TAG 24 indicates that what follows is a byte string which is
+// encoded in CBOR format. We use this as a wrapper for
+// maps and arrays, allowing us to skip them, because the
+// byte string carries its size (byte length).
+// https://tools.ietf.org/html/rfc7049#section-2.4.4.1
+static constexpr uint8_t kInitialByteForEnvelope =
+ EncodeInitialByte(MajorType::TAG, 24);
+// The initial byte for a byte string with at most 2^32 bytes
+// of payload. This is used for envelope encoding, even if
+// the byte string is shorter.
+static constexpr uint8_t kInitialByteFor32BitLengthByteString =
+ EncodeInitialByte(MajorType::BYTE_STRING, 26);
+
+// See RFC 7049 Section 2.2.1, indefinite length arrays / maps have additional
+// info = 31.
+static constexpr uint8_t kInitialByteIndefiniteLengthArray =
+ EncodeInitialByte(MajorType::ARRAY, 31);
+static constexpr uint8_t kInitialByteIndefiniteLengthMap =
+ EncodeInitialByte(MajorType::MAP, 31);
+// See RFC 7049 Section 2.3, Table 1; this is used for finishing indefinite
+// length maps / arrays.
+static constexpr uint8_t kStopByte =
+ EncodeInitialByte(MajorType::SIMPLE_VALUE, 31);
+
+// See RFC 7049 Section 2.3, Table 2.
+static constexpr uint8_t kEncodedTrue =
+ EncodeInitialByte(MajorType::SIMPLE_VALUE, 21);
+static constexpr uint8_t kEncodedFalse =
+ EncodeInitialByte(MajorType::SIMPLE_VALUE, 20);
+static constexpr uint8_t kEncodedNull =
+ EncodeInitialByte(MajorType::SIMPLE_VALUE, 22);
+static constexpr uint8_t kInitialByteForDouble =
+ EncodeInitialByte(MajorType::SIMPLE_VALUE, 27);
+
+// See RFC 7049 Table 3 and Section 2.4.4.2. This is used as a prefix for
+// arbitrary binary data encoded as BYTE_STRING.
+static constexpr uint8_t kExpectedConversionToBase64Tag =
+ EncodeInitialByte(MajorType::TAG, 22);
+
+// Writes the bytes for |v| to |out|, starting with the most significant byte.
+// See also: https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html
+template <typename T, class C>
+void WriteBytesMostSignificantByteFirst(T v, C* out) {
+ for (int shift_bytes = sizeof(T) - 1; shift_bytes >= 0; --shift_bytes)
+ out->push_back(0xff & (v >> (shift_bytes * 8)));
+}
+
+// Extracts sizeof(T) bytes from |in| to extract a value of type T
+// (e.g. uint64_t, uint32_t, ...), most significant byte first.
+// See also: https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html
+template <typename T>
+T ReadBytesMostSignificantByteFirst(span<uint8_t> in) {
+ assert(in.size() >= sizeof(T));
+ T result = 0;
+ for (size_t shift_bytes = 0; shift_bytes < sizeof(T); ++shift_bytes)
+ result |= T(in[sizeof(T) - 1 - shift_bytes]) << (shift_bytes * 8);
+ return result;
+}
+} // namespace
+
+namespace internals {
+// Reads the start of a token with definitive size from |bytes|.
+// |type| is the major type as specified in RFC 7049 Section 2.1.
+// |value| is the payload (e.g. for MajorType::UNSIGNED) or is the size
+// (e.g. for BYTE_STRING).
+// If successful, returns the number of bytes read. Otherwise returns -1.
+// TODO(johannes): change return type to size_t and use 0 for error.
+int8_t ReadTokenStart(span<uint8_t> bytes, MajorType* type, uint64_t* value) {
+ if (bytes.empty())
+ return -1;
+ uint8_t initial_byte = bytes[0];
+ *type = MajorType((initial_byte & kMajorTypeMask) >> kMajorTypeBitShift);
+
+ uint8_t additional_information = initial_byte & kAdditionalInformationMask;
+ if (additional_information < 24) {
+ // Values 0-23 are encoded directly into the additional info of the
+ // initial byte.
+ *value = additional_information;
+ return 1;
+ }
+ if (additional_information == kAdditionalInformation1Byte) {
+ // Values 24-255 are encoded with one initial byte, followed by the value.
+ if (bytes.size() < 2)
+ return -1;
+ *value = ReadBytesMostSignificantByteFirst<uint8_t>(bytes.subspan(1));
+ return 2;
+ }
+ if (additional_information == kAdditionalInformation2Bytes) {
+ // Values 256-65535: 1 initial byte + 2 bytes payload.
+ if (bytes.size() < 1 + sizeof(uint16_t))
+ return -1;
+ *value = ReadBytesMostSignificantByteFirst<uint16_t>(bytes.subspan(1));
+ return 3;
+ }
+ if (additional_information == kAdditionalInformation4Bytes) {
+ // 32 bit uint: 1 initial byte + 4 bytes payload.
+ if (bytes.size() < 1 + sizeof(uint32_t))
+ return -1;
+ *value = ReadBytesMostSignificantByteFirst<uint32_t>(bytes.subspan(1));
+ return 5;
+ }
+ if (additional_information == kAdditionalInformation8Bytes) {
+ // 64 bit uint: 1 initial byte + 8 bytes payload.
+ if (bytes.size() < 1 + sizeof(uint64_t))
+ return -1;
+ *value = ReadBytesMostSignificantByteFirst<uint64_t>(bytes.subspan(1));
+ return 9;
+ }
+ return -1;
+}
+
+// Writes the start of a token with |type|. The |value| may indicate the size,
+// or it may be the payload if the value is an unsigned integer.
+template <typename C>
+void WriteTokenStartTmpl(MajorType type, uint64_t value, C* encoded) {
+ if (value < 24) {
+ // Values 0-23 are encoded directly into the additional info of the
+ // initial byte.
+ encoded->push_back(EncodeInitialByte(type, /*additional_info=*/value));
+ return;
+ }
+ if (value <= std::numeric_limits<uint8_t>::max()) {
+ // Values 24-255 are encoded with one initial byte, followed by the value.
+ encoded->push_back(EncodeInitialByte(type, kAdditionalInformation1Byte));
+ encoded->push_back(value);
+ return;
+ }
+ if (value <= std::numeric_limits<uint16_t>::max()) {
+ // Values 256-65535: 1 initial byte + 2 bytes payload.
+ encoded->push_back(EncodeInitialByte(type, kAdditionalInformation2Bytes));
+ WriteBytesMostSignificantByteFirst<uint16_t>(value, encoded);
+ return;
+ }
+ if (value <= std::numeric_limits<uint32_t>::max()) {
+ // 32 bit uint: 1 initial byte + 4 bytes payload.
+ encoded->push_back(EncodeInitialByte(type, kAdditionalInformation4Bytes));
+ WriteBytesMostSignificantByteFirst<uint32_t>(static_cast<uint32_t>(value),
+ encoded);
+ return;
+ }
+ // 64 bit uint: 1 initial byte + 8 bytes payload.
+ encoded->push_back(EncodeInitialByte(type, kAdditionalInformation8Bytes));
+ WriteBytesMostSignificantByteFirst<uint64_t>(value, encoded);
+}
+void WriteTokenStart(MajorType type,
+ uint64_t value,
+ std::vector<uint8_t>* encoded) {
+ WriteTokenStartTmpl(type, value, encoded);
+}
+void WriteTokenStart(MajorType type, uint64_t value, std::string* encoded) {
+ WriteTokenStartTmpl(type, value, encoded);
+}
+} // namespace internals
+
+// =============================================================================
+// Detecting CBOR content
+// =============================================================================
+
+uint8_t InitialByteForEnvelope() {
+ return kInitialByteForEnvelope;
+}
+uint8_t InitialByteFor32BitLengthByteString() {
+ return kInitialByteFor32BitLengthByteString;
+}
+bool IsCBORMessage(span<uint8_t> msg) {
+ return msg.size() >= 6 && msg[0] == InitialByteForEnvelope() &&
+ msg[1] == InitialByteFor32BitLengthByteString();
+}
+
+// =============================================================================
+// Encoding invidiual CBOR items
+// =============================================================================
+
+uint8_t EncodeTrue() {
+ return kEncodedTrue;
+}
+uint8_t EncodeFalse() {
+ return kEncodedFalse;
+}
+uint8_t EncodeNull() {
+ return kEncodedNull;
+}
+
+uint8_t EncodeIndefiniteLengthArrayStart() {
+ return kInitialByteIndefiniteLengthArray;
+}
+
+uint8_t EncodeIndefiniteLengthMapStart() {
+ return kInitialByteIndefiniteLengthMap;
+}
+
+uint8_t EncodeStop() {
+ return kStopByte;
+}
+
+template <typename C>
+void EncodeInt32Tmpl(int32_t value, C* out) {
+ if (value >= 0) {
+ internals::WriteTokenStart(MajorType::UNSIGNED, value, out);
+ } else {
+ uint64_t representation = static_cast<uint64_t>(-(value + 1));
+ internals::WriteTokenStart(MajorType::NEGATIVE, representation, out);
+ }
+}
+void EncodeInt32(int32_t value, std::vector<uint8_t>* out) {
+ EncodeInt32Tmpl(value, out);
+}
+void EncodeInt32(int32_t value, std::string* out) {
+ EncodeInt32Tmpl(value, out);
+}
+
+template <typename C>
+void EncodeString16Tmpl(span<uint16_t> in, C* out) {
+ uint64_t byte_length = static_cast<uint64_t>(in.size_bytes());
+ internals::WriteTokenStart(MajorType::BYTE_STRING, byte_length, out);
+ // When emitting UTF16 characters, we always write the least significant byte
+ // first; this is because it's the native representation for X86.
+ // TODO(johannes): Implement a more efficient thing here later, e.g.
+ // casting *iff* the machine has this byte order.
+ // The wire format for UTF16 chars will probably remain the same
+ // (least significant byte first) since this way we can have
+ // golden files, unittests, etc. that port easily and universally.
+ // See also:
+ // https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html
+ for (const uint16_t two_bytes : in) {
+ out->push_back(two_bytes);
+ out->push_back(two_bytes >> 8);
+ }
+}
+void EncodeString16(span<uint16_t> in, std::vector<uint8_t>* out) {
+ EncodeString16Tmpl(in, out);
+}
+void EncodeString16(span<uint16_t> in, std::string* out) {
+ EncodeString16Tmpl(in, out);
+}
+
+template <typename C>
+void EncodeString8Tmpl(span<uint8_t> in, C* out) {
+ internals::WriteTokenStart(MajorType::STRING,
+ static_cast<uint64_t>(in.size_bytes()), out);
+ out->insert(out->end(), in.begin(), in.end());
+}
+void EncodeString8(span<uint8_t> in, std::vector<uint8_t>* out) {
+ EncodeString8Tmpl(in, out);
+}
+void EncodeString8(span<uint8_t> in, std::string* out) {
+ EncodeString8Tmpl(in, out);
+}
+
+template <typename C>
+void EncodeFromLatin1Tmpl(span<uint8_t> latin1, C* out) {
+ for (size_t ii = 0; ii < latin1.size(); ++ii) {
+ if (latin1[ii] <= 127)
+ continue;
+ // If there's at least one non-ASCII char, convert to UTF8.
+ std::vector<uint8_t> utf8(latin1.begin(), latin1.begin() + ii);
+ for (; ii < latin1.size(); ++ii) {
+ if (latin1[ii] <= 127) {
+ utf8.push_back(latin1[ii]);
+ } else {
+ // 0xC0 means it's a UTF8 sequence with 2 bytes.
+ utf8.push_back((latin1[ii] >> 6) | 0xc0);
+ utf8.push_back((latin1[ii] | 0x80) & 0xbf);
+ }
+ }
+ EncodeString8(SpanFrom(utf8), out);
+ return;
+ }
+ EncodeString8(latin1, out);
+}
+void EncodeFromLatin1(span<uint8_t> latin1, std::vector<uint8_t>* out) {
+ EncodeFromLatin1Tmpl(latin1, out);
+}
+void EncodeFromLatin1(span<uint8_t> latin1, std::string* out) {
+ EncodeFromLatin1Tmpl(latin1, out);
+}
+
+template <typename C>
+void EncodeFromUTF16Tmpl(span<uint16_t> utf16, C* out) {
+ // If there's at least one non-ASCII char, encode as STRING16 (UTF16).
+ for (uint16_t ch : utf16) {
+ if (ch <= 127)
+ continue;
+ EncodeString16(utf16, out);
+ return;
+ }
+ // It's all US-ASCII, strip out every second byte and encode as UTF8.
+ internals::WriteTokenStart(MajorType::STRING,
+ static_cast<uint64_t>(utf16.size()), out);
+ out->insert(out->end(), utf16.begin(), utf16.end());
+}
+void EncodeFromUTF16(span<uint16_t> utf16, std::vector<uint8_t>* out) {
+ EncodeFromUTF16Tmpl(utf16, out);
+}
+void EncodeFromUTF16(span<uint16_t> utf16, std::string* out) {
+ EncodeFromUTF16Tmpl(utf16, out);
+}
+
+template <typename C>
+void EncodeBinaryTmpl(span<uint8_t> in, C* out) {
+ out->push_back(kExpectedConversionToBase64Tag);
+ uint64_t byte_length = static_cast<uint64_t>(in.size_bytes());
+ internals::WriteTokenStart(MajorType::BYTE_STRING, byte_length, out);
+ out->insert(out->end(), in.begin(), in.end());
+}
+void EncodeBinary(span<uint8_t> in, std::vector<uint8_t>* out) {
+ EncodeBinaryTmpl(in, out);
+}
+void EncodeBinary(span<uint8_t> in, std::string* out) {
+ EncodeBinaryTmpl(in, out);
+}
+
+// A double is encoded with a specific initial byte
+// (kInitialByteForDouble) plus the 64 bits of payload for its value.
+constexpr size_t kEncodedDoubleSize = 1 + sizeof(uint64_t);
+
+// An envelope is encoded with a specific initial byte
+// (kInitialByteForEnvelope), plus the start byte for a BYTE_STRING with a 32
+// bit wide length, plus a 32 bit length for that string.
+constexpr size_t kEncodedEnvelopeHeaderSize = 1 + 1 + sizeof(uint32_t);
+
+template <typename C>
+void EncodeDoubleTmpl(double value, C* out) {
+ // The additional_info=27 indicates 64 bits for the double follow.
+ // See RFC 7049 Section 2.3, Table 1.
+ out->push_back(kInitialByteForDouble);
+ union {
+ double from_double;
+ uint64_t to_uint64;
+ } reinterpret;
+ reinterpret.from_double = value;
+ WriteBytesMostSignificantByteFirst<uint64_t>(reinterpret.to_uint64, out);
+}
+void EncodeDouble(double value, std::vector<uint8_t>* out) {
+ EncodeDoubleTmpl(value, out);
+}
+void EncodeDouble(double value, std::string* out) {
+ EncodeDoubleTmpl(value, out);
+}
+
+// =============================================================================
+// cbor::EnvelopeEncoder - for wrapping submessages
+// =============================================================================
+
+template <typename C>
+void EncodeStartTmpl(C* out, size_t* byte_size_pos) {
+ assert(*byte_size_pos == 0);
+ out->push_back(kInitialByteForEnvelope);
+ out->push_back(kInitialByteFor32BitLengthByteString);
+ *byte_size_pos = out->size();
+ out->resize(out->size() + sizeof(uint32_t));
+}
+
+void EnvelopeEncoder::EncodeStart(std::vector<uint8_t>* out) {
+ EncodeStartTmpl<std::vector<uint8_t>>(out, &byte_size_pos_);
+}
+
+void EnvelopeEncoder::EncodeStart(std::string* out) {
+ EncodeStartTmpl<std::string>(out, &byte_size_pos_);
+}
+
+template <typename C>
+bool EncodeStopTmpl(C* out, size_t* byte_size_pos) {
+ assert(*byte_size_pos != 0);
+ // The byte size is the size of the payload, that is, all the
+ // bytes that were written past the byte size position itself.
+ uint64_t byte_size = out->size() - (*byte_size_pos + sizeof(uint32_t));
+ // We store exactly 4 bytes, so at most INT32MAX, with most significant
+ // byte first.
+ if (byte_size > std::numeric_limits<uint32_t>::max())
+ return false;
+ for (int shift_bytes = sizeof(uint32_t) - 1; shift_bytes >= 0;
+ --shift_bytes) {
+ (*out)[(*byte_size_pos)++] = 0xff & (byte_size >> (shift_bytes * 8));
+ }
+ return true;
+}
+
+bool EnvelopeEncoder::EncodeStop(std::vector<uint8_t>* out) {
+ return EncodeStopTmpl(out, &byte_size_pos_);
+}
+
+bool EnvelopeEncoder::EncodeStop(std::string* out) {
+ return EncodeStopTmpl(out, &byte_size_pos_);
+}
+
+// =============================================================================
+// cbor::NewCBOREncoder - for encoding from a streaming parser
+// =============================================================================
+
+namespace {
+template <typename C>
+class CBOREncoder : public StreamingParserHandler {
+ public:
+ CBOREncoder(C* out, Status* status) : out_(out), status_(status) {
+ *status_ = Status();
+ }
+
+ void HandleMapBegin() override {
+ if (!status_->ok())
+ return;
+ envelopes_.emplace_back();
+ envelopes_.back().EncodeStart(out_);
+ out_->push_back(kInitialByteIndefiniteLengthMap);
+ }
+
+ void HandleMapEnd() override {
+ if (!status_->ok())
+ return;
+ out_->push_back(kStopByte);
+ assert(!envelopes_.empty());
+ if (!envelopes_.back().EncodeStop(out_)) {
+ HandleError(
+ Status(Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED, out_->size()));
+ return;
+ }
+ envelopes_.pop_back();
+ }
+
+ void HandleArrayBegin() override {
+ if (!status_->ok())
+ return;
+ envelopes_.emplace_back();
+ envelopes_.back().EncodeStart(out_);
+ out_->push_back(kInitialByteIndefiniteLengthArray);
+ }
+
+ void HandleArrayEnd() override {
+ if (!status_->ok())
+ return;
+ out_->push_back(kStopByte);
+ assert(!envelopes_.empty());
+ if (!envelopes_.back().EncodeStop(out_)) {
+ HandleError(
+ Status(Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED, out_->size()));
+ return;
+ }
+ envelopes_.pop_back();
+ }
+
+ void HandleString8(span<uint8_t> chars) override {
+ if (!status_->ok())
+ return;
+ EncodeString8(chars, out_);
+ }
+
+ void HandleString16(span<uint16_t> chars) override {
+ if (!status_->ok())
+ return;
+ EncodeFromUTF16(chars, out_);
+ }
+
+ void HandleBinary(span<uint8_t> bytes) override {
+ if (!status_->ok())
+ return;
+ EncodeBinary(bytes, out_);
+ }
+
+ void HandleDouble(double value) override {
+ if (!status_->ok())
+ return;
+ EncodeDouble(value, out_);
+ }
+
+ void HandleInt32(int32_t value) override {
+ if (!status_->ok())
+ return;
+ EncodeInt32(value, out_);
+ }
+
+ void HandleBool(bool value) override {
+ if (!status_->ok())
+ return;
+ // See RFC 7049 Section 2.3, Table 2.
+ out_->push_back(value ? kEncodedTrue : kEncodedFalse);
+ }
+
+ void HandleNull() override {
+ if (!status_->ok())
+ return;
+ // See RFC 7049 Section 2.3, Table 2.
+ out_->push_back(kEncodedNull);
+ }
+
+ void HandleError(Status error) override {
+ if (!status_->ok())
+ return;
+ *status_ = error;
+ out_->clear();
+ }
+
+ private:
+ C* out_;
+ std::vector<EnvelopeEncoder> envelopes_;
+ Status* status_;
+};
+} // namespace
+
+std::unique_ptr<StreamingParserHandler> NewCBOREncoder(
+ std::vector<uint8_t>* out,
+ Status* status) {
+ return std::unique_ptr<StreamingParserHandler>(
+ new CBOREncoder<std::vector<uint8_t>>(out, status));
+}
+std::unique_ptr<StreamingParserHandler> NewCBOREncoder(std::string* out,
+ Status* status) {
+ return std::unique_ptr<StreamingParserHandler>(
+ new CBOREncoder<std::string>(out, status));
+}
+
+// =============================================================================
+// cbor::CBORTokenizer - for parsing individual CBOR items
+// =============================================================================
+
+CBORTokenizer::CBORTokenizer(span<uint8_t> bytes) : bytes_(bytes) {
+ ReadNextToken(/*enter_envelope=*/false);
+}
+CBORTokenizer::~CBORTokenizer() {}
+
+CBORTokenTag CBORTokenizer::TokenTag() const {
+ return token_tag_;
+}
+
+void CBORTokenizer::Next() {
+ if (token_tag_ == CBORTokenTag::ERROR_VALUE ||
+ token_tag_ == CBORTokenTag::DONE)
+ return;
+ ReadNextToken(/*enter_envelope=*/false);
+}
+
+void CBORTokenizer::EnterEnvelope() {
+ assert(token_tag_ == CBORTokenTag::ENVELOPE);
+ ReadNextToken(/*enter_envelope=*/true);
+}
+
+Status CBORTokenizer::Status() const {
+ return status_;
+}
+
+// The following accessor functions ::GetInt32, ::GetDouble,
+// ::GetString8, ::GetString16WireRep, ::GetBinary, ::GetEnvelopeContents
+// assume that a particular token was recognized in ::ReadNextToken.
+// That's where all the error checking is done. By design,
+// the accessors (assuming the token was recognized) never produce
+// an error.
+
+int32_t CBORTokenizer::GetInt32() const {
+ assert(token_tag_ == CBORTokenTag::INT32);
+ // The range checks happen in ::ReadNextToken().
+ return static_cast<int32_t>(
+ token_start_type_ == MajorType::UNSIGNED
+ ? token_start_internal_value_
+ : -static_cast<int64_t>(token_start_internal_value_) - 1);
+}
+
+double CBORTokenizer::GetDouble() const {
+ assert(token_tag_ == CBORTokenTag::DOUBLE);
+ union {
+ uint64_t from_uint64;
+ double to_double;
+ } reinterpret;
+ reinterpret.from_uint64 = ReadBytesMostSignificantByteFirst<uint64_t>(
+ bytes_.subspan(status_.pos + 1));
+ return reinterpret.to_double;
+}
+
+span<uint8_t> CBORTokenizer::GetString8() const {
+ assert(token_tag_ == CBORTokenTag::STRING8);
+ auto length = static_cast<size_t>(token_start_internal_value_);
+ return bytes_.subspan(status_.pos + (token_byte_length_ - length), length);
+}
+
+span<uint8_t> CBORTokenizer::GetString16WireRep() const {
+ assert(token_tag_ == CBORTokenTag::STRING16);
+ auto length = static_cast<size_t>(token_start_internal_value_);
+ return bytes_.subspan(status_.pos + (token_byte_length_ - length), length);
+}
+
+span<uint8_t> CBORTokenizer::GetBinary() const {
+ assert(token_tag_ == CBORTokenTag::BINARY);
+ auto length = static_cast<size_t>(token_start_internal_value_);
+ return bytes_.subspan(status_.pos + (token_byte_length_ - length), length);
+}
+
+span<uint8_t> CBORTokenizer::GetEnvelopeContents() const {
+ assert(token_tag_ == CBORTokenTag::ENVELOPE);
+ auto length = static_cast<size_t>(token_start_internal_value_);
+ return bytes_.subspan(status_.pos + kEncodedEnvelopeHeaderSize, length);
+}
+
+// All error checking happens in ::ReadNextToken, so that the accessors
+// can avoid having to carry an error return value.
+//
+// With respect to checking the encoded lengths of strings, arrays, etc:
+// On the wire, CBOR uses 1,2,4, and 8 byte unsigned integers, so
+// we initially read them as uint64_t, usually into token_start_internal_value_.
+//
+// However, since these containers have a representation on the machine,
+// we need to do corresponding size computations on the input byte array,
+// output span (e.g. the payload for a string), etc., and size_t is
+// machine specific (in practice either 32 bit or 64 bit).
+//
+// Further, we must avoid overflowing size_t. Therefore, we use this
+// kMaxValidLength constant to:
+// - Reject values that are larger than the architecture specific
+// max size_t (differs between 32 bit and 64 bit arch).
+// - Reserve at least one bit so that we can check against overflows
+// when adding lengths (array / string length / etc.); we do this by
+// ensuring that the inputs to an addition are <= kMaxValidLength,
+// and then checking whether the sum went past it.
+//
+// See also
+// https://chromium.googlesource.com/chromium/src/+/master/docs/security/integer-semantics.md
+static const uint64_t kMaxValidLength =
+ std::min<uint64_t>(std::numeric_limits<uint64_t>::max() >> 2,
+ std::numeric_limits<size_t>::max());
+
+void CBORTokenizer::ReadNextToken(bool enter_envelope) {
+ if (enter_envelope) {
+ status_.pos += kEncodedEnvelopeHeaderSize;
+ } else {
+ status_.pos =
+ status_.pos == Status::npos() ? 0 : status_.pos + token_byte_length_;
+ }
+ status_.error = Error::OK;
+ if (status_.pos >= bytes_.size()) {
+ token_tag_ = CBORTokenTag::DONE;
+ return;
+ }
+ const size_t remaining_bytes = bytes_.size() - status_.pos;
+ switch (bytes_[status_.pos]) {
+ case kStopByte:
+ SetToken(CBORTokenTag::STOP, 1);
+ return;
+ case kInitialByteIndefiniteLengthMap:
+ SetToken(CBORTokenTag::MAP_START, 1);
+ return;
+ case kInitialByteIndefiniteLengthArray:
+ SetToken(CBORTokenTag::ARRAY_START, 1);
+ return;
+ case kEncodedTrue:
+ SetToken(CBORTokenTag::TRUE_VALUE, 1);
+ return;
+ case kEncodedFalse:
+ SetToken(CBORTokenTag::FALSE_VALUE, 1);
+ return;
+ case kEncodedNull:
+ SetToken(CBORTokenTag::NULL_VALUE, 1);
+ return;
+ case kExpectedConversionToBase64Tag: { // BINARY
+ const int8_t bytes_read = internals::ReadTokenStart(
+ bytes_.subspan(status_.pos + 1), &token_start_type_,
+ &token_start_internal_value_);
+ if (bytes_read < 0 || token_start_type_ != MajorType::BYTE_STRING ||
+ token_start_internal_value_ > kMaxValidLength) {
+ SetError(Error::CBOR_INVALID_BINARY);
+ return;
+ }
+ const uint64_t token_byte_length = token_start_internal_value_ +
+ /* tag before token start: */ 1 +
+ /* token start: */ bytes_read;
+ if (token_byte_length > remaining_bytes) {
+ SetError(Error::CBOR_INVALID_BINARY);
+ return;
+ }
+ SetToken(CBORTokenTag::BINARY, static_cast<size_t>(token_byte_length));
+ return;
+ }
+ case kInitialByteForDouble: { // DOUBLE
+ if (kEncodedDoubleSize > remaining_bytes) {
+ SetError(Error::CBOR_INVALID_DOUBLE);
+ return;
+ }
+ SetToken(CBORTokenTag::DOUBLE, kEncodedDoubleSize);
+ return;
+ }
+ case kInitialByteForEnvelope: { // ENVELOPE
+ if (kEncodedEnvelopeHeaderSize > remaining_bytes) {
+ SetError(Error::CBOR_INVALID_ENVELOPE);
+ return;
+ }
+ // The envelope must be a byte string with 32 bit length.
+ if (bytes_[status_.pos + 1] != kInitialByteFor32BitLengthByteString) {
+ SetError(Error::CBOR_INVALID_ENVELOPE);
+ return;
+ }
+ // Read the length of the byte string.
+ token_start_internal_value_ = ReadBytesMostSignificantByteFirst<uint32_t>(
+ bytes_.subspan(status_.pos + 2));
+ if (token_start_internal_value_ > kMaxValidLength) {
+ SetError(Error::CBOR_INVALID_ENVELOPE);
+ return;
+ }
+ uint64_t token_byte_length =
+ token_start_internal_value_ + kEncodedEnvelopeHeaderSize;
+ if (token_byte_length > remaining_bytes) {
+ SetError(Error::CBOR_INVALID_ENVELOPE);
+ return;
+ }
+ SetToken(CBORTokenTag::ENVELOPE, static_cast<size_t>(token_byte_length));
+ return;
+ }
+ default: {
+ const int8_t token_start_length = internals::ReadTokenStart(
+ bytes_.subspan(status_.pos), &token_start_type_,
+ &token_start_internal_value_);
+ const bool success = token_start_length >= 0;
+ switch (token_start_type_) {
+ case MajorType::UNSIGNED: // INT32.
+ // INT32 is a signed int32 (int32 makes sense for the
+ // inspector_protocol, it's not a CBOR limitation), so we check
+ // against the signed max, so that the allowable values are
+ // 0, 1, 2, ... 2^31 - 1.
+ if (!success || std::numeric_limits<int32_t>::max() <
+ token_start_internal_value_) {
+ SetError(Error::CBOR_INVALID_INT32);
+ return;
+ }
+ SetToken(CBORTokenTag::INT32, token_start_length);
+ return;
+ case MajorType::NEGATIVE: { // INT32.
+ // INT32 is a signed int32 (int32 makes sense for the
+ // inspector_protocol, it's not a CBOR limitation); in CBOR, the
+ // negative values for INT32 are represented as NEGATIVE, that is, -1
+ // INT32 is represented as 1 << 5 | 0 (major type 1, additional info
+ // value 0). The minimal allowed INT32 value in our protocol is
+ // std::numeric_limits<int32_t>::min(). We check for it by directly
+ // checking the payload against the maximal allowed signed (!) int32
+ // value.
+ if (!success || token_start_internal_value_ >
+ std::numeric_limits<int32_t>::max()) {
+ SetError(Error::CBOR_INVALID_INT32);
+ return;
+ }
+ SetToken(CBORTokenTag::INT32, token_start_length);
+ return;
+ }
+ case MajorType::STRING: { // STRING8.
+ if (!success || token_start_internal_value_ > kMaxValidLength) {
+ SetError(Error::CBOR_INVALID_STRING8);
+ return;
+ }
+ uint64_t token_byte_length =
+ token_start_internal_value_ + token_start_length;
+ if (token_byte_length > remaining_bytes) {
+ SetError(Error::CBOR_INVALID_STRING8);
+ return;
+ }
+ SetToken(CBORTokenTag::STRING8,
+ static_cast<size_t>(token_byte_length));
+ return;
+ }
+ case MajorType::BYTE_STRING: { // STRING16.
+ // Length must be divisible by 2 since UTF16 is 2 bytes per
+ // character, hence the &1 check.
+ if (!success || token_start_internal_value_ > kMaxValidLength ||
+ token_start_internal_value_ & 1) {
+ SetError(Error::CBOR_INVALID_STRING16);
+ return;
+ }
+ uint64_t token_byte_length =
+ token_start_internal_value_ + token_start_length;
+ if (token_byte_length > remaining_bytes) {
+ SetError(Error::CBOR_INVALID_STRING16);
+ return;
+ }
+ SetToken(CBORTokenTag::STRING16,
+ static_cast<size_t>(token_byte_length));
+ return;
+ }
+ case MajorType::ARRAY:
+ case MajorType::MAP:
+ case MajorType::TAG:
+ case MajorType::SIMPLE_VALUE:
+ SetError(Error::CBOR_UNSUPPORTED_VALUE);
+ return;
+ }
+ }
+ }
+}
+
+void CBORTokenizer::SetToken(CBORTokenTag token_tag, size_t token_byte_length) {
+ token_tag_ = token_tag;
+ token_byte_length_ = token_byte_length;
+}
+
+void CBORTokenizer::SetError(Error error) {
+ token_tag_ = CBORTokenTag::ERROR_VALUE;
+ status_.error = error;
+}
+
+// =============================================================================
+// cbor::ParseCBOR - for receiving streaming parser events for CBOR messages
+// =============================================================================
+
+namespace {
+// When parsing CBOR, we limit recursion depth for objects and arrays
+// to this constant.
+static constexpr int kStackLimit = 300;
+
+// Below are three parsing routines for CBOR, which cover enough
+// to roundtrip JSON messages.
+bool ParseMap(int32_t stack_depth,
+ CBORTokenizer* tokenizer,
+ StreamingParserHandler* out);
+bool ParseArray(int32_t stack_depth,
+ CBORTokenizer* tokenizer,
+ StreamingParserHandler* out);
+bool ParseValue(int32_t stack_depth,
+ CBORTokenizer* tokenizer,
+ StreamingParserHandler* out);
+
+void ParseUTF16String(CBORTokenizer* tokenizer, StreamingParserHandler* out) {
+ std::vector<uint16_t> value;
+ span<uint8_t> rep = tokenizer->GetString16WireRep();
+ for (size_t ii = 0; ii < rep.size(); ii += 2)
+ value.push_back((rep[ii + 1] << 8) | rep[ii]);
+ out->HandleString16(span<uint16_t>(value.data(), value.size()));
+ tokenizer->Next();
+}
+
+bool ParseUTF8String(CBORTokenizer* tokenizer, StreamingParserHandler* out) {
+ assert(tokenizer->TokenTag() == CBORTokenTag::STRING8);
+ out->HandleString8(tokenizer->GetString8());
+ tokenizer->Next();
+ return true;
+}
+
+bool ParseValue(int32_t stack_depth,
+ CBORTokenizer* tokenizer,
+ StreamingParserHandler* out) {
+ if (stack_depth > kStackLimit) {
+ out->HandleError(
+ Status{Error::CBOR_STACK_LIMIT_EXCEEDED, tokenizer->Status().pos});
+ return false;
+ }
+ // Skip past the envelope to get to what's inside.
+ if (tokenizer->TokenTag() == CBORTokenTag::ENVELOPE)
+ tokenizer->EnterEnvelope();
+ switch (tokenizer->TokenTag()) {
+ case CBORTokenTag::ERROR_VALUE:
+ out->HandleError(tokenizer->Status());
+ return false;
+ case CBORTokenTag::DONE:
+ out->HandleError(Status{Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE,
+ tokenizer->Status().pos});
+ return false;
+ case CBORTokenTag::TRUE_VALUE:
+ out->HandleBool(true);
+ tokenizer->Next();
+ return true;
+ case CBORTokenTag::FALSE_VALUE:
+ out->HandleBool(false);
+ tokenizer->Next();
+ return true;
+ case CBORTokenTag::NULL_VALUE:
+ out->HandleNull();
+ tokenizer->Next();
+ return true;
+ case CBORTokenTag::INT32:
+ out->HandleInt32(tokenizer->GetInt32());
+ tokenizer->Next();
+ return true;
+ case CBORTokenTag::DOUBLE:
+ out->HandleDouble(tokenizer->GetDouble());
+ tokenizer->Next();
+ return true;
+ case CBORTokenTag::STRING8:
+ return ParseUTF8String(tokenizer, out);
+ case CBORTokenTag::STRING16:
+ ParseUTF16String(tokenizer, out);
+ return true;
+ case CBORTokenTag::BINARY: {
+ out->HandleBinary(tokenizer->GetBinary());
+ tokenizer->Next();
+ return true;
+ }
+ case CBORTokenTag::MAP_START:
+ return ParseMap(stack_depth + 1, tokenizer, out);
+ case CBORTokenTag::ARRAY_START:
+ return ParseArray(stack_depth + 1, tokenizer, out);
+ default:
+ out->HandleError(
+ Status{Error::CBOR_UNSUPPORTED_VALUE, tokenizer->Status().pos});
+ return false;
+ }
+}
+
+// |bytes| must start with the indefinite length array byte, so basically,
+// ParseArray may only be called after an indefinite length array has been
+// detected.
+bool ParseArray(int32_t stack_depth,
+ CBORTokenizer* tokenizer,
+ StreamingParserHandler* out) {
+ assert(tokenizer->TokenTag() == CBORTokenTag::ARRAY_START);
+ tokenizer->Next();
+ out->HandleArrayBegin();
+ while (tokenizer->TokenTag() != CBORTokenTag::STOP) {
+ if (tokenizer->TokenTag() == CBORTokenTag::DONE) {
+ out->HandleError(
+ Status{Error::CBOR_UNEXPECTED_EOF_IN_ARRAY, tokenizer->Status().pos});
+ return false;
+ }
+ if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) {
+ out->HandleError(tokenizer->Status());
+ return false;
+ }
+ // Parse value.
+ if (!ParseValue(stack_depth, tokenizer, out))
+ return false;
+ }
+ out->HandleArrayEnd();
+ tokenizer->Next();
+ return true;
+}
+
+// |bytes| must start with the indefinite length array byte, so basically,
+// ParseArray may only be called after an indefinite length array has been
+// detected.
+bool ParseMap(int32_t stack_depth,
+ CBORTokenizer* tokenizer,
+ StreamingParserHandler* out) {
+ assert(tokenizer->TokenTag() == CBORTokenTag::MAP_START);
+ out->HandleMapBegin();
+ tokenizer->Next();
+ while (tokenizer->TokenTag() != CBORTokenTag::STOP) {
+ if (tokenizer->TokenTag() == CBORTokenTag::DONE) {
+ out->HandleError(
+ Status{Error::CBOR_UNEXPECTED_EOF_IN_MAP, tokenizer->Status().pos});
+ return false;
+ }
+ if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) {
+ out->HandleError(tokenizer->Status());
+ return false;
+ }
+ // Parse key.
+ if (tokenizer->TokenTag() == CBORTokenTag::STRING8) {
+ if (!ParseUTF8String(tokenizer, out))
+ return false;
+ } else if (tokenizer->TokenTag() == CBORTokenTag::STRING16) {
+ ParseUTF16String(tokenizer, out);
+ } else {
+ out->HandleError(
+ Status{Error::CBOR_INVALID_MAP_KEY, tokenizer->Status().pos});
+ return false;
+ }
+ // Parse value.
+ if (!ParseValue(stack_depth, tokenizer, out))
+ return false;
+ }
+ out->HandleMapEnd();
+ tokenizer->Next();
+ return true;
+}
+} // namespace
+
+void ParseCBOR(span<uint8_t> bytes, StreamingParserHandler* out) {
+ if (bytes.empty()) {
+ out->HandleError(Status{Error::CBOR_NO_INPUT, 0});
+ return;
+ }
+ if (bytes[0] != kInitialByteForEnvelope) {
+ out->HandleError(Status{Error::CBOR_INVALID_START_BYTE, 0});
+ return;
+ }
+ CBORTokenizer tokenizer(bytes);
+ if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) {
+ out->HandleError(tokenizer.Status());
+ return;
+ }
+ // We checked for the envelope start byte above, so the tokenizer
+ // must agree here, since it's not an error.
+ assert(tokenizer.TokenTag() == CBORTokenTag::ENVELOPE);
+ tokenizer.EnterEnvelope();
+ if (tokenizer.TokenTag() != CBORTokenTag::MAP_START) {
+ out->HandleError(
+ Status{Error::CBOR_MAP_START_EXPECTED, tokenizer.Status().pos});
+ return;
+ }
+ if (!ParseMap(/*stack_depth=*/1, &tokenizer, out))
+ return;
+ if (tokenizer.TokenTag() == CBORTokenTag::DONE)
+ return;
+ if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) {
+ out->HandleError(tokenizer.Status());
+ return;
+ }
+ out->HandleError(Status{Error::CBOR_TRAILING_JUNK, tokenizer.Status().pos});
+}
+
+// =============================================================================
+// cbor::AppendString8EntryToMap - for limited in-place editing of messages
+// =============================================================================
+
+template <typename C>
+Status AppendString8EntryToCBORMapTmpl(span<uint8_t> string8_key,
+ span<uint8_t> string8_value,
+ C* cbor) {
+ // Careful below: Don't compare (*cbor)[idx] with a uint8_t, since
+ // it could be a char (signed!). Instead, use bytes.
+ span<uint8_t> bytes(reinterpret_cast<const uint8_t*>(cbor->data()),
+ cbor->size());
+ CBORTokenizer tokenizer(bytes);
+ if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE)
+ return tokenizer.Status();
+ if (tokenizer.TokenTag() != CBORTokenTag::ENVELOPE)
+ return Status(Error::CBOR_INVALID_ENVELOPE, 0);
+ size_t envelope_size = tokenizer.GetEnvelopeContents().size();
+ size_t old_size = cbor->size();
+ if (old_size != envelope_size + kEncodedEnvelopeHeaderSize)
+ return Status(Error::CBOR_INVALID_ENVELOPE, 0);
+ if (envelope_size == 0 ||
+ (tokenizer.GetEnvelopeContents()[0] != EncodeIndefiniteLengthMapStart()))
+ return Status(Error::CBOR_MAP_START_EXPECTED, kEncodedEnvelopeHeaderSize);
+ if (bytes[bytes.size() - 1] != EncodeStop())
+ return Status(Error::CBOR_MAP_STOP_EXPECTED, cbor->size() - 1);
+ cbor->pop_back();
+ EncodeString8(string8_key, cbor);
+ EncodeString8(string8_value, cbor);
+ cbor->push_back(EncodeStop());
+ size_t new_envelope_size = envelope_size + (cbor->size() - old_size);
+ if (new_envelope_size > std::numeric_limits<uint32_t>::max())
+ return Status(Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED, 0);
+ size_t size_pos = cbor->size() - new_envelope_size - sizeof(uint32_t);
+ uint8_t* out = reinterpret_cast<uint8_t*>(&cbor->at(size_pos));
+ *(out++) = (new_envelope_size >> 24) & 0xff;
+ *(out++) = (new_envelope_size >> 16) & 0xff;
+ *(out++) = (new_envelope_size >> 8) & 0xff;
+ *(out) = new_envelope_size & 0xff;
+ return Status();
+}
+Status AppendString8EntryToCBORMap(span<uint8_t> string8_key,
+ span<uint8_t> string8_value,
+ std::vector<uint8_t>* cbor) {
+ return AppendString8EntryToCBORMapTmpl(string8_key, string8_value, cbor);
+}
+Status AppendString8EntryToCBORMap(span<uint8_t> string8_key,
+ span<uint8_t> string8_value,
+ std::string* cbor) {
+ return AppendString8EntryToCBORMapTmpl(string8_key, string8_value, cbor);
+}
+} // namespace cbor
+
+namespace json {
+
+// =============================================================================
+// json::NewJSONEncoder - for encoding streaming parser events as JSON
+// =============================================================================
+
+namespace {
+// Prints |value| to |out| with 4 hex digits, most significant chunk first.
+template <typename C>
+void PrintHex(uint16_t value, C* out) {
+ for (int ii = 3; ii >= 0; --ii) {
+ int four_bits = 0xf & (value >> (4 * ii));
+ out->push_back(four_bits + ((four_bits <= 9) ? '0' : ('a' - 10)));
+ }
+}
+
+// In the writer below, we maintain a stack of State instances.
+// It is just enough to emit the appropriate delimiters and brackets
+// in JSON.
+enum class Container {
+ // Used for the top-level, initial state.
+ NONE,
+ // Inside a JSON object.
+ MAP,
+ // Inside a JSON array.
+ ARRAY
+};
+class State {
+ public:
+ explicit State(Container container) : container_(container) {}
+ void StartElement(std::vector<uint8_t>* out) { StartElementTmpl(out); }
+ void StartElement(std::string* out) { StartElementTmpl(out); }
+ Container container() const { return container_; }
+
+ private:
+ template <typename C>
+ void StartElementTmpl(C* out) {
+ assert(container_ != Container::NONE || size_ == 0);
+ if (size_ != 0) {
+ char delim = (!(size_ & 1) || container_ == Container::ARRAY) ? ',' : ':';
+ out->push_back(delim);
+ }
+ ++size_;
+ }
+
+ Container container_ = Container::NONE;
+ int size_ = 0;
+};
+
+constexpr char kBase64Table[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz0123456789+/";
+
+template <typename C>
+void Base64Encode(const span<uint8_t>& in, C* out) {
+ // The following three cases are based on the tables in the example
+ // section in https://en.wikipedia.org/wiki/Base64. We process three
+ // input bytes at a time, emitting 4 output bytes at a time.
+ size_t ii = 0;
+
+ // While possible, process three input bytes.
+ for (; ii + 3 <= in.size(); ii += 3) {
+ uint32_t twentyfour_bits = (in[ii] << 16) | (in[ii + 1] << 8) | in[ii + 2];
+ out->push_back(kBase64Table[(twentyfour_bits >> 18)]);
+ out->push_back(kBase64Table[(twentyfour_bits >> 12) & 0x3f]);
+ out->push_back(kBase64Table[(twentyfour_bits >> 6) & 0x3f]);
+ out->push_back(kBase64Table[twentyfour_bits & 0x3f]);
+ }
+ if (ii + 2 <= in.size()) { // Process two input bytes.
+ uint32_t twentyfour_bits = (in[ii] << 16) | (in[ii + 1] << 8);
+ out->push_back(kBase64Table[(twentyfour_bits >> 18)]);
+ out->push_back(kBase64Table[(twentyfour_bits >> 12) & 0x3f]);
+ out->push_back(kBase64Table[(twentyfour_bits >> 6) & 0x3f]);
+ out->push_back('='); // Emit padding.
+ return;
+ }
+ if (ii + 1 <= in.size()) { // Process a single input byte.
+ uint32_t twentyfour_bits = (in[ii] << 16);
+ out->push_back(kBase64Table[(twentyfour_bits >> 18)]);
+ out->push_back(kBase64Table[(twentyfour_bits >> 12) & 0x3f]);
+ out->push_back('='); // Emit padding.
+ out->push_back('='); // Emit padding.
+ }
+}
+
+// Implements a handler for JSON parser events to emit a JSON string.
+template <typename C>
+class JSONEncoder : public StreamingParserHandler {
+ public:
+ JSONEncoder(const Platform* platform, C* out, Status* status)
+ : platform_(platform), out_(out), status_(status) {
+ *status_ = Status();
+ state_.emplace(Container::NONE);
+ }
+
+ void HandleMapBegin() override {
+ if (!status_->ok())
+ return;
+ assert(!state_.empty());
+ state_.top().StartElement(out_);
+ state_.emplace(Container::MAP);
+ Emit('{');
+ }
+
+ void HandleMapEnd() override {
+ if (!status_->ok())
+ return;
+ assert(state_.size() >= 2 && state_.top().container() == Container::MAP);
+ state_.pop();
+ Emit('}');
+ }
+
+ void HandleArrayBegin() override {
+ if (!status_->ok())
+ return;
+ state_.top().StartElement(out_);
+ state_.emplace(Container::ARRAY);
+ Emit('[');
+ }
+
+ void HandleArrayEnd() override {
+ if (!status_->ok())
+ return;
+ assert(state_.size() >= 2 && state_.top().container() == Container::ARRAY);
+ state_.pop();
+ Emit(']');
+ }
+
+ void HandleString16(span<uint16_t> chars) override {
+ if (!status_->ok())
+ return;
+ state_.top().StartElement(out_);
+ Emit('"');
+ for (const uint16_t ch : chars) {
+ if (ch == '"') {
+ Emit("\\\"");
+ } else if (ch == '\\') {
+ Emit("\\\\");
+ } else if (ch == '\b') {
+ Emit("\\b");
+ } else if (ch == '\f') {
+ Emit("\\f");
+ } else if (ch == '\n') {
+ Emit("\\n");
+ } else if (ch == '\r') {
+ Emit("\\r");
+ } else if (ch == '\t') {
+ Emit("\\t");
+ } else if (ch >= 32 && ch <= 126) {
+ Emit(ch);
+ } else {
+ Emit("\\u");
+ PrintHex(ch, out_);
+ }
+ }
+ Emit('"');
+ }
+
+ void HandleString8(span<uint8_t> chars) override {
+ if (!status_->ok())
+ return;
+ state_.top().StartElement(out_);
+ Emit('"');
+ for (size_t ii = 0; ii < chars.size(); ++ii) {
+ uint8_t c = chars[ii];
+ if (c == '"') {
+ Emit("\\\"");
+ } else if (c == '\\') {
+ Emit("\\\\");
+ } else if (c == '\b') {
+ Emit("\\b");
+ } else if (c == '\f') {
+ Emit("\\f");
+ } else if (c == '\n') {
+ Emit("\\n");
+ } else if (c == '\r') {
+ Emit("\\r");
+ } else if (c == '\t') {
+ Emit("\\t");
+ } else if (c >= 32 && c <= 126) {
+ Emit(c);
+ } else if (c < 32) {
+ Emit("\\u");
+ PrintHex(static_cast<uint16_t>(c), out_);
+ } else {
+ // Inspect the leading byte to figure out how long the utf8
+ // byte sequence is; while doing this initialize |codepoint|
+ // with the first few bits.
+ // See table in: https://en.wikipedia.org/wiki/UTF-8
+ // byte one is 110x xxxx -> 2 byte utf8 sequence
+ // byte one is 1110 xxxx -> 3 byte utf8 sequence
+ // byte one is 1111 0xxx -> 4 byte utf8 sequence
+ uint32_t codepoint;
+ int num_bytes_left;
+ if ((c & 0xe0) == 0xc0) { // 2 byte utf8 sequence
+ num_bytes_left = 1;
+ codepoint = c & 0x1f;
+ } else if ((c & 0xf0) == 0xe0) { // 3 byte utf8 sequence
+ num_bytes_left = 2;
+ codepoint = c & 0x0f;
+ } else if ((c & 0xf8) == 0xf0) { // 4 byte utf8 sequence
+ codepoint = c & 0x07;
+ num_bytes_left = 3;
+ } else {
+ continue; // invalid leading byte
+ }
+
+ // If we have enough bytes in our input, decode the remaining ones
+ // belonging to this Unicode character into |codepoint|.
+ if (ii + num_bytes_left > chars.size())
+ continue;
+ while (num_bytes_left > 0) {
+ c = chars[++ii];
+ --num_bytes_left;
+ // Check the next byte is a continuation byte, that is 10xx xxxx.
+ if ((c & 0xc0) != 0x80)
+ continue;
+ codepoint = (codepoint << 6) | (c & 0x3f);
+ }
+
+ // Disallow overlong encodings for ascii characters, as these
+ // would include " and other characters significant to JSON
+ // string termination / control.
+ if (codepoint < 0x7f)
+ continue;
+ // Invalid in UTF8, and can't be represented in UTF16 anyway.
+ if (codepoint > 0x10ffff)
+ continue;
+
+ // So, now we transcode to UTF16,
+ // using the math described at https://en.wikipedia.org/wiki/UTF-16,
+ // for either one or two 16 bit characters.
+ if (codepoint < 0xffff) {
+ Emit("\\u");
+ PrintHex(static_cast<uint16_t>(codepoint), out_);
+ continue;
+ }
+ codepoint -= 0x10000;
+ // high surrogate
+ Emit("\\u");
+ PrintHex(static_cast<uint16_t>((codepoint >> 10) + 0xd800), out_);
+ // low surrogate
+ Emit("\\u");
+ PrintHex(static_cast<uint16_t>((codepoint & 0x3ff) + 0xdc00), out_);
+ }
+ }
+ Emit('"');
+ }
+
+ void HandleBinary(span<uint8_t> bytes) override {
+ if (!status_->ok())
+ return;
+ state_.top().StartElement(out_);
+ Emit('"');
+ Base64Encode(bytes, out_);
+ Emit('"');
+ }
+
+ void HandleDouble(double value) override {
+ if (!status_->ok())
+ return;
+ state_.top().StartElement(out_);
+ // JSON cannot represent NaN or Infinity. So, for compatibility,
+ // we behave like the JSON object in web browsers: emit 'null'.
+ if (!std::isfinite(value)) {
+ Emit("null");
+ return;
+ }
+ std::unique_ptr<char[]> str_value = platform_->DToStr(value);
+
+ // DToStr may fail to emit a 0 before the decimal dot. E.g. this is
+ // the case in base::NumberToString in Chromium (which is based on
+ // dmg_fp). So, much like
+ // https://cs.chromium.org/chromium/src/base/json/json_writer.cc
+ // we probe for this and emit the leading 0 anyway if necessary.
+ const char* chars = str_value.get();
+ if (chars[0] == '.') {
+ Emit('0');
+ } else if (chars[0] == '-' && chars[1] == '.') {
+ Emit("-0");
+ ++chars;
+ }
+ Emit(chars);
+ }
+
+ void HandleInt32(int32_t value) override {
+ if (!status_->ok())
+ return;
+ state_.top().StartElement(out_);
+ Emit(std::to_string(value));
+ }
+
+ void HandleBool(bool value) override {
+ if (!status_->ok())
+ return;
+ state_.top().StartElement(out_);
+ Emit(value ? "true" : "false");
+ }
+
+ void HandleNull() override {
+ if (!status_->ok())
+ return;
+ state_.top().StartElement(out_);
+ Emit("null");
+ }
+
+ void HandleError(Status error) override {
+ assert(!error.ok());
+ *status_ = error;
+ out_->clear();
+ }
+
+ private:
+ void Emit(char c) { out_->push_back(c); }
+ void Emit(const char* str) {
+ out_->insert(out_->end(), str, str + strlen(str));
+ }
+ void Emit(const std::string& str) {
+ out_->insert(out_->end(), str.begin(), str.end());
+ }
+
+ const Platform* platform_;
+ C* out_;
+ Status* status_;
+ std::stack<State> state_;
+};
+} // namespace
+
+std::unique_ptr<StreamingParserHandler> NewJSONEncoder(
+ const Platform* platform,
+ std::vector<uint8_t>* out,
+ Status* status) {
+ return std::unique_ptr<StreamingParserHandler>(
+ new JSONEncoder<std::vector<uint8_t>>(platform, out, status));
+}
+std::unique_ptr<StreamingParserHandler> NewJSONEncoder(const Platform* platform,
+ std::string* out,
+ Status* status) {
+ return std::unique_ptr<StreamingParserHandler>(
+ new JSONEncoder<std::string>(platform, out, status));
+}
+
+// =============================================================================
+// json::ParseJSON - for receiving streaming parser events for JSON.
+// =============================================================================
+
+namespace {
+const int kStackLimit = 300;
+
+enum Token {
+ ObjectBegin,
+ ObjectEnd,
+ ArrayBegin,
+ ArrayEnd,
+ StringLiteral,
+ Number,
+ BoolTrue,
+ BoolFalse,
+ NullToken,
+ ListSeparator,
+ ObjectPairSeparator,
+ InvalidToken,
+ NoInput
+};
+
+const char* const kNullString = "null";
+const char* const kTrueString = "true";
+const char* const kFalseString = "false";
+
+template <typename Char>
+class JsonParser {
+ public:
+ JsonParser(const Platform* platform, StreamingParserHandler* handler)
+ : platform_(platform), handler_(handler) {}
+
+ void Parse(const Char* start, size_t length) {
+ start_pos_ = start;
+ const Char* end = start + length;
+ const Char* tokenEnd = nullptr;
+ ParseValue(start, end, &tokenEnd, 0);
+ if (error_)
+ return;
+ if (tokenEnd != end) {
+ HandleError(Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS, tokenEnd);
+ }
+ }
+
+ private:
+ bool CharsToDouble(const uint16_t* chars, size_t length, double* result) {
+ std::string buffer;
+ buffer.reserve(length + 1);
+ for (size_t ii = 0; ii < length; ++ii) {
+ bool is_ascii = !(chars[ii] & ~0x7F);
+ if (!is_ascii)
+ return false;
+ buffer.push_back(static_cast<char>(chars[ii]));
+ }
+ return platform_->StrToD(buffer.c_str(), result);
+ }
+
+ bool CharsToDouble(const uint8_t* chars, size_t length, double* result) {
+ std::string buffer(reinterpret_cast<const char*>(chars), length);
+ return platform_->StrToD(buffer.c_str(), result);
+ }
+
+ static bool ParseConstToken(const Char* start,
+ const Char* end,
+ const Char** token_end,
+ const char* token) {
+ // |token| is \0 terminated, it's one of the constants at top of the file.
+ while (start < end && *token != '\0' && *start++ == *token++) {
+ }
+ if (*token != '\0')
+ return false;
+ *token_end = start;
+ return true;
+ }
+
+ static bool ReadInt(const Char* start,
+ const Char* end,
+ const Char** token_end,
+ bool allow_leading_zeros) {
+ if (start == end)
+ return false;
+ bool has_leading_zero = '0' == *start;
+ int length = 0;
+ while (start < end && '0' <= *start && *start <= '9') {
+ ++start;
+ ++length;
+ }
+ if (!length)
+ return false;
+ if (!allow_leading_zeros && length > 1 && has_leading_zero)
+ return false;
+ *token_end = start;
+ return true;
+ }
+
+ static bool ParseNumberToken(const Char* start,
+ const Char* end,
+ const Char** token_end) {
+ // We just grab the number here. We validate the size in DecodeNumber.
+ // According to RFC4627, a valid number is: [minus] int [frac] [exp]
+ if (start == end)
+ return false;
+ Char c = *start;
+ if ('-' == c)
+ ++start;
+
+ if (!ReadInt(start, end, &start, /*allow_leading_zeros=*/false))
+ return false;
+ if (start == end) {
+ *token_end = start;
+ return true;
+ }
+
+ // Optional fraction part
+ c = *start;
+ if ('.' == c) {
+ ++start;
+ if (!ReadInt(start, end, &start, /*allow_leading_zeros=*/true))
+ return false;
+ if (start == end) {
+ *token_end = start;
+ return true;
+ }
+ c = *start;
+ }
+
+ // Optional exponent part
+ if ('e' == c || 'E' == c) {
+ ++start;
+ if (start == end)
+ return false;
+ c = *start;
+ if ('-' == c || '+' == c) {
+ ++start;
+ if (start == end)
+ return false;
+ }
+ if (!ReadInt(start, end, &start, /*allow_leading_zeros=*/true))
+ return false;
+ }
+
+ *token_end = start;
+ return true;
+ }
+
+ static bool ReadHexDigits(const Char* start,
+ const Char* end,
+ const Char** token_end,
+ int digits) {
+ if (end - start < digits)
+ return false;
+ for (int i = 0; i < digits; ++i) {
+ Char c = *start++;
+ if (!(('0' <= c && c <= '9') || ('a' <= c && c <= 'f') ||
+ ('A' <= c && c <= 'F')))
+ return false;
+ }
+ *token_end = start;
+ return true;
+ }
+
+ static bool ParseStringToken(const Char* start,
+ const Char* end,
+ const Char** token_end) {
+ while (start < end) {
+ Char c = *start++;
+ if ('\\' == c) {
+ if (start == end)
+ return false;
+ c = *start++;
+ // Make sure the escaped char is valid.
+ switch (c) {
+ case 'x':
+ if (!ReadHexDigits(start, end, &start, 2))
+ return false;
+ break;
+ case 'u':
+ if (!ReadHexDigits(start, end, &start, 4))
+ return false;
+ break;
+ case '\\':
+ case '/':
+ case 'b':
+ case 'f':
+ case 'n':
+ case 'r':
+ case 't':
+ case 'v':
+ case '"':
+ break;
+ default:
+ return false;
+ }
+ } else if ('"' == c) {
+ *token_end = start;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static bool SkipComment(const Char* start,
+ const Char* end,
+ const Char** comment_end) {
+ if (start == end)
+ return false;
+
+ if (*start != '/' || start + 1 >= end)
+ return false;
+ ++start;
+
+ if (*start == '/') {
+ // Single line comment, read to newline.
+ for (++start; start < end; ++start) {
+ if (*start == '\n' || *start == '\r') {
+ *comment_end = start + 1;
+ return true;
+ }
+ }
+ *comment_end = end;
+ // Comment reaches end-of-input, which is fine.
+ return true;
+ }
+
+ if (*start == '*') {
+ Char previous = '\0';
+ // Block comment, read until end marker.
+ for (++start; start < end; previous = *start++) {
+ if (previous == '*' && *start == '/') {
+ *comment_end = start + 1;
+ return true;
+ }
+ }
+ // Block comment must close before end-of-input.
+ return false;
+ }
+
+ return false;
+ }
+
+ static bool IsSpaceOrNewLine(Char c) {
+ // \v = vertial tab; \f = form feed page break.
+ return c == ' ' || c == '\n' || c == '\v' || c == '\f' || c == '\r' ||
+ c == '\t';
+ }
+
+ static void SkipWhitespaceAndComments(const Char* start,
+ const Char* end,
+ const Char** whitespace_end) {
+ while (start < end) {
+ if (IsSpaceOrNewLine(*start)) {
+ ++start;
+ } else if (*start == '/') {
+ const Char* comment_end = nullptr;
+ if (!SkipComment(start, end, &comment_end))
+ break;
+ start = comment_end;
+ } else {
+ break;
+ }
+ }
+ *whitespace_end = start;
+ }
+
+ static Token ParseToken(const Char* start,
+ const Char* end,
+ const Char** tokenStart,
+ const Char** token_end) {
+ SkipWhitespaceAndComments(start, end, tokenStart);
+ start = *tokenStart;
+
+ if (start == end)
+ return NoInput;
+
+ switch (*start) {
+ case 'n':
+ if (ParseConstToken(start, end, token_end, kNullString))
+ return NullToken;
+ break;
+ case 't':
+ if (ParseConstToken(start, end, token_end, kTrueString))
+ return BoolTrue;
+ break;
+ case 'f':
+ if (ParseConstToken(start, end, token_end, kFalseString))
+ return BoolFalse;
+ break;
+ case '[':
+ *token_end = start + 1;
+ return ArrayBegin;
+ case ']':
+ *token_end = start + 1;
+ return ArrayEnd;
+ case ',':
+ *token_end = start + 1;
+ return ListSeparator;
+ case '{':
+ *token_end = start + 1;
+ return ObjectBegin;
+ case '}':
+ *token_end = start + 1;
+ return ObjectEnd;
+ case ':':
+ *token_end = start + 1;
+ return ObjectPairSeparator;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '-':
+ if (ParseNumberToken(start, end, token_end))
+ return Number;
+ break;
+ case '"':
+ if (ParseStringToken(start + 1, end, token_end))
+ return StringLiteral;
+ break;
+ }
+ return InvalidToken;
+ }
+
+ static int HexToInt(Char c) {
+ if ('0' <= c && c <= '9')
+ return c - '0';
+ if ('A' <= c && c <= 'F')
+ return c - 'A' + 10;
+ if ('a' <= c && c <= 'f')
+ return c - 'a' + 10;
+ assert(false); // Unreachable.
+ return 0;
+ }
+
+ static bool DecodeString(const Char* start,
+ const Char* end,
+ std::vector<uint16_t>* output) {
+ if (start == end)
+ return true;
+ if (start > end)
+ return false;
+ output->reserve(end - start);
+ while (start < end) {
+ uint16_t c = *start++;
+ // If the |Char| we're dealing with is really a byte, then
+ // we have utf8 here, and we need to check for multibyte characters
+ // and transcode them to utf16 (either one or two utf16 chars).
+ if (sizeof(Char) == sizeof(uint8_t) && c > 0x7f) {
+ // Inspect the leading byte to figure out how long the utf8
+ // byte sequence is; while doing this initialize |codepoint|
+ // with the first few bits.
+ // See table in: https://en.wikipedia.org/wiki/UTF-8
+ // byte one is 110x xxxx -> 2 byte utf8 sequence
+ // byte one is 1110 xxxx -> 3 byte utf8 sequence
+ // byte one is 1111 0xxx -> 4 byte utf8 sequence
+ uint32_t codepoint;
+ int num_bytes_left;
+ if ((c & 0xe0) == 0xc0) { // 2 byte utf8 sequence
+ num_bytes_left = 1;
+ codepoint = c & 0x1f;
+ } else if ((c & 0xf0) == 0xe0) { // 3 byte utf8 sequence
+ num_bytes_left = 2;
+ codepoint = c & 0x0f;
+ } else if ((c & 0xf8) == 0xf0) { // 4 byte utf8 sequence
+ codepoint = c & 0x07;
+ num_bytes_left = 3;
+ } else {
+ return false; // invalid leading byte
+ }
+
+ // If we have enough bytes in our inpput, decode the remaining ones
+ // belonging to this Unicode character into |codepoint|.
+ if (start + num_bytes_left > end)
+ return false;
+ while (num_bytes_left > 0) {
+ c = *start++;
+ --num_bytes_left;
+ // Check the next byte is a continuation byte, that is 10xx xxxx.
+ if ((c & 0xc0) != 0x80)
+ return false;
+ codepoint = (codepoint << 6) | (c & 0x3f);
+ }
+
+ // Disallow overlong encodings for ascii characters, as these
+ // would include " and other characters significant to JSON
+ // string termination / control.
+ if (codepoint <= 0x7f)
+ return false;
+ // Invalid in UTF8, and can't be represented in UTF16 anyway.
+ if (codepoint > 0x10ffff)
+ return false;
+
+ // So, now we transcode to UTF16,
+ // using the math described at https://en.wikipedia.org/wiki/UTF-16,
+ // for either one or two 16 bit characters.
+ if (codepoint < 0xffff) {
+ output->push_back(codepoint);
+ continue;
+ }
+ codepoint -= 0x10000;
+ output->push_back((codepoint >> 10) + 0xd800); // high surrogate
+ output->push_back((codepoint & 0x3ff) + 0xdc00); // low surrogate
+ continue;
+ }
+ if ('\\' != c) {
+ output->push_back(c);
+ continue;
+ }
+ if (start == end)
+ return false;
+ c = *start++;
+
+ if (c == 'x') {
+ // \x is not supported.
+ return false;
+ }
+
+ switch (c) {
+ case '"':
+ case '/':
+ case '\\':
+ break;
+ case 'b':
+ c = '\b';
+ break;
+ case 'f':
+ c = '\f';
+ break;
+ case 'n':
+ c = '\n';
+ break;
+ case 'r':
+ c = '\r';
+ break;
+ case 't':
+ c = '\t';
+ break;
+ case 'v':
+ c = '\v';
+ break;
+ case 'u':
+ c = (HexToInt(*start) << 12) + (HexToInt(*(start + 1)) << 8) +
+ (HexToInt(*(start + 2)) << 4) + HexToInt(*(start + 3));
+ start += 4;
+ break;
+ default:
+ return false;
+ }
+ output->push_back(c);
+ }
+ return true;
+ }
+
+ void ParseValue(const Char* start,
+ const Char* end,
+ const Char** value_token_end,
+ int depth) {
+ if (depth > kStackLimit) {
+ HandleError(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, start);
+ return;
+ }
+ const Char* token_start = nullptr;
+ const Char* token_end = nullptr;
+ Token token = ParseToken(start, end, &token_start, &token_end);
+ switch (token) {
+ case NoInput:
+ HandleError(Error::JSON_PARSER_NO_INPUT, token_start);
+ return;
+ case InvalidToken:
+ HandleError(Error::JSON_PARSER_INVALID_TOKEN, token_start);
+ return;
+ case NullToken:
+ handler_->HandleNull();
+ break;
+ case BoolTrue:
+ handler_->HandleBool(true);
+ break;
+ case BoolFalse:
+ handler_->HandleBool(false);
+ break;
+ case Number: {
+ double value;
+ if (!CharsToDouble(token_start, token_end - token_start, &value)) {
+ HandleError(Error::JSON_PARSER_INVALID_NUMBER, token_start);
+ return;
+ }
+ if (value >= std::numeric_limits<int32_t>::min() &&
+ value <= std::numeric_limits<int32_t>::max() &&
+ static_cast<int32_t>(value) == value)
+ handler_->HandleInt32(static_cast<int32_t>(value));
+ else
+ handler_->HandleDouble(value);
+ break;
+ }
+ case StringLiteral: {
+ std::vector<uint16_t> value;
+ bool ok = DecodeString(token_start + 1, token_end - 1, &value);
+ if (!ok) {
+ HandleError(Error::JSON_PARSER_INVALID_STRING, token_start);
+ return;
+ }
+ handler_->HandleString16(span<uint16_t>(value.data(), value.size()));
+ break;
+ }
+ case ArrayBegin: {
+ handler_->HandleArrayBegin();
+ start = token_end;
+ token = ParseToken(start, end, &token_start, &token_end);
+ while (token != ArrayEnd) {
+ ParseValue(start, end, &token_end, depth + 1);
+ if (error_)
+ return;
+
+ // After a list value, we expect a comma or the end of the list.
+ start = token_end;
+ token = ParseToken(start, end, &token_start, &token_end);
+ if (token == ListSeparator) {
+ start = token_end;
+ token = ParseToken(start, end, &token_start, &token_end);
+ if (token == ArrayEnd) {
+ HandleError(Error::JSON_PARSER_UNEXPECTED_ARRAY_END, token_start);
+ return;
+ }
+ } else if (token != ArrayEnd) {
+ // Unexpected value after list value. Bail out.
+ HandleError(Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED,
+ token_start);
+ return;
+ }
+ }
+ handler_->HandleArrayEnd();
+ break;
+ }
+ case ObjectBegin: {
+ handler_->HandleMapBegin();
+ start = token_end;
+ token = ParseToken(start, end, &token_start, &token_end);
+ while (token != ObjectEnd) {
+ if (token != StringLiteral) {
+ HandleError(Error::JSON_PARSER_STRING_LITERAL_EXPECTED,
+ token_start);
+ return;
+ }
+ std::vector<uint16_t> key;
+ if (!DecodeString(token_start + 1, token_end - 1, &key)) {
+ HandleError(Error::JSON_PARSER_INVALID_STRING, token_start);
+ return;
+ }
+ handler_->HandleString16(span<uint16_t>(key.data(), key.size()));
+ start = token_end;
+
+ token = ParseToken(start, end, &token_start, &token_end);
+ if (token != ObjectPairSeparator) {
+ HandleError(Error::JSON_PARSER_COLON_EXPECTED, token_start);
+ return;
+ }
+ start = token_end;
+
+ ParseValue(start, end, &token_end, depth + 1);
+ if (error_)
+ return;
+ start = token_end;
+
+ // After a key/value pair, we expect a comma or the end of the
+ // object.
+ token = ParseToken(start, end, &token_start, &token_end);
+ if (token == ListSeparator) {
+ start = token_end;
+ token = ParseToken(start, end, &token_start, &token_end);
+ if (token == ObjectEnd) {
+ HandleError(Error::JSON_PARSER_UNEXPECTED_MAP_END, token_start);
+ return;
+ }
+ } else if (token != ObjectEnd) {
+ // Unexpected value after last object value. Bail out.
+ HandleError(Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED,
+ token_start);
+ return;
+ }
+ }
+ handler_->HandleMapEnd();
+ break;
+ }
+
+ default:
+ // We got a token that's not a value.
+ HandleError(Error::JSON_PARSER_VALUE_EXPECTED, token_start);
+ return;
+ }
+
+ SkipWhitespaceAndComments(token_end, end, value_token_end);
+ }
+
+ void HandleError(Error error, const Char* pos) {
+ assert(error != Error::OK);
+ if (!error_) {
+ handler_->HandleError(
+ Status{error, static_cast<size_t>(pos - start_pos_)});
+ error_ = true;
+ }
+ }
+
+ const Char* start_pos_ = nullptr;
+ bool error_ = false;
+ const Platform* platform_;
+ StreamingParserHandler* handler_;
+};
+} // namespace
+
+void ParseJSON(const Platform& platform,
+ span<uint8_t> chars,
+ StreamingParserHandler* handler) {
+ JsonParser<uint8_t> parser(&platform, handler);
+ parser.Parse(chars.data(), chars.size());
+}
+
+void ParseJSON(const Platform& platform,
+ span<uint16_t> chars,
+ StreamingParserHandler* handler) {
+ JsonParser<uint16_t> parser(&platform, handler);
+ parser.Parse(chars.data(), chars.size());
+}
+
+// =============================================================================
+// json::ConvertCBORToJSON, json::ConvertJSONToCBOR - for transcoding
+// =============================================================================
+template <typename C>
+Status ConvertCBORToJSONTmpl(const Platform& platform,
+ span<uint8_t> cbor,
+ C* json) {
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&platform, json, &status);
+ cbor::ParseCBOR(cbor, json_writer.get());
+ return status;
+}
+
+Status ConvertCBORToJSON(const Platform& platform,
+ span<uint8_t> cbor,
+ std::vector<uint8_t>* json) {
+ return ConvertCBORToJSONTmpl(platform, cbor, json);
+}
+Status ConvertCBORToJSON(const Platform& platform,
+ span<uint8_t> cbor,
+ std::string* json) {
+ return ConvertCBORToJSONTmpl(platform, cbor, json);
+}
+
+template <typename T, typename C>
+Status ConvertJSONToCBORTmpl(const Platform& platform, span<T> json, C* cbor) {
+ Status status;
+ std::unique_ptr<StreamingParserHandler> encoder =
+ cbor::NewCBOREncoder(cbor, &status);
+ ParseJSON(platform, json, encoder.get());
+ return status;
+}
+Status ConvertJSONToCBOR(const Platform& platform,
+ span<uint8_t> json,
+ std::string* cbor) {
+ return ConvertJSONToCBORTmpl(platform, json, cbor);
+}
+Status ConvertJSONToCBOR(const Platform& platform,
+ span<uint16_t> json,
+ std::string* cbor) {
+ return ConvertJSONToCBORTmpl(platform, json, cbor);
+}
+Status ConvertJSONToCBOR(const Platform& platform,
+ span<uint8_t> json,
+ std::vector<uint8_t>* cbor) {
+ return ConvertJSONToCBORTmpl(platform, json, cbor);
+}
+Status ConvertJSONToCBOR(const Platform& platform,
+ span<uint16_t> json,
+ std::vector<uint8_t>* cbor) {
+ return ConvertJSONToCBORTmpl(platform, json, cbor);
+}
+} // namespace json
+} // namespace v8_inspector_protocol_encoding
diff --git a/deps/v8/third_party/inspector_protocol/encoding/encoding.h b/deps/v8/third_party/inspector_protocol/encoding/encoding.h
new file mode 100644
index 0000000000..90916d42b3
--- /dev/null
+++ b/deps/v8/third_party/inspector_protocol/encoding/encoding.h
@@ -0,0 +1,510 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_H_
+#define V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <limits>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace v8_inspector_protocol_encoding {
+
+// =============================================================================
+// span - sequence of bytes
+// =============================================================================
+
+// This template is similar to std::span, which will be included in C++20.
+template <typename T>
+class span {
+ public:
+ using index_type = size_t;
+
+ span() : data_(nullptr), size_(0) {}
+ span(const T* data, index_type size) : data_(data), size_(size) {}
+
+ const T* data() const { return data_; }
+
+ const T* begin() const { return data_; }
+ const T* end() const { return data_ + size_; }
+
+ const T& operator[](index_type idx) const { return data_[idx]; }
+
+ span<T> subspan(index_type offset, index_type count) const {
+ return span(data_ + offset, count);
+ }
+
+ span<T> subspan(index_type offset) const {
+ return span(data_ + offset, size_ - offset);
+ }
+
+ bool empty() const { return size_ == 0; }
+
+ index_type size() const { return size_; }
+ index_type size_bytes() const { return size_ * sizeof(T); }
+
+ private:
+ const T* data_;
+ index_type size_;
+};
+
+template <typename T>
+span<T> SpanFrom(const std::vector<T>& v) {
+ return span<T>(v.data(), v.size());
+}
+
+template <size_t N>
+span<uint8_t> SpanFrom(const char (&str)[N]) {
+ return span<uint8_t>(reinterpret_cast<const uint8_t*>(str), N - 1);
+}
+
+inline span<uint8_t> SpanFrom(const char* str) {
+ return str ? span<uint8_t>(reinterpret_cast<const uint8_t*>(str), strlen(str))
+ : span<uint8_t>();
+}
+
+inline span<uint8_t> SpanFrom(const std::string& v) {
+ return span<uint8_t>(reinterpret_cast<const uint8_t*>(v.data()), v.size());
+}
+
+// =============================================================================
+// Status and Error codes
+// =============================================================================
+enum class Error {
+ OK = 0,
+ // JSON parsing errors - json_parser.{h,cc}.
+ JSON_PARSER_UNPROCESSED_INPUT_REMAINS = 0x01,
+ JSON_PARSER_STACK_LIMIT_EXCEEDED = 0x02,
+ JSON_PARSER_NO_INPUT = 0x03,
+ JSON_PARSER_INVALID_TOKEN = 0x04,
+ JSON_PARSER_INVALID_NUMBER = 0x05,
+ JSON_PARSER_INVALID_STRING = 0x06,
+ JSON_PARSER_UNEXPECTED_ARRAY_END = 0x07,
+ JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED = 0x08,
+ JSON_PARSER_STRING_LITERAL_EXPECTED = 0x09,
+ JSON_PARSER_COLON_EXPECTED = 0x0a,
+ JSON_PARSER_UNEXPECTED_MAP_END = 0x0b,
+ JSON_PARSER_COMMA_OR_MAP_END_EXPECTED = 0x0c,
+ JSON_PARSER_VALUE_EXPECTED = 0x0d,
+
+ CBOR_INVALID_INT32 = 0x0e,
+ CBOR_INVALID_DOUBLE = 0x0f,
+ CBOR_INVALID_ENVELOPE = 0x10,
+ CBOR_INVALID_STRING8 = 0x11,
+ CBOR_INVALID_STRING16 = 0x12,
+ CBOR_INVALID_BINARY = 0x13,
+ CBOR_UNSUPPORTED_VALUE = 0x14,
+ CBOR_NO_INPUT = 0x15,
+ CBOR_INVALID_START_BYTE = 0x16,
+ CBOR_UNEXPECTED_EOF_EXPECTED_VALUE = 0x17,
+ CBOR_UNEXPECTED_EOF_IN_ARRAY = 0x18,
+ CBOR_UNEXPECTED_EOF_IN_MAP = 0x19,
+ CBOR_INVALID_MAP_KEY = 0x1a,
+ CBOR_STACK_LIMIT_EXCEEDED = 0x1b,
+ CBOR_TRAILING_JUNK = 0x1c,
+ CBOR_MAP_START_EXPECTED = 0x1d,
+ CBOR_MAP_STOP_EXPECTED = 0x1e,
+ CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED = 0x1f,
+};
+
+// A status value with position that can be copied. The default status
+// is OK. Usually, error status values should come with a valid position.
+struct Status {
+ static constexpr size_t npos() { return std::numeric_limits<size_t>::max(); }
+
+ bool ok() const { return error == Error::OK; }
+
+ Error error = Error::OK;
+ size_t pos = npos();
+ Status(Error error, size_t pos) : error(error), pos(pos) {}
+ Status() = default;
+
+ // Returns a 7 bit US-ASCII string, either "OK" or an error message
+ // that includes the position.
+ std::string ToASCIIString() const;
+
+ private:
+ std::string ToASCIIString(const char* msg) const;
+};
+
+// Handler interface for parser events emitted by a streaming parser.
+// See cbor::NewCBOREncoder, cbor::ParseCBOR, json::NewJSONEncoder,
+// json::ParseJSON.
+class StreamingParserHandler {
+ public:
+ virtual ~StreamingParserHandler() = default;
+ virtual void HandleMapBegin() = 0;
+ virtual void HandleMapEnd() = 0;
+ virtual void HandleArrayBegin() = 0;
+ virtual void HandleArrayEnd() = 0;
+ virtual void HandleString8(span<uint8_t> chars) = 0;
+ virtual void HandleString16(span<uint16_t> chars) = 0;
+ virtual void HandleBinary(span<uint8_t> bytes) = 0;
+ virtual void HandleDouble(double value) = 0;
+ virtual void HandleInt32(int32_t value) = 0;
+ virtual void HandleBool(bool value) = 0;
+ virtual void HandleNull() = 0;
+
+ // The parser may send one error even after other events have already
+ // been received. Client code is reponsible to then discard the
+ // already processed events.
+ // |error| must be an eror, as in, |error.is_ok()| can't be true.
+ virtual void HandleError(Status error) = 0;
+};
+
+namespace cbor {
+// The binary encoding for the inspector protocol follows the CBOR specification
+// (RFC 7049). Additional constraints:
+// - Only indefinite length maps and arrays are supported.
+// - Maps and arrays are wrapped with an envelope, that is, a
+// CBOR tag with value 24 followed by a byte string specifying
+// the byte length of the enclosed map / array. The byte string
+// must use a 32 bit wide length.
+// - At the top level, a message must be an indefinite length map
+// wrapped by an envelope.
+// - Maximal size for messages is 2^32 (4 GB).
+// - For scalars, we support only the int32_t range, encoded as
+// UNSIGNED/NEGATIVE (major types 0 / 1).
+// - UTF16 strings, including with unbalanced surrogate pairs, are encoded
+// as CBOR BYTE_STRING (major type 2). For such strings, the number of
+// bytes encoded must be even.
+// - UTF8 strings (major type 3) are supported.
+// - 7 bit US-ASCII strings must always be encoded as UTF8 strings, never
+// as UTF16 strings.
+// - Arbitrary byte arrays, in the inspector protocol called 'binary',
+// are encoded as BYTE_STRING (major type 2), prefixed with a byte
+// indicating base64 when rendered as JSON.
+
+// =============================================================================
+// Detecting CBOR content
+// =============================================================================
+
+// The first byte for an envelope, which we use for wrapping dictionaries
+// and arrays; and the byte that indicates a byte string with 32 bit length.
+// These two bytes start an envelope, and thereby also any CBOR message
+// produced or consumed by this protocol. See also |EnvelopeEncoder| below.
+uint8_t InitialByteForEnvelope();
+uint8_t InitialByteFor32BitLengthByteString();
+
+// Checks whether |msg| is a cbor message.
+bool IsCBORMessage(span<uint8_t> msg);
+
+// =============================================================================
+// Encoding individual CBOR items
+// =============================================================================
+
+// Some constants for CBOR tokens that only take a single byte on the wire.
+uint8_t EncodeTrue();
+uint8_t EncodeFalse();
+uint8_t EncodeNull();
+uint8_t EncodeIndefiniteLengthArrayStart();
+uint8_t EncodeIndefiniteLengthMapStart();
+uint8_t EncodeStop();
+
+// Encodes |value| as |UNSIGNED| (major type 0) iff >= 0, or |NEGATIVE|
+// (major type 1) iff < 0.
+void EncodeInt32(int32_t value, std::vector<uint8_t>* out);
+void EncodeInt32(int32_t value, std::string* out);
+
+// Encodes a UTF16 string as a BYTE_STRING (major type 2). Each utf16
+// character in |in| is emitted with most significant byte first,
+// appending to |out|.
+void EncodeString16(span<uint16_t> in, std::vector<uint8_t>* out);
+void EncodeString16(span<uint16_t> in, std::string* out);
+
+// Encodes a UTF8 string |in| as STRING (major type 3).
+void EncodeString8(span<uint8_t> in, std::vector<uint8_t>* out);
+void EncodeString8(span<uint8_t> in, std::string* out);
+
+// Encodes the given |latin1| string as STRING8.
+// If any non-ASCII character is present, it will be represented
+// as a 2 byte UTF8 sequence.
+void EncodeFromLatin1(span<uint8_t> latin1, std::vector<uint8_t>* out);
+void EncodeFromLatin1(span<uint8_t> latin1, std::string* out);
+
+// Encodes the given |utf16| string as STRING8 if it's entirely US-ASCII.
+// Otherwise, encodes as STRING16.
+void EncodeFromUTF16(span<uint16_t> utf16, std::vector<uint8_t>* out);
+void EncodeFromUTF16(span<uint16_t> utf16, std::string* out);
+
+// Encodes arbitrary binary data in |in| as a BYTE_STRING (major type 2) with
+// definitive length, prefixed with tag 22 indicating expected conversion to
+// base64 (see RFC 7049, Table 3 and Section 2.4.4.2).
+void EncodeBinary(span<uint8_t> in, std::vector<uint8_t>* out);
+void EncodeBinary(span<uint8_t> in, std::string* out);
+
+// Encodes / decodes a double as Major type 7 (SIMPLE_VALUE),
+// with additional info = 27, followed by 8 bytes in big endian.
+void EncodeDouble(double value, std::vector<uint8_t>* out);
+void EncodeDouble(double value, std::string* out);
+
+// =============================================================================
+// cbor::EnvelopeEncoder - for wrapping submessages
+// =============================================================================
+
+// An envelope indicates the byte length of a wrapped item.
+// We use this for maps and array, which allows the decoder
+// to skip such (nested) values whole sale.
+// It's implemented as a CBOR tag (major type 6) with additional
+// info = 24, followed by a byte string with a 32 bit length value;
+// so the maximal structure that we can wrap is 2^32 bits long.
+// See also: https://tools.ietf.org/html/rfc7049#section-2.4.4.1
+class EnvelopeEncoder {
+ public:
+ // Emits the envelope start bytes and records the position for the
+ // byte size in |byte_size_pos_|. Also emits empty bytes for the
+ // byte sisze so that encoding can continue.
+ void EncodeStart(std::vector<uint8_t>* out);
+ void EncodeStart(std::string* out);
+ // This records the current size in |out| at position byte_size_pos_.
+ // Returns true iff successful.
+ bool EncodeStop(std::vector<uint8_t>* out);
+ bool EncodeStop(std::string* out);
+
+ private:
+ size_t byte_size_pos_ = 0;
+};
+
+// =============================================================================
+// cbor::NewCBOREncoder - for encoding from a streaming parser
+// =============================================================================
+
+// This can be used to convert to CBOR, by passing the return value to a parser
+// that drives it. The handler will encode into |out|, and iff an error occurs
+// it will set |status| to an error and clear |out|. Otherwise, |status.ok()|
+// will be |true|.
+std::unique_ptr<StreamingParserHandler> NewCBOREncoder(
+ std::vector<uint8_t>* out,
+ Status* status);
+std::unique_ptr<StreamingParserHandler> NewCBOREncoder(std::string* out,
+ Status* status);
+
+// =============================================================================
+// cbor::CBORTokenizer - for parsing individual CBOR items
+// =============================================================================
+
+// Tags for the tokens within a CBOR message that CBORTokenizer understands.
+// Note that this is not the same terminology as the CBOR spec (RFC 7049),
+// but rather, our adaptation. For instance, we lump unsigned and signed
+// major type into INT32 here (and disallow values outside the int32_t range).
+enum class CBORTokenTag {
+ // Encountered an error in the structure of the message. Consult
+ // status() for details.
+ ERROR_VALUE,
+ // Booleans and NULL.
+ TRUE_VALUE,
+ FALSE_VALUE,
+ NULL_VALUE,
+ // An int32_t (signed 32 bit integer).
+ INT32,
+ // A double (64 bit floating point).
+ DOUBLE,
+ // A UTF8 string.
+ STRING8,
+ // A UTF16 string.
+ STRING16,
+ // A binary string.
+ BINARY,
+ // Starts an indefinite length map; after the map start we expect
+ // alternating keys and values, followed by STOP.
+ MAP_START,
+ // Starts an indefinite length array; after the array start we
+ // expect values, followed by STOP.
+ ARRAY_START,
+ // Ends a map or an array.
+ STOP,
+ // An envelope indicator, wrapping a map or array.
+ // Internally this carries the byte length of the wrapped
+ // map or array. While CBORTokenizer::Next() will read / skip the entire
+ // envelope, CBORTokenizer::EnterEnvelope() reads the tokens
+ // inside of it.
+ ENVELOPE,
+ // We've reached the end there is nothing else to read.
+ DONE,
+};
+
+// The major types from RFC 7049 Section 2.1.
+enum class MajorType {
+ UNSIGNED = 0,
+ NEGATIVE = 1,
+ BYTE_STRING = 2,
+ STRING = 3,
+ ARRAY = 4,
+ MAP = 5,
+ TAG = 6,
+ SIMPLE_VALUE = 7
+};
+
+// CBORTokenizer segments a CBOR message, presenting the tokens therein as
+// numbers, strings, etc. This is not a complete CBOR parser, but makes it much
+// easier to implement one (e.g. ParseCBOR, above). It can also be used to parse
+// messages partially.
+class CBORTokenizer {
+ public:
+ explicit CBORTokenizer(span<uint8_t> bytes);
+ ~CBORTokenizer();
+
+ // Identifies the current token that we're looking at,
+ // or ERROR_VALUE (in which ase ::Status() has details)
+ // or DONE (if we're past the last token).
+ CBORTokenTag TokenTag() const;
+
+ // Advances to the next token.
+ void Next();
+ // Can only be called if TokenTag() == CBORTokenTag::ENVELOPE.
+ // While Next() would skip past the entire envelope / what it's
+ // wrapping, EnterEnvelope positions the cursor inside of the envelope,
+ // letting the client explore the nested structure.
+ void EnterEnvelope();
+
+ // If TokenTag() is CBORTokenTag::ERROR_VALUE, then Status().error describes
+ // the error more precisely; otherwise it'll be set to Error::OK.
+ // In either case, Status().pos is the current position.
+ struct Status Status() const;
+
+ // The following methods retrieve the token values. They can only
+ // be called if TokenTag() matches.
+
+ // To be called only if ::TokenTag() == CBORTokenTag::INT32.
+ int32_t GetInt32() const;
+
+ // To be called only if ::TokenTag() == CBORTokenTag::DOUBLE.
+ double GetDouble() const;
+
+ // To be called only if ::TokenTag() == CBORTokenTag::STRING8.
+ span<uint8_t> GetString8() const;
+
+ // Wire representation for STRING16 is low byte first (little endian).
+ // To be called only if ::TokenTag() == CBORTokenTag::STRING16.
+ span<uint8_t> GetString16WireRep() const;
+
+ // To be called only if ::TokenTag() == CBORTokenTag::BINARY.
+ span<uint8_t> GetBinary() const;
+
+ // To be called only if ::TokenTag() == CBORTokenTag::ENVELOPE.
+ span<uint8_t> GetEnvelopeContents() const;
+
+ private:
+ void ReadNextToken(bool enter_envelope);
+ void SetToken(CBORTokenTag token, size_t token_byte_length);
+ void SetError(Error error);
+
+ span<uint8_t> bytes_;
+ CBORTokenTag token_tag_;
+ struct Status status_;
+ size_t token_byte_length_;
+ MajorType token_start_type_;
+ uint64_t token_start_internal_value_;
+};
+
+// =============================================================================
+// cbor::ParseCBOR - for receiving streaming parser events for CBOR messages
+// =============================================================================
+
+// Parses a CBOR encoded message from |bytes|, sending events to
+// |out|. If an error occurs, sends |out->HandleError|, and parsing stops.
+// The client is responsible for discarding the already received information in
+// that case.
+void ParseCBOR(span<uint8_t> bytes, StreamingParserHandler* out);
+
+// =============================================================================
+// cbor::AppendString8EntryToMap - for limited in-place editing of messages
+// =============================================================================
+
+// Modifies the |cbor| message by appending a new key/value entry at the end
+// of the map. Patches up the envelope size; Status.ok() iff successful.
+// If not successful, |cbor| may be corrupted after this call.
+Status AppendString8EntryToCBORMap(span<uint8_t> string8_key,
+ span<uint8_t> string8_value,
+ std::vector<uint8_t>* cbor);
+Status AppendString8EntryToCBORMap(span<uint8_t> string8_key,
+ span<uint8_t> string8_value,
+ std::string* cbor);
+
+namespace internals { // Exposed only for writing tests.
+int8_t ReadTokenStart(span<uint8_t> bytes,
+ cbor::MajorType* type,
+ uint64_t* value);
+
+void WriteTokenStart(cbor::MajorType type,
+ uint64_t value,
+ std::vector<uint8_t>* encoded);
+void WriteTokenStart(cbor::MajorType type,
+ uint64_t value,
+ std::string* encoded);
+} // namespace internals
+} // namespace cbor
+
+namespace json {
+// Client code must provide an instance. Implementation should delegate
+// to whatever is appropriate.
+class Platform {
+ public:
+ virtual ~Platform() = default;
+ // Parses |str| into |result|. Returns false iff there are
+ // leftover characters or parsing errors.
+ virtual bool StrToD(const char* str, double* result) const = 0;
+
+ // Prints |value| in a format suitable for JSON.
+ virtual std::unique_ptr<char[]> DToStr(double value) const = 0;
+};
+
+// =============================================================================
+// json::NewJSONEncoder - for encoding streaming parser events as JSON
+// =============================================================================
+
+// Returns a handler object which will write ascii characters to |out|.
+// |status->ok()| will be false iff the handler routine HandleError() is called.
+// In that case, we'll stop emitting output.
+// Except for calling the HandleError routine at any time, the client
+// code must call the Handle* methods in an order in which they'd occur
+// in valid JSON; otherwise we may crash (the code uses assert).
+std::unique_ptr<StreamingParserHandler> NewJSONEncoder(
+ const Platform* platform,
+ std::vector<uint8_t>* out,
+ Status* status);
+std::unique_ptr<StreamingParserHandler> NewJSONEncoder(const Platform* platform,
+ std::string* out,
+ Status* status);
+
+// =============================================================================
+// json::ParseJSON - for receiving streaming parser events for JSON
+// =============================================================================
+
+void ParseJSON(const Platform& platform,
+ span<uint8_t> chars,
+ StreamingParserHandler* handler);
+void ParseJSON(const Platform& platform,
+ span<uint16_t> chars,
+ StreamingParserHandler* handler);
+
+// =============================================================================
+// json::ConvertCBORToJSON, json::ConvertJSONToCBOR - for transcoding
+// =============================================================================
+Status ConvertCBORToJSON(const Platform& platform,
+ span<uint8_t> cbor,
+ std::string* json);
+Status ConvertCBORToJSON(const Platform& platform,
+ span<uint8_t> cbor,
+ std::vector<uint8_t>* json);
+Status ConvertJSONToCBOR(const Platform& platform,
+ span<uint8_t> json,
+ std::vector<uint8_t>* cbor);
+Status ConvertJSONToCBOR(const Platform& platform,
+ span<uint16_t> json,
+ std::vector<uint8_t>* cbor);
+Status ConvertJSONToCBOR(const Platform& platform,
+ span<uint8_t> json,
+ std::string* cbor);
+Status ConvertJSONToCBOR(const Platform& platform,
+ span<uint16_t> json,
+ std::string* cbor);
+} // namespace json
+} // namespace v8_inspector_protocol_encoding
+
+#endif // V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_H_
diff --git a/deps/v8/third_party/inspector_protocol/encoding/encoding_test.cc b/deps/v8/third_party/inspector_protocol/encoding/encoding_test.cc
new file mode 100644
index 0000000000..338d1ece10
--- /dev/null
+++ b/deps/v8/third_party/inspector_protocol/encoding/encoding_test.cc
@@ -0,0 +1,1878 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "encoding.h"
+
+#include <array>
+#include <clocale>
+#include <cmath>
+#include <cstdlib>
+#include <cstring>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include "encoding_test_helper.h"
+
+using testing::ElementsAreArray;
+
+namespace v8_inspector_protocol_encoding {
+
+class TestPlatform : public json::Platform {
+ bool StrToD(const char* str, double* result) const override {
+ // This is not thread-safe
+ // (see https://en.cppreference.com/w/cpp/locale/setlocale)
+ // but good enough for a unittest.
+ const char* saved_locale = std::setlocale(LC_NUMERIC, nullptr);
+ char* end;
+ *result = std::strtod(str, &end);
+ std::setlocale(LC_NUMERIC, saved_locale);
+ if (errno == ERANGE) {
+ // errno must be reset, e.g. see the example here:
+ // https://en.cppreference.com/w/cpp/string/byte/strtof
+ errno = 0;
+ return false;
+ }
+ return end == str + strlen(str);
+ }
+
+ std::unique_ptr<char[]> DToStr(double value) const override {
+ std::stringstream ss;
+ ss.imbue(std::locale("C"));
+ ss << value;
+ std::string str = ss.str();
+ std::unique_ptr<char[]> result(new char[str.size() + 1]);
+ memcpy(result.get(), str.c_str(), str.size() + 1);
+ return result;
+ }
+};
+
+const json::Platform& GetTestPlatform() {
+ static TestPlatform* platform = new TestPlatform;
+ return *platform;
+}
+
+// =============================================================================
+// span - sequence of bytes
+// =============================================================================
+
+template <typename T>
+class SpanTest : public ::testing::Test {};
+
+using TestTypes = ::testing::Types<uint8_t, uint16_t>;
+TYPED_TEST_SUITE(SpanTest, TestTypes);
+
+TYPED_TEST(SpanTest, Empty) {
+ span<TypeParam> empty;
+ EXPECT_TRUE(empty.empty());
+ EXPECT_EQ(0u, empty.size());
+ EXPECT_EQ(0u, empty.size_bytes());
+ EXPECT_EQ(empty.begin(), empty.end());
+}
+
+TYPED_TEST(SpanTest, SingleItem) {
+ TypeParam single_item = 42;
+ span<TypeParam> singular(&single_item, 1);
+ EXPECT_FALSE(singular.empty());
+ EXPECT_EQ(1u, singular.size());
+ EXPECT_EQ(sizeof(TypeParam), singular.size_bytes());
+ EXPECT_EQ(singular.begin() + 1, singular.end());
+ EXPECT_EQ(42, singular[0]);
+}
+
+TYPED_TEST(SpanTest, FiveItems) {
+ std::vector<TypeParam> test_input = {31, 32, 33, 34, 35};
+ span<TypeParam> five_items(test_input.data(), 5);
+ EXPECT_FALSE(five_items.empty());
+ EXPECT_EQ(5u, five_items.size());
+ EXPECT_EQ(sizeof(TypeParam) * 5, five_items.size_bytes());
+ EXPECT_EQ(five_items.begin() + 5, five_items.end());
+ EXPECT_EQ(31, five_items[0]);
+ EXPECT_EQ(32, five_items[1]);
+ EXPECT_EQ(33, five_items[2]);
+ EXPECT_EQ(34, five_items[3]);
+ EXPECT_EQ(35, five_items[4]);
+ span<TypeParam> three_items = five_items.subspan(2);
+ EXPECT_EQ(3u, three_items.size());
+ EXPECT_EQ(33, three_items[0]);
+ EXPECT_EQ(34, three_items[1]);
+ EXPECT_EQ(35, three_items[2]);
+ span<TypeParam> two_items = five_items.subspan(2, 2);
+ EXPECT_EQ(2u, two_items.size());
+ EXPECT_EQ(33, two_items[0]);
+ EXPECT_EQ(34, two_items[1]);
+}
+
+TEST(SpanFromTest, FromConstCharAndLiteral) {
+ // Testing this is useful because strlen(nullptr) is undefined.
+ EXPECT_EQ(nullptr, SpanFrom(nullptr).data());
+ EXPECT_EQ(0u, SpanFrom(nullptr).size());
+
+ const char* kEmpty = "";
+ EXPECT_EQ(kEmpty, reinterpret_cast<const char*>(SpanFrom(kEmpty).data()));
+ EXPECT_EQ(0u, SpanFrom(kEmpty).size());
+
+ const char* kFoo = "foo";
+ EXPECT_EQ(kFoo, reinterpret_cast<const char*>(SpanFrom(kFoo).data()));
+ EXPECT_EQ(3u, SpanFrom(kFoo).size());
+
+ EXPECT_EQ(3u, SpanFrom("foo").size());
+}
+
+// =============================================================================
+// Status and Error codes
+// =============================================================================
+
+TEST(StatusTest, StatusToASCIIString) {
+ Status ok_status;
+ EXPECT_EQ("OK", ok_status.ToASCIIString());
+ Status json_error(Error::JSON_PARSER_COLON_EXPECTED, 42);
+ EXPECT_EQ("JSON: colon expected at position 42", json_error.ToASCIIString());
+ Status cbor_error(Error::CBOR_TRAILING_JUNK, 21);
+ EXPECT_EQ("CBOR: trailing junk at position 21", cbor_error.ToASCIIString());
+}
+
+namespace cbor {
+
+// =============================================================================
+// Detecting CBOR content
+// =============================================================================
+
+TEST(IsCBORMessage, SomeSmokeTests) {
+ std::vector<uint8_t> empty;
+ EXPECT_FALSE(IsCBORMessage(SpanFrom(empty)));
+ std::vector<uint8_t> hello = {'H', 'e', 'l', 'o', ' ', 't',
+ 'h', 'e', 'r', 'e', '!'};
+ EXPECT_FALSE(IsCBORMessage(SpanFrom(hello)));
+ std::vector<uint8_t> example = {0xd8, 0x5a, 0, 0, 0, 0};
+ EXPECT_TRUE(IsCBORMessage(SpanFrom(example)));
+ std::vector<uint8_t> one = {0xd8, 0x5a, 0, 0, 0, 1, 1};
+ EXPECT_TRUE(IsCBORMessage(SpanFrom(one)));
+}
+
+// =============================================================================
+// Encoding individual CBOR items
+// cbor::CBORTokenizer - for parsing individual CBOR items
+// =============================================================================
+
+//
+// EncodeInt32 / CBORTokenTag::INT32
+//
+TEST(EncodeDecodeInt32Test, Roundtrips23) {
+ // This roundtrips the int32_t value 23 through the pair of EncodeInt32 /
+ // CBORTokenizer; this is interesting since 23 is encoded as a single byte.
+ std::vector<uint8_t> encoded;
+ EncodeInt32(23, &encoded);
+ // first three bits: major type = 0; remaining five bits: additional info =
+ // value 23.
+ EXPECT_THAT(encoded, ElementsAreArray(std::array<uint8_t, 1>{{23}}));
+
+ // Reverse direction: decode with CBORTokenizer.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag());
+ EXPECT_EQ(23, tokenizer.GetInt32());
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeInt32Test, RoundtripsUint8) {
+ // This roundtrips the int32_t value 42 through the pair of EncodeInt32 /
+ // CBORTokenizer. This is different from Roundtrip23 because 42 is encoded
+ // in an extra byte after the initial one.
+ std::vector<uint8_t> encoded;
+ EncodeInt32(42, &encoded);
+ // first three bits: major type = 0;
+ // remaining five bits: additional info = 24, indicating payload is uint8.
+ EXPECT_THAT(encoded, ElementsAreArray(std::array<uint8_t, 2>{{24, 42}}));
+
+ // Reverse direction: decode with CBORTokenizer.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag());
+ EXPECT_EQ(42, tokenizer.GetInt32());
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeInt32Test, RoundtripsUint16) {
+ // 500 is encoded as a uint16 after the initial byte.
+ std::vector<uint8_t> encoded;
+ EncodeInt32(500, &encoded);
+ // 1 for initial byte, 2 for uint16.
+ EXPECT_EQ(3u, encoded.size());
+ // first three bits: major type = 0;
+ // remaining five bits: additional info = 25, indicating payload is uint16.
+ EXPECT_EQ(25, encoded[0]);
+ EXPECT_EQ(0x01, encoded[1]);
+ EXPECT_EQ(0xf4, encoded[2]);
+
+ // Reverse direction: decode with CBORTokenizer.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag());
+ EXPECT_EQ(500, tokenizer.GetInt32());
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeInt32Test, RoundtripsInt32Max) {
+ // std::numeric_limits<int32_t> is encoded as a uint32 after the initial byte.
+ std::vector<uint8_t> encoded;
+ EncodeInt32(std::numeric_limits<int32_t>::max(), &encoded);
+ // 1 for initial byte, 4 for the uint32.
+ // first three bits: major type = 0;
+ // remaining five bits: additional info = 26, indicating payload is uint32.
+ EXPECT_THAT(
+ encoded,
+ ElementsAreArray(std::array<uint8_t, 5>{{26, 0x7f, 0xff, 0xff, 0xff}}));
+
+ // Reverse direction: decode with CBORTokenizer.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag());
+ EXPECT_EQ(std::numeric_limits<int32_t>::max(), tokenizer.GetInt32());
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeInt32Test, RoundtripsInt32Min) {
+ // std::numeric_limits<int32_t> is encoded as a uint32 after the initial byte.
+ std::vector<uint8_t> encoded;
+ EncodeInt32(std::numeric_limits<int32_t>::min(), &encoded);
+ // 1 for initial byte, 4 for the uint32.
+ // first three bits: major type = 1;
+ // remaining five bits: additional info = 26, indicating payload is uint32.
+ EXPECT_THAT(encoded, ElementsAreArray(std::array<uint8_t, 5>{
+ {1 << 5 | 26, 0x7f, 0xff, 0xff, 0xff}}));
+
+ // Reverse direction: decode with CBORTokenizer.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag());
+ EXPECT_EQ(std::numeric_limits<int32_t>::min(), tokenizer.GetInt32());
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeInt32Test, CantRoundtripUint32) {
+ // 0xdeadbeef is a value which does not fit below
+ // std::numerical_limits<int32_t>::max(), so we can't encode
+ // it with EncodeInt32. However, CBOR does support this, so we
+ // encode it here manually with the internal routine, just to observe
+ // that it's considered an invalid int32 by CBORTokenizer.
+ std::vector<uint8_t> encoded;
+ internals::WriteTokenStart(MajorType::UNSIGNED, 0xdeadbeef, &encoded);
+ // 1 for initial byte, 4 for the uint32.
+ // first three bits: major type = 0;
+ // remaining five bits: additional info = 26, indicating payload is uint32.
+ EXPECT_THAT(
+ encoded,
+ ElementsAreArray(std::array<uint8_t, 5>{{26, 0xde, 0xad, 0xbe, 0xef}}));
+
+ // Now try to decode; we treat this as an invalid INT32.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ // 0xdeadbeef is > std::numerical_limits<int32_t>::max().
+ EXPECT_EQ(CBORTokenTag::ERROR_VALUE, tokenizer.TokenTag());
+ EXPECT_EQ(Error::CBOR_INVALID_INT32, tokenizer.Status().error);
+}
+
+TEST(EncodeDecodeInt32Test, DecodeErrorCases) {
+ struct TestCase {
+ std::vector<uint8_t> data;
+ std::string msg;
+ };
+ std::vector<TestCase> tests{{
+ TestCase{
+ {24},
+ "additional info = 24 would require 1 byte of payload (but it's 0)"},
+ TestCase{{27, 0xaa, 0xbb, 0xcc},
+ "additional info = 27 would require 8 bytes of payload (but "
+ "it's 3)"},
+ TestCase{{29}, "additional info = 29 isn't recognized"},
+ TestCase{{1 << 5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+ "Max UINT64 payload is outside the allowed range"},
+ TestCase{{1 << 5 | 26, 0xff, 0xff, 0xff, 0xff},
+ "Max UINT32 payload is outside the allowed range"},
+ TestCase{{1 << 5 | 26, 0x80, 0x00, 0x00, 0x00},
+ "UINT32 payload w/ high bit set is outside the allowed range"},
+ }};
+ for (const TestCase& test : tests) {
+ SCOPED_TRACE(test.msg);
+ CBORTokenizer tokenizer(SpanFrom(test.data));
+ EXPECT_EQ(CBORTokenTag::ERROR_VALUE, tokenizer.TokenTag());
+ EXPECT_EQ(Error::CBOR_INVALID_INT32, tokenizer.Status().error);
+ }
+}
+
+TEST(EncodeDecodeInt32Test, RoundtripsMinus24) {
+ // This roundtrips the int32_t value -24 through the pair of EncodeInt32 /
+ // CBORTokenizer; this is interesting since -24 is encoded as
+ // a single byte as NEGATIVE, and it tests the specific encoding
+ // (note how for unsigned the single byte covers values up to 23).
+ // Additional examples are covered in RoundtripsAdditionalExamples.
+ std::vector<uint8_t> encoded;
+ EncodeInt32(-24, &encoded);
+ // first three bits: major type = 1; remaining five bits: additional info =
+ // value 23.
+ EXPECT_THAT(encoded, ElementsAreArray(std::array<uint8_t, 1>{{1 << 5 | 23}}));
+
+ // Reverse direction: decode with CBORTokenizer.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag());
+ EXPECT_EQ(-24, tokenizer.GetInt32());
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeInt32Test, RoundtripsAdditionalNegativeExamples) {
+ std::vector<int32_t> examples = {-1,
+ -10,
+ -24,
+ -25,
+ -300,
+ -30000,
+ -300 * 1000,
+ -1000 * 1000,
+ -1000 * 1000 * 1000,
+ std::numeric_limits<int32_t>::min()};
+ for (int32_t example : examples) {
+ SCOPED_TRACE(std::string("example ") + std::to_string(example));
+ std::vector<uint8_t> encoded;
+ EncodeInt32(example, &encoded);
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag());
+ EXPECT_EQ(example, tokenizer.GetInt32());
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+ }
+}
+
+//
+// EncodeString16 / CBORTokenTag::STRING16
+//
+TEST(EncodeDecodeString16Test, RoundtripsEmpty) {
+ // This roundtrips the empty utf16 string through the pair of EncodeString16 /
+ // CBORTokenizer.
+ std::vector<uint8_t> encoded;
+ EncodeString16(span<uint16_t>(), &encoded);
+ EXPECT_EQ(1u, encoded.size());
+ // first three bits: major type = 2; remaining five bits: additional info =
+ // size 0.
+ EXPECT_EQ(2 << 5, encoded[0]);
+
+ // Reverse direction: decode with CBORTokenizer.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::STRING16, tokenizer.TokenTag());
+ span<uint8_t> decoded_string16_wirerep = tokenizer.GetString16WireRep();
+ EXPECT_TRUE(decoded_string16_wirerep.empty());
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+// On the wire, we STRING16 is encoded as little endian (least
+// significant byte first). The host may or may not be little endian,
+// so this routine follows the advice in
+// https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html.
+std::vector<uint16_t> String16WireRepToHost(span<uint8_t> in) {
+ // must be even number of bytes.
+ CHECK_EQ(in.size() & 1, 0u);
+ std::vector<uint16_t> host_out;
+ for (size_t ii = 0; ii < in.size(); ii += 2)
+ host_out.push_back(in[ii + 1] << 8 | in[ii]);
+ return host_out;
+}
+
+TEST(EncodeDecodeString16Test, RoundtripsHelloWorld) {
+ // This roundtrips the hello world message which is given here in utf16
+ // characters. 0xd83c, 0xdf0e: UTF16 encoding for the "Earth Globe Americas"
+ // character, 🌎.
+ std::array<uint16_t, 10> msg{
+ {'H', 'e', 'l', 'l', 'o', ',', ' ', 0xd83c, 0xdf0e, '.'}};
+ std::vector<uint8_t> encoded;
+ EncodeString16(span<uint16_t>(msg.data(), msg.size()), &encoded);
+ // This will be encoded as BYTE_STRING of length 20, so the 20 is encoded in
+ // the additional info part of the initial byte. Payload is two bytes for each
+ // UTF16 character.
+ uint8_t initial_byte = /*major type=*/2 << 5 | /*additional info=*/20;
+ std::array<uint8_t, 21> encoded_expected = {
+ {initial_byte, 'H', 0, 'e', 0, 'l', 0, 'l', 0, 'o', 0,
+ ',', 0, ' ', 0, 0x3c, 0xd8, 0x0e, 0xdf, '.', 0}};
+ EXPECT_THAT(encoded, ElementsAreArray(encoded_expected));
+
+ // Now decode to complete the roundtrip.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::STRING16, tokenizer.TokenTag());
+ std::vector<uint16_t> decoded =
+ String16WireRepToHost(tokenizer.GetString16WireRep());
+ EXPECT_THAT(decoded, ElementsAreArray(msg));
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+
+ // For bonus points, we look at the decoded message in UTF8 as well so we can
+ // easily see it on the terminal screen.
+ std::string utf8_decoded = UTF16ToUTF8(SpanFrom(decoded));
+ EXPECT_EQ("Hello, 🌎.", utf8_decoded);
+}
+
+TEST(EncodeDecodeString16Test, Roundtrips500) {
+ // We roundtrip a message that has 250 16 bit values. Each of these are just
+ // set to their index. 250 is interesting because the cbor spec uses a
+ // BYTE_STRING of length 500 for one of their examples of how to encode the
+ // start of it (section 2.1) so it's easy for us to look at the first three
+ // bytes closely.
+ std::vector<uint16_t> two_fifty;
+ for (uint16_t ii = 0; ii < 250; ++ii)
+ two_fifty.push_back(ii);
+ std::vector<uint8_t> encoded;
+ EncodeString16(span<uint16_t>(two_fifty.data(), two_fifty.size()), &encoded);
+ EXPECT_EQ(3u + 250u * 2, encoded.size());
+ // Now check the first three bytes:
+ // Major type: 2 (BYTE_STRING)
+ // Additional information: 25, indicating size is represented by 2 bytes.
+ // Bytes 1 and 2 encode 500 (0x01f4).
+ EXPECT_EQ(2 << 5 | 25, encoded[0]);
+ EXPECT_EQ(0x01, encoded[1]);
+ EXPECT_EQ(0xf4, encoded[2]);
+
+ // Now decode to complete the roundtrip.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::STRING16, tokenizer.TokenTag());
+ std::vector<uint16_t> decoded =
+ String16WireRepToHost(tokenizer.GetString16WireRep());
+ EXPECT_THAT(decoded, ElementsAreArray(two_fifty));
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeString16Test, ErrorCases) {
+ struct TestCase {
+ std::vector<uint8_t> data;
+ std::string msg;
+ };
+ std::vector<TestCase> tests{
+ {TestCase{{2 << 5 | 1, 'a'},
+ "length must be divisible by 2 (but it's 1)"},
+ TestCase{{2 << 5 | 29}, "additional info = 29 isn't recognized"},
+ TestCase{{2 << 5 | 9, 1, 2, 3, 4, 5, 6, 7, 8},
+ "length (9) points just past the end of the test case"},
+ TestCase{{2 << 5 | 27, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 'a', 'b', 'c'},
+ "large length pointing past the end of the test case"}}};
+ for (const TestCase& test : tests) {
+ SCOPED_TRACE(test.msg);
+ CBORTokenizer tokenizer(SpanFrom(test.data));
+ EXPECT_EQ(CBORTokenTag::ERROR_VALUE, tokenizer.TokenTag());
+ EXPECT_EQ(Error::CBOR_INVALID_STRING16, tokenizer.Status().error);
+ }
+}
+
+//
+// EncodeString8 / CBORTokenTag::STRING8
+//
+TEST(EncodeDecodeString8Test, RoundtripsHelloWorld) {
+ // This roundtrips the hello world message which is given here in utf8
+ // characters. 🌎 is a four byte utf8 character.
+ std::string utf8_msg = "Hello, 🌎.";
+ std::vector<uint8_t> msg(utf8_msg.begin(), utf8_msg.end());
+ std::vector<uint8_t> encoded;
+ EncodeString8(SpanFrom(utf8_msg), &encoded);
+ // This will be encoded as STRING of length 12, so the 12 is encoded in
+ // the additional info part of the initial byte. Payload is one byte per
+ // utf8 byte.
+ uint8_t initial_byte = /*major type=*/3 << 5 | /*additional info=*/12;
+ std::array<uint8_t, 13> encoded_expected = {{initial_byte, 'H', 'e', 'l', 'l',
+ 'o', ',', ' ', 0xF0, 0x9f, 0x8c,
+ 0x8e, '.'}};
+ EXPECT_THAT(encoded, ElementsAreArray(encoded_expected));
+
+ // Now decode to complete the roundtrip.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::STRING8, tokenizer.TokenTag());
+ std::vector<uint8_t> decoded(tokenizer.GetString8().begin(),
+ tokenizer.GetString8().end());
+ EXPECT_THAT(decoded, ElementsAreArray(msg));
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeString8Test, ErrorCases) {
+ struct TestCase {
+ std::vector<uint8_t> data;
+ std::string msg;
+ };
+ std::vector<TestCase> tests{
+ {TestCase{{3 << 5 | 29}, "additional info = 29 isn't recognized"},
+ TestCase{{3 << 5 | 9, 1, 2, 3, 4, 5, 6, 7, 8},
+ "length (9) points just past the end of the test case"},
+ TestCase{{3 << 5 | 27, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 'a', 'b', 'c'},
+ "large length pointing past the end of the test case"}}};
+ for (const TestCase& test : tests) {
+ SCOPED_TRACE(test.msg);
+ CBORTokenizer tokenizer(SpanFrom(test.data));
+ EXPECT_EQ(CBORTokenTag::ERROR_VALUE, tokenizer.TokenTag());
+ EXPECT_EQ(Error::CBOR_INVALID_STRING8, tokenizer.Status().error);
+ }
+}
+
+TEST(EncodeFromLatin1Test, ConvertsToUTF8IfNeeded) {
+ std::vector<std::pair<std::string, std::string>> examples = {
+ {"Hello, world.", "Hello, world."},
+ {"Above: \xDC"
+ "ber",
+ "Above: Über"},
+ {"\xA5 500 are about \xA3 3.50; a y with umlaut is \xFF",
+ "¥ 500 are about £ 3.50; a y with umlaut is ÿ"}};
+
+ for (const auto& example : examples) {
+ const std::string& latin1 = example.first;
+ const std::string& expected_utf8 = example.second;
+ std::vector<uint8_t> encoded;
+ EncodeFromLatin1(SpanFrom(latin1), &encoded);
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::STRING8, tokenizer.TokenTag());
+ std::vector<uint8_t> decoded(tokenizer.GetString8().begin(),
+ tokenizer.GetString8().end());
+ std::string decoded_str(decoded.begin(), decoded.end());
+ EXPECT_THAT(decoded_str, testing::Eq(expected_utf8));
+ }
+}
+
+TEST(EncodeFromUTF16Test, ConvertsToUTF8IfEasy) {
+ std::vector<uint16_t> ascii = {'e', 'a', 's', 'y'};
+ std::vector<uint8_t> encoded;
+ EncodeFromUTF16(span<uint16_t>(ascii.data(), ascii.size()), &encoded);
+
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::STRING8, tokenizer.TokenTag());
+ std::vector<uint8_t> decoded(tokenizer.GetString8().begin(),
+ tokenizer.GetString8().end());
+ std::string decoded_str(decoded.begin(), decoded.end());
+ EXPECT_THAT(decoded_str, testing::Eq("easy"));
+}
+
+TEST(EncodeFromUTF16Test, EncodesAsString16IfNeeded) {
+ // Since this message contains non-ASCII characters, the routine is
+ // forced to encode as UTF16. We see this below by checking that the
+ // token tag is STRING16.
+ std::vector<uint16_t> msg = {'H', 'e', 'l', 'l', 'o',
+ ',', ' ', 0xd83c, 0xdf0e, '.'};
+ std::vector<uint8_t> encoded;
+ EncodeFromUTF16(span<uint16_t>(msg.data(), msg.size()), &encoded);
+
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::STRING16, tokenizer.TokenTag());
+ std::vector<uint16_t> decoded =
+ String16WireRepToHost(tokenizer.GetString16WireRep());
+ std::string utf8_decoded = UTF16ToUTF8(SpanFrom(decoded));
+ EXPECT_EQ("Hello, 🌎.", utf8_decoded);
+}
+
+//
+// EncodeBinary / CBORTokenTag::BINARY
+//
+TEST(EncodeDecodeBinaryTest, RoundtripsHelloWorld) {
+ std::vector<uint8_t> binary = {'H', 'e', 'l', 'l', 'o', ',', ' ',
+ 'w', 'o', 'r', 'l', 'd', '.'};
+ std::vector<uint8_t> encoded;
+ EncodeBinary(span<uint8_t>(binary.data(), binary.size()), &encoded);
+ // So, on the wire we see that the binary blob travels unmodified.
+ EXPECT_THAT(
+ encoded,
+ ElementsAreArray(std::array<uint8_t, 15>{
+ {(6 << 5 | 22), // tag 22 indicating base64 interpretation in JSON
+ (2 << 5 | 13), // BYTE_STRING (type 2) of length 13
+ 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '.'}}));
+ std::vector<uint8_t> decoded;
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::BINARY, tokenizer.TokenTag());
+ EXPECT_EQ(0, static_cast<int>(tokenizer.Status().error));
+ decoded = std::vector<uint8_t>(tokenizer.GetBinary().begin(),
+ tokenizer.GetBinary().end());
+ EXPECT_THAT(decoded, ElementsAreArray(binary));
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeBinaryTest, ErrorCases) {
+ struct TestCase {
+ std::vector<uint8_t> data;
+ std::string msg;
+ };
+ std::vector<TestCase> tests{{TestCase{
+ {6 << 5 | 22, // tag 22 indicating base64 interpretation in JSON
+ 2 << 5 | 27, // BYTE_STRING (type 2), followed by 8 bytes length
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ "large length pointing past the end of the test case"}}};
+ for (const TestCase& test : tests) {
+ SCOPED_TRACE(test.msg);
+ CBORTokenizer tokenizer(SpanFrom(test.data));
+ EXPECT_EQ(CBORTokenTag::ERROR_VALUE, tokenizer.TokenTag());
+ EXPECT_EQ(Error::CBOR_INVALID_BINARY, tokenizer.Status().error);
+ }
+}
+
+//
+// EncodeDouble / CBORTokenTag::DOUBLE
+//
+TEST(EncodeDecodeDoubleTest, RoundtripsWikipediaExample) {
+ // https://en.wikipedia.org/wiki/Double-precision_floating-point_format
+ // provides the example of a hex representation 3FD5 5555 5555 5555, which
+ // approximates 1/3.
+
+ const double kOriginalValue = 1.0 / 3;
+ std::vector<uint8_t> encoded;
+ EncodeDouble(kOriginalValue, &encoded);
+ // first three bits: major type = 7; remaining five bits: additional info =
+ // value 27. This is followed by 8 bytes of payload (which match Wikipedia).
+ EXPECT_THAT(
+ encoded,
+ ElementsAreArray(std::array<uint8_t, 9>{
+ {7 << 5 | 27, 0x3f, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55}}));
+
+ // Reverse direction: decode and compare with original value.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::DOUBLE, tokenizer.TokenTag());
+ EXPECT_THAT(tokenizer.GetDouble(), testing::DoubleEq(kOriginalValue));
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeDoubleTest, RoundtripsAdditionalExamples) {
+ std::vector<double> examples = {0.0,
+ 1.0,
+ -1.0,
+ 3.1415,
+ std::numeric_limits<double>::min(),
+ std::numeric_limits<double>::max(),
+ std::numeric_limits<double>::infinity(),
+ std::numeric_limits<double>::quiet_NaN()};
+ for (double example : examples) {
+ SCOPED_TRACE(std::string("example ") + std::to_string(example));
+ std::vector<uint8_t> encoded;
+ EncodeDouble(example, &encoded);
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::DOUBLE, tokenizer.TokenTag());
+ if (std::isnan(example))
+ EXPECT_TRUE(std::isnan(tokenizer.GetDouble()));
+ else
+ EXPECT_THAT(tokenizer.GetDouble(), testing::DoubleEq(example));
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+ }
+}
+
+// =============================================================================
+// cbor::NewCBOREncoder - for encoding from a streaming parser
+// =============================================================================
+
+void EncodeUTF8ForTest(const std::string& key, std::vector<uint8_t>* out) {
+ EncodeString8(SpanFrom(key), out);
+}
+TEST(JSONToCBOREncoderTest, SevenBitStrings) {
+ // When a string can be represented as 7 bit ASCII, the encoder will use the
+ // STRING (major Type 3) type, so the actual characters end up as bytes on the
+ // wire.
+ std::vector<uint8_t> encoded;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> encoder =
+ NewCBOREncoder(&encoded, &status);
+ std::vector<uint16_t> utf16 = {'f', 'o', 'o'};
+ encoder->HandleString16(span<uint16_t>(utf16.data(), utf16.size()));
+ EXPECT_EQ(Error::OK, status.error);
+ // Here we assert that indeed, seven bit strings are represented as
+ // bytes on the wire, "foo" is just "foo".
+ EXPECT_THAT(encoded,
+ ElementsAreArray(std::array<uint8_t, 4>{
+ {/*major type 3*/ 3 << 5 | /*length*/ 3, 'f', 'o', 'o'}}));
+}
+
+TEST(JsonCborRoundtrip, EncodingDecoding) {
+ // Hits all the cases except binary and error in StreamingParserHandler, first
+ // parsing a JSON message into CBOR, then parsing it back from CBOR into JSON.
+ std::string json =
+ "{"
+ "\"string\":\"Hello, \\ud83c\\udf0e.\","
+ "\"double\":3.1415,"
+ "\"int\":1,"
+ "\"negative int\":-1,"
+ "\"bool\":true,"
+ "\"null\":null,"
+ "\"array\":[1,2,3]"
+ "}";
+ std::vector<uint8_t> encoded;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> encoder =
+ NewCBOREncoder(&encoded, &status);
+ span<uint8_t> ascii_in = SpanFrom(json);
+ json::ParseJSON(GetTestPlatform(), ascii_in, encoder.get());
+ std::vector<uint8_t> expected = {
+ 0xd8, // envelope
+ 0x5a, // byte string with 32 bit length
+ 0, 0, 0, 94, // length is 94 bytes
+ };
+ expected.push_back(0xbf); // indef length map start
+ EncodeString8(SpanFrom("string"), &expected);
+ // This is followed by the encoded string for "Hello, 🌎."
+ // So, it's the same bytes that we tested above in
+ // EncodeDecodeString16Test.RoundtripsHelloWorld.
+ expected.push_back(/*major type=*/2 << 5 | /*additional info=*/20);
+ for (uint8_t ch : std::array<uint8_t, 20>{
+ {'H', 0, 'e', 0, 'l', 0, 'l', 0, 'o', 0,
+ ',', 0, ' ', 0, 0x3c, 0xd8, 0x0e, 0xdf, '.', 0}})
+ expected.push_back(ch);
+ EncodeString8(SpanFrom("double"), &expected);
+ EncodeDouble(3.1415, &expected);
+ EncodeString8(SpanFrom("int"), &expected);
+ EncodeInt32(1, &expected);
+ EncodeString8(SpanFrom("negative int"), &expected);
+ EncodeInt32(-1, &expected);
+ EncodeString8(SpanFrom("bool"), &expected);
+ expected.push_back(7 << 5 | 21); // RFC 7049 Section 2.3, Table 2: true
+ EncodeString8(SpanFrom("null"), &expected);
+ expected.push_back(7 << 5 | 22); // RFC 7049 Section 2.3, Table 2: null
+ EncodeString8(SpanFrom("array"), &expected);
+ expected.push_back(0xd8); // envelope
+ expected.push_back(0x5a); // byte string with 32 bit length
+ // the length is 5 bytes (that's up to end indef length array below).
+ for (uint8_t ch : std::array<uint8_t, 4>{{0, 0, 0, 5}})
+ expected.push_back(ch);
+ expected.push_back(0x9f); // RFC 7049 Section 2.2.1, indef length array start
+ expected.push_back(1); // Three UNSIGNED values (easy since Major Type 0)
+ expected.push_back(2);
+ expected.push_back(3);
+ expected.push_back(0xff); // End indef length array
+ expected.push_back(0xff); // End indef length map
+ EXPECT_TRUE(status.ok());
+ EXPECT_THAT(encoded, ElementsAreArray(expected));
+
+ // And now we roundtrip, decoding the message we just encoded.
+ std::string decoded;
+ std::unique_ptr<StreamingParserHandler> json_encoder =
+ NewJSONEncoder(&GetTestPlatform(), &decoded, &status);
+ ParseCBOR(span<uint8_t>(encoded.data(), encoded.size()), json_encoder.get());
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(json, decoded);
+}
+
+TEST(JsonCborRoundtrip, MoreRoundtripExamples) {
+ std::vector<std::string> examples = {
+ // Tests that after closing a nested objects, additional key/value pairs
+ // are considered.
+ "{\"foo\":{\"bar\":1},\"baz\":2}", "{\"foo\":[1,2,3],\"baz\":2}"};
+ for (const std::string& json : examples) {
+ SCOPED_TRACE(std::string("example: ") + json);
+ std::vector<uint8_t> encoded;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> encoder =
+ NewCBOREncoder(&encoded, &status);
+ span<uint8_t> ascii_in = SpanFrom(json);
+ ParseJSON(GetTestPlatform(), ascii_in, encoder.get());
+ std::string decoded;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &decoded, &status);
+ ParseCBOR(span<uint8_t>(encoded.data(), encoded.size()), json_writer.get());
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(json, decoded);
+ }
+}
+
+TEST(JSONToCBOREncoderTest, HelloWorldBinary_WithTripToJson) {
+ // The StreamingParserHandler::HandleBinary is a special case: The JSON parser
+ // will never call this method, because JSON does not natively support the
+ // binary type. So, we can't fully roundtrip. However, the other direction
+ // works: binary will be rendered in JSON, as a base64 string. So, we make
+ // calls to the encoder directly here, to construct a message, and one of
+ // these calls is ::HandleBinary, to which we pass a "binary" string
+ // containing "Hello, world.".
+ std::vector<uint8_t> encoded;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> encoder =
+ NewCBOREncoder(&encoded, &status);
+ encoder->HandleMapBegin();
+ // Emit a key.
+ std::vector<uint16_t> key = {'f', 'o', 'o'};
+ encoder->HandleString16(SpanFrom(key));
+ // Emit the binary payload, an arbitrary array of bytes that happens to
+ // be the ascii message "Hello, world.".
+ encoder->HandleBinary(SpanFrom(std::vector<uint8_t>{
+ 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '.'}));
+ encoder->HandleMapEnd();
+ EXPECT_EQ(Error::OK, status.error);
+
+ // Now drive the json writer via the CBOR decoder.
+ std::string decoded;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &decoded, &status);
+ ParseCBOR(SpanFrom(encoded), json_writer.get());
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+ // "Hello, world." in base64 is "SGVsbG8sIHdvcmxkLg==".
+ EXPECT_EQ("{\"foo\":\"SGVsbG8sIHdvcmxkLg==\"}", decoded);
+}
+
+// =============================================================================
+// cbor::ParseCBOR - for receiving streaming parser events for CBOR messages
+// =============================================================================
+
+TEST(ParseCBORTest, ParseEmptyCBORMessage) {
+ // An envelope starting with 0xd8, 0x5a, with the byte length
+ // of 2, containing a map that's empty (0xbf for map
+ // start, and 0xff for map end).
+ std::vector<uint8_t> in = {0xd8, 0x5a, 0, 0, 0, 2, 0xbf, 0xff};
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(in.data(), in.size()), json_writer.get());
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ("{}", out);
+}
+
+TEST(ParseCBORTest, ParseCBORHelloWorld) {
+ const uint8_t kPayloadLen = 27;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen};
+ bytes.push_back(0xbf); // start indef length map.
+ EncodeString8(SpanFrom("msg"), &bytes); // key: msg
+ // Now write the value, the familiar "Hello, 🌎." where the globe is expressed
+ // as two utf16 chars.
+ bytes.push_back(/*major type=*/2 << 5 | /*additional info=*/20);
+ for (uint8_t ch : std::array<uint8_t, 20>{
+ {'H', 0, 'e', 0, 'l', 0, 'l', 0, 'o', 0,
+ ',', 0, ' ', 0, 0x3c, 0xd8, 0x0e, 0xdf, '.', 0}})
+ bytes.push_back(ch);
+ bytes.push_back(0xff); // stop byte
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ("{\"msg\":\"Hello, \\ud83c\\udf0e.\"}", out);
+}
+
+TEST(ParseCBORTest, UTF8IsSupportedInKeys) {
+ const uint8_t kPayloadLen = 11;
+ std::vector<uint8_t> bytes = {cbor::InitialByteForEnvelope(),
+ cbor::InitialByteFor32BitLengthByteString(),
+ 0,
+ 0,
+ 0,
+ kPayloadLen};
+ bytes.push_back(cbor::EncodeIndefiniteLengthMapStart());
+ // Two UTF16 chars.
+ EncodeString8(SpanFrom("🌎"), &bytes);
+ // Can be encoded as a single UTF16 char.
+ EncodeString8(SpanFrom("☾"), &bytes);
+ bytes.push_back(cbor::EncodeStop());
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ("{\"\\ud83c\\udf0e\":\"\\u263e\"}", out);
+}
+
+TEST(ParseCBORTest, NoInputError) {
+ std::vector<uint8_t> in = {};
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(in.data(), in.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_NO_INPUT, status.error);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, InvalidStartByteError) {
+ // Here we test that some actual json, which usually starts with {,
+ // is not considered CBOR. CBOR messages must start with 0x5a, the
+ // envelope start byte.
+ std::string json = "{\"msg\": \"Hello, world.\"}";
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(SpanFrom(json), json_writer.get());
+ EXPECT_EQ(Error::CBOR_INVALID_START_BYTE, status.error);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, UnexpectedEofExpectedValueError) {
+ constexpr uint8_t kPayloadLen = 5;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // map start
+ // A key; so value would be next.
+ EncodeString8(SpanFrom("key"), &bytes);
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE, status.error);
+ EXPECT_EQ(bytes.size(), status.pos);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, UnexpectedEofInArrayError) {
+ constexpr uint8_t kPayloadLen = 8;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // The byte for starting a map.
+ // A key; so value would be next.
+ EncodeString8(SpanFrom("array"), &bytes);
+ bytes.push_back(0x9f); // byte for indefinite length array start.
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_UNEXPECTED_EOF_IN_ARRAY, status.error);
+ EXPECT_EQ(bytes.size(), status.pos);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, UnexpectedEofInMapError) {
+ constexpr uint8_t kPayloadLen = 1;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // The byte for starting a map.
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_UNEXPECTED_EOF_IN_MAP, status.error);
+ EXPECT_EQ(7u, status.pos);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, InvalidMapKeyError) {
+ constexpr uint8_t kPayloadLen = 2;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0,
+ 0, 0, kPayloadLen, // envelope
+ 0xbf, // map start
+ 7 << 5 | 22}; // null (not a valid map key)
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_INVALID_MAP_KEY, status.error);
+ EXPECT_EQ(7u, status.pos);
+ EXPECT_EQ("", out);
+}
+
+std::vector<uint8_t> MakeNestedCBOR(int depth) {
+ std::vector<uint8_t> bytes;
+ std::vector<EnvelopeEncoder> envelopes;
+ for (int ii = 0; ii < depth; ++ii) {
+ envelopes.emplace_back();
+ envelopes.back().EncodeStart(&bytes);
+ bytes.push_back(0xbf); // indef length map start
+ EncodeString8(SpanFrom("key"), &bytes);
+ }
+ EncodeString8(SpanFrom("innermost_value"), &bytes);
+ for (int ii = 0; ii < depth; ++ii) {
+ bytes.push_back(0xff); // stop byte, finishes map.
+ envelopes.back().EncodeStop(&bytes);
+ envelopes.pop_back();
+ }
+ return bytes;
+}
+
+TEST(ParseCBORTest, StackLimitExceededError) {
+ { // Depth 3: no stack limit exceeded error and is easy to inspect.
+ std::vector<uint8_t> bytes = MakeNestedCBOR(3);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+ EXPECT_EQ("{\"key\":{\"key\":{\"key\":\"innermost_value\"}}}", out);
+ }
+ { // Depth 300: no stack limit exceeded.
+ std::vector<uint8_t> bytes = MakeNestedCBOR(300);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+ }
+
+ // We just want to know the length of one opening map so we can compute
+ // where the error is encountered. So we look at a small example and find
+ // the second envelope start.
+ std::vector<uint8_t> small_example = MakeNestedCBOR(3);
+ size_t opening_segment_size = 1; // Start after the first envelope start.
+ while (opening_segment_size < small_example.size() &&
+ small_example[opening_segment_size] != 0xd8)
+ opening_segment_size++;
+
+ { // Depth 301: limit exceeded.
+ std::vector<uint8_t> bytes = MakeNestedCBOR(301);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_STACK_LIMIT_EXCEEDED, status.error);
+ EXPECT_EQ(opening_segment_size * 301, status.pos);
+ }
+ { // Depth 320: still limit exceeded, and at the same pos as for 1001
+ std::vector<uint8_t> bytes = MakeNestedCBOR(320);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_STACK_LIMIT_EXCEEDED, status.error);
+ EXPECT_EQ(opening_segment_size * 301, status.pos);
+ }
+}
+
+TEST(ParseCBORTest, UnsupportedValueError) {
+ constexpr uint8_t kPayloadLen = 6;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // map start
+ EncodeString8(SpanFrom("key"), &bytes);
+ size_t error_pos = bytes.size();
+ bytes.push_back(6 << 5 | 5); // tags aren't supported yet.
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_UNSUPPORTED_VALUE, status.error);
+ EXPECT_EQ(error_pos, status.pos);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, InvalidString16Error) {
+ constexpr uint8_t kPayloadLen = 11;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // map start
+ EncodeString8(SpanFrom("key"), &bytes);
+ size_t error_pos = bytes.size();
+ // a BYTE_STRING of length 5 as value; since we interpret these as string16,
+ // it's going to be invalid as each character would need two bytes, but
+ // 5 isn't divisible by 2.
+ bytes.push_back(2 << 5 | 5);
+ for (int ii = 0; ii < 5; ++ii)
+ bytes.push_back(' ');
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_INVALID_STRING16, status.error);
+ EXPECT_EQ(error_pos, status.pos);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, InvalidString8Error) {
+ constexpr uint8_t kPayloadLen = 6;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // map start
+ EncodeString8(SpanFrom("key"), &bytes);
+ size_t error_pos = bytes.size();
+ // a STRING of length 5 as value, but we're at the end of the bytes array
+ // so it can't be decoded successfully.
+ bytes.push_back(3 << 5 | 5);
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_INVALID_STRING8, status.error);
+ EXPECT_EQ(error_pos, status.pos);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, InvalidBinaryError) {
+ constexpr uint8_t kPayloadLen = 9;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // map start
+ EncodeString8(SpanFrom("key"), &bytes);
+ size_t error_pos = bytes.size();
+ bytes.push_back(6 << 5 | 22); // base64 hint for JSON; indicates binary
+ bytes.push_back(2 << 5 | 10); // BYTE_STRING (major type 2) of length 10
+ // Just two garbage bytes, not enough for the binary.
+ bytes.push_back(0x31);
+ bytes.push_back(0x23);
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_INVALID_BINARY, status.error);
+ EXPECT_EQ(error_pos, status.pos);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, InvalidDoubleError) {
+ constexpr uint8_t kPayloadLen = 8;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // map start
+ EncodeString8(SpanFrom("key"), &bytes);
+ size_t error_pos = bytes.size();
+ bytes.push_back(7 << 5 | 27); // initial byte for double
+ // Just two garbage bytes, not enough to represent an actual double.
+ bytes.push_back(0x31);
+ bytes.push_back(0x23);
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_INVALID_DOUBLE, status.error);
+ EXPECT_EQ(error_pos, status.pos);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, InvalidSignedError) {
+ constexpr uint8_t kPayloadLen = 14;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // map start
+ EncodeString8(SpanFrom("key"), &bytes);
+ size_t error_pos = bytes.size();
+ // uint64_t max is a perfectly fine value to encode as CBOR unsigned,
+ // but we don't support this since we only cover the int32_t range.
+ internals::WriteTokenStart(MajorType::UNSIGNED,
+ std::numeric_limits<uint64_t>::max(), &bytes);
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_INVALID_INT32, status.error);
+ EXPECT_EQ(error_pos, status.pos);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, TrailingJunk) {
+ constexpr uint8_t kPayloadLen = 35;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // map start
+ EncodeString8(SpanFrom("key"), &bytes);
+ EncodeString8(SpanFrom("value"), &bytes);
+ bytes.push_back(0xff); // Up to here, it's a perfectly fine msg.
+ size_t error_pos = bytes.size();
+ EncodeString8(SpanFrom("trailing junk"), &bytes);
+
+ internals::WriteTokenStart(MajorType::UNSIGNED,
+ std::numeric_limits<uint64_t>::max(), &bytes);
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_TRAILING_JUNK, status.error);
+ EXPECT_EQ(error_pos, status.pos);
+ EXPECT_EQ("", out);
+}
+
+// =============================================================================
+// cbor::AppendString8EntryToMap - for limited in-place editing of messages
+// =============================================================================
+
+template <typename T>
+class AppendString8EntryToMapTest : public ::testing::Test {};
+
+using ContainerTestTypes = ::testing::Types<std::vector<uint8_t>, std::string>;
+TYPED_TEST_SUITE(AppendString8EntryToMapTest, ContainerTestTypes);
+
+TYPED_TEST(AppendString8EntryToMapTest, AppendsEntrySuccessfully) {
+ constexpr uint8_t kPayloadLen = 12;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // map start
+ size_t pos_before_payload = bytes.size() - 1;
+ EncodeString8(SpanFrom("key"), &bytes);
+ EncodeString8(SpanFrom("value"), &bytes);
+ bytes.push_back(0xff); // A perfectly fine cbor message.
+ EXPECT_EQ(kPayloadLen, bytes.size() - pos_before_payload);
+
+ TypeParam msg(bytes.begin(), bytes.end());
+
+ Status status =
+ AppendString8EntryToCBORMap(SpanFrom("foo"), SpanFrom("bar"), &msg);
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+ std::string out;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(SpanFrom(msg), json_writer.get());
+ EXPECT_EQ("{\"key\":\"value\",\"foo\":\"bar\"}", out);
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+}
+
+TYPED_TEST(AppendString8EntryToMapTest, AppendThreeEntries) {
+ std::vector<uint8_t> encoded = {
+ 0xd8, 0x5a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), EncodeStop()};
+ EXPECT_EQ(Error::OK, AppendString8EntryToCBORMap(SpanFrom("key"),
+ SpanFrom("value"), &encoded)
+ .error);
+ EXPECT_EQ(Error::OK, AppendString8EntryToCBORMap(SpanFrom("key1"),
+ SpanFrom("value1"), &encoded)
+ .error);
+ EXPECT_EQ(Error::OK, AppendString8EntryToCBORMap(SpanFrom("key2"),
+ SpanFrom("value2"), &encoded)
+ .error);
+ TypeParam msg(encoded.begin(), encoded.end());
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(SpanFrom(msg), json_writer.get());
+ EXPECT_EQ("{\"key\":\"value\",\"key1\":\"value1\",\"key2\":\"value2\"}", out);
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+}
+
+TYPED_TEST(AppendString8EntryToMapTest, MapStartExpected_Error) {
+ std::vector<uint8_t> bytes = {
+ 0xd8, 0x5a, 0, 0, 0, 1, EncodeIndefiniteLengthArrayStart()};
+ TypeParam msg(bytes.begin(), bytes.end());
+ Status status =
+ AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg);
+ EXPECT_EQ(Error::CBOR_MAP_START_EXPECTED, status.error);
+ EXPECT_EQ(6u, status.pos);
+}
+
+TYPED_TEST(AppendString8EntryToMapTest, MapStopExpected_Error) {
+ std::vector<uint8_t> bytes = {
+ 0xd8, 0x5a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), 42};
+ TypeParam msg(bytes.begin(), bytes.end());
+ Status status =
+ AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg);
+ EXPECT_EQ(Error::CBOR_MAP_STOP_EXPECTED, status.error);
+ EXPECT_EQ(7u, status.pos);
+}
+
+TYPED_TEST(AppendString8EntryToMapTest, InvalidEnvelope_Error) {
+ { // Second byte is wrong.
+ std::vector<uint8_t> bytes = {
+ 0x5a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), EncodeStop(), 0};
+ TypeParam msg(bytes.begin(), bytes.end());
+ Status status =
+ AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg);
+ EXPECT_EQ(Error::CBOR_INVALID_ENVELOPE, status.error);
+ EXPECT_EQ(0u, status.pos);
+ }
+ { // Second byte is wrong.
+ std::vector<uint8_t> bytes = {
+ 0xd8, 0x7a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), EncodeStop()};
+ TypeParam msg(bytes.begin(), bytes.end());
+ Status status =
+ AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg);
+ EXPECT_EQ(Error::CBOR_INVALID_ENVELOPE, status.error);
+ EXPECT_EQ(0u, status.pos);
+ }
+ { // Invalid envelope size example.
+ std::vector<uint8_t> bytes = {
+ 0xd8, 0x5a, 0, 0, 0, 3, EncodeIndefiniteLengthMapStart(), EncodeStop(),
+ };
+ TypeParam msg(bytes.begin(), bytes.end());
+ Status status =
+ AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg);
+ EXPECT_EQ(Error::CBOR_INVALID_ENVELOPE, status.error);
+ EXPECT_EQ(0u, status.pos);
+ }
+ { // Invalid envelope size example.
+ std::vector<uint8_t> bytes = {
+ 0xd8, 0x5a, 0, 0, 0, 1, EncodeIndefiniteLengthMapStart(), EncodeStop(),
+ };
+ TypeParam msg(bytes.begin(), bytes.end());
+ Status status =
+ AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg);
+ EXPECT_EQ(Error::CBOR_INVALID_ENVELOPE, status.error);
+ EXPECT_EQ(0u, status.pos);
+ }
+}
+} // namespace cbor
+
+namespace json {
+
+// =============================================================================
+// json::NewJSONEncoder - for encoding streaming parser events as JSON
+// =============================================================================
+
+void WriteUTF8AsUTF16(StreamingParserHandler* writer, const std::string& utf8) {
+ writer->HandleString16(SpanFrom(UTF8ToUTF16(SpanFrom(utf8))));
+}
+
+TEST(JsonStdStringWriterTest, HelloWorld) {
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ writer->HandleMapBegin();
+ WriteUTF8AsUTF16(writer.get(), "msg1");
+ WriteUTF8AsUTF16(writer.get(), "Hello, 🌎.");
+ std::string key = "msg1-as-utf8";
+ std::string value = "Hello, 🌎.";
+ writer->HandleString8(SpanFrom(key));
+ writer->HandleString8(SpanFrom(value));
+ WriteUTF8AsUTF16(writer.get(), "msg2");
+ WriteUTF8AsUTF16(writer.get(), "\\\b\r\n\t\f\"");
+ WriteUTF8AsUTF16(writer.get(), "nested");
+ writer->HandleMapBegin();
+ WriteUTF8AsUTF16(writer.get(), "double");
+ writer->HandleDouble(3.1415);
+ WriteUTF8AsUTF16(writer.get(), "int");
+ writer->HandleInt32(-42);
+ WriteUTF8AsUTF16(writer.get(), "bool");
+ writer->HandleBool(false);
+ WriteUTF8AsUTF16(writer.get(), "null");
+ writer->HandleNull();
+ writer->HandleMapEnd();
+ WriteUTF8AsUTF16(writer.get(), "array");
+ writer->HandleArrayBegin();
+ writer->HandleInt32(1);
+ writer->HandleInt32(2);
+ writer->HandleInt32(3);
+ writer->HandleArrayEnd();
+ writer->HandleMapEnd();
+ EXPECT_TRUE(status.ok());
+ EXPECT_EQ(
+ "{\"msg1\":\"Hello, \\ud83c\\udf0e.\","
+ "\"msg1-as-utf8\":\"Hello, \\ud83c\\udf0e.\","
+ "\"msg2\":\"\\\\\\b\\r\\n\\t\\f\\\"\","
+ "\"nested\":{\"double\":3.1415,\"int\":-42,"
+ "\"bool\":false,\"null\":null},\"array\":[1,2,3]}",
+ out);
+}
+
+TEST(JsonStdStringWriterTest, RepresentingNonFiniteValuesAsNull) {
+ // JSON can't represent +Infinity, -Infinity, or NaN.
+ // So in practice it's mapped to null.
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ writer->HandleMapBegin();
+ writer->HandleString8(SpanFrom("Infinity"));
+ writer->HandleDouble(std::numeric_limits<double>::infinity());
+ writer->HandleString8(SpanFrom("-Infinity"));
+ writer->HandleDouble(-std::numeric_limits<double>::infinity());
+ writer->HandleString8(SpanFrom("NaN"));
+ writer->HandleDouble(std::numeric_limits<double>::quiet_NaN());
+ writer->HandleMapEnd();
+ EXPECT_TRUE(status.ok());
+ EXPECT_EQ("{\"Infinity\":null,\"-Infinity\":null,\"NaN\":null}", out);
+}
+
+TEST(JsonStdStringWriterTest, BinaryEncodedAsJsonString) {
+ // The encoder emits binary submitted to StreamingParserHandler::HandleBinary
+ // as base64. The following three examples are taken from
+ // https://en.wikipedia.org/wiki/Base64.
+ {
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ writer->HandleBinary(SpanFrom(std::vector<uint8_t>({'M', 'a', 'n'})));
+ EXPECT_TRUE(status.ok());
+ EXPECT_EQ("\"TWFu\"", out);
+ }
+ {
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ writer->HandleBinary(SpanFrom(std::vector<uint8_t>({'M', 'a'})));
+ EXPECT_TRUE(status.ok());
+ EXPECT_EQ("\"TWE=\"", out);
+ }
+ {
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ writer->HandleBinary(SpanFrom(std::vector<uint8_t>({'M'})));
+ EXPECT_TRUE(status.ok());
+ EXPECT_EQ("\"TQ==\"", out);
+ }
+ { // "Hello, world.", verified with base64decode.org.
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ writer->HandleBinary(SpanFrom(std::vector<uint8_t>(
+ {'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '.'})));
+ EXPECT_TRUE(status.ok());
+ EXPECT_EQ("\"SGVsbG8sIHdvcmxkLg==\"", out);
+ }
+}
+
+TEST(JsonStdStringWriterTest, HandlesErrors) {
+ // When an error is sent via HandleError, it saves it in the provided
+ // status and clears the output.
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ writer->HandleMapBegin();
+ WriteUTF8AsUTF16(writer.get(), "msg1");
+ writer->HandleError(Status{Error::JSON_PARSER_VALUE_EXPECTED, 42});
+ EXPECT_EQ(Error::JSON_PARSER_VALUE_EXPECTED, status.error);
+ EXPECT_EQ(42u, status.pos);
+ EXPECT_EQ("", out);
+}
+
+// We'd use Gmock but unfortunately it only handles copyable return types.
+class MockPlatform : public Platform {
+ public:
+ // Not implemented.
+ bool StrToD(const char* str, double* result) const override { return false; }
+
+ // A map with pre-registered responses for DToSTr.
+ std::map<double, std::string> dtostr_responses_;
+
+ std::unique_ptr<char[]> DToStr(double value) const override {
+ auto it = dtostr_responses_.find(value);
+ CHECK(it != dtostr_responses_.end());
+ const std::string& str = it->second;
+ std::unique_ptr<char[]> response(new char[str.size() + 1]);
+ memcpy(response.get(), str.c_str(), str.size() + 1);
+ return response;
+ }
+};
+
+TEST(JsonStdStringWriterTest, DoubleToString) {
+ // This "broken" platform responds without the leading 0 before the
+ // decimal dot, so it'd be invalid JSON.
+ MockPlatform platform;
+ platform.dtostr_responses_[.1] = ".1";
+ platform.dtostr_responses_[-.7] = "-.7";
+
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> writer =
+ NewJSONEncoder(&platform, &out, &status);
+ writer->HandleArrayBegin();
+ writer->HandleDouble(.1);
+ writer->HandleDouble(-.7);
+ writer->HandleArrayEnd();
+ EXPECT_EQ("[0.1,-0.7]", out);
+}
+
+// =============================================================================
+// json::ParseJSON - for receiving streaming parser events for JSON
+// =============================================================================
+
+class Log : public StreamingParserHandler {
+ public:
+ void HandleMapBegin() override { log_ << "map begin\n"; }
+
+ void HandleMapEnd() override { log_ << "map end\n"; }
+
+ void HandleArrayBegin() override { log_ << "array begin\n"; }
+
+ void HandleArrayEnd() override { log_ << "array end\n"; }
+
+ void HandleString8(span<uint8_t> chars) override {
+ log_ << "string8: " << std::string(chars.begin(), chars.end()) << "\n";
+ }
+
+ void HandleString16(span<uint16_t> chars) override {
+ log_ << "string16: " << UTF16ToUTF8(chars) << "\n";
+ }
+
+ void HandleBinary(span<uint8_t> bytes) override {
+ // JSON doesn't have native support for arbitrary bytes, so our parser will
+ // never call this.
+ CHECK(false);
+ }
+
+ void HandleDouble(double value) override {
+ log_ << "double: " << value << "\n";
+ }
+
+ void HandleInt32(int32_t value) override { log_ << "int: " << value << "\n"; }
+
+ void HandleBool(bool value) override { log_ << "bool: " << value << "\n"; }
+
+ void HandleNull() override { log_ << "null\n"; }
+
+ void HandleError(Status status) override { status_ = status; }
+
+ std::string str() const { return status_.ok() ? log_.str() : ""; }
+
+ Status status() const { return status_; }
+
+ private:
+ std::ostringstream log_;
+ Status status_;
+};
+
+class JsonParserTest : public ::testing::Test {
+ protected:
+ Log log_;
+};
+
+TEST_F(JsonParserTest, SimpleDictionary) {
+ std::string json = "{\"foo\": 42}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_TRUE(log_.status().ok());
+ EXPECT_EQ(
+ "map begin\n"
+ "string16: foo\n"
+ "int: 42\n"
+ "map end\n",
+ log_.str());
+}
+
+TEST_F(JsonParserTest, UsAsciiDelCornerCase) {
+ // DEL (0x7f) is a 7 bit US-ASCII character, and while it is a control
+ // character according to Unicode, it's not considered a control
+ // character in https://tools.ietf.org/html/rfc7159#section-7, so
+ // it can be placed directly into the JSON string, without JSON escaping.
+ std::string json = "{\"foo\": \"a\x7f\"}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_TRUE(log_.status().ok());
+ EXPECT_EQ(
+ "map begin\n"
+ "string16: foo\n"
+ "string16: a\x7f\n"
+ "map end\n",
+ log_.str());
+}
+
+TEST_F(JsonParserTest, Whitespace) {
+ std::string json = "\n {\n\"msg\"\n: \v\"Hello, world.\"\t\r}\t";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_TRUE(log_.status().ok());
+ EXPECT_EQ(
+ "map begin\n"
+ "string16: msg\n"
+ "string16: Hello, world.\n"
+ "map end\n",
+ log_.str());
+}
+
+TEST_F(JsonParserTest, NestedDictionary) {
+ std::string json = "{\"foo\": {\"bar\": {\"baz\": 1}, \"bar2\": 2}}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_TRUE(log_.status().ok());
+ EXPECT_EQ(
+ "map begin\n"
+ "string16: foo\n"
+ "map begin\n"
+ "string16: bar\n"
+ "map begin\n"
+ "string16: baz\n"
+ "int: 1\n"
+ "map end\n"
+ "string16: bar2\n"
+ "int: 2\n"
+ "map end\n"
+ "map end\n",
+ log_.str());
+}
+
+TEST_F(JsonParserTest, Doubles) {
+ std::string json = "{\"foo\": 3.1415, \"bar\": 31415e-4}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_TRUE(log_.status().ok());
+ EXPECT_EQ(
+ "map begin\n"
+ "string16: foo\n"
+ "double: 3.1415\n"
+ "string16: bar\n"
+ "double: 3.1415\n"
+ "map end\n",
+ log_.str());
+}
+
+TEST_F(JsonParserTest, Unicode) {
+ // Globe character. 0xF0 0x9F 0x8C 0x8E in utf8, 0xD83C 0xDF0E in utf16.
+ std::string json = "{\"msg\": \"Hello, \\uD83C\\uDF0E.\"}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_TRUE(log_.status().ok());
+ EXPECT_EQ(
+ "map begin\n"
+ "string16: msg\n"
+ "string16: Hello, 🌎.\n"
+ "map end\n",
+ log_.str());
+}
+
+TEST_F(JsonParserTest, Unicode_ParseUtf16) {
+ // Globe character. utf8: 0xF0 0x9F 0x8C 0x8E; utf16: 0xD83C 0xDF0E.
+ // Crescent moon character. utf8: 0xF0 0x9F 0x8C 0x99; utf16: 0xD83C 0xDF19.
+
+ // We provide the moon with json escape, but the earth as utf16 input.
+ // Either way they arrive as utf8 (after decoding in log_.str()).
+ std::vector<uint16_t> json =
+ UTF8ToUTF16(SpanFrom("{\"space\": \"🌎 \\uD83C\\uDF19.\"}"));
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_TRUE(log_.status().ok());
+ EXPECT_EQ(
+ "map begin\n"
+ "string16: space\n"
+ "string16: 🌎 🌙.\n"
+ "map end\n",
+ log_.str());
+}
+
+TEST_F(JsonParserTest, Unicode_ParseUtf8) {
+ // Used below:
+ // гласность - example for 2 byte utf8, Russian word "glasnost"
+ // 屋 - example for 3 byte utf8, Chinese word for "house"
+ // 🌎 - example for 4 byte utf8: 0xF0 0x9F 0x8C 0x8E; utf16: 0xD83C 0xDF0E.
+ // 🌙 - example for escapes: utf8: 0xF0 0x9F 0x8C 0x99; utf16: 0xD83C 0xDF19.
+
+ // We provide the moon with json escape, but the earth as utf8 input.
+ // Either way they arrive as utf8 (after decoding in log_.str()).
+ std::string json =
+ "{"
+ "\"escapes\": \"\\uD83C\\uDF19\","
+ "\"2 byte\":\"гласность\","
+ "\"3 byte\":\"屋\","
+ "\"4 byte\":\"🌎\""
+ "}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_TRUE(log_.status().ok());
+ EXPECT_EQ(
+ "map begin\n"
+ "string16: escapes\n"
+ "string16: 🌙\n"
+ "string16: 2 byte\n"
+ "string16: гласность\n"
+ "string16: 3 byte\n"
+ "string16: 屋\n"
+ "string16: 4 byte\n"
+ "string16: 🌎\n"
+ "map end\n",
+ log_.str());
+}
+
+TEST_F(JsonParserTest, UnprocessedInputRemainsError) {
+ // Trailing junk after the valid JSON.
+ std::string json = "{\"foo\": 3.1415} junk";
+ size_t junk_idx = json.find("junk");
+ EXPECT_NE(junk_idx, std::string::npos);
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS, log_.status().error);
+ EXPECT_EQ(junk_idx, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+std::string MakeNestedJson(int depth) {
+ std::string json;
+ for (int ii = 0; ii < depth; ++ii)
+ json += "{\"foo\":";
+ json += "42";
+ for (int ii = 0; ii < depth; ++ii)
+ json += "}";
+ return json;
+}
+
+TEST_F(JsonParserTest, StackLimitExceededError_BelowLimit) {
+ // kStackLimit is 300 (see json_parser.cc). First let's
+ // try with a small nested example.
+ std::string json_3 = MakeNestedJson(3);
+ ParseJSON(GetTestPlatform(), SpanFrom(json_3), &log_);
+ EXPECT_TRUE(log_.status().ok());
+ EXPECT_EQ(
+ "map begin\n"
+ "string16: foo\n"
+ "map begin\n"
+ "string16: foo\n"
+ "map begin\n"
+ "string16: foo\n"
+ "int: 42\n"
+ "map end\n"
+ "map end\n"
+ "map end\n",
+ log_.str());
+}
+
+TEST_F(JsonParserTest, StackLimitExceededError_AtLimit) {
+ // Now with kStackLimit (300).
+ std::string json_limit = MakeNestedJson(300);
+ ParseJSON(GetTestPlatform(),
+ span<uint8_t>(reinterpret_cast<const uint8_t*>(json_limit.data()),
+ json_limit.size()),
+ &log_);
+ EXPECT_TRUE(log_.status().ok());
+}
+
+TEST_F(JsonParserTest, StackLimitExceededError_AboveLimit) {
+ // Now with kStackLimit + 1 (301) - it exceeds in the innermost instance.
+ std::string exceeded = MakeNestedJson(301);
+ ParseJSON(GetTestPlatform(), SpanFrom(exceeded), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, log_.status().error);
+ EXPECT_EQ(strlen("{\"foo\":") * 301, log_.status().pos);
+}
+
+TEST_F(JsonParserTest, StackLimitExceededError_WayAboveLimit) {
+ // Now way past the limit. Still, the point of exceeding is 301.
+ std::string far_out = MakeNestedJson(320);
+ ParseJSON(GetTestPlatform(), SpanFrom(far_out), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, log_.status().error);
+ EXPECT_EQ(strlen("{\"foo\":") * 301, log_.status().pos);
+}
+
+TEST_F(JsonParserTest, NoInputError) {
+ std::string json = "";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_NO_INPUT, log_.status().error);
+ EXPECT_EQ(0u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, InvalidTokenError) {
+ std::string json = "|";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_INVALID_TOKEN, log_.status().error);
+ EXPECT_EQ(0u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, InvalidNumberError) {
+ // Mantissa exceeds max (the constant used here is int64_t max).
+ std::string json = "1E9223372036854775807";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_INVALID_NUMBER, log_.status().error);
+ EXPECT_EQ(0u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, InvalidStringError) {
+ // \x22 is an unsupported escape sequence
+ std::string json = "\"foo\\x22\"";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_INVALID_STRING, log_.status().error);
+ EXPECT_EQ(0u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, UnexpectedArrayEndError) {
+ std::string json = "[1,2,]";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_UNEXPECTED_ARRAY_END, log_.status().error);
+ EXPECT_EQ(5u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, CommaOrArrayEndExpectedError) {
+ std::string json = "[1,2 2";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED,
+ log_.status().error);
+ EXPECT_EQ(5u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, StringLiteralExpectedError) {
+ // There's an error because the key bar, a string, is not terminated.
+ std::string json = "{\"foo\": 3.1415, \"bar: 31415e-4}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_STRING_LITERAL_EXPECTED, log_.status().error);
+ EXPECT_EQ(16u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, ColonExpectedError) {
+ std::string json = "{\"foo\", 42}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_COLON_EXPECTED, log_.status().error);
+ EXPECT_EQ(6u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, UnexpectedMapEndError) {
+ std::string json = "{\"foo\": 42, }";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_UNEXPECTED_MAP_END, log_.status().error);
+ EXPECT_EQ(12u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, CommaOrMapEndExpectedError) {
+ // The second separator should be a comma.
+ std::string json = "{\"foo\": 3.1415: \"bar\": 0}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED, log_.status().error);
+ EXPECT_EQ(14u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, ValueExpectedError) {
+ std::string json = "}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_VALUE_EXPECTED, log_.status().error);
+ EXPECT_EQ(0u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+template <typename T>
+class ConvertJSONToCBORTest : public ::testing::Test {};
+
+using ContainerTestTypes = ::testing::Types<std::vector<uint8_t>, std::string>;
+TYPED_TEST_SUITE(ConvertJSONToCBORTest, ContainerTestTypes);
+
+TYPED_TEST(ConvertJSONToCBORTest, RoundTripValidJson) {
+ std::string json_in = "{\"msg\":\"Hello, world.\",\"lst\":[1,2,3]}";
+ TypeParam json(json_in.begin(), json_in.end());
+ TypeParam cbor;
+ {
+ Status status = ConvertJSONToCBOR(GetTestPlatform(), SpanFrom(json), &cbor);
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+ }
+ TypeParam roundtrip_json;
+ {
+ Status status =
+ ConvertCBORToJSON(GetTestPlatform(), SpanFrom(cbor), &roundtrip_json);
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+ }
+ EXPECT_EQ(json, roundtrip_json);
+}
+
+TYPED_TEST(ConvertJSONToCBORTest, RoundTripValidJson16) {
+ std::vector<uint16_t> json16 = {
+ '{', '"', 'm', 's', 'g', '"', ':', '"', 'H', 'e', 'l', 'l',
+ 'o', ',', ' ', 0xd83c, 0xdf0e, '.', '"', ',', '"', 'l', 's', 't',
+ '"', ':', '[', '1', ',', '2', ',', '3', ']', '}'};
+ TypeParam cbor;
+ {
+ Status status = ConvertJSONToCBOR(
+ GetTestPlatform(), span<uint16_t>(json16.data(), json16.size()), &cbor);
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+ }
+ TypeParam roundtrip_json;
+ {
+ Status status =
+ ConvertCBORToJSON(GetTestPlatform(), SpanFrom(cbor), &roundtrip_json);
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+ }
+ std::string json = "{\"msg\":\"Hello, \\ud83c\\udf0e.\",\"lst\":[1,2,3]}";
+ TypeParam expected_json(json.begin(), json.end());
+ EXPECT_EQ(expected_json, roundtrip_json);
+}
+} // namespace json
+} // namespace v8_inspector_protocol_encoding
diff --git a/deps/v8/third_party/inspector_protocol/encoding/encoding_test_helper.h b/deps/v8/third_party/inspector_protocol/encoding/encoding_test_helper.h
new file mode 100644
index 0000000000..84da2e72e8
--- /dev/null
+++ b/deps/v8/third_party/inspector_protocol/encoding/encoding_test_helper.h
@@ -0,0 +1,33 @@
+// Copyright 2019 The V8 Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is V8 specific, to make encoding_test.cc work.
+// It is not rolled from the upstream project.
+
+#ifndef V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_TEST_HELPER_H_
+#define V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_TEST_HELPER_H_
+
+#include <string>
+#include <vector>
+
+#include "src/base/logging.h"
+#include "src/inspector/v8-string-conversions.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace v8_inspector_protocol_encoding {
+
+std::string UTF16ToUTF8(span<uint16_t> in) {
+ return v8_inspector::UTF16ToUTF8(in.data(), in.size());
+}
+
+std::vector<uint16_t> UTF8ToUTF16(span<uint8_t> in) {
+ std::basic_string<uint16_t> utf16 = v8_inspector::UTF8ToUTF16(
+ reinterpret_cast<const char*>(in.data()), in.size());
+ return std::vector<uint16_t>(utf16.begin(), utf16.end());
+}
+
+} // namespace v8_inspector_protocol_encoding
+
+#endif // V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_TEST_HELPER_H_
diff --git a/deps/v8/third_party/inspector_protocol/lib/Values_cpp.template b/deps/v8/third_party/inspector_protocol/lib/Values_cpp.template
index 2d4463e2d6..7d3b907a26 100644
--- a/deps/v8/third_party/inspector_protocol/lib/Values_cpp.template
+++ b/deps/v8/third_party/inspector_protocol/lib/Values_cpp.template
@@ -137,7 +137,7 @@ std::unique_ptr<Value> parseValue(
}
case cbor::CBORTokenTag::STRING16: {
span<uint8_t> wire = tokenizer->GetString16WireRep();
- DCHECK_EQ(wire.size() & 1, 0);
+ DCHECK_EQ(wire.size() & 1, 0u);
std::unique_ptr<Value> value = StringValue::create(StringUtil::fromUTF16(
reinterpret_cast<const uint16_t*>(wire.data()), wire.size() / 2));
tokenizer->Next();
@@ -178,7 +178,12 @@ std::unique_ptr<DictionaryValue> parseMap(
key = StringUtil::fromUTF8(key_span.data(), key_span.size());
tokenizer->Next();
} else if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING16) {
- return nullptr; // STRING16 not supported yet.
+ span<uint8_t> key_span = tokenizer->GetString16WireRep();
+ if (key_span.size() & 1) return nullptr; // UTF16 is 2 byte multiple.
+ key = StringUtil::fromUTF16(
+ reinterpret_cast<const uint16_t*>(key_span.data()),
+ key_span.size() / 2);
+ tokenizer->Next();
} else {
// Error::CBOR_INVALID_MAP_KEY
return nullptr;
diff --git a/deps/v8/third_party/inspector_protocol/lib/base_string_adapter_cc.template b/deps/v8/third_party/inspector_protocol/lib/base_string_adapter_cc.template
index 94bcd8891e..639b39bb52 100644
--- a/deps/v8/third_party/inspector_protocol/lib/base_string_adapter_cc.template
+++ b/deps/v8/third_party/inspector_protocol/lib/base_string_adapter_cc.template
@@ -237,75 +237,6 @@ Binary Binary::fromSpan(const uint8_t* data, size_t size) {
new base::RefCountedBytes(data, size)));
}
-namespace {
-int32_t ReadEnvelopeSize(const uint8_t* in) {
- return (in[0] << 24) + (in[1] << 16) + (in[2] << 8) + in[3];
-}
-
-void WriteEnvelopeSize(uint32_t value, uint8_t* out) {
- *(out++) = (value >> 24) & 0xFF;
- *(out++) = (value >> 16) & 0xFF;
- *(out++) = (value >> 8) & 0xFF;
- *(out++) = (value) & 0xFF;
-}
-
-}
-
-bool AppendStringValueToMapBinary(base::StringPiece in,
- base::StringPiece key, base::StringPiece value, std::string* out) {
- if (in.size() < 1 + 1 + 4 + 1 + 1)
- return false;
- const uint8_t* envelope = reinterpret_cast<const uint8_t*>(in.data());
- if (cbor::InitialByteForEnvelope() != envelope[0])
- return false;
- if (cbor::InitialByteFor32BitLengthByteString() != envelope[1])
- return false;
- if (cbor::EncodeIndefiniteLengthMapStart() != envelope[6])
- return false;
-
- uint32_t envelope_size = ReadEnvelopeSize(envelope + 2);
- if (envelope_size + 2 + 4 != in.size())
- return false;
- if (cbor::EncodeStop() != static_cast<uint8_t>(*in.rbegin()))
- return false;
-
- std::vector<uint8_t> encoded_entry;
- encoded_entry.reserve(1 + 4 + key.size() + 1 + 4 + value.size());
- span<uint8_t> key_span(
- reinterpret_cast<const uint8_t*>(key.data()), key.size());
- cbor::EncodeString8(key_span, &encoded_entry);
- span<uint8_t> value_span(
- reinterpret_cast<const uint8_t*>(value.data()), value.size());
- cbor::EncodeString8(value_span, &encoded_entry);
-
- out->clear();
- out->reserve(in.size() + encoded_entry.size());
- out->append(in.begin(), in.end() - 1);
- out->append(reinterpret_cast<const char*>(encoded_entry.data()),
- encoded_entry.size());
- out->append(1, static_cast<char>(cbor::EncodeStop()));
- std::size_t new_size = envelope_size + out->size() - in.size();
- if (new_size > static_cast<std::size_t>(
- std::numeric_limits<uint32_t>::max())) {
- return false;
- }
- WriteEnvelopeSize(new_size, reinterpret_cast<uint8_t*>(&*out->begin() + 2));
- return true;
-}
-
-bool AppendStringValueToMapJSON(base::StringPiece in,
- base::StringPiece key, base::StringPiece value, std::string* out) {
- if (!in.length() || *in.rbegin() != '}')
- return false;
- std::string suffix =
- base::StringPrintf(", \"%s\": \"%s\"}", key.begin(), value.begin());
- out->clear();
- out->reserve(in.length() + suffix.length() - 1);
- out->append(in.data(), in.length() - 1);
- out->append(suffix);
- return true;
-}
-
{% for namespace in config.protocol.namespace %}
} // namespace {{namespace}}
{% endfor %}
diff --git a/deps/v8/third_party/inspector_protocol/lib/base_string_adapter_h.template b/deps/v8/third_party/inspector_protocol/lib/base_string_adapter_h.template
index 082c7c037e..8bf3c355c0 100644
--- a/deps/v8/third_party/inspector_protocol/lib/base_string_adapter_h.template
+++ b/deps/v8/third_party/inspector_protocol/lib/base_string_adapter_h.template
@@ -136,12 +136,6 @@ class {{config.lib.export_macro}} Binary {
std::unique_ptr<Value> toProtocolValue(const base::Value* value, int depth);
std::unique_ptr<base::Value> toBaseValue(Value* value, int depth);
-
-bool AppendStringValueToMapBinary(base::StringPiece in,
- base::StringPiece key, base::StringPiece value, std::string* out);
-bool AppendStringValueToMapJSON(base::StringPiece in,
- base::StringPiece key, base::StringPiece value, std::string* out);
-
{% for namespace in config.protocol.namespace %}
} // namespace {{namespace}}
{% endfor %}
diff --git a/deps/v8/third_party/inspector_protocol/lib/encoding_cpp.template b/deps/v8/third_party/inspector_protocol/lib/encoding_cpp.template
index 3009e3bccf..e55dffb5fd 100644
--- a/deps/v8/third_party/inspector_protocol/lib/encoding_cpp.template
+++ b/deps/v8/third_party/inspector_protocol/lib/encoding_cpp.template
@@ -6,6 +6,7 @@
// found in the LICENSE file.
+#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstring>
@@ -18,6 +19,86 @@ namespace {{namespace}} {
// ===== encoding/encoding.cc =====
+// =============================================================================
+// Status and Error codes
+// =============================================================================
+
+std::string Status::ToASCIIString() const {
+ switch (error) {
+ case Error::OK:
+ return "OK";
+ case Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS:
+ return ToASCIIString("JSON: unprocessed input remains");
+ case Error::JSON_PARSER_STACK_LIMIT_EXCEEDED:
+ return ToASCIIString("JSON: stack limit exceeded");
+ case Error::JSON_PARSER_NO_INPUT:
+ return ToASCIIString("JSON: no input");
+ case Error::JSON_PARSER_INVALID_TOKEN:
+ return ToASCIIString("JSON: invalid token");
+ case Error::JSON_PARSER_INVALID_NUMBER:
+ return ToASCIIString("JSON: invalid number");
+ case Error::JSON_PARSER_INVALID_STRING:
+ return ToASCIIString("JSON: invalid string");
+ case Error::JSON_PARSER_UNEXPECTED_ARRAY_END:
+ return ToASCIIString("JSON: unexpected array end");
+ case Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED:
+ return ToASCIIString("JSON: comma or array end expected");
+ case Error::JSON_PARSER_STRING_LITERAL_EXPECTED:
+ return ToASCIIString("JSON: string literal expected");
+ case Error::JSON_PARSER_COLON_EXPECTED:
+ return ToASCIIString("JSON: colon expected");
+ case Error::JSON_PARSER_UNEXPECTED_MAP_END:
+ return ToASCIIString("JSON: unexpected map end");
+ case Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED:
+ return ToASCIIString("JSON: comma or map end expected");
+ case Error::JSON_PARSER_VALUE_EXPECTED:
+ return ToASCIIString("JSON: value expected");
+
+ case Error::CBOR_INVALID_INT32:
+ return ToASCIIString("CBOR: invalid int32");
+ case Error::CBOR_INVALID_DOUBLE:
+ return ToASCIIString("CBOR: invalid double");
+ case Error::CBOR_INVALID_ENVELOPE:
+ return ToASCIIString("CBOR: invalid envelope");
+ case Error::CBOR_INVALID_STRING8:
+ return ToASCIIString("CBOR: invalid string8");
+ case Error::CBOR_INVALID_STRING16:
+ return ToASCIIString("CBOR: invalid string16");
+ case Error::CBOR_INVALID_BINARY:
+ return ToASCIIString("CBOR: invalid binary");
+ case Error::CBOR_UNSUPPORTED_VALUE:
+ return ToASCIIString("CBOR: unsupported value");
+ case Error::CBOR_NO_INPUT:
+ return ToASCIIString("CBOR: no input");
+ case Error::CBOR_INVALID_START_BYTE:
+ return ToASCIIString("CBOR: invalid start byte");
+ case Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE:
+ return ToASCIIString("CBOR: unexpected eof expected value");
+ case Error::CBOR_UNEXPECTED_EOF_IN_ARRAY:
+ return ToASCIIString("CBOR: unexpected eof in array");
+ case Error::CBOR_UNEXPECTED_EOF_IN_MAP:
+ return ToASCIIString("CBOR: unexpected eof in map");
+ case Error::CBOR_INVALID_MAP_KEY:
+ return ToASCIIString("CBOR: invalid map key");
+ case Error::CBOR_STACK_LIMIT_EXCEEDED:
+ return ToASCIIString("CBOR: stack limit exceeded");
+ case Error::CBOR_TRAILING_JUNK:
+ return ToASCIIString("CBOR: trailing junk");
+ case Error::CBOR_MAP_START_EXPECTED:
+ return ToASCIIString("CBOR: map start expected");
+ case Error::CBOR_MAP_STOP_EXPECTED:
+ return ToASCIIString("CBOR: map stop expected");
+ case Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED:
+ return ToASCIIString("CBOR: envelope size limit exceeded");
+ }
+ // Some compilers can't figure out that we can't get here.
+ return "INVALID ERROR CODE";
+}
+
+std::string Status::ToASCIIString(const char* msg) const {
+ return std::string(msg) + " at position " + std::to_string(pos);
+}
+
namespace cbor {
namespace {
// Indicates the number of bits the "initial byte" needs to be shifted to the
@@ -98,9 +179,9 @@ void WriteBytesMostSignificantByteFirst(T v, C* out) {
// See also: https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html
template <typename T>
T ReadBytesMostSignificantByteFirst(span<uint8_t> in) {
- assert(static_cast<std::size_t>(in.size()) >= sizeof(T));
+ assert(in.size() >= sizeof(T));
T result = 0;
- for (std::size_t shift_bytes = 0; shift_bytes < sizeof(T); ++shift_bytes)
+ for (size_t shift_bytes = 0; shift_bytes < sizeof(T); ++shift_bytes)
result |= T(in[sizeof(T) - 1 - shift_bytes]) << (shift_bytes * 8);
return result;
}
@@ -112,6 +193,7 @@ namespace internals {
// |value| is the payload (e.g. for MajorType::UNSIGNED) or is the size
// (e.g. for BYTE_STRING).
// If successful, returns the number of bytes read. Otherwise returns -1.
+// TODO(johannes): change return type to size_t and use 0 for error.
int8_t ReadTokenStart(span<uint8_t> bytes, MajorType* type, uint64_t* value) {
if (bytes.empty())
return -1;
@@ -134,21 +216,21 @@ int8_t ReadTokenStart(span<uint8_t> bytes, MajorType* type, uint64_t* value) {
}
if (additional_information == kAdditionalInformation2Bytes) {
// Values 256-65535: 1 initial byte + 2 bytes payload.
- if (static_cast<std::size_t>(bytes.size()) < 1 + sizeof(uint16_t))
+ if (bytes.size() < 1 + sizeof(uint16_t))
return -1;
*value = ReadBytesMostSignificantByteFirst<uint16_t>(bytes.subspan(1));
return 3;
}
if (additional_information == kAdditionalInformation4Bytes) {
// 32 bit uint: 1 initial byte + 4 bytes payload.
- if (static_cast<std::size_t>(bytes.size()) < 1 + sizeof(uint32_t))
+ if (bytes.size() < 1 + sizeof(uint32_t))
return -1;
*value = ReadBytesMostSignificantByteFirst<uint32_t>(bytes.subspan(1));
return 5;
}
if (additional_information == kAdditionalInformation8Bytes) {
// 64 bit uint: 1 initial byte + 8 bytes payload.
- if (static_cast<std::size_t>(bytes.size()) < 1 + sizeof(uint64_t))
+ if (bytes.size() < 1 + sizeof(uint64_t))
return -1;
*value = ReadBytesMostSignificantByteFirst<uint64_t>(bytes.subspan(1));
return 9;
@@ -296,7 +378,7 @@ void EncodeString8(span<uint8_t> in, std::string* out) {
template <typename C>
void EncodeFromLatin1Tmpl(span<uint8_t> latin1, C* out) {
- for (std::ptrdiff_t ii = 0; ii < latin1.size(); ++ii) {
+ for (size_t ii = 0; ii < latin1.size(); ++ii) {
if (latin1[ii] <= 127)
continue;
// If there's at least one non-ASCII char, convert to UTF8.
@@ -310,7 +392,7 @@ void EncodeFromLatin1Tmpl(span<uint8_t> latin1, C* out) {
utf8.push_back((latin1[ii] | 0x80) & 0xbf);
}
}
- EncodeString8(SpanFromVector(utf8), out);
+ EncodeString8(SpanFrom(utf8), out);
return;
}
EncodeString8(latin1, out);
@@ -359,12 +441,12 @@ void EncodeBinary(span<uint8_t> in, std::string* out) {
// A double is encoded with a specific initial byte
// (kInitialByteForDouble) plus the 64 bits of payload for its value.
-constexpr std::ptrdiff_t kEncodedDoubleSize = 1 + sizeof(uint64_t);
+constexpr size_t kEncodedDoubleSize = 1 + sizeof(uint64_t);
// An envelope is encoded with a specific initial byte
// (kInitialByteForEnvelope), plus the start byte for a BYTE_STRING with a 32
// bit wide length, plus a 32 bit length for that string.
-constexpr std::ptrdiff_t kEncodedEnvelopeHeaderSize = 1 + 1 + sizeof(uint32_t);
+constexpr size_t kEncodedEnvelopeHeaderSize = 1 + 1 + sizeof(uint32_t);
template <typename C>
void EncodeDoubleTmpl(double value, C* out) {
@@ -390,45 +472,45 @@ void EncodeDouble(double value, std::string* out) {
// =============================================================================
template <typename C>
-void EncodeStartTmpl(C* out, std::size_t& byte_size_pos) {
- assert(byte_size_pos == 0);
+void EncodeStartTmpl(C* out, size_t* byte_size_pos) {
+ assert(*byte_size_pos == 0);
out->push_back(kInitialByteForEnvelope);
out->push_back(kInitialByteFor32BitLengthByteString);
- byte_size_pos = out->size();
+ *byte_size_pos = out->size();
out->resize(out->size() + sizeof(uint32_t));
}
void EnvelopeEncoder::EncodeStart(std::vector<uint8_t>* out) {
- EncodeStartTmpl<std::vector<uint8_t>>(out, byte_size_pos_);
+ EncodeStartTmpl<std::vector<uint8_t>>(out, &byte_size_pos_);
}
void EnvelopeEncoder::EncodeStart(std::string* out) {
- EncodeStartTmpl<std::string>(out, byte_size_pos_);
+ EncodeStartTmpl<std::string>(out, &byte_size_pos_);
}
template <typename C>
-bool EncodeStopTmpl(C* out, std::size_t& byte_size_pos) {
- assert(byte_size_pos != 0);
+bool EncodeStopTmpl(C* out, size_t* byte_size_pos) {
+ assert(*byte_size_pos != 0);
// The byte size is the size of the payload, that is, all the
// bytes that were written past the byte size position itself.
- uint64_t byte_size = out->size() - (byte_size_pos + sizeof(uint32_t));
+ uint64_t byte_size = out->size() - (*byte_size_pos + sizeof(uint32_t));
// We store exactly 4 bytes, so at most INT32MAX, with most significant
// byte first.
if (byte_size > std::numeric_limits<uint32_t>::max())
return false;
for (int shift_bytes = sizeof(uint32_t) - 1; shift_bytes >= 0;
--shift_bytes) {
- (*out)[byte_size_pos++] = 0xff & (byte_size >> (shift_bytes * 8));
+ (*out)[(*byte_size_pos)++] = 0xff & (byte_size >> (shift_bytes * 8));
}
return true;
}
bool EnvelopeEncoder::EncodeStop(std::vector<uint8_t>* out) {
- return EncodeStopTmpl(out, byte_size_pos_);
+ return EncodeStopTmpl(out, &byte_size_pos_);
}
bool EnvelopeEncoder::EncodeStop(std::string* out) {
- return EncodeStopTmpl(out, byte_size_pos_);
+ return EncodeStopTmpl(out, &byte_size_pos_);
}
// =============================================================================
@@ -444,57 +526,94 @@ class CBOREncoder : public StreamingParserHandler {
}
void HandleMapBegin() override {
+ if (!status_->ok())
+ return;
envelopes_.emplace_back();
envelopes_.back().EncodeStart(out_);
out_->push_back(kInitialByteIndefiniteLengthMap);
}
void HandleMapEnd() override {
+ if (!status_->ok())
+ return;
out_->push_back(kStopByte);
assert(!envelopes_.empty());
- envelopes_.back().EncodeStop(out_);
+ if (!envelopes_.back().EncodeStop(out_)) {
+ HandleError(
+ Status(Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED, out_->size()));
+ return;
+ }
envelopes_.pop_back();
}
void HandleArrayBegin() override {
+ if (!status_->ok())
+ return;
envelopes_.emplace_back();
envelopes_.back().EncodeStart(out_);
out_->push_back(kInitialByteIndefiniteLengthArray);
}
void HandleArrayEnd() override {
+ if (!status_->ok())
+ return;
out_->push_back(kStopByte);
assert(!envelopes_.empty());
- envelopes_.back().EncodeStop(out_);
+ if (!envelopes_.back().EncodeStop(out_)) {
+ HandleError(
+ Status(Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED, out_->size()));
+ return;
+ }
envelopes_.pop_back();
}
void HandleString8(span<uint8_t> chars) override {
+ if (!status_->ok())
+ return;
EncodeString8(chars, out_);
}
void HandleString16(span<uint16_t> chars) override {
+ if (!status_->ok())
+ return;
EncodeFromUTF16(chars, out_);
}
- void HandleBinary(span<uint8_t> bytes) override { EncodeBinary(bytes, out_); }
+ void HandleBinary(span<uint8_t> bytes) override {
+ if (!status_->ok())
+ return;
+ EncodeBinary(bytes, out_);
+ }
- void HandleDouble(double value) override { EncodeDouble(value, out_); }
+ void HandleDouble(double value) override {
+ if (!status_->ok())
+ return;
+ EncodeDouble(value, out_);
+ }
- void HandleInt32(int32_t value) override { EncodeInt32(value, out_); }
+ void HandleInt32(int32_t value) override {
+ if (!status_->ok())
+ return;
+ EncodeInt32(value, out_);
+ }
void HandleBool(bool value) override {
+ if (!status_->ok())
+ return;
// See RFC 7049 Section 2.3, Table 2.
out_->push_back(value ? kEncodedTrue : kEncodedFalse);
}
void HandleNull() override {
+ if (!status_->ok())
+ return;
// See RFC 7049 Section 2.3, Table 2.
out_->push_back(kEncodedNull);
}
void HandleError(Status error) override {
- assert(!error.ok());
+ if (!status_->ok())
+ return;
*status_ = error;
out_->clear();
}
@@ -547,10 +666,17 @@ Status CBORTokenizer::Status() const {
return status_;
}
+// The following accessor functions ::GetInt32, ::GetDouble,
+// ::GetString8, ::GetString16WireRep, ::GetBinary, ::GetEnvelopeContents
+// assume that a particular token was recognized in ::ReadNextToken.
+// That's where all the error checking is done. By design,
+// the accessors (assuming the token was recognized) never produce
+// an error.
+
int32_t CBORTokenizer::GetInt32() const {
assert(token_tag_ == CBORTokenTag::INT32);
// The range checks happen in ::ReadNextToken().
- return static_cast<uint32_t>(
+ return static_cast<int32_t>(
token_start_type_ == MajorType::UNSIGNED
? token_start_internal_value_
: -static_cast<int64_t>(token_start_internal_value_) - 1);
@@ -569,28 +695,55 @@ double CBORTokenizer::GetDouble() const {
span<uint8_t> CBORTokenizer::GetString8() const {
assert(token_tag_ == CBORTokenTag::STRING8);
- auto length = static_cast<std::ptrdiff_t>(token_start_internal_value_);
+ auto length = static_cast<size_t>(token_start_internal_value_);
return bytes_.subspan(status_.pos + (token_byte_length_ - length), length);
}
span<uint8_t> CBORTokenizer::GetString16WireRep() const {
assert(token_tag_ == CBORTokenTag::STRING16);
- auto length = static_cast<std::ptrdiff_t>(token_start_internal_value_);
+ auto length = static_cast<size_t>(token_start_internal_value_);
return bytes_.subspan(status_.pos + (token_byte_length_ - length), length);
}
span<uint8_t> CBORTokenizer::GetBinary() const {
assert(token_tag_ == CBORTokenTag::BINARY);
- auto length = static_cast<std::ptrdiff_t>(token_start_internal_value_);
+ auto length = static_cast<size_t>(token_start_internal_value_);
return bytes_.subspan(status_.pos + (token_byte_length_ - length), length);
}
span<uint8_t> CBORTokenizer::GetEnvelopeContents() const {
assert(token_tag_ == CBORTokenTag::ENVELOPE);
- auto length = static_cast<std::ptrdiff_t>(token_start_internal_value_);
+ auto length = static_cast<size_t>(token_start_internal_value_);
return bytes_.subspan(status_.pos + kEncodedEnvelopeHeaderSize, length);
}
+// All error checking happens in ::ReadNextToken, so that the accessors
+// can avoid having to carry an error return value.
+//
+// With respect to checking the encoded lengths of strings, arrays, etc:
+// On the wire, CBOR uses 1,2,4, and 8 byte unsigned integers, so
+// we initially read them as uint64_t, usually into token_start_internal_value_.
+//
+// However, since these containers have a representation on the machine,
+// we need to do corresponding size computations on the input byte array,
+// output span (e.g. the payload for a string), etc., and size_t is
+// machine specific (in practice either 32 bit or 64 bit).
+//
+// Further, we must avoid overflowing size_t. Therefore, we use this
+// kMaxValidLength constant to:
+// - Reject values that are larger than the architecture specific
+// max size_t (differs between 32 bit and 64 bit arch).
+// - Reserve at least one bit so that we can check against overflows
+// when adding lengths (array / string length / etc.); we do this by
+// ensuring that the inputs to an addition are <= kMaxValidLength,
+// and then checking whether the sum went past it.
+//
+// See also
+// https://chromium.googlesource.com/chromium/src/+/master/docs/security/integer-semantics.md
+static const uint64_t kMaxValidLength =
+ std::min<uint64_t>(std::numeric_limits<uint64_t>::max() >> 2,
+ std::numeric_limits<size_t>::max());
+
void CBORTokenizer::ReadNextToken(bool enter_envelope) {
if (enter_envelope) {
status_.pos += kEncodedEnvelopeHeaderSize;
@@ -603,6 +756,7 @@ void CBORTokenizer::ReadNextToken(bool enter_envelope) {
token_tag_ = CBORTokenTag::DONE;
return;
}
+ const size_t remaining_bytes = bytes_.size() - status_.pos;
switch (bytes_[status_.pos]) {
case kStopByte:
SetToken(CBORTokenTag::STOP, 1);
@@ -623,21 +777,26 @@ void CBORTokenizer::ReadNextToken(bool enter_envelope) {
SetToken(CBORTokenTag::NULL_VALUE, 1);
return;
case kExpectedConversionToBase64Tag: { // BINARY
- int8_t bytes_read = internals::ReadTokenStart(
+ const int8_t bytes_read = internals::ReadTokenStart(
bytes_.subspan(status_.pos + 1), &token_start_type_,
&token_start_internal_value_);
- int64_t token_byte_length = 1 + bytes_read + token_start_internal_value_;
- if (-1 == bytes_read || token_start_type_ != MajorType::BYTE_STRING ||
- status_.pos + token_byte_length > bytes_.size()) {
+ if (bytes_read < 0 || token_start_type_ != MajorType::BYTE_STRING ||
+ token_start_internal_value_ > kMaxValidLength) {
SetError(Error::CBOR_INVALID_BINARY);
return;
}
- SetToken(CBORTokenTag::BINARY,
- static_cast<std::ptrdiff_t>(token_byte_length));
+ const uint64_t token_byte_length = token_start_internal_value_ +
+ /* tag before token start: */ 1 +
+ /* token start: */ bytes_read;
+ if (token_byte_length > remaining_bytes) {
+ SetError(Error::CBOR_INVALID_BINARY);
+ return;
+ }
+ SetToken(CBORTokenTag::BINARY, static_cast<size_t>(token_byte_length));
return;
}
case kInitialByteForDouble: { // DOUBLE
- if (status_.pos + kEncodedDoubleSize > bytes_.size()) {
+ if (kEncodedDoubleSize > remaining_bytes) {
SetError(Error::CBOR_INVALID_DOUBLE);
return;
}
@@ -645,7 +804,7 @@ void CBORTokenizer::ReadNextToken(bool enter_envelope) {
return;
}
case kInitialByteForEnvelope: { // ENVELOPE
- if (status_.pos + kEncodedEnvelopeHeaderSize > bytes_.size()) {
+ if (kEncodedEnvelopeHeaderSize > remaining_bytes) {
SetError(Error::CBOR_INVALID_ENVELOPE);
return;
}
@@ -657,26 +816,30 @@ void CBORTokenizer::ReadNextToken(bool enter_envelope) {
// Read the length of the byte string.
token_start_internal_value_ = ReadBytesMostSignificantByteFirst<uint32_t>(
bytes_.subspan(status_.pos + 2));
- // Make sure the payload is contained within the message.
- if (token_start_internal_value_ + kEncodedEnvelopeHeaderSize +
- status_.pos >
- static_cast<std::size_t>(bytes_.size())) {
+ if (token_start_internal_value_ > kMaxValidLength) {
SetError(Error::CBOR_INVALID_ENVELOPE);
return;
}
- auto length = static_cast<std::ptrdiff_t>(token_start_internal_value_);
- SetToken(CBORTokenTag::ENVELOPE, kEncodedEnvelopeHeaderSize + length);
+ uint64_t token_byte_length =
+ token_start_internal_value_ + kEncodedEnvelopeHeaderSize;
+ if (token_byte_length > remaining_bytes) {
+ SetError(Error::CBOR_INVALID_ENVELOPE);
+ return;
+ }
+ SetToken(CBORTokenTag::ENVELOPE, static_cast<size_t>(token_byte_length));
return;
}
default: {
- span<uint8_t> remainder =
- bytes_.subspan(status_.pos, bytes_.size() - status_.pos);
- assert(!remainder.empty());
- int8_t token_start_length = internals::ReadTokenStart(
- remainder, &token_start_type_, &token_start_internal_value_);
- bool success = token_start_length != -1;
+ const int8_t token_start_length = internals::ReadTokenStart(
+ bytes_.subspan(status_.pos), &token_start_type_,
+ &token_start_internal_value_);
+ const bool success = token_start_length >= 0;
switch (token_start_type_) {
case MajorType::UNSIGNED: // INT32.
+ // INT32 is a signed int32 (int32 makes sense for the
+ // inspector_protocol, it's not a CBOR limitation), so we check
+ // against the signed max, so that the allowable values are
+ // 0, 1, 2, ... 2^31 - 1.
if (!success || std::numeric_limits<int32_t>::max() <
token_start_internal_value_) {
SetError(Error::CBOR_INVALID_INT32);
@@ -684,38 +847,54 @@ void CBORTokenizer::ReadNextToken(bool enter_envelope) {
}
SetToken(CBORTokenTag::INT32, token_start_length);
return;
- case MajorType::NEGATIVE: // INT32.
- if (!success ||
- std::numeric_limits<int32_t>::min() >
- -static_cast<int64_t>(token_start_internal_value_) - 1) {
+ case MajorType::NEGATIVE: { // INT32.
+ // INT32 is a signed int32 (int32 makes sense for the
+ // inspector_protocol, it's not a CBOR limitation); in CBOR, the
+ // negative values for INT32 are represented as NEGATIVE, that is, -1
+ // INT32 is represented as 1 << 5 | 0 (major type 1, additional info
+ // value 0). The minimal allowed INT32 value in our protocol is
+ // std::numeric_limits<int32_t>::min(). We check for it by directly
+ // checking the payload against the maximal allowed signed (!) int32
+ // value.
+ if (!success || token_start_internal_value_ >
+ std::numeric_limits<int32_t>::max()) {
SetError(Error::CBOR_INVALID_INT32);
return;
}
SetToken(CBORTokenTag::INT32, token_start_length);
return;
+ }
case MajorType::STRING: { // STRING8.
- if (!success || remainder.size() < static_cast<int64_t>(
- token_start_internal_value_)) {
+ if (!success || token_start_internal_value_ > kMaxValidLength) {
SetError(Error::CBOR_INVALID_STRING8);
return;
}
- auto length =
- static_cast<std::ptrdiff_t>(token_start_internal_value_);
- SetToken(CBORTokenTag::STRING8, token_start_length + length);
+ uint64_t token_byte_length =
+ token_start_internal_value_ + token_start_length;
+ if (token_byte_length > remaining_bytes) {
+ SetError(Error::CBOR_INVALID_STRING8);
+ return;
+ }
+ SetToken(CBORTokenTag::STRING8,
+ static_cast<size_t>(token_byte_length));
return;
}
case MajorType::BYTE_STRING: { // STRING16.
- if (!success ||
- remainder.size() <
- static_cast<int64_t>(token_start_internal_value_) ||
- // Must be divisible by 2 since UTF16 is 2 bytes per character.
+ // Length must be divisible by 2 since UTF16 is 2 bytes per
+ // character, hence the &1 check.
+ if (!success || token_start_internal_value_ > kMaxValidLength ||
token_start_internal_value_ & 1) {
SetError(Error::CBOR_INVALID_STRING16);
return;
}
- auto length =
- static_cast<std::ptrdiff_t>(token_start_internal_value_);
- SetToken(CBORTokenTag::STRING16, token_start_length + length);
+ uint64_t token_byte_length =
+ token_start_internal_value_ + token_start_length;
+ if (token_byte_length > remaining_bytes) {
+ SetError(Error::CBOR_INVALID_STRING16);
+ return;
+ }
+ SetToken(CBORTokenTag::STRING16,
+ static_cast<size_t>(token_byte_length));
return;
}
case MajorType::ARRAY:
@@ -729,8 +908,7 @@ void CBORTokenizer::ReadNextToken(bool enter_envelope) {
}
}
-void CBORTokenizer::SetToken(CBORTokenTag token_tag,
- std::ptrdiff_t token_byte_length) {
+void CBORTokenizer::SetToken(CBORTokenTag token_tag, size_t token_byte_length) {
token_tag_ = token_tag;
token_byte_length_ = token_byte_length;
}
@@ -764,7 +942,7 @@ bool ParseValue(int32_t stack_depth,
void ParseUTF16String(CBORTokenizer* tokenizer, StreamingParserHandler* out) {
std::vector<uint16_t> value;
span<uint8_t> rep = tokenizer->GetString16WireRep();
- for (std::ptrdiff_t ii = 0; ii < rep.size(); ii += 2)
+ for (size_t ii = 0; ii < rep.size(); ii += 2)
value.push_back((rep[ii + 1] << 8) | rep[ii]);
out->HandleString16(span<uint16_t>(value.data(), value.size()));
tokenizer->Next();
@@ -947,6 +1125,8 @@ template <typename C>
Status AppendString8EntryToCBORMapTmpl(span<uint8_t> string8_key,
span<uint8_t> string8_value,
C* cbor) {
+ // Careful below: Don't compare (*cbor)[idx] with a uint8_t, since
+ // it could be a char (signed!). Instead, use bytes.
span<uint8_t> bytes(reinterpret_cast<const uint8_t*>(cbor->data()),
cbor->size());
CBORTokenizer tokenizer(bytes);
@@ -954,23 +1134,23 @@ Status AppendString8EntryToCBORMapTmpl(span<uint8_t> string8_key,
return tokenizer.Status();
if (tokenizer.TokenTag() != CBORTokenTag::ENVELOPE)
return Status(Error::CBOR_INVALID_ENVELOPE, 0);
- std::ptrdiff_t envelope_size = tokenizer.GetEnvelopeContents().size();
- std::size_t old_size = cbor->size();
- if (old_size != std::size_t(envelope_size) + kEncodedEnvelopeHeaderSize)
+ size_t envelope_size = tokenizer.GetEnvelopeContents().size();
+ size_t old_size = cbor->size();
+ if (old_size != envelope_size + kEncodedEnvelopeHeaderSize)
return Status(Error::CBOR_INVALID_ENVELOPE, 0);
if (envelope_size == 0 ||
(tokenizer.GetEnvelopeContents()[0] != EncodeIndefiniteLengthMapStart()))
return Status(Error::CBOR_MAP_START_EXPECTED, kEncodedEnvelopeHeaderSize);
- if (cbor->back() != EncodeStop())
+ if (bytes[bytes.size() - 1] != EncodeStop())
return Status(Error::CBOR_MAP_STOP_EXPECTED, cbor->size() - 1);
cbor->pop_back();
EncodeString8(string8_key, cbor);
EncodeString8(string8_value, cbor);
cbor->push_back(EncodeStop());
- std::size_t new_envelope_size = envelope_size + (cbor->size() - old_size);
+ size_t new_envelope_size = envelope_size + (cbor->size() - old_size);
if (new_envelope_size > std::numeric_limits<uint32_t>::max())
return Status(Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED, 0);
- std::size_t size_pos = cbor->size() - new_envelope_size - sizeof(uint32_t);
+ size_t size_pos = cbor->size() - new_envelope_size - sizeof(uint32_t);
uint8_t* out = reinterpret_cast<uint8_t*>(&cbor->at(size_pos));
*(out++) = (new_envelope_size >> 24) & 0xff;
*(out++) = (new_envelope_size >> 16) & 0xff;
@@ -1020,20 +1200,21 @@ enum class Container {
class State {
public:
explicit State(Container container) : container_(container) {}
- void StartElement(std::vector<uint8_t>* out) {
- // FIXME!!!
- }
- void StartElement(std::string* out) {
+ void StartElement(std::vector<uint8_t>* out) { StartElementTmpl(out); }
+ void StartElement(std::string* out) { StartElementTmpl(out); }
+ Container container() const { return container_; }
+
+ private:
+ template <typename C>
+ void StartElementTmpl(C* out) {
assert(container_ != Container::NONE || size_ == 0);
if (size_ != 0) {
char delim = (!(size_ & 1) || container_ == Container::ARRAY) ? ',' : ':';
- out->append(1, delim);
+ out->push_back(delim);
}
++size_;
}
- Container container() const { return container_; }
- private:
Container container_ = Container::NONE;
int size_ = 0;
};
@@ -1047,7 +1228,7 @@ void Base64Encode(const span<uint8_t>& in, C* out) {
// The following three cases are based on the tables in the example
// section in https://en.wikipedia.org/wiki/Base64. We process three
// input bytes at a time, emitting 4 output bytes at a time.
- std::ptrdiff_t ii = 0;
+ size_t ii = 0;
// While possible, process three input bytes.
for (; ii + 3 <= in.size(); ii += 3) {
@@ -1152,7 +1333,7 @@ class JSONEncoder : public StreamingParserHandler {
return;
state_.top().StartElement(out_);
Emit('"');
- for (std::ptrdiff_t ii = 0; ii < chars.size(); ++ii) {
+ for (size_t ii = 0; ii < chars.size(); ++ii) {
uint8_t c = chars[ii];
if (c == '"') {
Emit("\\\"");
@@ -1364,23 +1545,23 @@ class JsonParser {
JsonParser(const Platform* platform, StreamingParserHandler* handler)
: platform_(platform), handler_(handler) {}
- void Parse(const Char* start, std::size_t length) {
+ void Parse(const Char* start, size_t length) {
start_pos_ = start;
const Char* end = start + length;
- const Char* tokenEnd;
+ const Char* tokenEnd = nullptr;
ParseValue(start, end, &tokenEnd, 0);
+ if (error_)
+ return;
if (tokenEnd != end) {
HandleError(Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS, tokenEnd);
}
}
private:
- bool CharsToDouble(const uint16_t* chars,
- std::size_t length,
- double* result) {
+ bool CharsToDouble(const uint16_t* chars, size_t length, double* result) {
std::string buffer;
buffer.reserve(length + 1);
- for (std::size_t ii = 0; ii < length; ++ii) {
+ for (size_t ii = 0; ii < length; ++ii) {
bool is_ascii = !(chars[ii] & ~0x7F);
if (!is_ascii)
return false;
@@ -1389,7 +1570,7 @@ class JsonParser {
return platform_->StrToD(buffer.c_str(), result);
}
- bool CharsToDouble(const uint8_t* chars, std::size_t length, double* result) {
+ bool CharsToDouble(const uint8_t* chars, size_t length, double* result) {
std::string buffer(reinterpret_cast<const char*>(chars), length);
return platform_->StrToD(buffer.c_str(), result);
}
@@ -1585,7 +1766,7 @@ class JsonParser {
if (IsSpaceOrNewLine(*start)) {
++start;
} else if (*start == '/') {
- const Char* comment_end;
+ const Char* comment_end = nullptr;
if (!SkipComment(start, end, &comment_end))
break;
start = comment_end;
@@ -1683,7 +1864,7 @@ class JsonParser {
// If the |Char| we're dealing with is really a byte, then
// we have utf8 here, and we need to check for multibyte characters
// and transcode them to utf16 (either one or two utf16 chars).
- if (sizeof(Char) == sizeof(uint8_t) && c >= 0x7f) {
+ if (sizeof(Char) == sizeof(uint8_t) && c > 0x7f) {
// Inspect the leading byte to figure out how long the utf8
// byte sequence is; while doing this initialize |codepoint|
// with the first few bits.
@@ -1722,7 +1903,7 @@ class JsonParser {
// Disallow overlong encodings for ascii characters, as these
// would include " and other characters significant to JSON
// string termination / control.
- if (codepoint < 0x7f)
+ if (codepoint <= 0x7f)
return false;
// Invalid in UTF8, and can't be represented in UTF16 anyway.
if (codepoint > 0x10ffff)
@@ -1797,8 +1978,8 @@ class JsonParser {
HandleError(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, start);
return;
}
- const Char* token_start;
- const Char* token_end;
+ const Char* token_start = nullptr;
+ const Char* token_end = nullptr;
Token token = ParseToken(start, end, &token_start, &token_end);
switch (token) {
case NoInput:
@@ -1932,7 +2113,8 @@ class JsonParser {
void HandleError(Error error, const Char* pos) {
assert(error != Error::OK);
if (!error_) {
- handler_->HandleError(Status{error, pos - start_pos_});
+ handler_->HandleError(
+ Status{error, static_cast<size_t>(pos - start_pos_)});
error_ = true;
}
}
@@ -1983,10 +2165,8 @@ Status ConvertCBORToJSON(const Platform& platform,
return ConvertCBORToJSONTmpl(platform, cbor, json);
}
-template <typename C>
-Status ConvertJSONToCBORTmpl(const Platform& platform,
- span<uint8_t> json,
- C* cbor) {
+template <typename T, typename C>
+Status ConvertJSONToCBORTmpl(const Platform& platform, span<T> json, C* cbor) {
Status status;
std::unique_ptr<StreamingParserHandler> encoder =
cbor::NewCBOREncoder(cbor, &status);
@@ -1999,12 +2179,23 @@ Status ConvertJSONToCBOR(const Platform& platform,
return ConvertJSONToCBORTmpl(platform, json, cbor);
}
Status ConvertJSONToCBOR(const Platform& platform,
+ span<uint16_t> json,
+ std::string* cbor) {
+ return ConvertJSONToCBORTmpl(platform, json, cbor);
+}
+Status ConvertJSONToCBOR(const Platform& platform,
span<uint8_t> json,
std::vector<uint8_t>* cbor) {
return ConvertJSONToCBORTmpl(platform, json, cbor);
}
+Status ConvertJSONToCBOR(const Platform& platform,
+ span<uint16_t> json,
+ std::vector<uint8_t>* cbor) {
+ return ConvertJSONToCBORTmpl(platform, json, cbor);
+}
} // namespace json
{% for namespace in config.protocol.namespace %}
} // namespace {{namespace}}
{% endfor %}
+
diff --git a/deps/v8/third_party/inspector_protocol/lib/encoding_h.template b/deps/v8/third_party/inspector_protocol/lib/encoding_h.template
index bc10ed83d6..f1a52a1958 100644
--- a/deps/v8/third_party/inspector_protocol/lib/encoding_h.template
+++ b/deps/v8/third_party/inspector_protocol/lib/encoding_h.template
@@ -10,6 +10,8 @@
#include <cstddef>
#include <cstdint>
+#include <cstring>
+#include <limits>
#include <memory>
#include <string>
#include <vector>
@@ -25,13 +27,11 @@ namespace {{namespace}} {
// span - sequence of bytes
// =============================================================================
-// This template is similar to std::span, which will be included in C++20. Like
-// std::span it uses ptrdiff_t, which is signed (and thus a bit annoying
-// sometimes when comparing with size_t), but other than this it's much simpler.
+// This template is similar to std::span, which will be included in C++20.
template <typename T>
class span {
public:
- using index_type = std::ptrdiff_t;
+ using index_type = size_t;
span() : data_(nullptr), size_(0) {}
span(const T* data, index_type size) : data_(data), size_(size) {}
@@ -62,15 +62,27 @@ class span {
};
template <typename T>
-span<T> SpanFromVector(const std::vector<T>& v) {
+span<T> SpanFrom(const std::vector<T>& v) {
return span<T>(v.data(), v.size());
}
-inline span<uint8_t> SpanFromStdString(const std::string& v) {
+template <size_t N>
+span<uint8_t> SpanFrom(const char (&str)[N]) {
+ return span<uint8_t>(reinterpret_cast<const uint8_t*>(str), N - 1);
+}
+
+inline span<uint8_t> SpanFrom(const char* str) {
+ return str ? span<uint8_t>(reinterpret_cast<const uint8_t*>(str), strlen(str))
+ : span<uint8_t>();
+}
+
+inline span<uint8_t> SpanFrom(const std::string& v) {
return span<uint8_t>(reinterpret_cast<const uint8_t*>(v.data()), v.size());
}
-// Error codes.
+// =============================================================================
+// Status and Error codes
+// =============================================================================
enum class Error {
OK = 0,
// JSON parsing errors - json_parser.{h,cc}.
@@ -102,24 +114,30 @@ enum class Error {
CBOR_UNEXPECTED_EOF_IN_MAP = 0x19,
CBOR_INVALID_MAP_KEY = 0x1a,
CBOR_STACK_LIMIT_EXCEEDED = 0x1b,
- CBOR_STRING8_MUST_BE_7BIT = 0x1c,
- CBOR_TRAILING_JUNK = 0x1d,
- CBOR_MAP_START_EXPECTED = 0x1e,
- CBOR_MAP_STOP_EXPECTED = 0x1f,
- CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED = 0x20,
+ CBOR_TRAILING_JUNK = 0x1c,
+ CBOR_MAP_START_EXPECTED = 0x1d,
+ CBOR_MAP_STOP_EXPECTED = 0x1e,
+ CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED = 0x1f,
};
// A status value with position that can be copied. The default status
// is OK. Usually, error status values should come with a valid position.
struct Status {
- static constexpr std::ptrdiff_t npos() { return -1; }
+ static constexpr size_t npos() { return std::numeric_limits<size_t>::max(); }
bool ok() const { return error == Error::OK; }
Error error = Error::OK;
- std::ptrdiff_t pos = npos();
- Status(Error error, std::ptrdiff_t pos) : error(error), pos(pos) {}
+ size_t pos = npos();
+ Status(Error error, size_t pos) : error(error), pos(pos) {}
Status() = default;
+
+ // Returns a 7 bit US-ASCII string, either "OK" or an error message
+ // that includes the position.
+ std::string ToASCIIString() const;
+
+ private:
+ std::string ToASCIIString(const char* msg) const;
};
// Handler interface for parser events emitted by a streaming parser.
@@ -257,7 +275,7 @@ class EnvelopeEncoder {
bool EncodeStop(std::string* out);
private:
- std::size_t byte_size_pos_ = 0;
+ size_t byte_size_pos_ = 0;
};
// =============================================================================
@@ -381,13 +399,13 @@ class CBORTokenizer {
private:
void ReadNextToken(bool enter_envelope);
- void SetToken(CBORTokenTag token, std::ptrdiff_t token_byte_length);
+ void SetToken(CBORTokenTag token, size_t token_byte_length);
void SetError(Error error);
span<uint8_t> bytes_;
CBORTokenTag token_tag_;
struct Status status_;
- std::ptrdiff_t token_byte_length_;
+ size_t token_byte_length_;
MajorType token_start_type_;
uint64_t token_start_internal_value_;
};
@@ -486,8 +504,14 @@ Status ConvertJSONToCBOR(const Platform& platform,
span<uint8_t> json,
std::vector<uint8_t>* cbor);
Status ConvertJSONToCBOR(const Platform& platform,
+ span<uint16_t> json,
+ std::vector<uint8_t>* cbor);
+Status ConvertJSONToCBOR(const Platform& platform,
span<uint8_t> json,
std::string* cbor);
+Status ConvertJSONToCBOR(const Platform& platform,
+ span<uint16_t> json,
+ std::string* cbor);
} // namespace json
{% for namespace in config.protocol.namespace %}
diff --git a/deps/v8/third_party/inspector_protocol/pdl.py b/deps/v8/third_party/inspector_protocol/pdl.py
index 43111e944b..03d11b39d6 100644
--- a/deps/v8/third_party/inspector_protocol/pdl.py
+++ b/deps/v8/third_party/inspector_protocol/pdl.py
@@ -74,20 +74,20 @@ def parse(data, file_name, map_binary_to_string=False):
if len(trimLine) == 0:
continue
- match = re.compile('^(experimental )?(deprecated )?domain (.*)').match(line)
+ match = re.compile(r'^(experimental )?(deprecated )?domain (.*)').match(line)
if match:
domain = createItem({'domain' : match.group(3)}, match.group(1), match.group(2))
protocol['domains'].append(domain)
continue
- match = re.compile('^ depends on ([^\s]+)').match(line)
+ match = re.compile(r'^ depends on ([^\s]+)').match(line)
if match:
if 'dependencies' not in domain:
domain['dependencies'] = []
domain['dependencies'].append(match.group(1))
continue
- match = re.compile('^ (experimental )?(deprecated )?type (.*) extends (array of )?([^\s]+)').match(line)
+ match = re.compile(r'^ (experimental )?(deprecated )?type (.*) extends (array of )?([^\s]+)').match(line)
if match:
if 'types' not in domain:
domain['types'] = []
@@ -96,7 +96,7 @@ def parse(data, file_name, map_binary_to_string=False):
domain['types'].append(item)
continue
- match = re.compile('^ (experimental )?(deprecated )?(command|event) (.*)').match(line)
+ match = re.compile(r'^ (experimental )?(deprecated )?(command|event) (.*)').match(line)
if match:
list = []
if match.group(3) == 'command':
@@ -114,7 +114,7 @@ def parse(data, file_name, map_binary_to_string=False):
list.append(item)
continue
- match = re.compile('^ (experimental )?(deprecated )?(optional )?(array of )?([^\s]+) ([^\s]+)').match(line)
+ match = re.compile(r'^ (experimental )?(deprecated )?(optional )?(array of )?([^\s]+) ([^\s]+)').match(line)
if match:
param = createItem({}, match.group(1), match.group(2), match.group(6))
if match.group(3):
@@ -125,36 +125,36 @@ def parse(data, file_name, map_binary_to_string=False):
subitems.append(param)
continue
- match = re.compile('^ (parameters|returns|properties)').match(line)
+ match = re.compile(r'^ (parameters|returns|properties)').match(line)
if match:
subitems = item[match.group(1)] = []
continue
- match = re.compile('^ enum').match(line)
+ match = re.compile(r'^ enum').match(line)
if match:
enumliterals = item['enum'] = []
continue
- match = re.compile('^version').match(line)
+ match = re.compile(r'^version').match(line)
if match:
continue
- match = re.compile('^ major (\d+)').match(line)
+ match = re.compile(r'^ major (\d+)').match(line)
if match:
protocol['version']['major'] = match.group(1)
continue
- match = re.compile('^ minor (\d+)').match(line)
+ match = re.compile(r'^ minor (\d+)').match(line)
if match:
protocol['version']['minor'] = match.group(1)
continue
- match = re.compile('^ redirect ([^\s]+)').match(line)
+ match = re.compile(r'^ redirect ([^\s]+)').match(line)
if match:
item['redirect'] = match.group(1)
continue
- match = re.compile('^ ( )?[^\s]+$').match(line)
+ match = re.compile(r'^ ( )?[^\s]+$').match(line)
if match:
# enum literal
enumliterals.append(trimLine)
diff --git a/deps/v8/third_party/inspector_protocol/roll.py b/deps/v8/third_party/inspector_protocol/roll.py
index ee9c1d099e..abe636e270 100755
--- a/deps/v8/third_party/inspector_protocol/roll.py
+++ b/deps/v8/third_party/inspector_protocol/roll.py
@@ -18,6 +18,9 @@ FILES_TO_SYNC = [
'code_generator.py',
'concatenate_protocols.py',
'convert_protocol_to_json.py',
+ 'encoding/encoding.h',
+ 'encoding/encoding.cc',
+ 'encoding/encoding_test.cc',
'inspector_protocol.gni',
'inspector_protocol.gypi',
'lib/*',
@@ -95,11 +98,6 @@ def main(argv):
parser.add_argument("--v8_src_downstream",
help="The V8 src tree.",
default="~/v8/v8")
- parser.add_argument('--reverse', dest='reverse', action='store_true',
- help=("Whether to roll the opposite direction, from "
- "V8 (downstream) to inspector_protocol "
- "(upstream)."))
- parser.set_defaults(reverse=False)
parser.add_argument('--force', dest='force', action='store_true',
help=("Whether to carry out the modifications "
"in the destination tree."))
@@ -116,14 +114,9 @@ def main(argv):
# Check that the destination Git repo isn't at the master branch - it's
# generally a bad idea to check into the master branch, so we catch this
# common pilot error here early.
- if args.reverse:
- CheckRepoIsNotAtMasterBranch(upstream)
- src_dir = os.path.join(downstream, 'third_party/inspector_protocol')
- dest_dir = upstream
- else:
- CheckRepoIsNotAtMasterBranch(downstream)
- src_dir = upstream
- dest_dir = os.path.join(downstream, 'third_party/inspector_protocol')
+ CheckRepoIsNotAtMasterBranch(downstream)
+ src_dir = upstream
+ dest_dir = os.path.join(downstream, 'third_party/inspector_protocol')
print('Rolling %s into %s ...' % (src_dir, dest_dir))
src_files = set(FindFilesToSyncIn(src_dir))
dest_files = set(FindFilesToSyncIn(dest_dir))
@@ -143,20 +136,26 @@ def main(argv):
sys.exit(1)
print('You said --force ... as you wish, modifying the destination.')
for f in to_add + to_copy:
- shutil.copyfile(os.path.join(src_dir, f), os.path.join(dest_dir, f))
+ contents = open(os.path.join(src_dir, f)).read()
+ contents = contents.replace(
+ 'INSPECTOR_PROTOCOL_ENCODING_ENCODING_H_',
+ 'V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_H_')
+ contents = contents.replace(
+ 'namespace inspector_protocol_encoding',
+ 'namespace v8_inspector_protocol_encoding')
+ open(os.path.join(dest_dir, f), 'w').write(contents)
shutil.copymode(os.path.join(src_dir, f), os.path.join(dest_dir, f))
for f in to_delete:
os.unlink(os.path.join(dest_dir, f))
- if not args.reverse:
- head_revision = GetHeadRevision(upstream)
- lines = open(os.path.join(dest_dir, 'README.v8')).readlines()
- f = open(os.path.join(dest_dir, 'README.v8'), 'w')
- for line in lines:
- if line.startswith('Revision: '):
- f.write('Revision: %s' % head_revision)
- else:
- f.write(line)
- f.close()
+ head_revision = GetHeadRevision(upstream)
+ lines = open(os.path.join(dest_dir, 'README.v8')).readlines()
+ f = open(os.path.join(dest_dir, 'README.v8'), 'w')
+ for line in lines:
+ if line.startswith('Revision: '):
+ f.write('Revision: %s' % head_revision)
+ else:
+ f.write(line)
+ f.close()
if __name__ == '__main__':
diff --git a/deps/v8/third_party/inspector_protocol/templates/TypeBuilder_cpp.template b/deps/v8/third_party/inspector_protocol/templates/TypeBuilder_cpp.template
index 4ef60a6ea2..982e2c61b8 100644
--- a/deps/v8/third_party/inspector_protocol/templates/TypeBuilder_cpp.template
+++ b/deps/v8/third_party/inspector_protocol/templates/TypeBuilder_cpp.template
@@ -203,12 +203,12 @@ void Frontend::flush()
m_frontendChannel->flushProtocolNotifications();
}
-void Frontend::sendRawNotification(String notification)
+void Frontend::sendRawJSONNotification(String notification)
{
m_frontendChannel->sendProtocolNotification(InternalRawNotification::fromJSON(std::move(notification)));
}
-void Frontend::sendRawNotification(std::vector<uint8_t> notification)
+void Frontend::sendRawCBORNotification(std::vector<uint8_t> notification)
{
m_frontendChannel->sendProtocolNotification(InternalRawNotification::fromBinary(std::move(notification)));
}
diff --git a/deps/v8/third_party/inspector_protocol/templates/TypeBuilder_h.template b/deps/v8/third_party/inspector_protocol/templates/TypeBuilder_h.template
index c670d65c46..9d86d7a4ac 100644
--- a/deps/v8/third_party/inspector_protocol/templates/TypeBuilder_h.template
+++ b/deps/v8/third_party/inspector_protocol/templates/TypeBuilder_h.template
@@ -269,8 +269,8 @@ public:
{% endfor %}
void flush();
- void sendRawNotification(String);
- void sendRawNotification(std::vector<uint8_t>);
+ void sendRawJSONNotification(String);
+ void sendRawCBORNotification(std::vector<uint8_t>);
private:
FrontendChannel* m_frontendChannel;
};
diff --git a/deps/v8/third_party/v8/builtins/array-sort.tq b/deps/v8/third_party/v8/builtins/array-sort.tq
index e5df6768de..c751e4831d 100644
--- a/deps/v8/third_party/v8/builtins/array-sort.tq
+++ b/deps/v8/third_party/v8/builtins/array-sort.tq
@@ -34,7 +34,7 @@ namespace array {
ResetToGenericAccessor() {
this.loadFn = Load<GenericElementsAccessor>;
this.storeFn = Store<GenericElementsAccessor>;
- this.bailoutStatus = kSuccess;
+ this.deleteFn = Delete<GenericElementsAccessor>;
}
// The receiver of the Array.p.sort call.
@@ -54,17 +54,14 @@ namespace array {
// uses ToString and a lexicographical compare.
sortComparePtr: CompareBuiltinFn;
- // The following three function pointer represent a Accessor/Path.
- // These are used to Load/Store elements and to check whether to bail to the
- // baseline GenericElementsAccessor.
+ // The following four function pointer represent a Accessor/Path.
+ // These are used to Load/Store/Delete elements and to check whether
+ // to bail to the baseline GenericElementsAccessor.
loadFn: LoadFn;
storeFn: StoreFn;
+ deleteFn: DeleteFn;
canUseSameAccessorFn: CanUseSameAccessorFn;
- // If this field has the value kFailure, we need to bail to the baseline
- // GenericElementsAccessor.
- bailoutStatus: Smi;
-
// This controls when we get *into* galloping mode. It's initialized to
// kMinGallop. mergeLow and mergeHigh tend to nudge it higher for random
// data, and lower for highly structured data.
@@ -90,49 +87,96 @@ namespace array {
// Pointer to the temporary array.
tempArray: FixedArray;
+
+ // The initialReceiverLength converted and clamped to Smi.
+ sortLength: Smi;
+
+ // The number of undefined that need to be inserted after sorting
+ // when the elements are copied back from the workArray to the receiver.
+ numberOfUndefined: Smi;
+ }
+
+ type FastSmiElements;
+ type FastObjectElements;
+
+ // With the pre-processing step in Torque, the exact number of elements
+ // to sort is unknown at the time the sort state is created.
+ // The 'length' property is an upper bound (as per spec),
+ // while the actual size of the backing store is a good guess.
+ // After the pre-processing step, the workarray won't change in length.
+ macro CalculateWorkArrayLength(
+ receiver: JSReceiver, initialReceiverLength: Number): intptr {
+ // TODO(szuend): Implement full range sorting, not only up to MaxSmi.
+ // https://crbug.com/v8/7970.
+ let clampedReceiverLength: uintptr =
+ Convert<uintptr>(initialReceiverLength);
+ if (clampedReceiverLength > kSmiMaxValue) {
+ clampedReceiverLength = kSmiMaxValue;
+ }
+
+ let workArrayLength: intptr = Convert<intptr>(clampedReceiverLength);
+ try {
+ const object = Cast<JSObject>(receiver) otherwise NoJsObject;
+ const elementsLength = Convert<intptr>(object.elements.length);
+
+ // In some cases, elements are only on prototypes, but not on the receiver
+ // itself. Do nothing then, as {workArrayLength} got initialized with the
+ // {length} property.
+ if (elementsLength != 0) {
+ workArrayLength = IntPtrMin(workArrayLength, elementsLength);
+ }
+ }
+ label NoJsObject {}
+
+ return workArrayLength;
}
transitioning macro NewSortState(implicit context: Context)(
receiver: JSReceiver, comparefn: Undefined | Callable,
- initialReceiverLength: Number, sortLength: Smi,
- forceGeneric: constexpr bool): SortState {
+ initialReceiverLength: Number): SortState {
const sortComparePtr =
comparefn != Undefined ? SortCompareUserFn : SortCompareDefault;
const map = receiver.map;
- let loadFn = Load<GenericElementsAccessor>;
- let storeFn = Store<GenericElementsAccessor>;
- let canUseSameAccessorFn = CanUseSameAccessor<GenericElementsAccessor>;
+ let loadFn: LoadFn;
+ let storeFn: StoreFn;
+ let deleteFn: DeleteFn;
+ let canUseSameAccessorFn: CanUseSameAccessorFn;
try {
- if constexpr (!forceGeneric) {
- GotoIfForceSlowPath() otherwise Slow;
- let a: FastJSArray = Cast<FastJSArray>(receiver) otherwise Slow;
-
- const elementsKind: ElementsKind = map.elements_kind;
- if (IsDoubleElementsKind(elementsKind)) {
- loadFn = Load<FastDoubleElements>;
- storeFn = Store<FastDoubleElements>;
- canUseSameAccessorFn = CanUseSameAccessor<FastDoubleElements>;
- } else if (elementsKind == PACKED_SMI_ELEMENTS) {
- loadFn = Load<FastPackedSmiElements>;
- storeFn = Store<FastPackedSmiElements>;
- canUseSameAccessorFn = CanUseSameAccessor<FastPackedSmiElements>;
- } else {
- loadFn = Load<FastSmiOrObjectElements>;
- storeFn = Store<FastSmiOrObjectElements>;
- canUseSameAccessorFn = CanUseSameAccessor<FastSmiOrObjectElements>;
- }
+ GotoIfForceSlowPath() otherwise Slow;
+ let a: FastJSArray = Cast<FastJSArray>(receiver) otherwise Slow;
+
+ // Copy copy-on-write (COW) arrays.
+ array::EnsureWriteableFastElements(a);
+
+ const elementsKind: ElementsKind = map.elements_kind;
+ if (IsDoubleElementsKind(elementsKind)) {
+ loadFn = Load<FastDoubleElements>;
+ storeFn = Store<FastDoubleElements>;
+ deleteFn = Delete<FastDoubleElements>;
+ canUseSameAccessorFn = CanUseSameAccessor<FastDoubleElements>;
+ } else if (IsFastSmiElementsKind(elementsKind)) {
+ loadFn = Load<FastSmiElements>;
+ storeFn = Store<FastSmiElements>;
+ deleteFn = Delete<FastSmiElements>;
+ canUseSameAccessorFn = CanUseSameAccessor<FastSmiElements>;
+ } else {
+ loadFn = Load<FastObjectElements>;
+ storeFn = Store<FastObjectElements>;
+ deleteFn = Delete<FastObjectElements>;
+ canUseSameAccessorFn = CanUseSameAccessor<FastObjectElements>;
}
}
label Slow {
- if (map.elements_kind == DICTIONARY_ELEMENTS && IsExtensibleMap(map) &&
- !IsCustomElementsReceiverInstanceType(map.instance_type)) {
- loadFn = Load<DictionaryElements>;
- storeFn = Store<DictionaryElements>;
- canUseSameAccessorFn = CanUseSameAccessor<DictionaryElements>;
- }
+ loadFn = Load<GenericElementsAccessor>;
+ storeFn = Store<GenericElementsAccessor>;
+ deleteFn = Delete<GenericElementsAccessor>;
+ canUseSameAccessorFn = CanUseSameAccessor<GenericElementsAccessor>;
}
+ const workArrayLength =
+ CalculateWorkArrayLength(receiver, initialReceiverLength);
+
return new SortState{
receiver,
initialReceiverMap: map,
@@ -141,17 +185,18 @@ namespace array {
sortComparePtr,
loadFn,
storeFn,
+ deleteFn,
canUseSameAccessorFn,
- bailoutStatus: kSuccess,
minGallop: kMinGallopWins,
pendingRunsSize: 0,
pendingRuns: AllocateZeroedFixedArray(Convert<intptr>(kMaxMergePending)),
- workArray: AllocateZeroedFixedArray(Convert<intptr>(sortLength)),
- tempArray: kEmptyFixedArray
+ workArray: AllocateZeroedFixedArray(workArrayLength),
+ tempArray: kEmptyFixedArray,
+ sortLength: 0,
+ numberOfUndefined: 0
};
}
- const kFailure: Smi = -1;
const kSuccess: Smi = 0;
// The maximum number of entries in a SortState's pending-runs stack.
@@ -171,6 +216,7 @@ namespace array {
type LoadFn = builtin(Context, SortState, Smi) => Object;
type StoreFn = builtin(Context, SortState, Smi, Object) => Smi;
+ type DeleteFn = builtin(Context, SortState, Smi) => Smi;
type CanUseSameAccessorFn = builtin(Context, JSReceiver, Object, Number) =>
Boolean;
type CompareBuiltinFn = builtin(Context, Object, Object, Object) => Number;
@@ -183,28 +229,23 @@ namespace array {
transitioning builtin Load<ElementsAccessor: type>(
context: Context, sortState: SortState, index: Smi): Object {
- return GetProperty(sortState.receiver, index);
+ const receiver = sortState.receiver;
+ if (!HasProperty_Inline(receiver, index)) return Hole;
+ return GetProperty(receiver, index);
}
- Load<FastPackedSmiElements>(
- context: Context, sortState: SortState, index: Smi): Object {
+ Load<FastSmiElements>(context: Context, sortState: SortState, index: Smi):
+ Object {
const object = UnsafeCast<JSObject>(sortState.receiver);
const elements = UnsafeCast<FixedArray>(object.elements);
return elements.objects[index];
}
- Load<FastSmiOrObjectElements>(
- context: Context, sortState: SortState, index: Smi): Object {
+ Load<FastObjectElements>(context: Context, sortState: SortState, index: Smi):
+ Object {
const object = UnsafeCast<JSObject>(sortState.receiver);
const elements = UnsafeCast<FixedArray>(object.elements);
- const result: Object = elements.objects[index];
- if (IsTheHole(result)) {
- // The pre-processing step removed all holes by compacting all elements
- // at the start of the array. Finding a hole means the cmp function or
- // ToString changes the array.
- return Failure(sortState);
- }
- return result;
+ return elements.objects[index];
}
Load<FastDoubleElements>(context: Context, sortState: SortState, index: Smi):
@@ -212,28 +253,11 @@ namespace array {
try {
const object = UnsafeCast<JSObject>(sortState.receiver);
const elements = UnsafeCast<FixedDoubleArray>(object.elements);
- const value = LoadDoubleWithHoleCheck(elements, index) otherwise Bailout;
+ const value = LoadDoubleWithHoleCheck(elements, index) otherwise IfHole;
return AllocateHeapNumberWithValue(value);
}
- label Bailout {
- // The pre-processing step removed all holes by compacting all elements
- // at the start of the array. Finding a hole means the cmp function or
- // ToString changes the array.
- return Failure(sortState);
- }
- }
-
- Load<DictionaryElements>(context: Context, sortState: SortState, index: Smi):
- Object {
- try {
- const object = UnsafeCast<JSObject>(sortState.receiver);
- const dictionary = UnsafeCast<NumberDictionary>(object.elements);
- const intptrIndex = Convert<intptr>(index);
- return BasicLoadNumberDictionaryElement(dictionary, intptrIndex)
- otherwise Bailout, Bailout;
- }
- label Bailout {
- return Failure(sortState);
+ label IfHole {
+ return Hole;
}
}
@@ -243,15 +267,16 @@ namespace array {
return kSuccess;
}
- Store<FastPackedSmiElements>(
+ Store<FastSmiElements>(
context: Context, sortState: SortState, index: Smi, value: Object): Smi {
const object = UnsafeCast<JSObject>(sortState.receiver);
const elements = UnsafeCast<FixedArray>(object.elements);
- StoreFixedArrayElementSmi(elements, index, value, SKIP_WRITE_BARRIER);
+ const value = UnsafeCast<Smi>(value);
+ StoreFixedArrayElement(elements, index, value, SKIP_WRITE_BARRIER);
return kSuccess;
}
- Store<FastSmiOrObjectElements>(
+ Store<FastObjectElements>(
context: Context, sortState: SortState, index: Smi, value: Object): Smi {
const object = UnsafeCast<JSObject>(sortState.receiver);
const elements = UnsafeCast<FixedArray>(object.elements);
@@ -269,26 +294,42 @@ namespace array {
return kSuccess;
}
- Store<DictionaryElements>(
- context: Context, sortState: SortState, index: Smi, value: Object): Smi {
+ transitioning builtin Delete<ElementsAccessor: type>(
+ context: Context, sortState: SortState, index: Smi): Smi {
+ const receiver = sortState.receiver;
+ if (!HasProperty_Inline(receiver, index)) return kSuccess;
+ DeleteProperty(receiver, index, kSloppy);
+ return kSuccess;
+ }
+
+ Delete<FastSmiElements>(context: Context, sortState: SortState, index: Smi):
+ Smi {
+ assert(IsHoleyFastElementsKind(sortState.receiver.map.elements_kind));
+
const object = UnsafeCast<JSObject>(sortState.receiver);
- const dictionary = UnsafeCast<NumberDictionary>(object.elements);
- const intptrIndex = Convert<intptr>(index);
- try {
- BasicStoreNumberDictionaryElement(dictionary, intptrIndex, value)
- otherwise Fail, Fail, ReadOnly;
- return kSuccess;
- }
- label ReadOnly {
- // We cannot write to read-only data properties. Throw the same TypeError
- // as SetProperty would.
- const receiver = sortState.receiver;
- ThrowTypeError(
- kStrictReadOnlyProperty, index, Typeof(receiver), receiver);
- }
- label Fail {
- return Failure(sortState);
- }
+ const elements = UnsafeCast<FixedArray>(object.elements);
+ elements.objects[index] = Hole;
+ return kSuccess;
+ }
+
+ Delete<FastObjectElements>(
+ context: Context, sortState: SortState, index: Smi): Smi {
+ assert(IsHoleyFastElementsKind(sortState.receiver.map.elements_kind));
+
+ const object = UnsafeCast<JSObject>(sortState.receiver);
+ const elements = UnsafeCast<FixedArray>(object.elements);
+ elements.objects[index] = Hole;
+ return kSuccess;
+ }
+
+ Delete<FastDoubleElements>(
+ context: Context, sortState: SortState, index: Smi): Smi {
+ assert(IsHoleyFastElementsKind(sortState.receiver.map.elements_kind));
+
+ const object = UnsafeCast<JSObject>(sortState.receiver);
+ const elements = UnsafeCast<FixedDoubleArray>(object.elements);
+ StoreFixedDoubleArrayHoleSmi(elements, index);
+ return kSuccess;
}
transitioning builtin SortCompareDefault(
@@ -354,12 +395,6 @@ namespace array {
return True;
}
- CanUseSameAccessor<DictionaryElements>(
- context: Context, receiver: JSReceiver, initialReceiverMap: Object,
- initialReceiverLength: Number): Boolean {
- return SelectBooleanConstant(receiver.map == initialReceiverMap);
- }
-
// Re-loading the stack-size is done in a few places. The small macro allows
// for easier invariant checks at all use sites.
macro GetPendingRunsSize(implicit context: Context)(sortState: SortState):
@@ -418,36 +453,6 @@ namespace array {
return tempArray;
}
- // This macro jumps to the Bailout label iff kBailoutStatus is kFailure.
- macro EnsureSuccess(implicit context: Context)(sortState:
- SortState) labels Bailout {
- if (sortState.bailoutStatus == kFailure) goto Bailout;
- }
-
- // Sets kBailoutStatus to kFailure and returns kFailure.
- macro Failure(sortState: SortState): Smi {
- sortState.bailoutStatus = kFailure;
- return kFailure;
- }
-
- // The following Call* macros wrap builtin calls, making call sites more
- // readable since we can use labels and do not have to check kBailoutStatus
- // or the return value.
-
- macro CallLoad(implicit context: Context, sortState: SortState)(
- load: LoadFn, index: Smi): Object
- labels Bailout {
- const result: Object = load(context, sortState, index);
- EnsureSuccess(sortState) otherwise Bailout;
- return result;
- }
-
- macro CallStore(implicit context: Context, sortState: SortState)(
- store: StoreFn, index: Smi, value: Object) labels Bailout {
- store(context, sortState, index, value);
- EnsureSuccess(sortState) otherwise Bailout;
- }
-
transitioning builtin
Copy(implicit context: Context)(
source: FixedArray, srcPos: Smi, target: FixedArray, dstPos: Smi,
@@ -1267,49 +1272,87 @@ namespace array {
}
transitioning macro
- CopyReceiverElementsToWorkArray(
- implicit context: Context, sortState: SortState)(length: Smi) {
- // TODO(szuend): Investigate if we can use COW arrays or a memcpy + range
- // barrier to speed this step up.
- let loadFn = sortState.loadFn;
- const workArray = sortState.workArray;
+ CompactReceiverElementsIntoWorkArray(
+ implicit context: Context, sortState: SortState)(): Smi {
+ let growableWorkArray = growable_fixed_array::GrowableFixedArray{
+ array: sortState.workArray,
+ capacity: Convert<intptr>(sortState.workArray.length),
+ length: 0
+ };
- for (let i: Smi = 0; i < length; ++i) {
- try {
- workArray.objects[i] = CallLoad(loadFn, i) otherwise Bailout;
- }
- label Bailout deferred {
- sortState.ResetToGenericAccessor();
- loadFn = sortState.loadFn;
- workArray.objects[i] = CallLoad(loadFn, i) otherwise unreachable;
+ const loadFn = sortState.loadFn;
+
+ // TODO(szuend): Implement full range sorting, not only up to MaxSmi.
+ // https://crbug.com/v8/7970.
+ const receiverLength: Number = sortState.initialReceiverLength;
+ assert(IsNumberNormalized(receiverLength));
+
+ const sortLength: Smi = TaggedIsSmi(receiverLength) ?
+ UnsafeCast<Smi>(receiverLength) :
+ Convert<PositiveSmi>(kSmiMax) otherwise unreachable;
+
+ // Move all non-undefined elements into {sortState.workArray}, holes
+ // are ignored.
+ let numberOfUndefined: Smi = 0;
+ for (let i: Smi = 0; i < receiverLength; ++i) {
+ const element: Object = loadFn(context, sortState, i);
+
+ if (element == Hole) {
+ // Do nothing for holes. The result is that elements are
+ // compacted at the front of the work array.
+ } else if (element == Undefined) {
+ numberOfUndefined++;
+ } else {
+ growableWorkArray.Push(element);
}
}
+
+ // Reset the workArray on the frameState, as it may have grown.
+ sortState.workArray = growableWorkArray.array;
+ sortState.sortLength = sortLength;
+ sortState.numberOfUndefined = numberOfUndefined;
+
+ return Convert<Smi>(growableWorkArray.length);
}
transitioning macro
CopyWorkArrayToReceiver(implicit context: Context, sortState: SortState)(
- length: Smi) {
- // TODO(szuend): Build fast-path that simply installs the work array as the
- // new backing store where applicable.
- let storeFn = sortState.storeFn;
+ numberOfNonUndefined: Smi) {
+ const storeFn = sortState.storeFn;
const workArray = sortState.workArray;
- for (let i: Smi = 0; i < length; ++i) {
- try {
- CallStore(storeFn, i, workArray.objects[i]) otherwise Bailout;
- }
- label Bailout deferred {
- sortState.ResetToGenericAccessor();
- storeFn = sortState.storeFn;
- CallStore(storeFn, i, workArray.objects[i]) otherwise unreachable;
- }
+ assert(numberOfNonUndefined <= workArray.length);
+ assert(
+ numberOfNonUndefined + sortState.numberOfUndefined <=
+ sortState.sortLength);
+
+ // Writing the elements back is a 3 step process:
+ // 1. Copy the sorted elements from the workarray to the receiver.
+ // 2. Add {nOfUndefined} undefineds to the receiver.
+ // 3. Depending on the backing store either delete properties or
+ // set them to the Hole up to {sortState.sortLength}.
+ let index: Smi = 0;
+ for (; index < numberOfNonUndefined; ++index) {
+ storeFn(context, sortState, index, workArray.objects[index]);
+ }
+
+ const numberOfUndefinedEnd: Smi =
+ sortState.numberOfUndefined + numberOfNonUndefined;
+ for (; index < numberOfUndefinedEnd; ++index) {
+ storeFn(context, sortState, index, Undefined);
+ }
+
+ const end: Smi = sortState.sortLength;
+ const deleteFn = sortState.deleteFn;
+ for (; index < end; ++index) {
+ deleteFn(context, sortState, index);
}
}
transitioning builtin
- ArrayTimSort(context: Context, sortState: SortState, length: Smi): Object {
- CopyReceiverElementsToWorkArray(length);
- ArrayTimSortImpl(context, sortState, length);
+ ArrayTimSort(context: Context, sortState: SortState): Object {
+ const numberOfNonUndefined: Smi = CompactReceiverElementsIntoWorkArray();
+ ArrayTimSortImpl(context, sortState, numberOfNonUndefined);
try {
// The comparison function or toString might have changed the
@@ -1320,24 +1363,10 @@ namespace array {
sortState.ResetToGenericAccessor();
}
- CopyWorkArrayToReceiver(length);
+ CopyWorkArrayToReceiver(numberOfNonUndefined);
return kSuccess;
}
- // For compatibility with JSC, we also sort elements inherited from
- // the prototype chain on non-Array objects.
- // We do this by copying them to this object and sorting only
- // own elements. This is not very efficient, but sorting with
- // inherited elements happens very, very rarely, if at all.
- // The specification allows "implementation dependent" behavior
- // if an element on the prototype chain has an element that
- // might interact with sorting.
- //
- // We also move all non-undefined elements to the front of the
- // array and move the undefineds after that. Holes are removed.
- // This happens for Array as well as non-Array objects.
- extern runtime PrepareElementsForSort(Context, Object, Number): Smi;
-
// https://tc39.github.io/ecma262/#sec-array.prototype.sort
transitioning javascript builtin
ArrayPrototypeSort(context: Context, receiver: Object, ...arguments): Object {
@@ -1355,16 +1384,8 @@ namespace array {
if (len < 2) return receiver;
- // TODO(szuend): Investigate performance tradeoff of skipping this step
- // for PACKED_* and handling Undefineds during sorting.
- const nofNonUndefined: Smi = PrepareElementsForSort(context, obj, len);
- assert(nofNonUndefined <= len);
-
- if (nofNonUndefined < 2) return receiver;
-
- const sortState: SortState =
- NewSortState(obj, comparefn, len, nofNonUndefined, false);
- ArrayTimSort(context, sortState, nofNonUndefined);
+ const sortState: SortState = NewSortState(obj, comparefn, len);
+ ArrayTimSort(context, sortState);
return receiver;
}