Commit edaec0cb authored by Johannes Henkel's avatar Johannes Henkel Committed by Commit Bot

[DevTools] Roll inspector_protocol (V8)

"Remove the JSON parser and revamp Value::parseBinary."
Upstream review: https://chromium-review.googlesource.com/c/deps/inspector_protocol/+/2026351

In addition to the upstream changes, this PR includes the necessary
tweaks to the V8 inspector (now taking the detour via
CBOR to parse Javascript).

New Revision: 0e0a1995497511008864546c094e885f3f1e13a3

Change-Id: I5ccfea5a3e1bab3e183b45c87726747d17d06944
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2020518Reviewed-by: 's avatarDmitry Gozman <dgozman@chromium.org>
Commit-Queue: Johannes Henkel <johannes@chromium.org>
Cr-Commit-Position: refs/heads/master@{#66116}
parent 5ae7258c
......@@ -521,8 +521,12 @@ Response InjectedScript::getInternalAndPrivateProperties(
}
void InjectedScript::releaseObject(const String16& objectId) {
std::vector<uint8_t> cbor;
v8_crdtp::json::ConvertJSONToCBOR(
v8_crdtp::span<uint16_t>(objectId.characters16(), objectId.length()),
&cbor);
std::unique_ptr<protocol::Value> parsedObjectId =
protocol::StringUtil::parseJSON(objectId);
protocol::Value::parseBinary(cbor.data(), cbor.size());
if (!parsedObjectId) return;
protocol::DictionaryValue* object =
protocol::DictionaryValue::cast(parsedObjectId.get());
......
......@@ -4,6 +4,7 @@
#include "src/inspector/remote-object-id.h"
#include "../../third_party/inspector_protocol/crdtp/json.h"
#include "src/inspector/protocol/Protocol.h"
#include "src/inspector/string-util.h"
......@@ -13,8 +14,12 @@ RemoteObjectIdBase::RemoteObjectIdBase() : m_injectedScriptId(0) {}
std::unique_ptr<protocol::DictionaryValue>
RemoteObjectIdBase::parseInjectedScriptId(const String16& objectId) {
std::vector<uint8_t> cbor;
v8_crdtp::json::ConvertJSONToCBOR(
v8_crdtp::span<uint16_t>(objectId.characters16(), objectId.length()),
&cbor);
std::unique_ptr<protocol::Value> parsedValue =
protocol::StringUtil::parseJSON(objectId);
protocol::Value::parseBinary(cbor.data(), cbor.size());
if (!parsedValue || parsedValue->type() != protocol::Value::TypeObject)
return nullptr;
......
......@@ -105,23 +105,6 @@ double StringUtil::toDouble(const char* s, size_t len, bool* isOk) {
*isOk = !std::isnan(result);
return result;
}
std::unique_ptr<protocol::Value> StringUtil::parseJSON(
const StringView& string) {
if (!string.length()) return nullptr;
if (string.is8Bit()) {
return parseJSONCharacters(string.characters8(),
static_cast<int>(string.length()));
}
return parseJSONCharacters(string.characters16(),
static_cast<int>(string.length()));
}
std::unique_ptr<protocol::Value> StringUtil::parseJSON(const String16& string) {
if (!string.length()) return nullptr;
return parseJSONCharacters(string.characters16(),
static_cast<int>(string.length()));
}
} // namespace protocol
namespace {
......
......@@ -65,8 +65,6 @@ class StringUtil {
StringBuilder& builder) { // NOLINT(runtime/references)
return builder.toString();
}
static std::unique_ptr<protocol::Value> parseJSON(const String16& json);
static std::unique_ptr<protocol::Value> parseJSON(const StringView& json);
static String fromUTF8(const uint8_t* data, size_t length) {
return String16::fromUTF8(reinterpret_cast<const char*>(data), length);
......
......@@ -6,6 +6,7 @@
#include <algorithm>
#include "../../third_party/inspector_protocol/crdtp/json.h"
#include "src/base/safe_conversions.h"
#include "src/debug/debug-interface.h"
#include "src/inspector/injected-script.h"
......@@ -1418,8 +1419,12 @@ void V8DebuggerAgentImpl::didParseSource(
if (inspected) {
// Script reused between different groups/sessions can have a stale
// execution context id.
const String16& aux = inspected->auxData();
std::vector<uint8_t> cbor;
v8_crdtp::json::ConvertJSONToCBOR(
v8_crdtp::span<uint16_t>(aux.characters16(), aux.length()), &cbor);
executionContextAuxData = protocol::DictionaryValue::cast(
protocol::StringUtil::parseJSON(inspected->auxData()));
protocol::Value::parseBinary(cbor.data(), cbor.size()));
}
bool isLiveEdit = script->isLiveEdit();
bool hasSourceURLComment = script->hasSourceURLComment();
......
......@@ -347,16 +347,9 @@ void V8InspectorSessionImpl::dispatchProtocolMessage(
m_state->setBoolean("use_binary_protocol", true);
cbor = span<uint8_t>(message.characters8(), message.length());
} else {
if (message.is8Bit()) {
// We're ignoring the return value of these conversion functions
// We're ignoring the return value of the conversion function
// intentionally. It means the |parsed_message| below will be nullptr.
ConvertJSONToCBOR(span<uint8_t>(message.characters8(), message.length()),
&converted_cbor);
} else {
ConvertJSONToCBOR(
span<uint16_t>(message.characters16(), message.length()),
&converted_cbor);
}
ConvertToCBOR(message, &converted_cbor);
cbor = SpanFrom(converted_cbor);
}
int callId;
......@@ -426,10 +419,12 @@ V8InspectorSession::Inspectable* V8InspectorSessionImpl::inspectedObject(
void V8InspectorSessionImpl::schedulePauseOnNextStatement(
const StringView& breakReason, const StringView& breakDetails) {
std::vector<uint8_t> cbor;
ConvertToCBOR(breakDetails, &cbor);
m_debuggerAgent->schedulePauseOnNextStatement(
toString16(breakReason),
protocol::DictionaryValue::cast(
protocol::StringUtil::parseJSON(breakDetails)));
protocol::Value::parseBinary(cbor.data(), cbor.size())));
}
void V8InspectorSessionImpl::cancelPauseOnNextStatement() {
......@@ -438,10 +433,12 @@ void V8InspectorSessionImpl::cancelPauseOnNextStatement() {
void V8InspectorSessionImpl::breakProgram(const StringView& breakReason,
const StringView& breakDetails) {
std::vector<uint8_t> cbor;
ConvertToCBOR(breakDetails, &cbor);
m_debuggerAgent->breakProgram(
toString16(breakReason),
protocol::DictionaryValue::cast(
protocol::StringUtil::parseJSON(breakDetails)));
protocol::Value::parseBinary(cbor.data(), cbor.size())));
}
void V8InspectorSessionImpl::setSkipAllPauses(bool skip) {
......
......@@ -32,6 +32,7 @@
#include <inttypes.h>
#include "../../third_party/inspector_protocol/crdtp/json.h"
#include "src/debug/debug-interface.h"
#include "src/inspector/injected-script.h"
#include "src/inspector/inspected-context.h"
......@@ -823,9 +824,14 @@ void V8RuntimeAgentImpl::reportExecutionContextCreated(
.setName(context->humanReadableName())
.setOrigin(context->origin())
.build();
if (!context->auxData().isEmpty())
const String16& aux = context->auxData();
if (!aux.isEmpty()) {
std::vector<uint8_t> cbor;
v8_crdtp::json::ConvertJSONToCBOR(
v8_crdtp::span<uint16_t>(aux.characters16(), aux.length()), &cbor);
description->setAuxData(protocol::DictionaryValue::cast(
protocol::StringUtil::parseJSON(context->auxData())));
protocol::Value::parseBinary(cbor.data(), cbor.size())));
}
m_frontend.executionContextCreated(std::move(description));
}
......
......@@ -10,6 +10,10 @@
#include "src/inspector/v8-debugger.h"
#include "src/inspector/v8-inspector-impl.h"
using v8_crdtp::SpanFrom;
using v8_crdtp::json::ConvertCBORToJSON;
using v8_crdtp::json::ConvertJSONToCBOR;
namespace v8_inspector {
int V8StackTraceImpl::maxCallStackSizeToCapture = 200;
......@@ -126,8 +130,17 @@ V8StackTraceId::V8StackTraceId(uintptr_t id,
V8StackTraceId::V8StackTraceId(const StringView& json)
: id(0), debugger_id(V8DebuggerId().pair()) {
auto dict =
protocol::DictionaryValue::cast(protocol::StringUtil::parseJSON(json));
if (json.length() == 0) return;
std::vector<uint8_t> cbor;
if (json.is8Bit()) {
ConvertJSONToCBOR(
v8_crdtp::span<uint8_t>(json.characters8(), json.length()), &cbor);
} else {
ConvertJSONToCBOR(
v8_crdtp::span<uint16_t>(json.characters16(), json.length()), &cbor);
}
auto dict = protocol::DictionaryValue::cast(
protocol::Value::parseBinary(cbor.data(), cbor.size()));
if (!dict) return;
String16 s;
if (!dict->getString(kId, &s)) return;
......@@ -152,7 +165,7 @@ std::unique_ptr<StringBuffer> V8StackTraceId::ToString() {
dict->setBoolean(kShouldPause, should_pause);
std::vector<uint8_t> json;
std::vector<uint8_t> cbor = std::move(*dict).TakeSerialized();
v8_crdtp::json::ConvertCBORToJSON(v8_crdtp::SpanFrom(cbor), &json);
ConvertCBORToJSON(SpanFrom(cbor), &json);
return StringBufferFrom(std::move(json));
}
......
......@@ -2,7 +2,7 @@ Name: inspector protocol
Short Name: inspector_protocol
URL: https://chromium.googlesource.com/deps/inspector_protocol/
Version: 0
Revision: 0213a8545f6362cd1cd5091cedf29747736552e8
Revision: 0e0a1995497511008864546c094e885f3f1e13a3
License: BSD
License File: LICENSE
Security Critical: no
......
......@@ -4,545 +4,4 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{% for namespace in config.protocol.namespace %}
namespace {{namespace}} {
{% endfor %}
namespace {
const int stackLimit = 1000;
enum Token {
ObjectBegin,
ObjectEnd,
ArrayBegin,
ArrayEnd,
StringLiteral,
Number,
BoolTrue,
BoolFalse,
NullToken,
ListSeparator,
ObjectPairSeparator,
InvalidToken,
};
const char* const nullString = "null";
const char* const trueString = "true";
const char* const falseString = "false";
bool isASCII(uint16_t c)
{
return !(c & ~0x7F);
}
bool isSpaceOrNewLine(uint16_t c)
{
return isASCII(c) && c <= ' ' && (c == ' ' || (c <= 0xD && c >= 0x9));
}
double charactersToDouble(const uint16_t* characters, size_t length, bool* ok)
{
std::vector<char> buffer;
buffer.reserve(length + 1);
for (size_t i = 0; i < length; ++i) {
if (!isASCII(characters[i])) {
*ok = false;
return 0;
}
buffer.push_back(static_cast<char>(characters[i]));
}
buffer.push_back('\0');
return StringUtil::toDouble(buffer.data(), length, ok);
}
double charactersToDouble(const uint8_t* characters, size_t length, bool* ok)
{
std::string buffer(reinterpret_cast<const char*>(characters), length);
return StringUtil::toDouble(buffer.data(), length, ok);
}
template<typename Char>
bool parseConstToken(const Char* start, const Char* end, const Char** tokenEnd, const char* token)
{
while (start < end && *token != '\0' && *start++ == *token++) { }
if (*token != '\0')
return false;
*tokenEnd = start;
return true;
}
template<typename Char>
bool readInt(const Char* start, const Char* end, const Char** tokenEnd, bool canHaveLeadingZeros)
{
if (start == end)
return false;
bool haveLeadingZero = '0' == *start;
int length = 0;
while (start < end && '0' <= *start && *start <= '9') {
++start;
++length;
}
if (!length)
return false;
if (!canHaveLeadingZeros && length > 1 && haveLeadingZero)
return false;
*tokenEnd = start;
return true;
}
template<typename Char>
bool parseNumberToken(const Char* start, const Char* end, const Char** tokenEnd)
{
// 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, false))
return false;
if (start == end) {
*tokenEnd = start;
return true;
}
// Optional fraction part
c = *start;
if ('.' == c) {
++start;
if (!readInt(start, end, &start, true))
return false;
if (start == end) {
*tokenEnd = 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, true))
return false;
}
*tokenEnd = start;
return true;
}
template<typename Char>
bool readHexDigits(const Char* start, const Char* end, const Char** tokenEnd, 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;
}
*tokenEnd = start;
return true;
}
template<typename Char>
bool parseStringToken(const Char* start, const Char* end, const Char** tokenEnd)
{
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) {
*tokenEnd = start;
return true;
}
}
return false;
}
template<typename Char>
bool skipComment(const Char* start, const Char* end, const Char** commentEnd)
{
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') {
*commentEnd = start + 1;
return true;
}
}
*commentEnd = 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 == '/') {
*commentEnd = start + 1;
return true;
}
}
// Block comment must close before end-of-input.
return false;
}
return false;
}
template<typename Char>
void skipWhitespaceAndComments(const Char* start, const Char* end, const Char** whitespaceEnd)
{
while (start < end) {
if (isSpaceOrNewLine(*start)) {
++start;
} else if (*start == '/') {
const Char* commentEnd;
if (!skipComment(start, end, &commentEnd))
break;
start = commentEnd;
} else {
break;
}
}
*whitespaceEnd = start;
}
template<typename Char>
Token parseToken(const Char* start, const Char* end, const Char** tokenStart, const Char** tokenEnd)
{
skipWhitespaceAndComments(start, end, tokenStart);
start = *tokenStart;
if (start == end)
return InvalidToken;
switch (*start) {
case 'n':
if (parseConstToken(start, end, tokenEnd, nullString))
return NullToken;
break;
case 't':
if (parseConstToken(start, end, tokenEnd, trueString))
return BoolTrue;
break;
case 'f':
if (parseConstToken(start, end, tokenEnd, falseString))
return BoolFalse;
break;
case '[':
*tokenEnd = start + 1;
return ArrayBegin;
case ']':
*tokenEnd = start + 1;
return ArrayEnd;
case ',':
*tokenEnd = start + 1;
return ListSeparator;
case '{':
*tokenEnd = start + 1;
return ObjectBegin;
case '}':
*tokenEnd = start + 1;
return ObjectEnd;
case ':':
*tokenEnd = 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, tokenEnd))
return Number;
break;
case '"':
if (parseStringToken(start + 1, end, tokenEnd))
return StringLiteral;
break;
}
return InvalidToken;
}
template<typename Char>
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;
DCHECK(false);
return 0;
}
template<typename Char>
bool decodeString(const Char* start, const Char* end, StringBuilder* output)
{
while (start < end) {
uint16_t c = *start++;
if ('\\' != c) {
StringUtil::builderAppend(*output, 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;
}
StringUtil::builderAppend(*output, c);
}
return true;
}
template<typename Char>
bool decodeString(const Char* start, const Char* end, String* output)
{
if (start == end) {
*output = "";
return true;
}
if (start > end)
return false;
StringBuilder buffer;
StringUtil::builderReserve(buffer, end - start);
if (!decodeString(start, end, &buffer))
return false;
*output = StringUtil::builderToString(buffer);
return true;
}
template<typename Char>
std::unique_ptr<Value> buildValue(const Char* start, const Char* end, const Char** valueTokenEnd, int depth)
{
if (depth > stackLimit)
return nullptr;
std::unique_ptr<Value> result;
const Char* tokenStart;
const Char* tokenEnd;
Token token = parseToken(start, end, &tokenStart, &tokenEnd);
switch (token) {
case InvalidToken:
return nullptr;
case NullToken:
result = Value::null();
break;
case BoolTrue:
result = FundamentalValue::create(true);
break;
case BoolFalse:
result = FundamentalValue::create(false);
break;
case Number: {
bool ok;
double value = charactersToDouble(tokenStart, tokenEnd - tokenStart, &ok);
if (!ok)
return nullptr;
if (value >= INT_MIN && value <= INT_MAX && static_cast<int>(value) == value)
result = FundamentalValue::create(static_cast<int>(value));
else
result = FundamentalValue::create(value);
break;
}
case StringLiteral: {
String value;
bool ok = decodeString(tokenStart + 1, tokenEnd - 1, &value);
if (!ok)
return nullptr;
result = StringValue::create(value);
break;
}
case ArrayBegin: {
std::unique_ptr<ListValue> array = ListValue::create();
start = tokenEnd;
token = parseToken(start, end, &tokenStart, &tokenEnd);
while (token != ArrayEnd) {
std::unique_ptr<Value> arrayNode = buildValue(start, end, &tokenEnd, depth + 1);
if (!arrayNode)
return nullptr;
array->pushValue(std::move(arrayNode));
// After a list value, we expect a comma or the end of the list.
start = tokenEnd;
token = parseToken(start, end, &tokenStart, &tokenEnd);
if (token == ListSeparator) {
start = tokenEnd;
token = parseToken(start, end, &tokenStart, &tokenEnd);
if (token == ArrayEnd)
return nullptr;
} else if (token != ArrayEnd) {
// Unexpected value after list value. Bail out.
return nullptr;
}
}
if (token != ArrayEnd)
return nullptr;
result = std::move(array);
break;
}
case ObjectBegin: {
std::unique_ptr<DictionaryValue> object = DictionaryValue::create();
start = tokenEnd;
token = parseToken(start, end, &tokenStart, &tokenEnd);
while (token != ObjectEnd) {
if (token != StringLiteral)
return nullptr;
String key;
if (!decodeString(tokenStart + 1, tokenEnd - 1, &key))
return nullptr;
start = tokenEnd;
token = parseToken(start, end, &tokenStart, &tokenEnd);
if (token != ObjectPairSeparator)
return nullptr;
start = tokenEnd;
std::unique_ptr<Value> value = buildValue(start, end, &tokenEnd, depth + 1);
if (!value)
return nullptr;
object->setValue(key, std::move(value));
start = tokenEnd;
// After a key/value pair, we expect a comma or the end of the
// object.
token = parseToken(start, end, &tokenStart, &tokenEnd);
if (token == ListSeparator) {
start = tokenEnd;
token = parseToken(start, end, &tokenStart, &tokenEnd);
if (token == ObjectEnd)
return nullptr;
} else if (token != ObjectEnd) {
// Unexpected value after last object value. Bail out.
return nullptr;
}
}
if (token != ObjectEnd)
return nullptr;
result = std::move(object);
break;
}
default:
// We got a token that's not a value.
return nullptr;
}
skipWhitespaceAndComments(tokenEnd, end, valueTokenEnd);
return result;
}
template<typename Char>
std::unique_ptr<Value> parseJSONInternal(const Char* start, unsigned length)
{
const Char* end = start + length;
const Char *tokenEnd;
std::unique_ptr<Value> value = buildValue(start, end, &tokenEnd, 0);
if (!value || tokenEnd != end)
return nullptr;
return value;
}
} // anonymous namespace
std::unique_ptr<Value> parseJSONCharacters(const uint16_t* characters, unsigned length)
{
return parseJSONInternal<uint16_t>(characters, length);
}
std::unique_ptr<Value> parseJSONCharacters(const uint8_t* characters, unsigned length)
{
return parseJSONInternal<uint8_t>(characters, length);
}
{% for namespace in config.protocol.namespace %}
} // namespace {{namespace}}
{% endfor %}
// TODO(johannes): Remove Parser_cpp.template.
......@@ -4,21 +4,4 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef {{"_".join(config.protocol.namespace)}}_Parser_h
#define {{"_".join(config.protocol.namespace)}}_Parser_h
//#include "Forward.h"
//#include "Values.h"
{% for namespace in config.protocol.namespace %}
namespace {{namespace}} {
{% endfor %}
{{config.lib.export_macro}} std::unique_ptr<Value> parseJSONCharacters(const uint8_t*, unsigned);
{{config.lib.export_macro}} std::unique_ptr<Value> parseJSONCharacters(const uint16_t*, unsigned);
{% for namespace in config.protocol.namespace %}
} // namespace {{namespace}}
{% endfor %}
#endif // !defined({{"_".join(config.protocol.namespace)}}_Parser_h)
// TODO(johannes): Remove Parser_h.template.
......@@ -13,16 +13,11 @@ namespace {{namespace}} {
{% endfor %}
namespace {
// When parsing CBOR, we limit recursion depth for objects and arrays
// to this constant.
static constexpr int kStackLimitValues = 1000;
using {{config.crdtp.namespace}}::Error;
using {{config.crdtp.namespace}}::Status;
using {{config.crdtp.namespace}}::ParserHandler;
using {{config.crdtp.namespace}}::span;
namespace cbor {
using {{config.crdtp.namespace}}::cbor::CBORTokenTag;
using {{config.crdtp.namespace}}::cbor::CBORTokenizer;
using {{config.crdtp.namespace}}::cbor::ParseCBOR;
using {{config.crdtp.namespace}}::cbor::EncodeBinary;
using {{config.crdtp.namespace}}::cbor::EncodeDouble;
using {{config.crdtp.namespace}}::cbor::EncodeFalse;
......@@ -39,165 +34,161 @@ using {{config.crdtp.namespace}}::cbor::EnvelopeEncoder;
using {{config.crdtp.namespace}}::cbor::InitialByteForEnvelope;
} // namespace cbor
// Below are three parsing routines for CBOR, which cover enough
// to roundtrip JSON messages.
std::unique_ptr<DictionaryValue> parseMap(int32_t stack_depth, cbor::CBORTokenizer* tokenizer);
std::unique_ptr<ListValue> parseArray(int32_t stack_depth, cbor::CBORTokenizer* tokenizer);
std::unique_ptr<Value> parseValue(int32_t stack_depth, cbor::CBORTokenizer* tokenizer);
// |bytes| must start with the indefinite length array byte, so basically,
// ParseArray may only be called after an indefinite length array has been
// detected.
std::unique_ptr<ListValue> parseArray(int32_t stack_depth, cbor::CBORTokenizer* tokenizer) {
DCHECK(tokenizer->TokenTag() == cbor::CBORTokenTag::ARRAY_START);
tokenizer->Next();
auto list = ListValue::create();
while (tokenizer->TokenTag() != cbor::CBORTokenTag::STOP) {
// Error::CBOR_UNEXPECTED_EOF_IN_ARRAY
if (tokenizer->TokenTag() == cbor::CBORTokenTag::DONE) return nullptr;
if (tokenizer->TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) return nullptr;
// Parse value.
auto value = parseValue(stack_depth, tokenizer);
if (!value) return nullptr;
list->pushValue(std::move(value));
// Uses the parsing events received from driver of |ParserHandler|
// (e.g. cbor::ParseCBOR) into a protocol::Value instance.
class ValueParserHandler : public ParserHandler {
public:
// Provides the parsed protocol::Value.
std::unique_ptr<Value> ReleaseRoot() { return std::move(root_); }
// The first parsing error encountered; |status().ok()| is the default.
Status status() const { return status_; }
private:
//
// Implementation of ParserHandler.
//
void HandleMapBegin() override {
if (!status_.ok()) return;
std::unique_ptr<DictionaryValue> dict = DictionaryValue::create();
DictionaryValue* dict_ptr = dict.get();
AddValueToParent(std::move(dict));
stack_.emplace_back(dict_ptr);
}
tokenizer->Next();
return list;
}
std::unique_ptr<Value> parseValue(
int32_t stack_depth, cbor::CBORTokenizer* tokenizer) {
// Error::CBOR_STACK_LIMIT_EXCEEDED
if (stack_depth > kStackLimitValues) return nullptr;
// Skip past the envelope to get to what's inside.
if (tokenizer->TokenTag() == cbor::CBORTokenTag::ENVELOPE)
tokenizer->EnterEnvelope();
switch (tokenizer->TokenTag()) {
case cbor::CBORTokenTag::ERROR_VALUE:
return nullptr;
case cbor::CBORTokenTag::DONE:
// Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE
return nullptr;
case cbor::CBORTokenTag::TRUE_VALUE: {
std::unique_ptr<Value> value = FundamentalValue::create(true);
tokenizer->Next();
return value;
void HandleMapEnd() override {
if (!status_.ok()) return;
DCHECK(!stack_.empty());
DCHECK(stack_.back().is_dict);
stack_.pop_back();
}
case cbor::CBORTokenTag::FALSE_VALUE: {
std::unique_ptr<Value> value = FundamentalValue::create(false);
tokenizer->Next();
return value;
void HandleArrayBegin() override {
if (!status_.ok()) return;
std::unique_ptr<ListValue> list = ListValue::create();
ListValue* list_ptr = list.get();
AddValueToParent(std::move(list));
stack_.emplace_back(list_ptr);
}
case cbor::CBORTokenTag::NULL_VALUE: {
std::unique_ptr<Value> value = FundamentalValue::null();
tokenizer->Next();
return value;
void HandleArrayEnd() override {
if (!status_.ok()) return;
DCHECK(!stack_.empty());
DCHECK(!stack_.back().is_dict);
stack_.pop_back();
}
case cbor::CBORTokenTag::INT32: {
std::unique_ptr<Value> value = FundamentalValue::create(tokenizer->GetInt32());
tokenizer->Next();
return value;
void HandleString8(span<uint8_t> chars) override {
AddStringToParent(StringUtil::fromUTF8(chars.data(), chars.size()));
}
case cbor::CBORTokenTag::DOUBLE: {
std::unique_ptr<Value> value = FundamentalValue::create(tokenizer->GetDouble());
tokenizer->Next();
return value;
void HandleString16(span<uint16_t> chars) override {
AddStringToParent(
StringUtil::fromUTF16LE(chars.data(), chars.size()));
}
case cbor::CBORTokenTag::STRING8: {
span<uint8_t> str = tokenizer->GetString8();
std::unique_ptr<Value> value =
StringValue::create(StringUtil::fromUTF8(str.data(), str.size()));
tokenizer->Next();
return value;
void HandleBinary(span<uint8_t> bytes) override {
AddValueToParent(
BinaryValue::create(Binary::fromSpan(bytes.data(), bytes.size())));
}
case cbor::CBORTokenTag::STRING16: {
span<uint8_t> wire = tokenizer->GetString16WireRep();
DCHECK_EQ(wire.size() & 1, 0u);
std::unique_ptr<Value> value = StringValue::create(StringUtil::fromUTF16LE(
reinterpret_cast<const uint16_t*>(wire.data()), wire.size() / 2));
tokenizer->Next();
return value;
void HandleDouble(double value) override {
AddValueToParent(FundamentalValue::create(value));
}
case cbor::CBORTokenTag::BINARY: {
span<uint8_t> payload = tokenizer->GetBinary();
tokenizer->Next();
return BinaryValue::create(Binary::fromSpan(payload.data(), payload.size()));
void HandleInt32(int32_t value) override {
AddValueToParent(FundamentalValue::create(value));
}
case cbor::CBORTokenTag::MAP_START:
return parseMap(stack_depth + 1, tokenizer);
case cbor::CBORTokenTag::ARRAY_START:
return parseArray(stack_depth + 1, tokenizer);
default:
// Error::CBOR_UNSUPPORTED_VALUE
return nullptr;
void HandleBool(bool value) override {
AddValueToParent(FundamentalValue::create(value));
}
}
// |bytes| must start with the indefinite length array byte, so basically,
// ParseArray may only be called after an indefinite length array has been
// detected.
std::unique_ptr<DictionaryValue> parseMap(
int32_t stack_depth, cbor::CBORTokenizer* tokenizer) {
auto dict = DictionaryValue::create();
tokenizer->Next();
while (tokenizer->TokenTag() != cbor::CBORTokenTag::STOP) {
if (tokenizer->TokenTag() == cbor::CBORTokenTag::DONE) {
// Error::CBOR_UNEXPECTED_EOF_IN_MAP
return nullptr;
void HandleNull() override {
AddValueToParent(Value::null());
}
if (tokenizer->TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) return nullptr;
// Parse key.
String key;
if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING8) {
span<uint8_t> key_span = tokenizer->GetString8();
key = StringUtil::fromUTF8(key_span.data(), key_span.size());
tokenizer->Next();
} else if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING16) {
span<uint8_t> key_span = tokenizer->GetString16WireRep();
if (key_span.size() & 1) return nullptr; // UTF16 is 2 byte multiple.
key = StringUtil::fromUTF16LE(
reinterpret_cast<const uint16_t*>(key_span.data()),
key_span.size() / 2);
tokenizer->Next();
void HandleError(Status error) override {
status_ = error;
}
//
// Adding strings and values to the parent value.
// Strings are handled separately because they can be keys for
// dictionary values.
//
void AddStringToParent(String str) {
if (!status_.ok()) return;
if (!root_) {
DCHECK(!key_is_pending_);
root_ = StringValue::create(str);
} else if (stack_.back().is_dict) {
// If we already have a pending key, then this is the value of the
// key/value pair. Otherwise, it's the new pending key.
if (key_is_pending_) {
stack_.back().dict->setString(pending_key_, str);
key_is_pending_ = false;
} else {
// Error::CBOR_INVALID_MAP_KEY
return nullptr;
pending_key_ = std::move(str);
key_is_pending_ = true;
}
} else { // Top of the stack is a list.
DCHECK(!key_is_pending_);
stack_.back().list->pushValue(StringValue::create(str));
}
}
void AddValueToParent(std::unique_ptr<Value> value) {
if (!status_.ok()) return;
if (!root_) {
DCHECK(!key_is_pending_);
root_ = std::move(value);
} else if (stack_.back().is_dict) {
DCHECK(key_is_pending_);
stack_.back().dict->setValue(pending_key_, std::move(value));
key_is_pending_ = false;
} else { // Top of the stack is a list.
DCHECK(!key_is_pending_);
stack_.back().list->pushValue(std::move(value));
}
// Parse value.
auto value = parseValue(stack_depth, tokenizer);
if (!value) return nullptr;
dict->setValue(key, std::move(value));
}
tokenizer->Next();
return dict;
}
// |status_.ok()| is the default; if we receive an error event
// we keep the first one and stop modifying any other state.
Status status_;
// The root of the parsed protocol::Value tree.
std::unique_ptr<Value> root_;
// If root_ is a list or a dictionary, this stack keeps track of
// the container we're currently parsing as well as its ancestors.
struct ContainerState {
ContainerState(DictionaryValue* dict) : is_dict(true), dict(dict) {}
ContainerState(ListValue* list) : is_dict(false), list(list) {}
bool is_dict;
union {
DictionaryValue* dict;
ListValue* list;
};
};
std::vector<ContainerState> stack_;
// For maps, keys and values are alternating events, so we keep the
// key around and process it when the value arrives.
bool key_is_pending_ = false;
String pending_key_;
};
} // anonymous namespace
// static
std::unique_ptr<Value> Value::parseBinary(const uint8_t* data, size_t size) {
span<uint8_t> bytes(data, size);
// Error::CBOR_NO_INPUT
if (bytes.empty()) return nullptr;
// Error::CBOR_INVALID_START_BYTE
if (bytes[0] != cbor::InitialByteForEnvelope()) return nullptr;
cbor::CBORTokenizer tokenizer(bytes);
if (tokenizer.TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) return nullptr;
// We checked for the envelope start byte above, so the tokenizer
// must agree here, since it's not an error.
DCHECK(tokenizer.TokenTag() == cbor::CBORTokenTag::ENVELOPE);
tokenizer.EnterEnvelope();
// Error::MAP_START_EXPECTED
if (tokenizer.TokenTag() != cbor::CBORTokenTag::MAP_START) return nullptr;
std::unique_ptr<Value> result = parseMap(/*stack_depth=*/1, &tokenizer);
if (!result) return nullptr;
if (tokenizer.TokenTag() == cbor::CBORTokenTag::DONE) return result;
if (tokenizer.TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) return nullptr;
// Error::CBOR_TRAILING_JUNK
ValueParserHandler handler;
cbor::ParseCBOR(span<uint8_t>(data, size), &handler);
// TODO(johannes): We have decent error info in handler.status(); provide
// a richer interface that makes this available to client code.
if (handler.status().ok())
return handler.ReleaseRoot();
return nullptr;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment