From 3573d5e0faf3098600993625b3f07b83f8753867 Mon Sep 17 00:00:00 2001 From: Andrey Kosyakov Date: Wed, 1 Jul 2020 08:01:18 -0700 Subject: [PATCH] Roll inspector_protocol library to inculude unified (de)serialization support Note that changes in test expectation come from a more verbose error diagnostics for expected errors around input parameter validation. Original change: https://chromium-review.googlesource.com/c/deps/inspector_protocol/+/2270757 Bug: chromium:1099809 Change-Id: I4fc2efc9c89d0af645dad937d719fa36e1d33489 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2277142 Reviewed-by: Yang Guo Commit-Queue: Andrey Kosyakov Cr-Commit-Position: refs/heads/master@{#68657} --- src/inspector/injected-script.cc | 3 +- src/inspector/string-util.cc | 61 ++- src/inspector/string-util.h | 36 +- src/inspector/v8-debugger-agent-impl.cc | 14 +- ...e-on-call-frame-return-values-expected.txt | 14 +- .../evaluate-on-call-frame-return-values.js | 8 +- ...et-breakpoint-before-enabling-expected.txt | 2 +- .../set-breakpoint-before-enabling.js | 6 +- .../debugger/set-variable-value-expected.txt | 6 +- test/inspector/debugger/set-variable-value.js | 10 +- test/inspector/protocol-test.js | 8 + .../runtime/release-object-expected.txt | 6 +- test/inspector/runtime/release-object.js | 4 +- third_party/inspector_protocol/BUILD.gn | 4 +- third_party/inspector_protocol/README.v8 | 2 +- .../inspector_protocol/code_generator.py | 18 +- .../inspector_protocol/crdtp/dispatch.cc | 28 + .../inspector_protocol/crdtp/dispatch.h | 5 +- third_party/inspector_protocol/crdtp/maybe.h | 104 ++++ .../inspector_protocol/crdtp/maybe_test.cc | 44 ++ .../inspector_protocol/crdtp/protocol_core.cc | 288 ++++++++++ .../inspector_protocol/crdtp/protocol_core.h | 406 ++++++++++++++ .../crdtp/protocol_core_test.cc | 497 ++++++++++++++++++ .../crdtp/serializer_traits.h | 10 +- .../crdtp/serializer_traits_test.cc | 21 +- third_party/inspector_protocol/crdtp/span.cc | 15 + third_party/inspector_protocol/crdtp/span.h | 13 +- third_party/inspector_protocol/crdtp/status.h | 30 ++ .../inspector_protocol/inspector_protocol.gni | 1 + .../inspector_protocol/lib/Forward_h.template | 35 +- .../inspector_protocol/lib/Object_h.template | 4 + .../lib/ValueConversions_cpp.template | 123 +++++ .../lib/ValueConversions_h.template | 53 ++ .../lib/Values_cpp.template | 11 +- .../inspector_protocol/lib/Values_h.template | 5 + .../lib/base_string_adapter_cc.template | 57 ++ .../lib/base_string_adapter_h.template | 28 + third_party/inspector_protocol/roll.py | 7 +- .../templates/Imported_h.template | 47 ++ .../templates/TypeBuilder_cpp.template | 187 +++---- .../templates/TypeBuilder_h.template | 16 +- 41 files changed, 1997 insertions(+), 240 deletions(-) create mode 100644 third_party/inspector_protocol/crdtp/maybe.h create mode 100644 third_party/inspector_protocol/crdtp/maybe_test.cc create mode 100644 third_party/inspector_protocol/crdtp/protocol_core.cc create mode 100644 third_party/inspector_protocol/crdtp/protocol_core.h create mode 100644 third_party/inspector_protocol/crdtp/protocol_core_test.cc create mode 100644 third_party/inspector_protocol/lib/ValueConversions_cpp.template diff --git a/src/inspector/injected-script.cc b/src/inspector/injected-script.cc index 734c70141d..acd9609a9c 100644 --- a/src/inspector/injected-script.cc +++ b/src/inspector/injected-script.cc @@ -247,8 +247,7 @@ class InjectedScript::ProtocolPromiseHandler { // we try to capture a fresh stack trace. if (maybeMessage.ToLocal(&message)) { v8::Local exception = result; - protocol::detail::PtrMaybe - exceptionDetails; + protocol::PtrMaybe exceptionDetails; response = scope.injectedScript()->createExceptionDetails( message, exception, m_objectGroup, &exceptionDetails); if (!response.IsSuccess()) { diff --git a/src/inspector/string-util.cc b/src/inspector/string-util.cc index b1c0d6411a..38ced64521 100644 --- a/src/inspector/string-util.cc +++ b/src/inspector/string-util.cc @@ -246,7 +246,66 @@ String16 stackTraceIdToString(uintptr_t id) { } // namespace v8_inspector namespace v8_crdtp { -void SerializerTraits::Serialize( + +using v8_inspector::String16; +using v8_inspector::protocol::Binary; +using v8_inspector::protocol::StringUtil; + +// static +bool ProtocolTypeTraits::Deserialize(DeserializerState* state, + String16* value) { + auto* tokenizer = state->tokenizer(); + if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING8) { + const auto str = tokenizer->GetString8(); + *value = StringUtil::fromUTF8(str.data(), str.size()); + return true; + } + if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING16) { + const auto str = tokenizer->GetString16WireRep(); + *value = StringUtil::fromUTF16LE( + reinterpret_cast(str.data()), str.size() / 2); + return true; + } + state->RegisterError(Error::BINDINGS_STRING_VALUE_EXPECTED); + return false; +} + +// static +void ProtocolTypeTraits::Serialize(const String16& value, + std::vector* bytes) { + cbor::EncodeFromUTF16( + span(reinterpret_cast(value.characters16()), + value.length()), + bytes); +} + +// static +bool ProtocolTypeTraits::Deserialize(DeserializerState* state, + Binary* value) { + auto* tokenizer = state->tokenizer(); + if (tokenizer->TokenTag() == cbor::CBORTokenTag::BINARY) { + const span bin = tokenizer->GetBinary(); + *value = Binary::fromSpan(bin.data(), bin.size()); + return true; + } + if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING8) { + const auto str_span = tokenizer->GetString8(); + auto str = StringUtil::fromUTF8(str_span.data(), str_span.size()); + bool success = false; + *value = Binary::fromBase64(str, &success); + return success; + } + state->RegisterError(Error::BINDINGS_BINARY_VALUE_EXPECTED); + return false; +} + +// static +void ProtocolTypeTraits::Serialize(const Binary& value, + std::vector* bytes) { + cbor::EncodeBinary(span(value.data(), value.size()), bytes); +} + +void SerializerTraits::Serialize( const v8_inspector::protocol::Binary& binary, std::vector* out) { cbor::EncodeBinary(span(binary.data(), binary.size()), out); } diff --git a/src/inspector/string-util.h b/src/inspector/string-util.h index 1b5f9e34ab..d7f12bb96a 100644 --- a/src/inspector/string-util.h +++ b/src/inspector/string-util.h @@ -6,14 +6,15 @@ #define V8_INSPECTOR_STRING_UTIL_H_ #include + #include +#include "../../third_party/inspector_protocol/crdtp/protocol_core.h" +#include "include/v8-inspector.h" #include "src/base/logging.h" #include "src/base/macros.h" #include "src/inspector/string-16.h" -#include "include/v8-inspector.h" - namespace v8_inspector { namespace protocol { @@ -86,11 +87,42 @@ String16 stackTraceIdToString(uintptr_t id); // See third_party/inspector_protocol/crdtp/serializer_traits.h. namespace v8_crdtp { + +template <> +struct ProtocolTypeTraits { + static bool Deserialize(DeserializerState* state, + v8_inspector::String16* value); + static void Serialize(const v8_inspector::String16& value, + std::vector* bytes); +}; + +template <> +struct ProtocolTypeTraits { + static bool Deserialize(DeserializerState* state, + v8_inspector::protocol::Binary* value); + static void Serialize(const v8_inspector::protocol::Binary& value, + std::vector* bytes); +}; + template <> struct SerializerTraits { static void Serialize(const v8_inspector::protocol::Binary& binary, std::vector* out); }; + +namespace detail { +template <> +struct MaybeTypedef { + typedef ValueMaybe type; +}; + +template <> +struct MaybeTypedef { + typedef ValueMaybe type; +}; + +} // namespace detail + } // namespace v8_crdtp #endif // V8_INSPECTOR_STRING_UTIL_H_ diff --git a/src/inspector/v8-debugger-agent-impl.cc b/src/inspector/v8-debugger-agent-impl.cc index 8c54ad491b..a1dbc4fe9a 100644 --- a/src/inspector/v8-debugger-agent-impl.cc +++ b/src/inspector/v8-debugger-agent-impl.cc @@ -1552,10 +1552,9 @@ void V8DebuggerAgentImpl::didParseSource( String16 scriptId = script->scriptId(); String16 scriptURL = script->sourceURL(); String16 scriptLanguage = getScriptLanguage(*script); - Maybe codeOffset = - script->getLanguage() == V8DebuggerScript::Language::JavaScript - ? Maybe() - : script->codeOffset(); + Maybe codeOffset; + if (script->getLanguage() == V8DebuggerScript::Language::WebAssembly) + codeOffset = script->codeOffset(); std::unique_ptr debugSymbols = getDebugSymbols(*script); @@ -1727,10 +1726,11 @@ void V8DebuggerAgentImpl::didPause( WrapMode::kNoPreview, &obj); std::unique_ptr breakAuxData; if (obj) { - breakAuxData = obj->toValue(); + std::vector serialized; + obj->AppendSerialized(&serialized); + breakAuxData = protocol::DictionaryValue::cast( + protocol::Value::parseBinary(serialized.data(), serialized.size())); breakAuxData->setBoolean("uncaught", isUncaught); - } else { - breakAuxData = nullptr; } hitReasons.push_back( std::make_pair(breakReason, std::move(breakAuxData))); diff --git a/test/inspector/debugger/evaluate-on-call-frame-return-values-expected.txt b/test/inspector/debugger/evaluate-on-call-frame-return-values-expected.txt index 359282b851..b92951747c 100644 --- a/test/inspector/debugger/evaluate-on-call-frame-return-values-expected.txt +++ b/test/inspector/debugger/evaluate-on-call-frame-return-values-expected.txt @@ -108,14 +108,14 @@ Running test: testConsoleLog functionName : eval lineNumber : 0 scriptId : - url : + url : } [1] : { columnNumber : 0 - functionName : + functionName : lineNumber : 0 scriptId : - url : + url : } ] } @@ -232,7 +232,7 @@ ReleaseObject with invalid params. { error : { code : -32602 - data : objectId: string value expected + data : Failed to deserialize params.objectId - BINDINGS: mandatory field missing at message : Invalid parameters } id : @@ -299,7 +299,7 @@ ReleaseObjectGroup with invalid params { error : { code : -32602 - data : objectGroup: string value expected + data : Failed to deserialize params.objectGroup - BINDINGS: mandatory field missing at message : Invalid parameters } id : @@ -327,7 +327,7 @@ Running test: testCallFrameIdTypeError { error : { code : -32602 - data : callFrameId: string value expected + data : Failed to deserialize params.callFrameId - BINDINGS: string value expected at message : Invalid parameters } id : @@ -347,7 +347,7 @@ Running test: testNullExpression { error : { code : -32602 - data : expression: string value expected + data : Failed to deserialize params.expression - BINDINGS: string value expected at message : Invalid parameters } id : diff --git a/test/inspector/debugger/evaluate-on-call-frame-return-values.js b/test/inspector/debugger/evaluate-on-call-frame-return-values.js index e0cc5344b7..e3cc285d2f 100644 --- a/test/inspector/debugger/evaluate-on-call-frame-return-values.js +++ b/test/inspector/debugger/evaluate-on-call-frame-return-values.js @@ -66,7 +66,7 @@ const {Protocol} = InspectorTest.start( async function testReleaseObjectInvalid() { const releaseObjectResult = await Protocol.Runtime.releaseObject({}); InspectorTest.log('ReleaseObject with invalid params.'); - InspectorTest.logMessage(releaseObjectResult); + InspectorTest.logMessage(InspectorTest.trimErrorMessage(releaseObjectResult)); }, async function testObjectGroups() { await Protocol.Runtime.evaluate({ expression: 'var a = {x:3};', callFrameId }); @@ -89,7 +89,7 @@ const {Protocol} = InspectorTest.start( async function testReleaseObjectGroupInvalid() { const releaseObjectGroupResult = await Protocol.Runtime.releaseObjectGroup({}); InspectorTest.log('ReleaseObjectGroup with invalid params'); - InspectorTest.logMessage(releaseObjectGroupResult); + InspectorTest.logMessage(InspectorTest.trimErrorMessage(releaseObjectGroupResult)); }, async function testEvaluateSyntaxError() { const result = await Protocol.Debugger.evaluateOnCallFrame({ expression: `[]]`, callFrameId }); @@ -101,7 +101,7 @@ const {Protocol} = InspectorTest.start( }, async function testCallFrameIdTypeError() { const result = await Protocol.Debugger.evaluateOnCallFrame({ expression: `console.log(42)`, callFrameId: {} }); - InspectorTest.logMessage(result); + InspectorTest.logMessage(InspectorTest.trimErrorMessage(result)); }, async function testCallFrameIdInvalidInput() { InspectorTest.log('Testing evaluateOnCallFrame with non-existent callFrameId'); @@ -115,7 +115,7 @@ const {Protocol} = InspectorTest.start( async function evalAndLog(expression, callFrameId, returnByValue) { const result = await Protocol.Debugger.evaluateOnCallFrame({ expression, callFrameId, returnByValue }); - InspectorTest.logMessage(result); + InspectorTest.logMessage(InspectorTest.trimErrorMessage(result)); } // Helper function that calls a function on all objects with ids in objectIds, then returns diff --git a/test/inspector/debugger/set-breakpoint-before-enabling-expected.txt b/test/inspector/debugger/set-breakpoint-before-enabling-expected.txt index 26017349ef..02bfe0d80c 100644 --- a/test/inspector/debugger/set-breakpoint-before-enabling-expected.txt +++ b/test/inspector/debugger/set-breakpoint-before-enabling-expected.txt @@ -3,5 +3,5 @@ setBreakpointByUrl error: undefined setBreakpoint error: { "code": -32602, "message": "Invalid parameters", - "data": "location: object expected" + "data": "Failed to deserialize params.location - BINDINGS: mandatory field missing at " } diff --git a/test/inspector/debugger/set-breakpoint-before-enabling.js b/test/inspector/debugger/set-breakpoint-before-enabling.js index 84541be37d..5af1085c87 100644 --- a/test/inspector/debugger/set-breakpoint-before-enabling.js +++ b/test/inspector/debugger/set-breakpoint-before-enabling.js @@ -8,12 +8,14 @@ Protocol.Debugger.setBreakpointByUrl({ url: "http://example.com", lineNumber: 10 function didSetBreakpointByUrlBeforeEnable(message) { - InspectorTest.log("setBreakpointByUrl error: " + JSON.stringify(message.error, null, 2)); + InspectorTest.log("setBreakpointByUrl error: " + JSON.stringify( + InspectorTest.trimErrorMessage(message).error, null, 2)); Protocol.Debugger.setBreakpoint().then(didSetBreakpointBeforeEnable); } function didSetBreakpointBeforeEnable(message) { - InspectorTest.log("setBreakpoint error: " + JSON.stringify(message.error, null, 2)); + InspectorTest.log("setBreakpoint error: " + JSON.stringify( + InspectorTest.trimErrorMessage(message).error, null, 2)); InspectorTest.completeTest(); } diff --git a/test/inspector/debugger/set-variable-value-expected.txt b/test/inspector/debugger/set-variable-value-expected.txt index 33bdfd1706..b50adf7a58 100644 --- a/test/inspector/debugger/set-variable-value-expected.txt +++ b/test/inspector/debugger/set-variable-value-expected.txt @@ -218,7 +218,7 @@ setVariableValue with invalid scopeNumber { error : { code : -32602 - data : scopeNumber: integer value expected + data : Failed to deserialize params.scopeNumber - BINDINGS: int32 value expected at message : Invalid parameters } id : @@ -253,8 +253,8 @@ setVariableValue with invalid objectId { error : { code : -32602 - data : newValue.objectId: string value expected + data : Failed to deserialize params.newValue.objectId - BINDINGS: string value expected at message : Invalid parameters } id : -} \ No newline at end of file +} diff --git a/test/inspector/debugger/set-variable-value.js b/test/inspector/debugger/set-variable-value.js index 48bde89fbf..a9c08a84c7 100644 --- a/test/inspector/debugger/set-variable-value.js +++ b/test/inspector/debugger/set-variable-value.js @@ -66,24 +66,24 @@ const { contextGroup, Protocol } = InspectorTest.start( async function testInvalidFrame() { let result = await Protocol.Debugger.setVariableValue({ scopeNumber: 0, variableName: 'num', newValue: { unserializableValue: 'NaN' }, callFrameId: 'fakeCallFrame' }); InspectorTest.log('setVariableValue with invalid callFrameId'); - InspectorTest.logMessage(result); + InspectorTest.logMessage(InspectorTest.trimErrorMessage(result)); result = await Protocol.Debugger.setVariableValue({ scopeNumber: 'invalidScopeType', variableName: 'num', newValue: { unserializableValue: 'NaN' }, callFrameId }); InspectorTest.log('setVariableValue with invalid scopeNumber') - InspectorTest.logMessage(result); + InspectorTest.logMessage(InspectorTest.trimErrorMessage(result)); result = await Protocol.Debugger.setVariableValue({ scopeNumber: 1000, variableName: 'num', newValue: { unserializableValue: 'NaN' }, callFrameId }); InspectorTest.log('setVariableValue with invalid scopeNumber'); InspectorTest.logMessage(result); result = await Protocol.Debugger.setVariableValue({ scopeNumber: 0, variableName: 'FakeObjectName', newValue: { unserializableValue: 'NaN' }, callFrameId }); InspectorTest.log('setVariableValue with invalid variableName'); - InspectorTest.logMessage(result); + InspectorTest.logMessage(InspectorTest.trimErrorMessage(result)); }, async function testNewValueErrors() { let result = await Protocol.Debugger.setVariableValue({ scopeNumber: 0, variableName: 'num', newValue: { unserializableValue: 'not unserializable value' }, callFrameId }); InspectorTest.log('setVariableValue with invalid unserializableValue'); - InspectorTest.logMessage(result); + InspectorTest.logMessage(InspectorTest.trimErrorMessage(result)); result = await Protocol.Debugger.setVariableValue({ scopeNumber: 0, variableName: 'num', newValue: { objectId: 2000 }, callFrameId }); InspectorTest.log('setVariableValue with invalid objectId'); - InspectorTest.logMessage(result); + InspectorTest.logMessage(InspectorTest.trimErrorMessage(result)); } ]); diff --git a/test/inspector/protocol-test.js b/test/inspector/protocol-test.js index 3b514ab538..5f115ae91f 100644 --- a/test/inspector/protocol-test.js +++ b/test/inspector/protocol-test.js @@ -129,6 +129,14 @@ InspectorTest.decodeBase64 = function(base64) { return bytes; } +InspectorTest.trimErrorMessage = function(message) { + if (!message.error || !message.error.data) + return message; + message.error.data = message.error.data.replace(/at position \d+/, + 'at '); + return message; +} + InspectorTest.ContextGroup = class { constructor() { this.id = utils.createContextGroup(); diff --git a/test/inspector/runtime/release-object-expected.txt b/test/inspector/runtime/release-object-expected.txt index 4c479c7558..2a9697f5d4 100644 --- a/test/inspector/runtime/release-object-expected.txt +++ b/test/inspector/runtime/release-object-expected.txt @@ -71,7 +71,7 @@ ReleaseObject with invalid params. { error : { code : -32602 - data : objectId: string value expected + data : Failed to deserialize params.objectId - BINDINGS: mandatory field missing at message : Invalid parameters } id : @@ -150,8 +150,8 @@ ReleaseObjectGroup with invalid params { error : { code : -32602 - data : objectGroup: string value expected + data : Failed to deserialize params.objectGroup - BINDINGS: mandatory field missing at message : Invalid parameters } id : -} \ No newline at end of file +} diff --git a/test/inspector/runtime/release-object.js b/test/inspector/runtime/release-object.js index ae388ff9c4..9cae573277 100644 --- a/test/inspector/runtime/release-object.js +++ b/test/inspector/runtime/release-object.js @@ -31,7 +31,7 @@ const {Protocol} = InspectorTest.start( async function testReleaseObjectInvalid() { const releaseObjectResult = await Protocol.Runtime.releaseObject({}); InspectorTest.log('ReleaseObject with invalid params.'); - InspectorTest.logMessage(releaseObjectResult); + InspectorTest.logMessage(InspectorTest.trimErrorMessage(releaseObjectResult)); }, async function testObjectGroups() { await logAndEvaluate('var a = {x:3};'); @@ -58,7 +58,7 @@ const {Protocol} = InspectorTest.start( async function testReleaseObjectGroupInvalid() { const releaseObjectGroupResult = await Protocol.Runtime.releaseObjectGroup({}); InspectorTest.log('ReleaseObjectGroup with invalid params'); - InspectorTest.logMessage(releaseObjectGroupResult); + InspectorTest.logMessage(InspectorTest.trimErrorMessage(releaseObjectGroupResult)); } ]); diff --git a/third_party/inspector_protocol/BUILD.gn b/third_party/inspector_protocol/BUILD.gn index 33ef78c24b..b6389eab9e 100644 --- a/third_party/inspector_protocol/BUILD.gn +++ b/third_party/inspector_protocol/BUILD.gn @@ -20,10 +20,12 @@ v8_source_set("crdtp") { "crdtp/error_support.h", "crdtp/export.h", "crdtp/find_by_first.h", - "crdtp/glue.h", "crdtp/json.cc", "crdtp/json.h", + "crdtp/maybe.h", "crdtp/parser_handler.h", + "crdtp/protocol_core.cc", + "crdtp/protocol_core.h", "crdtp/serializable.cc", "crdtp/serializable.h", "crdtp/serializer_traits.h", diff --git a/third_party/inspector_protocol/README.v8 b/third_party/inspector_protocol/README.v8 index d2d558ad3e..f29217b448 100644 --- a/third_party/inspector_protocol/README.v8 +++ b/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: b7cda08cd6e522df2159413ba5f29d2a953cc1c4 +Revision: 22455bf37cd7a1913918e626a1997cbe77f32d1a License: BSD License File: LICENSE Security Critical: no diff --git a/third_party/inspector_protocol/code_generator.py b/third_party/inspector_protocol/code_generator.py index 92207b9159..92ca530e05 100755 --- a/third_party/inspector_protocol/code_generator.py +++ b/third_party/inspector_protocol/code_generator.py @@ -371,7 +371,6 @@ class Protocol(object): for rule in config.imported.options] self.patch_full_qualified_refs() - self.create_notification_types() self.create_type_definitions() self.generate_used_types() @@ -434,8 +433,6 @@ class Protocol(object): for event in domain["events"]: if self.generate_event(domain_name, event["name"]): all_refs |= self.all_references(event) - all_refs.add('%s.%sNotification' % (domain_name, - to_title_case(event["name"]))) dependencies = self.generate_type_dependencies() queue = set(all_refs) @@ -457,20 +454,6 @@ class Protocol(object): dependencies[domain_name + "." + type["id"]] = related_types return dependencies - def create_notification_types(self): - for domain in self.json_api["domains"]: - if "events" in domain: - for event in domain["events"]: - event_type = dict() - event_type["description"] = "Wrapper for notification params" - event_type["type"] = "object" - event_type["id"] = to_title_case(event["name"]) + "Notification" - if "parameters" in event: - event_type["properties"] = copy.deepcopy(event["parameters"]) - if "types" not in domain: - domain["types"] = list() - domain["types"].append(event_type) - def create_type_definitions(self): imported_namespace = "" if self.config.imported: @@ -664,6 +647,7 @@ def main(): "Protocol_cpp.template", "Values_cpp.template", "Object_cpp.template", + "ValueConversions_cpp.template", ] forward_h_templates = [ diff --git a/third_party/inspector_protocol/crdtp/dispatch.cc b/third_party/inspector_protocol/crdtp/dispatch.cc index eb2b2d4fdc..3562009032 100644 --- a/third_party/inspector_protocol/crdtp/dispatch.cc +++ b/third_party/inspector_protocol/crdtp/dispatch.cc @@ -9,6 +9,7 @@ #include "error_support.h" #include "find_by_first.h" #include "frontend_channel.h" +#include "protocol_core.h" namespace v8_crdtp { // ============================================================================= @@ -321,6 +322,18 @@ std::unique_ptr CreateErrorResponse( return protocol_error; } +std::unique_ptr CreateErrorResponse( + int call_id, + DispatchResponse dispatch_response, + const DeserializerState& state) { + auto protocol_error = + std::make_unique(std::move(dispatch_response)); + protocol_error->SetCallId(call_id); + // TODO(caseq): should we plumb the call name here? + protocol_error->SetData(state.ErrorMessage(MakeSpan("params"))); + return protocol_error; +} + std::unique_ptr CreateErrorNotification( DispatchResponse dispatch_response) { return std::make_unique(std::move(dispatch_response)); @@ -475,6 +488,21 @@ bool DomainDispatcher::MaybeReportInvalidParams( return true; } +bool DomainDispatcher::MaybeReportInvalidParams( + const Dispatchable& dispatchable, + const DeserializerState& state) { + if (state.status().ok()) + return false; + if (frontend_channel_) { + frontend_channel_->SendProtocolResponse( + dispatchable.CallId(), + CreateErrorResponse( + dispatchable.CallId(), + DispatchResponse::InvalidParams("Invalid parameters"), state)); + } + return true; +} + void DomainDispatcher::clearFrontend() { frontend_channel_ = nullptr; for (auto& weak : weak_ptrs_) diff --git a/third_party/inspector_protocol/crdtp/dispatch.h b/third_party/inspector_protocol/crdtp/dispatch.h index 5253cd87ed..7b233b03cd 100644 --- a/third_party/inspector_protocol/crdtp/dispatch.h +++ b/third_party/inspector_protocol/crdtp/dispatch.h @@ -16,8 +16,9 @@ #include "status.h" namespace v8_crdtp { -class FrontendChannel; +class DeserializerState; class ErrorSupport; +class FrontendChannel; namespace cbor { class CBORTokenizer; } // namespace cbor @@ -234,6 +235,8 @@ class DomainDispatcher { // optimized for code size of the callee. bool MaybeReportInvalidParams(const Dispatchable& dispatchable, const ErrorSupport& errors); + bool MaybeReportInvalidParams(const Dispatchable& dispatchable, + const DeserializerState& state); FrontendChannel* channel() { return frontend_channel_; } diff --git a/third_party/inspector_protocol/crdtp/maybe.h b/third_party/inspector_protocol/crdtp/maybe.h new file mode 100644 index 0000000000..97844b65a1 --- /dev/null +++ b/third_party/inspector_protocol/crdtp/maybe.h @@ -0,0 +1,104 @@ +// 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_CRDTP_MAYBE_H_ +#define V8_CRDTP_MAYBE_H_ + +#include +#include + +namespace v8_crdtp { + +// ============================================================================= +// detail::PtrMaybe, detail::ValueMaybe, templates for optional +// pointers / values which are used in ../lib/Forward_h.template. +// ============================================================================= + +namespace detail { +template +class PtrMaybe { + public: + PtrMaybe() = default; + PtrMaybe(std::unique_ptr value) : value_(std::move(value)) {} + PtrMaybe(PtrMaybe&& other) noexcept : value_(std::move(other.value_)) {} + void operator=(std::unique_ptr value) { value_ = std::move(value); } + T* fromJust() const { + assert(value_); + return value_.get(); + } + T* fromMaybe(T* default_value) const { + return value_ ? value_.get() : default_value; + } + bool isJust() const { return value_ != nullptr; } + std::unique_ptr takeJust() { + assert(value_); + return std::move(value_); + } + + private: + std::unique_ptr value_; +}; + +template +class ValueMaybe { + public: + ValueMaybe() : is_just_(false), value_() {} + ValueMaybe(T value) : is_just_(true), value_(std::move(value)) {} + ValueMaybe(ValueMaybe&& other) noexcept + : is_just_(other.is_just_), value_(std::move(other.value_)) {} + void operator=(T value) { + value_ = value; + is_just_ = true; + } + const T& fromJust() const { + assert(is_just_); + return value_; + } + const T& fromMaybe(const T& default_value) const { + return is_just_ ? value_ : default_value; + } + bool isJust() const { return is_just_; } + T takeJust() { + assert(is_just_); + is_just_ = false; + return std::move(value_); + } + + private: + bool is_just_; + T value_; +}; + +template +struct MaybeTypedef { + typedef PtrMaybe type; +}; + +template <> +struct MaybeTypedef { + typedef ValueMaybe type; +}; + +template <> +struct MaybeTypedef { + typedef ValueMaybe type; +}; + +template <> +struct MaybeTypedef { + typedef ValueMaybe type; +}; + +template <> +struct MaybeTypedef { + typedef ValueMaybe type; +}; + +} // namespace detail + +template +using Maybe = typename detail::MaybeTypedef::type; + +} // namespace v8_crdtp + +#endif // V8_CRDTP_MAYBE_H_ diff --git a/third_party/inspector_protocol/crdtp/maybe_test.cc b/third_party/inspector_protocol/crdtp/maybe_test.cc new file mode 100644 index 0000000000..8e8690a1c1 --- /dev/null +++ b/third_party/inspector_protocol/crdtp/maybe_test.cc @@ -0,0 +1,44 @@ +// 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 "maybe.h" + +#include +#include + +#include "test_platform.h" + +namespace v8_crdtp { + +// ============================================================================= +// detail::PtrMaybe, detail::ValueMaybe, templates for optional +// pointers / values which are used in ../lib/Forward_h.template. +// ============================================================================= +TEST(PtrMaybeTest, SmokeTest) { + detail::PtrMaybe> example; + EXPECT_FALSE(example.isJust()); + EXPECT_TRUE(nullptr == example.fromMaybe(nullptr)); + std::unique_ptr> v(new std::vector); + v->push_back(42); + v->push_back(21); + example = std::move(v); + EXPECT_TRUE(example.isJust()); + EXPECT_THAT(*example.fromJust(), testing::ElementsAre(42, 21)); + std::unique_ptr> out = example.takeJust(); + EXPECT_FALSE(example.isJust()); + EXPECT_THAT(*out, testing::ElementsAre(42, 21)); +} + +TEST(PtrValueTest, SmokeTest) { + detail::ValueMaybe example; + EXPECT_FALSE(example.isJust()); + EXPECT_EQ(-1, example.fromMaybe(-1)); + example = 42; + EXPECT_TRUE(example.isJust()); + EXPECT_EQ(42, example.fromJust()); + int32_t out = example.takeJust(); + EXPECT_EQ(out, 42); +} + +} // namespace v8_crdtp diff --git a/third_party/inspector_protocol/crdtp/protocol_core.cc b/third_party/inspector_protocol/crdtp/protocol_core.cc new file mode 100644 index 0000000000..82a0c69b84 --- /dev/null +++ b/third_party/inspector_protocol/crdtp/protocol_core.cc @@ -0,0 +1,288 @@ +// Copyright 2020 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 "protocol_core.h" + +#include +#include + +namespace v8_crdtp { + +DeserializerState::DeserializerState(std::vector bytes) + : storage_(new std::vector(std::move(bytes))), + tokenizer_(span(storage_->data(), storage_->size())) {} + +DeserializerState::DeserializerState(Storage storage, span span) + : storage_(std::move(storage)), tokenizer_(span) {} + +void DeserializerState::RegisterError(Error error) { + assert(Error::OK != error); + if (tokenizer_.Status().ok()) + status_ = Status{error, tokenizer_.Status().pos}; +} + +void DeserializerState::RegisterFieldPath(span name) { + field_path_.push_back(name); +} + +std::string DeserializerState::ErrorMessage(span message_name) const { + std::string msg = "Failed to deserialize "; + msg.append(message_name.begin(), message_name.end()); + for (int field = static_cast(field_path_.size()) - 1; field >= 0; + --field) { + msg.append("."); + msg.append(field_path_[field].begin(), field_path_[field].end()); + } + Status s = status(); + if (!s.ok()) + msg += " - " + s.ToASCIIString(); + return msg; +} + +Status DeserializerState::status() const { + if (!tokenizer_.Status().ok()) + return tokenizer_.Status(); + return status_; +} + +namespace { +constexpr int32_t GetMandatoryFieldMask( + const DeserializerDescriptor::Field* fields, + size_t count) { + int32_t mask = 0; + for (size_t i = 0; i < count; ++i) { + if (!fields[i].is_optional) + mask |= (1 << i); + } + return mask; +} +} // namespace + +DeserializerDescriptor::DeserializerDescriptor(const Field* fields, + size_t field_count) + : fields_(fields), + field_count_(field_count), + mandatory_field_mask_(GetMandatoryFieldMask(fields, field_count)) {} + +bool DeserializerDescriptor::Deserialize(DeserializerState* state, + void* obj) const { + auto* tokenizer = state->tokenizer(); + + // As a special compatibility quirk, allow empty objects if + // no mandatory fields are required. + if (tokenizer->TokenTag() == cbor::CBORTokenTag::DONE && + !mandatory_field_mask_) { + return true; + } + if (tokenizer->TokenTag() == cbor::CBORTokenTag::ENVELOPE) + tokenizer->EnterEnvelope(); + if (tokenizer->TokenTag() != cbor::CBORTokenTag::MAP_START) { + state->RegisterError(Error::CBOR_MAP_START_EXPECTED); + return false; + } + tokenizer->Next(); + int32_t seen_mandatory_fields = 0; + for (; tokenizer->TokenTag() != cbor::CBORTokenTag::STOP; tokenizer->Next()) { + if (tokenizer->TokenTag() != cbor::CBORTokenTag::STRING8) { + state->RegisterError(Error::CBOR_INVALID_MAP_KEY); + return false; + } + span u_key = tokenizer->GetString8(); + span key(reinterpret_cast(u_key.data()), u_key.size()); + tokenizer->Next(); + if (!DeserializeField(state, key, &seen_mandatory_fields, obj)) + return false; + } + // Only compute mandatory fields once per type. + int32_t missing_fields = seen_mandatory_fields ^ mandatory_field_mask_; + if (missing_fields) { + int32_t idx = 0; + while ((missing_fields & 1) == 0) { + missing_fields >>= 1; + ++idx; + } + state->RegisterError(Error::BINDINGS_MANDATORY_FIELD_MISSING); + state->RegisterFieldPath(fields_[idx].name); + return false; + } + return true; +} + +bool DeserializerDescriptor::DeserializeField(DeserializerState* state, + span name, + int* seen_mandatory_fields, + void* obj) const { + // TODO(caseq): consider checking if the sought field is the one + // after the last deserialized. + const auto* begin = fields_; + const auto* end = fields_ + field_count_; + auto entry = std::lower_bound( + begin, end, name, [](const Field& field_desc, span field_name) { + return SpanLessThan(field_desc.name, field_name); + }); + // Unknown field is not an error -- we may be working against an + // implementation of a later version of the protocol. + // TODO(caseq): support unknown arrays and maps not enclosed by an envelope. + if (entry == end || !SpanEquals(entry->name, name)) + return true; + if (!entry->deserializer(state, obj)) { + state->RegisterFieldPath(name); + return false; + } + if (!entry->is_optional) + *seen_mandatory_fields |= 1 << (entry - begin); + return true; +} + +bool ProtocolTypeTraits::Deserialize(DeserializerState* state, + bool* value) { + const auto tag = state->tokenizer()->TokenTag(); + if (tag == cbor::CBORTokenTag::TRUE_VALUE) { + *value = true; + return true; + } + if (tag == cbor::CBORTokenTag::FALSE_VALUE) { + *value = false; + return true; + } + state->RegisterError(Error::BINDINGS_BOOL_VALUE_EXPECTED); + return false; +} + +void ProtocolTypeTraits::Serialize(bool value, + std::vector* bytes) { + bytes->push_back(value ? cbor::EncodeTrue() : cbor::EncodeFalse()); +} + +bool ProtocolTypeTraits::Deserialize(DeserializerState* state, + int32_t* value) { + if (state->tokenizer()->TokenTag() != cbor::CBORTokenTag::INT32) { + state->RegisterError(Error::BINDINGS_INT32_VALUE_EXPECTED); + return false; + } + *value = state->tokenizer()->GetInt32(); + return true; +} + +void ProtocolTypeTraits::Serialize(int32_t value, + std::vector* bytes) { + cbor::EncodeInt32(value, bytes); +} + +ContainerSerializer::ContainerSerializer(std::vector* bytes, + uint8_t tag) + : bytes_(bytes) { + envelope_.EncodeStart(bytes_); + bytes_->push_back(tag); +} + +void ContainerSerializer::EncodeStop() { + bytes_->push_back(cbor::EncodeStop()); + envelope_.EncodeStop(bytes_); +} + +ObjectSerializer::ObjectSerializer() + : serializer_(&owned_bytes_, cbor::EncodeIndefiniteLengthMapStart()) {} + +ObjectSerializer::~ObjectSerializer() = default; + +std::unique_ptr ObjectSerializer::Finish() { + serializer_.EncodeStop(); + return Serializable::From(std::move(owned_bytes_)); +} + +bool ProtocolTypeTraits::Deserialize(DeserializerState* state, + double* value) { + // Double values that round-trip through JSON may end up getting represented + // as an int32 (SIGNED, UNSIGNED) on the wire in CBOR. Therefore, we also + // accept an INT32 here. + if (state->tokenizer()->TokenTag() == cbor::CBORTokenTag::INT32) { + *value = state->tokenizer()->GetInt32(); + return true; + } + if (state->tokenizer()->TokenTag() != cbor::CBORTokenTag::DOUBLE) { + state->RegisterError(Error::BINDINGS_DOUBLE_VALUE_EXPECTED); + return false; + } + *value = state->tokenizer()->GetDouble(); + return true; +} + +void ProtocolTypeTraits::Serialize(double value, + std::vector* bytes) { + cbor::EncodeDouble(value, bytes); +} + +class IncomingDeferredMessage : public DeferredMessage { + public: + // Creates the state from the part of another message. + // Note storage is opaque and is mostly to retain ownership. + // It may be null in case caller owns the memory and will dispose + // of the message synchronously. + IncomingDeferredMessage(DeserializerState::Storage storage, + span span) + : storage_(storage), span_(span) {} + + private: + DeserializerState MakeDeserializer() const override { + return DeserializerState(storage_, span_); + } + void AppendSerialized(std::vector* out) const override { + out->insert(out->end(), span_.begin(), span_.end()); + } + + DeserializerState::Storage storage_; + span span_; +}; + +class OutgoingDeferredMessage : public DeferredMessage { + public: + OutgoingDeferredMessage() = default; + explicit OutgoingDeferredMessage(std::unique_ptr serializable) + : serializable_(std::move(serializable)) { + assert(!!serializable_); + } + + private: + DeserializerState MakeDeserializer() const override { + return DeserializerState(serializable_->Serialize()); + } + void AppendSerialized(std::vector* out) const override { + serializable_->AppendSerialized(out); + } + + std::unique_ptr serializable_; +}; + +// static +std::unique_ptr DeferredMessage::FromSerializable( + std::unique_ptr serializeable) { + return std::make_unique(std::move(serializeable)); +} + +// static +std::unique_ptr DeferredMessage::FromSpan( + span bytes) { + return std::make_unique(nullptr, bytes); +} + +bool ProtocolTypeTraits>::Deserialize( + DeserializerState* state, + std::unique_ptr* value) { + if (state->tokenizer()->TokenTag() != cbor::CBORTokenTag::ENVELOPE) { + state->RegisterError(Error::CBOR_INVALID_ENVELOPE); + return false; + } + *value = std::make_unique( + state->storage(), state->tokenizer()->GetEnvelope()); + return true; +} + +void ProtocolTypeTraits>::Serialize( + const std::unique_ptr& value, + std::vector* bytes) { + value->AppendSerialized(bytes); +} + +} // namespace v8_crdtp diff --git a/third_party/inspector_protocol/crdtp/protocol_core.h b/third_party/inspector_protocol/crdtp/protocol_core.h new file mode 100644 index 0000000000..51d9db98b0 --- /dev/null +++ b/third_party/inspector_protocol/crdtp/protocol_core.h @@ -0,0 +1,406 @@ +// Copyright 2020 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_CRDTP_PROTOCOL_CORE_H_ +#define V8_CRDTP_PROTOCOL_CORE_H_ + +#include + +#include +#include +#include + +#include "cbor.h" +#include "maybe.h" +#include "serializable.h" +#include "span.h" +#include "status.h" + +namespace v8_crdtp { + +class DeserializerState { + public: + using Storage = std::shared_ptr>; + + // Creates a state from the raw bytes received from the peer. + explicit DeserializerState(std::vector bytes); + // Creates the state from the part of another message. + DeserializerState(Storage storage, span span); + DeserializerState(const DeserializerState& r) = delete; + DeserializerState(DeserializerState&& r) = default; + + // Registers |error|, unless the tokenizer's status is already an error. + void RegisterError(Error error); + // Registers |name| as a segment of the field path. + void RegisterFieldPath(span name); + + // Produces an error message considering |tokenizer.Status()|, + // status_, and field_path_. + std::string ErrorMessage(span message_name) const; + Status status() const; + const Storage& storage() const { return storage_; } + cbor::CBORTokenizer* tokenizer() { return &tokenizer_; } + + private: + const Storage storage_; + cbor::CBORTokenizer tokenizer_; + Status status_; + std::vector> field_path_; +}; + +template +struct ProtocolTypeTraits {}; + +template <> +struct ProtocolTypeTraits { + static bool Deserialize(DeserializerState* state, bool* value); + static void Serialize(bool value, std::vector* bytes); +}; + +template <> +struct ProtocolTypeTraits { + static bool Deserialize(DeserializerState* state, int* value); + static void Serialize(int value, std::vector* bytes); +}; + +template <> +struct ProtocolTypeTraits { + static bool Deserialize(DeserializerState* state, double* value); + static void Serialize(double value, std::vector* bytes); +}; + +class ContainerSerializer { + public: + ContainerSerializer(std::vector* bytes, uint8_t tag); + + template + void AddField(span field_name, const T& value) { + cbor::EncodeString8( + span(reinterpret_cast(field_name.data()), + field_name.size()), + bytes_); + ProtocolTypeTraits::Serialize(value, bytes_); + } + template + void AddField(span field_name, const detail::ValueMaybe& value) { + if (!value.isJust()) + return; + AddField(field_name, value.fromJust()); + } + template + void AddField(span field_name, const detail::PtrMaybe& value) { + if (!value.isJust()) + return; + AddField(field_name, *value.fromJust()); + } + + void EncodeStop(); + + private: + std::vector* const bytes_; + cbor::EnvelopeEncoder envelope_; +}; + +class ObjectSerializer { + public: + ObjectSerializer(); + ~ObjectSerializer(); + + template + void AddField(span name, const T& field) { + serializer_.AddField(name, field); + } + std::unique_ptr Finish(); + + private: + std::vector owned_bytes_; + ContainerSerializer serializer_; +}; + +class DeserializerDescriptor { + public: + struct Field { + span name; + bool is_optional; + bool (*deserializer)(DeserializerState* state, void* obj); + }; + + DeserializerDescriptor(const Field* fields, size_t field_count); + + bool Deserialize(DeserializerState* state, void* obj) const; + + private: + bool DeserializeField(DeserializerState* state, + span name, + int* seen_mandatory_fields, + void* obj) const; + + const Field* const fields_; + const size_t field_count_; + const int mandatory_field_mask_; +}; + +template +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, std::vector* value) { + auto* tokenizer = state->tokenizer(); + if (tokenizer->TokenTag() == cbor::CBORTokenTag::ENVELOPE) + tokenizer->EnterEnvelope(); + if (tokenizer->TokenTag() != cbor::CBORTokenTag::ARRAY_START) { + state->RegisterError(Error::CBOR_ARRAY_START_EXPECTED); + return false; + } + assert(value->empty()); + tokenizer->Next(); + for (; tokenizer->TokenTag() != cbor::CBORTokenTag::STOP; + tokenizer->Next()) { + value->emplace_back(); + if (!ProtocolTypeTraits::Deserialize(state, &value->back())) + return false; + } + return true; + } + + static void Serialize(const std::vector& value, + std::vector* bytes) { + ContainerSerializer container_serializer( + bytes, cbor::EncodeIndefiniteLengthArrayStart()); + for (const auto& item : value) + ProtocolTypeTraits::Serialize(item, bytes); + container_serializer.EncodeStop(); + } +}; + +template +struct ProtocolTypeTraits>> { + static bool Deserialize(DeserializerState* state, + std::unique_ptr>* value) { + auto res = std::make_unique>(); + if (!ProtocolTypeTraits>::Deserialize(state, res.get())) + return false; + *value = std::move(res); + return true; + } + static void Serialize(const std::unique_ptr>& value, + std::vector* bytes) { + ProtocolTypeTraits>::Serialize(*value, bytes); + } +}; + +class DeferredMessage : public Serializable { + public: + static std::unique_ptr FromSerializable( + std::unique_ptr serializeable); + static std::unique_ptr FromSpan(span bytes); + + ~DeferredMessage() override = default; + virtual DeserializerState MakeDeserializer() const = 0; + + protected: + DeferredMessage() = default; +}; + +template <> +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, + std::unique_ptr* value); + static void Serialize(const std::unique_ptr& value, + std::vector* bytes); +}; + +template +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, + detail::ValueMaybe* value) { + T res; + if (!ProtocolTypeTraits::Deserialize(state, &res)) + return false; + *value = std::move(res); + return true; + } + + static void Serialize(const detail::ValueMaybe& value, + std::vector* bytes) { + ProtocolTypeTraits::Serialize(value.fromJust(), bytes); + } +}; + +template +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, + detail::PtrMaybe* value) { + std::unique_ptr res; + if (!ProtocolTypeTraits>::Deserialize(state, &res)) + return false; + *value = std::move(res); + return true; + } + + static void Serialize(const detail::PtrMaybe& value, + std::vector* bytes) { + ProtocolTypeTraits::Serialize(*value.fromJust(), bytes); + } +}; + +template +class DeserializableProtocolObject { + public: + static StatusOr> ReadFrom( + const DeferredMessage& deferred_message) { + auto state = deferred_message.MakeDeserializer(); + if (auto res = Deserialize(&state)) + return StatusOr>(std::move(res)); + return StatusOr>(state.status()); + } + + static StatusOr> ReadFrom(std::vector bytes) { + auto state = DeserializerState(std::move(bytes)); + if (auto res = Deserialize(&state)) + return StatusOr>(std::move(res)); + return StatusOr>(state.status()); + } + + // Short-hand for legacy clients. This would swallow any errors, consider + // using ReadFrom. + static std::unique_ptr FromBinary(const uint8_t* bytes, size_t size) { + std::unique_ptr value(new T()); + auto deserializer = DeferredMessage::FromSpan(span(bytes, size)) + ->MakeDeserializer(); + Deserialize(&deserializer, value.get()); + return value; + } + + static bool Deserialize(DeserializerState* state, T* value) { + return T::deserializer_descriptor().Deserialize(state, value); + } + + protected: + // This is for the sake of the macros used by derived classes thay may be in + // a different namespace; + using ProtocolType = T; + using DeserializerDescriptorType = DeserializerDescriptor; + template + using DeserializableBase = DeserializableProtocolObject; + + DeserializableProtocolObject() = default; + ~DeserializableProtocolObject() = default; + + private: + friend struct ProtocolTypeTraits>; + + static std::unique_ptr Deserialize(DeserializerState* state) { + std::unique_ptr value(new T()); + if (Deserialize(state, value.get())) + return value; + return nullptr; + } +}; + +template +class ProtocolObject : public Serializable, + public DeserializableProtocolObject { + public: + std::unique_ptr Clone() const { + std::vector serialized; + AppendSerialized(&serialized); + return T::ReadFrom(std::move(serialized)).value(); + } + // TODO(caseq): compatibility only, remove. + std::unique_ptr clone() const { return Clone(); } + + protected: + using ProtocolType = T; + + ProtocolObject() = default; +}; + +template +struct ProtocolTypeTraits< + T, + typename std::enable_if< + std::is_base_of, T>::value>::type> { + static bool Deserialize(DeserializerState* state, T* value) { + return T::Deserialize(state, value); + } + + static void Serialize(const T& value, std::vector* bytes) { + value.AppendSerialized(bytes); + } +}; + +template +struct ProtocolTypeTraits< + std::unique_ptr, + typename std::enable_if< + std::is_base_of, T>::value>::type> { + static bool Deserialize(DeserializerState* state, std::unique_ptr* value) { + std::unique_ptr res = T::Deserialize(state); + if (!res) + return false; + *value = std::move(res); + return true; + } + + static void Serialize(const std::unique_ptr& value, + std::vector* bytes) { + ProtocolTypeTraits::Serialize(*value, bytes); + } +}; + +#define DECLARE_DESERIALIZATION_SUPPORT() \ + friend DeserializableBase; \ + static const DeserializerDescriptorType& deserializer_descriptor() + +#define DECLARE_SERIALIZATION_SUPPORT() \ + public: \ + void AppendSerialized(std::vector* bytes) const override; \ + \ + private: \ + friend DeserializableBase; \ + static const DeserializerDescriptorType& deserializer_descriptor() + +#define V8_CRDTP_DESERIALIZE_FILED_IMPL(name, field, is_optional) \ + { \ + MakeSpan(name), is_optional, \ + [](DeserializerState* __state, void* __obj) -> bool { \ + return ProtocolTypeTraits::Deserialize( \ + __state, &static_cast(__obj)->field); \ + } \ + } + +// clang-format off +#define V8_CRDTP_BEGIN_DESERIALIZER(type) \ + const type::DeserializerDescriptorType& type::deserializer_descriptor() { \ + using namespace v8_crdtp; \ + static const DeserializerDescriptorType::Field fields[] = { + +#define V8_CRDTP_END_DESERIALIZER() \ + }; \ + static const DeserializerDescriptorType s_desc( \ + fields, sizeof fields / sizeof fields[0]); \ + return s_desc; \ + } + +#define V8_CRDTP_DESERIALIZE_FIELD(name, field) \ + V8_CRDTP_DESERIALIZE_FILED_IMPL(name, field, false) +#define V8_CRDTP_DESERIALIZE_FIELD_OPT(name, field) \ + V8_CRDTP_DESERIALIZE_FILED_IMPL(name, field, true) + +#define V8_CRDTP_BEGIN_SERIALIZER(type) \ + void type::AppendSerialized(std::vector* bytes) const { \ + using namespace v8_crdtp; \ + ContainerSerializer __serializer(bytes, \ + cbor::EncodeIndefiniteLengthMapStart()); + +#define V8_CRDTP_SERIALIZE_FIELD(name, field) \ + __serializer.AddField(MakeSpan(name), field) + +#define V8_CRDTP_END_SERIALIZER() \ + __serializer.EncodeStop(); \ + } class __cddtp_dummy_name +// clang-format on + +} // namespace v8_crdtp + +#endif // V8_CRDTP_PROTOCOL_CORE_H_ diff --git a/third_party/inspector_protocol/crdtp/protocol_core_test.cc b/third_party/inspector_protocol/crdtp/protocol_core_test.cc new file mode 100644 index 0000000000..501075c073 --- /dev/null +++ b/third_party/inspector_protocol/crdtp/protocol_core_test.cc @@ -0,0 +1,497 @@ +// Copyright 2020 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 "protocol_core.h" + +#include + +#include "cbor.h" +#include "maybe.h" +#include "status_test_support.h" +#include "test_platform.h" + +namespace v8_crdtp { + +// Test-only. Real-life bindings use UTF8/16 conversions as needed. +template <> +struct ProtocolTypeTraits { + static bool Deserialize(DeserializerState* state, std::string* value) { + if (state->tokenizer()->TokenTag() == cbor::CBORTokenTag::STRING8) { + auto cbor_span = state->tokenizer()->GetString8(); + value->assign(reinterpret_cast(cbor_span.data()), + cbor_span.size()); + return true; + } + state->RegisterError(Error::BINDINGS_STRING8_VALUE_EXPECTED); + return false; + } + + static void Serialize(const std::string& value, std::vector* bytes) { + cbor::EncodeString8( + span(reinterpret_cast(value.data()), + value.size()), + bytes); + } +}; + +namespace { +using ::testing::Eq; + +template +std::unique_ptr RoundtripToType(const TArg& obj) { + std::vector bytes; + obj.AppendSerialized(&bytes); + + StatusOr> result = + TResult::ReadFrom(std::move(bytes)); + return std::move(result).value(); +} + +template +std::unique_ptr Roundtrip(const T& obj) { + return RoundtripToType(obj); +} + +// These TestTypeFOO classes below would normally be generated +// by the protocol generator. + +class TestTypeBasic : public ProtocolObject { + public: + TestTypeBasic() = default; + + const std::string& GetValue() const { return value_; } + void SetValue(std::string value) { value_ = std::move(value); } + + private: + DECLARE_SERIALIZATION_SUPPORT(); + + std::string value_; +}; + +// clang-format off +V8_CRDTP_BEGIN_DESERIALIZER(TestTypeBasic) + V8_CRDTP_DESERIALIZE_FIELD("value", value_) +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(TestTypeBasic) + V8_CRDTP_SERIALIZE_FIELD("value", value_); +V8_CRDTP_END_SERIALIZER(); +// clang-format on + +TEST(ProtocolCoreTest, Basic) { + TestTypeBasic obj1; + obj1.SetValue("foo"); + + auto obj2 = Roundtrip(obj1); + ASSERT_THAT(obj2, Not(testing::IsNull())); + EXPECT_THAT(obj2->GetValue(), Eq("foo")); +} + +TEST(ProtocolCoreTest, FailedToDeserializeTestTypeBasic) { + std::vector garbage = {'g', 'a', 'r', 'b', 'a', 'g', 'e'}; + StatusOr> result = + TestTypeBasic::ReadFrom(std::move(garbage)); + EXPECT_THAT(result.status(), StatusIs(Error::CBOR_INVALID_STRING8, 0)); +} + +class TestTypeBasicDouble : public ProtocolObject { + public: + TestTypeBasicDouble() = default; + + double GetValue() const { return value_; } + void SetValue(double value) { value_ = value; } + + private: + DECLARE_SERIALIZATION_SUPPORT(); + + double value_; +}; + +// clang-format off +V8_CRDTP_BEGIN_DESERIALIZER(TestTypeBasicDouble) + V8_CRDTP_DESERIALIZE_FIELD("value", value_) +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(TestTypeBasicDouble) + V8_CRDTP_SERIALIZE_FIELD("value", value_); +V8_CRDTP_END_SERIALIZER(); +// clang-format on + +TEST(TestBasicDouble, ParserAllowsAllowsDoubleEncodedAsInt) { + // We allow double's encoded as INT32, because this is what a roundtrip via + // JSON would produce. + std::vector encoded; + crdtp::cbor::EnvelopeEncoder envelope; + envelope.EncodeStart(&encoded); + encoded.push_back(crdtp::cbor::EncodeIndefiniteLengthMapStart()); + crdtp::cbor::EncodeString8(crdtp::SpanFrom("value"), &encoded); + crdtp::cbor::EncodeInt32( + 42, &encoded); // It's a double field, but we encode an int. + encoded.push_back(crdtp::cbor::EncodeStop()); + envelope.EncodeStop(&encoded); + auto obj = TestTypeBasicDouble::ReadFrom(encoded).value(); + ASSERT_THAT(obj, Not(testing::IsNull())); + EXPECT_THAT(obj->GetValue(), Eq(42)); +} + +class TestTypeComposite : public ProtocolObject { + public: + bool GetBoolField() const { return bool_field_; } + void SetBoolField(bool value) { bool_field_ = value; } + + int GetIntField() const { return int_field_; } + void SetIntField(int value) { int_field_ = value; } + + double GetDoubleField() const { return double_field_; } + void SetDoubleField(double value) { double_field_ = value; } + + const std::string& GetStrField() const { return str_field_; } + void SetStrField(std::string value) { str_field_ = std::move(value); } + + const TestTypeBasic* GetTestTypeBasicField() { + return test_type1_field_.get(); + } + void SetTestTypeBasicField(std::unique_ptr value) { + test_type1_field_ = std::move(value); + } + + private: + DECLARE_SERIALIZATION_SUPPORT(); + + bool bool_field_ = false; + int int_field_ = 0; + double double_field_ = 0.0; + std::string str_field_; + std::unique_ptr test_type1_field_; +}; + +// clang-format off +V8_CRDTP_BEGIN_DESERIALIZER(TestTypeComposite) + V8_CRDTP_DESERIALIZE_FIELD("bool_field", bool_field_), + V8_CRDTP_DESERIALIZE_FIELD("double_field", double_field_), + V8_CRDTP_DESERIALIZE_FIELD("int_field", int_field_), + V8_CRDTP_DESERIALIZE_FIELD("str_field", str_field_), + V8_CRDTP_DESERIALIZE_FIELD("test_type1_field", test_type1_field_), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(TestTypeComposite) + V8_CRDTP_SERIALIZE_FIELD("bool_field", bool_field_), + V8_CRDTP_SERIALIZE_FIELD("double_field", double_field_), + V8_CRDTP_SERIALIZE_FIELD("int_field", int_field_), + V8_CRDTP_SERIALIZE_FIELD("str_field", str_field_), + V8_CRDTP_SERIALIZE_FIELD("test_type1_field", test_type1_field_), +V8_CRDTP_END_SERIALIZER(); +// clang-format on + +TEST(ProtocolCoreTest, Composite) { + TestTypeComposite obj1; + obj1.SetBoolField(true); + obj1.SetIntField(42); + obj1.SetDoubleField(2.718281828); + obj1.SetStrField("bar"); + auto val1 = std::make_unique(); + val1->SetValue("bazzzz"); + obj1.SetTestTypeBasicField(std::move(val1)); + + auto obj2 = Roundtrip(obj1); + ASSERT_THAT(obj2, Not(testing::IsNull())); + EXPECT_THAT(obj2->GetBoolField(), Eq(true)); + EXPECT_THAT(obj2->GetIntField(), Eq(42)); + EXPECT_THAT(obj2->GetDoubleField(), Eq(2.718281828)); + EXPECT_THAT(obj2->GetStrField(), Eq("bar")); + EXPECT_THAT(obj2->GetTestTypeBasicField()->GetValue(), Eq("bazzzz")); +} + +class CompositeParsingTest : public testing::Test { + public: + CompositeParsingTest() { + TestTypeComposite top; + top.SetIntField(42); + top.SetBoolField(true); + top.SetIntField(42); + top.SetDoubleField(2.718281828); + top.SetStrField("junk"); + auto child = std::make_unique(); + child->SetValue("child_value"); + top.SetTestTypeBasicField(std::move(child)); + + // Let's establish that |serialized_| is a properly serialized + // representation of |top|, by checking that it deserializes ok. + top.AppendSerialized(&serialized_); + TestTypeComposite::ReadFrom(serialized_).value(); + } + + protected: + std::vector serialized_; +}; + +TEST_F(CompositeParsingTest, DecodingFailure_CBORTokenizer) { + // Mutates |serialized_| so that it won't parse correctly. In this case, + // we're changing a string value so that it's invalid, making CBORTokenizer + // unhappy. + size_t position = + std::string(reinterpret_cast(serialized_.data()), + serialized_.size()) + .find("child_value"); + EXPECT_GT(position, 0ul); + // We override the byte just before so that it's still a string + // (3 << 5), but the length is encoded in the bytes that follows. + // So, we override that with 0xff (255), which exceeds the length + // of the message and thereby makes the string8 invalid. + --position; + serialized_[position] = 3 << 5 | // major type: STRING + 25; // length in encoded in byte that follows. + serialized_[position + 1] = 0xff; // length + auto result = TestTypeComposite::ReadFrom(serialized_); + + EXPECT_THAT(result.status(), StatusIs(Error::CBOR_INVALID_STRING8, position)); +} + +TEST_F(CompositeParsingTest, DecodingFailure_MandatoryFieldMissingShallow) { + // We're changing the string key "int_field" to something else ("lnt_field"), + // so that the mandatory field value won't be found. Unknown fields are + // ignored for compatibility, so that's why this simple technique works here. + size_t position = + std::string(reinterpret_cast(serialized_.data()), + serialized_.size()) + .find("int_field"); + serialized_[position] = 'l'; // Change 'i' to 'l'. + // serialized_.size() - 1 is the STOP character for the entire message, + size_t expected_error_pos = serialized_.size() - 1; + auto result = TestTypeComposite::ReadFrom(serialized_); + EXPECT_THAT(result.status(), StatusIs(Error::BINDINGS_MANDATORY_FIELD_MISSING, + expected_error_pos)); +} + +TEST_F(CompositeParsingTest, DecodingFailure_MandatoryFieldMissingNested) { + // We're changing the string key "value" to something else ("falue"), so that + // the mandatory field value in TestTypeBasic in the child won't be found. + size_t position = + std::string(reinterpret_cast(serialized_.data()), + serialized_.size()) + .find("value"); + serialized_[position] = 'f'; // Change 'v' to 'f'. + // serialized_.size() - 1 is the STOP character for the enclosing message, + // and serialized_.size() - 2 is the STOP character for TestTypeBasic. + size_t expected_error_pos = serialized_.size() - 2; + auto result = TestTypeComposite::ReadFrom(serialized_); + EXPECT_THAT(result.status(), StatusIs(Error::BINDINGS_MANDATORY_FIELD_MISSING, + expected_error_pos)); +} + +TEST_F(CompositeParsingTest, DecodingFailure_BoolValueExpected) { + // We're changing the bool value (true) to null; we do this by looking + // for bool_field, and searching from there for TRUE; both TRUE and null + // are just one byte in the serialized buffer, so this swap is convenient. + std::string serialized_view(reinterpret_cast(serialized_.data()), + serialized_.size()); + size_t position = serialized_view.find("bool_field"); + for (; position < serialized_.size(); ++position) { + if (serialized_[position] == crdtp::cbor::EncodeTrue()) { + serialized_[position] = crdtp::cbor::EncodeNull(); + break; + } + } + auto result = TestTypeComposite::ReadFrom(serialized_); + EXPECT_THAT(result.status(), + StatusIs(Error::BINDINGS_BOOL_VALUE_EXPECTED, position)); +} + +class TestTypeArrays : public ProtocolObject { + public: + const std::vector* GetIntArray() const { return &int_array_; } + void SetIntArray(std::vector value) { int_array_ = std::move(value); } + + const std::vector* GetDoubleArray() const { return &double_array_; } + void SetDoubleArray(std::vector value) { + double_array_ = std::move(value); + } + + const std::vector* GetStrArray() const { return &str_array_; } + void SetStrArray(std::vector value) { + str_array_ = std::move(value); + } + + const std::vector>* GetTestTypeBasicArray() + const { + return &test_type_basic_array_; + } + + void SetTestTypeBasicArray( + std::vector> value) { + test_type_basic_array_ = std::move(value); + } + + private: + DECLARE_SERIALIZATION_SUPPORT(); + + std::vector int_array_; + std::vector double_array_; + std::vector str_array_; + std::vector> test_type_basic_array_; +}; + +// clang-format off +V8_CRDTP_BEGIN_DESERIALIZER(TestTypeArrays) + V8_CRDTP_DESERIALIZE_FIELD("int_array", int_array_), + V8_CRDTP_DESERIALIZE_FIELD("str_array", str_array_), + V8_CRDTP_DESERIALIZE_FIELD("test_type_basic_array", test_type_basic_array_), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(TestTypeArrays) + V8_CRDTP_SERIALIZE_FIELD("int_array", int_array_), + V8_CRDTP_SERIALIZE_FIELD("str_array", str_array_), + V8_CRDTP_SERIALIZE_FIELD("test_type_basic_array", test_type_basic_array_), +V8_CRDTP_END_SERIALIZER(); +// clang-format on + +TEST_F(CompositeParsingTest, Arrays) { + TestTypeArrays obj1; + obj1.SetIntArray(std::vector{1, 3, 5, 7}); + std::vector strs; + strs.emplace_back("foo"); + strs.emplace_back(std::string("bar")); + obj1.SetStrArray(std::move(strs)); + auto val1 = std::make_unique(); + val1->SetValue("bazzzz"); + std::vector> vec1; + vec1.emplace_back(std::move(val1)); + obj1.SetTestTypeBasicArray(std::move(vec1)); + + auto obj2 = Roundtrip(obj1); + ASSERT_THAT(obj2, Not(testing::IsNull())); + EXPECT_THAT(*obj2->GetIntArray(), testing::ElementsAre(1, 3, 5, 7)); + EXPECT_THAT(*obj2->GetStrArray(), testing::ElementsAre("foo", "bar")); + EXPECT_THAT(obj2->GetDoubleArray()->size(), Eq(0ul)); + EXPECT_THAT(obj2->GetTestTypeBasicArray()->size(), Eq(1ul)); + EXPECT_THAT(obj2->GetTestTypeBasicArray()->front()->GetValue(), Eq("bazzzz")); +} + +class TestTypeOptional : public ProtocolObject { + public: + TestTypeOptional() = default; + + bool HasIntField() const { return int_field_.isJust(); } + int GetIntField() const { return int_field_.fromJust(); } + void SetIntField(int value) { int_field_ = value; } + + bool HasStrField() { return str_field_.isJust(); } + const std::string& GetStrField() const { return str_field_.fromJust(); } + void SetStrField(std::string value) { str_field_ = std::move(value); } + + bool HasTestTypeBasicField() { return test_type_basic_field_.isJust(); } + const TestTypeBasic* GetTestTypeBasicField() const { + return test_type_basic_field_.isJust() ? test_type_basic_field_.fromJust() + : nullptr; + } + void SetTestTypeBasicField(std::unique_ptr value) { + test_type_basic_field_ = std::move(value); + } + + private: + DECLARE_SERIALIZATION_SUPPORT(); + + Maybe int_field_; + Maybe str_field_; + Maybe test_type_basic_field_; +}; + +// clang-format off +V8_CRDTP_BEGIN_DESERIALIZER(TestTypeOptional) + V8_CRDTP_DESERIALIZE_FIELD_OPT("int_field", int_field_), + V8_CRDTP_DESERIALIZE_FIELD_OPT("str_field", str_field_), + V8_CRDTP_DESERIALIZE_FIELD_OPT("test_type_basic_field", test_type_basic_field_), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(TestTypeOptional) + V8_CRDTP_SERIALIZE_FIELD("int_field", int_field_), + V8_CRDTP_SERIALIZE_FIELD("str_field", str_field_), + V8_CRDTP_SERIALIZE_FIELD("test_type_basic_field", test_type_basic_field_), +V8_CRDTP_END_SERIALIZER(); +// clang-format on + +TEST(ProtocolCoreTest, OptionalAbsent) { + TestTypeOptional obj1; + auto obj2 = Roundtrip(obj1); + ASSERT_THAT(obj2, Not(testing::IsNull())); + + EXPECT_THAT(obj2->HasIntField(), Eq(false)); + EXPECT_THAT(obj2->HasStrField(), Eq(false)); + EXPECT_THAT(obj2->HasTestTypeBasicField(), Eq(false)); +} + +TEST(ProtocolCoreTest, OptionalPresent) { + TestTypeOptional obj1; + obj1.SetIntField(42); + obj1.SetStrField("foo"); + + auto val1 = std::make_unique(); + val1->SetValue("bar"); + obj1.SetTestTypeBasicField(std::move(val1)); + + auto obj2 = Roundtrip(obj1); + ASSERT_THAT(obj2, Not(testing::IsNull())); + + EXPECT_THAT(obj2->HasIntField(), Eq(true)); + EXPECT_THAT(obj2->GetIntField(), Eq(42)); + EXPECT_THAT(obj2->HasStrField(), Eq(true)); + EXPECT_THAT(obj2->GetStrField(), Eq("foo")); + EXPECT_THAT(obj2->HasTestTypeBasicField(), Eq(true)); + EXPECT_THAT(obj2->GetTestTypeBasicField()->GetValue(), Eq("bar")); +} + +class TestTypeLazy : public ProtocolObject { + public: + TestTypeLazy() = default; + + const std::string& GetStrField() const { return str_field_; } + void SetStrField(std::string value) { str_field_ = std::move(value); } + + const DeferredMessage* deferred_test_type1_field() const { + return test_type1_field_.get(); + } + + private: + DECLARE_SERIALIZATION_SUPPORT(); + + std::string str_field_; + std::unique_ptr test_type1_field_; +}; + +// clang-format off +V8_CRDTP_BEGIN_DESERIALIZER(TestTypeLazy) + V8_CRDTP_DESERIALIZE_FIELD("str_field", str_field_), + V8_CRDTP_DESERIALIZE_FIELD_OPT("test_type1_field", test_type1_field_), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(TestTypeLazy) + V8_CRDTP_SERIALIZE_FIELD("str_field", str_field_), + V8_CRDTP_SERIALIZE_FIELD("test_type1_field", test_type1_field_), +V8_CRDTP_END_SERIALIZER(); +// clang-format on + +TEST(ProtocolCoreTest, TestDeferredMessage) { + TestTypeComposite obj1; + obj1.SetStrField("bar"); + auto val1 = std::make_unique(); + val1->SetValue("bazzzz"); + obj1.SetTestTypeBasicField(std::move(val1)); + + auto obj2 = RoundtripToType(obj1); + EXPECT_THAT(obj2->GetStrField(), Eq("bar")); + + TestTypeBasic basic_val; + auto deserializer = obj2->deferred_test_type1_field()->MakeDeserializer(); + EXPECT_THAT(TestTypeBasic::Deserialize(&deserializer, &basic_val), Eq(true)); + EXPECT_THAT(basic_val.GetValue(), Eq("bazzzz")); + + StatusOr> maybe_parsed = + TestTypeBasic::ReadFrom(*obj2->deferred_test_type1_field()); + ASSERT_THAT(maybe_parsed.status(), StatusIsOk()); + ASSERT_THAT((*maybe_parsed), Not(testing::IsNull())); + ASSERT_EQ((*maybe_parsed)->GetValue(), "bazzzz"); +} + +} // namespace +} // namespace v8_crdtp diff --git a/third_party/inspector_protocol/crdtp/serializer_traits.h b/third_party/inspector_protocol/crdtp/serializer_traits.h index cc275b54d6..0bb4d90ada 100644 --- a/third_party/inspector_protocol/crdtp/serializer_traits.h +++ b/third_party/inspector_protocol/crdtp/serializer_traits.h @@ -9,7 +9,7 @@ #include #include #include "cbor.h" -#include "glue.h" +#include "maybe.h" #include "span.h" namespace v8_crdtp { @@ -124,9 +124,9 @@ struct FieldSerializerTraits { }; template -struct FieldSerializerTraits> { +struct FieldSerializerTraits> { static void Serialize(span field_name, - const glue::detail::PtrMaybe& field_value, + const detail::PtrMaybe& field_value, std::vector* out) { if (!field_value.isJust()) return; @@ -136,9 +136,9 @@ struct FieldSerializerTraits> { }; template -struct FieldSerializerTraits> { +struct FieldSerializerTraits> { static void Serialize(span field_name, - const glue::detail::ValueMaybe& field_value, + const detail::ValueMaybe& field_value, std::vector* out) { if (!field_value.isJust()) return; diff --git a/third_party/inspector_protocol/crdtp/serializer_traits_test.cc b/third_party/inspector_protocol/crdtp/serializer_traits_test.cc index 27810ccd5f..e5543bd930 100644 --- a/third_party/inspector_protocol/crdtp/serializer_traits_test.cc +++ b/third_party/inspector_protocol/crdtp/serializer_traits_test.cc @@ -172,12 +172,12 @@ TEST(FieldSerializerTraits, RequiredField) { template class FieldSerializerTraits_MaybeTest : public ::testing::Test {}; using MaybeTypes = - ::testing::Types, - glue::detail::ValueMaybe, - glue::detail::ValueMaybe, - glue::detail::ValueMaybe, - glue::detail::PtrMaybe, - glue::detail::PtrMaybe>>>; + ::testing::Types, + detail::ValueMaybe, + detail::ValueMaybe, + detail::ValueMaybe, + detail::PtrMaybe, + detail::PtrMaybe>>>; TYPED_TEST_SUITE(FieldSerializerTraits_MaybeTest, MaybeTypes); TYPED_TEST(FieldSerializerTraits_MaybeTest, NoOutputForFieldsIfNotJust) { @@ -188,9 +188,8 @@ TYPED_TEST(FieldSerializerTraits_MaybeTest, NoOutputForFieldsIfNotJust) { TEST(FieldSerializerTraits, MaybeBool) { std::vector out; - SerializeField(SpanFrom("true"), glue::detail::ValueMaybe(true), &out); - SerializeField(SpanFrom("false"), glue::detail::ValueMaybe(false), - &out); + SerializeField(SpanFrom("true"), detail::ValueMaybe(true), &out); + SerializeField(SpanFrom("false"), detail::ValueMaybe(false), &out); std::vector expected; cbor::EncodeString8(SpanFrom("true"), &expected); @@ -203,7 +202,7 @@ TEST(FieldSerializerTraits, MaybeBool) { TEST(FieldSerializerTraits, MaybeDouble) { std::vector out; - SerializeField(SpanFrom("dbl"), glue::detail::ValueMaybe(3.14), &out); + SerializeField(SpanFrom("dbl"), detail::ValueMaybe(3.14), &out); std::vector expected; cbor::EncodeString8(SpanFrom("dbl"), &expected); @@ -215,7 +214,7 @@ TEST(FieldSerializerTraits, MaybeDouble) { TEST(FieldSerializerTraits, MaybePtrFoo) { std::vector out; SerializeField(SpanFrom("foo"), - glue::detail::PtrMaybe(std::make_unique(42)), &out); + detail::PtrMaybe(std::make_unique(42)), &out); std::vector expected; cbor::EncodeString8(SpanFrom("foo"), &expected); diff --git a/third_party/inspector_protocol/crdtp/span.cc b/third_party/inspector_protocol/crdtp/span.cc index af30bb4fd9..5953ba1287 100644 --- a/third_party/inspector_protocol/crdtp/span.cc +++ b/third_party/inspector_protocol/crdtp/span.cc @@ -21,4 +21,19 @@ bool SpanEquals(span x, span y) noexcept { return x.data() == y.data() || len == 0 || std::memcmp(x.data(), y.data(), len) == 0; } + +bool SpanLessThan(span x, span y) noexcept { + auto min_size = std::min(x.size(), y.size()); + const int r = min_size == 0 ? 0 : memcmp(x.data(), y.data(), min_size); + return (r < 0) || (r == 0 && x.size() < y.size()); +} + +bool SpanEquals(span x, span y) noexcept { + auto len = x.size(); + if (len != y.size()) + return false; + return x.data() == y.data() || len == 0 || + std::memcmp(x.data(), y.data(), len) == 0; +} + } // namespace v8_crdtp diff --git a/third_party/inspector_protocol/crdtp/span.h b/third_party/inspector_protocol/crdtp/span.h index ace5b511f6..ea3a6f4962 100644 --- a/third_party/inspector_protocol/crdtp/span.h +++ b/third_party/inspector_protocol/crdtp/span.h @@ -50,6 +50,11 @@ class span { index_type size_; }; +template +constexpr span MakeSpan(const char (&str)[N]) { + return span(str, N - 1); +} + template constexpr span SpanFrom(const char (&str)[N]) { return span(reinterpret_cast(str), N - 1); @@ -75,11 +80,15 @@ inline span SpanFrom(const C& v) { } // Less than / equality comparison functions for sorting / searching for byte -// spans. These are similar to absl::string_view's < and == operators. +// spans. bool SpanLessThan(span x, span y) noexcept; - bool SpanEquals(span x, span y) noexcept; +// Less than / equality comparison functions for sorting / searching for byte +// spans. +bool SpanLessThan(span x, span y) noexcept; +bool SpanEquals(span x, span y) noexcept; + struct SpanLt { bool operator()(span l, span r) const { return SpanLessThan(l, r); diff --git a/third_party/inspector_protocol/crdtp/status.h b/third_party/inspector_protocol/crdtp/status.h index ebb8ec98c0..45e0a57acf 100644 --- a/third_party/inspector_protocol/crdtp/status.h +++ b/third_party/inspector_protocol/crdtp/status.h @@ -5,6 +5,7 @@ #ifndef V8_CRDTP_STATUS_H_ #define V8_CRDTP_STATUS_H_ +#include #include #include #include @@ -103,6 +104,35 @@ struct Status { // includes the position. std::string ToASCIIString() const; }; + +template +class StatusOr { + public: + explicit StatusOr(const T& value) : value_(value) {} + explicit StatusOr(T&& value) : value_(std::move(value)) {} + explicit StatusOr(const Status& status) : status_(status) {} + + bool ok() const { return status_.ok(); } + T& operator*() & { + assert(ok()); + return value_; + } + const T& operator*() const& { return value(); } + T&& operator*() && { return value(); } + const Status& status() const { return status_; } + + T& value() & { return *this; } + T&& value() && { + assert(ok()); + return std::move(value_); + } + const T& value() const& { return *this; } + + private: + Status status_; + T value_; +}; + } // namespace v8_crdtp #endif // V8_CRDTP_STATUS_H_ diff --git a/third_party/inspector_protocol/inspector_protocol.gni b/third_party/inspector_protocol/inspector_protocol.gni index f4823847df..2d241d6546 100644 --- a/third_party/inspector_protocol/inspector_protocol.gni +++ b/third_party/inspector_protocol/inspector_protocol.gni @@ -37,6 +37,7 @@ template("inspector_protocol_generate") { "$inspector_protocol_dir/lib/Object_cpp.template", "$inspector_protocol_dir/lib/Object_h.template", "$inspector_protocol_dir/lib/Protocol_cpp.template", + "$inspector_protocol_dir/lib/ValueConversions_cpp.template", "$inspector_protocol_dir/lib/ValueConversions_h.template", "$inspector_protocol_dir/lib/Values_cpp.template", "$inspector_protocol_dir/lib/Values_h.template", diff --git a/third_party/inspector_protocol/lib/Forward_h.template b/third_party/inspector_protocol/lib/Forward_h.template index e2eef3042f..57961185d1 100644 --- a/third_party/inspector_protocol/lib/Forward_h.template +++ b/third_party/inspector_protocol/lib/Forward_h.template @@ -21,7 +21,7 @@ #include "{{config.crdtp.dir}}/error_support.h" #include "{{config.crdtp.dir}}/dispatch.h" #include "{{config.crdtp.dir}}/frontend_channel.h" -#include "{{config.crdtp.dir}}/glue.h" +#include "{{config.crdtp.dir}}/protocol_core.h" {% for namespace in config.protocol.namespace %} namespace {{namespace}} { @@ -42,7 +42,14 @@ class SerializedValue; class StringValue; class Value; +using {{config.crdtp.namespace}}::detail::PtrMaybe; +using {{config.crdtp.namespace}}::detail::ValueMaybe; + +template +using Maybe = {{config.crdtp.namespace}}::Maybe; + namespace detail { + template struct ArrayTypedef { typedef std::vector> type; }; @@ -62,32 +69,6 @@ struct ArrayTypedef { typedef std::vector type; }; template using Array = typename detail::ArrayTypedef::type; -namespace detail { -using {{config.crdtp.namespace}}::glue::detail::PtrMaybe; -using {{config.crdtp.namespace}}::glue::detail::ValueMaybe; - -template -struct MaybeTypedef { typedef PtrMaybe type; }; - -template <> -struct MaybeTypedef { typedef ValueMaybe type; }; - -template <> -struct MaybeTypedef { typedef ValueMaybe type; }; - -template <> -struct MaybeTypedef { typedef ValueMaybe type; }; - -template <> -struct MaybeTypedef { typedef ValueMaybe type; }; - -template <> -struct MaybeTypedef { typedef ValueMaybe type; }; -} // namespace detail - -template -using Maybe = typename detail::MaybeTypedef::type; - {% for namespace in config.protocol.namespace %} } // namespace {{namespace}} {% endfor %} diff --git a/third_party/inspector_protocol/lib/Object_h.template b/third_party/inspector_protocol/lib/Object_h.template index 8a1a2e39f8..f0dce5d1b7 100644 --- a/third_party/inspector_protocol/lib/Object_h.template +++ b/third_party/inspector_protocol/lib/Object_h.template @@ -28,7 +28,11 @@ public: std::unique_ptr toValue() const; std::unique_ptr clone() const; + private: + Object() = default; + friend struct {{config.crdtp.namespace}}::ProtocolTypeTraits, void>; + std::unique_ptr m_object; }; diff --git a/third_party/inspector_protocol/lib/ValueConversions_cpp.template b/third_party/inspector_protocol/lib/ValueConversions_cpp.template new file mode 100644 index 0000000000..36c8dcc356 --- /dev/null +++ b/third_party/inspector_protocol/lib/ValueConversions_cpp.template @@ -0,0 +1,123 @@ +// This file is generated by ValueConversions_cpp.template. + +// Copyright 2020 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 {{format_include(config.protocol.package, "Protocol")}} + +#include +#include +#include + +//#include "ValueConversions.h" +//#include "Values.h" + +{% for namespace in config.protocol.namespace %} +namespace {{namespace}} { +{% endfor %} + +{% for namespace in config.protocol.namespace %} +} // namespce +{% endfor %} + + +namespace {{config.crdtp.namespace}} { + +namespace { + +using {{"::".join(config.protocol.namespace)}}::Binary; +using {{"::".join(config.protocol.namespace)}}::Object; +using {{"::".join(config.protocol.namespace)}}::Value; +using {{"::".join(config.protocol.namespace)}}::String; +using {{"::".join(config.protocol.namespace)}}::DictionaryValue; +using {{"::".join(config.protocol.namespace)}}::FundamentalValue; +using {{"::".join(config.protocol.namespace)}}::StringValue; +using {{"::".join(config.protocol.namespace)}}::StringUtil; +//using {{"::".join(config.protocol.namespace)}}::EncodeString; + +std::unique_ptr ReadValue(DeserializerState* state) { + cbor::CBORTokenizer* tokenizer = state->tokenizer(); + switch (tokenizer->TokenTag()) { + case cbor::CBORTokenTag::TRUE_VALUE: + return FundamentalValue::create(true); + case cbor::CBORTokenTag::FALSE_VALUE: + return FundamentalValue::create(false); + case cbor::CBORTokenTag::NULL_VALUE: + return Value::null(); + case cbor::CBORTokenTag::INT32: + return FundamentalValue::create(tokenizer->GetInt32()); + case cbor::CBORTokenTag::DOUBLE: + return FundamentalValue::create(tokenizer->GetDouble()); + case cbor::CBORTokenTag::STRING8: { + const auto str = tokenizer->GetString8(); + return StringValue::create(StringUtil::fromUTF8(str.data(), str.size())); + } + case cbor::CBORTokenTag::STRING16: { + const auto str = tokenizer->GetString16WireRep(); + return StringValue::create(StringUtil::fromUTF16LE(reinterpret_cast(str.data()), str.size() / 2)); + } + case cbor::CBORTokenTag::ENVELOPE: { + const auto env = tokenizer->GetEnvelope(); + return Value::parseBinary(env.data(), env.size()); + } + // Intentionally not supported. + case cbor::CBORTokenTag::BINARY: + // Should not be encountered outside of envelope. + case cbor::CBORTokenTag::MAP_START: + case cbor::CBORTokenTag::ARRAY_START: + default: + state->RegisterError(Error::CBOR_UNSUPPORTED_VALUE); + return nullptr; + } +} + +} // namespace + +// static +bool ProtocolTypeTraits>::Deserialize( + DeserializerState* state, std::unique_ptr* value) { + auto result = ReadValue(state); + if (!result) + return false; + *value = std::move(result); + return true; +} + +// static +void ProtocolTypeTraits>::Serialize( + const std::unique_ptr& value, std::vector* bytes) { + value->AppendSerialized(bytes); +} + +// static +bool ProtocolTypeTraits>::Deserialize( + DeserializerState* state, std::unique_ptr* value) { + std::unique_ptr res; + if (!ProtocolTypeTraits>::Deserialize(state, &res)) + return false; + *value = DictionaryValue::cast(std::move(res)); + return true; +} + +// static +void ProtocolTypeTraits>::Serialize( + const std::unique_ptr& value, std::vector* bytes) { + value->AppendSerialized(bytes); +} + +// static +bool ProtocolTypeTraits>::Deserialize(DeserializerState* state, std::unique_ptr* value) { + auto res = DictionaryValue::create(); + if (ProtocolTypeTraits>::Deserialize(state, &res)) { + *value = std::make_unique(std::move(res)); + return true; + } + return false; +} + +void ProtocolTypeTraits>::Serialize(const std::unique_ptr& value, std::vector* bytes) { + value->AppendSerialized(bytes); +} + +} // namespace {{config.crdtp.namespace}} diff --git a/third_party/inspector_protocol/lib/ValueConversions_h.template b/third_party/inspector_protocol/lib/ValueConversions_h.template index 15961a6321..0fac003d90 100644 --- a/third_party/inspector_protocol/lib/ValueConversions_h.template +++ b/third_party/inspector_protocol/lib/ValueConversions_h.template @@ -259,8 +259,61 @@ struct ValueConversions { } }; +template struct ValueTypeConverter { + static std::unique_ptr FromValue(const protocol::Value& value) { + std::vector bytes; + value.AppendSerialized(&bytes); + return T::FromBinary(bytes.data(), bytes.size()); + } + + static std::unique_ptr ToValue(const T& obj) { + std::vector bytes; + obj.AppendSerialized(&bytes); + auto result = Value::parseBinary(bytes.data(), bytes.size()); + return DictionaryValue::cast(std::move(result)); + } +}; + {% for namespace in config.protocol.namespace %} } // namespace {{namespace}} {% endfor %} +namespace {{config.crdtp.namespace}} { + +template +struct ProtocolTypeTraits::value>::type> { + static void Serialize(const {{"::".join(config.protocol.namespace)}}::Value& value, std::vector* bytes) { + value.AppendSerialized(bytes); + } +}; + +template <> +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, std::unique_ptr<{{"::".join(config.protocol.namespace)}}::Value>* value); + static void Serialize(const std::unique_ptr<{{"::".join(config.protocol.namespace)}}::Value>& value, std::vector* bytes); +}; + +template <> +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, std::unique_ptr<{{"::".join(config.protocol.namespace)}}::DictionaryValue>* value); + static void Serialize(const std::unique_ptr<{{"::".join(config.protocol.namespace)}}::DictionaryValue>& value, std::vector* bytes); +}; + +// TODO(caseq): get rid of it, it's just a DictionaryValue really. +template <> +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, std::unique_ptr<{{"::".join(config.protocol.namespace)}}::Object>* value); + static void Serialize(const std::unique_ptr<{{"::".join(config.protocol.namespace)}}::Object>& value, std::vector* bytes); +}; + +template<> +struct ProtocolTypeTraits<{{"::".join(config.protocol.namespace)}}::Object> { + static void Serialize(const {{"::".join(config.protocol.namespace)}}::Object& value, std::vector* bytes) { + value.AppendSerialized(bytes); + } +}; + +} // namespace {{config.crdtp.namespace}} + #endif // !defined({{"_".join(config.protocol.namespace)}}_ValueConversions_h) diff --git a/third_party/inspector_protocol/lib/Values_cpp.template b/third_party/inspector_protocol/lib/Values_cpp.template index 09f3bed136..5b7db4a23c 100644 --- a/third_party/inspector_protocol/lib/Values_cpp.template +++ b/third_party/inspector_protocol/lib/Values_cpp.template @@ -62,7 +62,7 @@ class ValueParserHandler : public ParserHandler { DCHECK(stack_.back().is_dict); stack_.pop_back(); } - + void HandleArrayBegin() override { if (!status_.ok()) return; std::unique_ptr list = ListValue::create(); @@ -91,19 +91,19 @@ class ValueParserHandler : public ParserHandler { AddValueToParent( BinaryValue::create(Binary::fromSpan(bytes.data(), bytes.size()))); } - + void HandleDouble(double value) override { AddValueToParent(FundamentalValue::create(value)); } - + void HandleInt32(int32_t value) override { AddValueToParent(FundamentalValue::create(value)); } - + void HandleBool(bool value) override { AddValueToParent(FundamentalValue::create(value)); } - + void HandleNull() override { AddValueToParent(Value::null()); } @@ -318,7 +318,6 @@ void EncodeString(const String& s, std::vector* out) { } } } // namespace - void StringValue::AppendSerialized(std::vector* bytes) const { EncodeString(m_stringValue, bytes); } diff --git a/third_party/inspector_protocol/lib/Values_h.template b/third_party/inspector_protocol/lib/Values_h.template index 8514123fb8..8208009009 100644 --- a/third_party/inspector_protocol/lib/Values_h.template +++ b/third_party/inspector_protocol/lib/Values_h.template @@ -20,6 +20,11 @@ class ListValue; class DictionaryValue; class Value; +#define PROTOCOL_DISALLOW_COPY(ClassName) \ + private: \ + ClassName(const ClassName&) = delete; \ + ClassName& operator=(const ClassName&) = delete + class {{config.lib.export_macro}} Value : public Serializable { PROTOCOL_DISALLOW_COPY(Value); public: diff --git a/third_party/inspector_protocol/lib/base_string_adapter_cc.template b/third_party/inspector_protocol/lib/base_string_adapter_cc.template index b1ec1e475c..e503f5c23e 100644 --- a/third_party/inspector_protocol/lib/base_string_adapter_cc.template +++ b/third_party/inspector_protocol/lib/base_string_adapter_cc.template @@ -16,6 +16,13 @@ #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "{{config.crdtp.dir}}/cbor.h" +#include "{{config.crdtp.dir}}/protocol_core.h" + +using namespace {{config.crdtp.namespace}}; + +using {{"::".join(config.protocol.namespace)}}::Binary; +using {{"::".join(config.protocol.namespace)}}::String; +using {{"::".join(config.protocol.namespace)}}::StringUtil; {% for namespace in config.protocol.namespace %} namespace {{namespace}} { @@ -138,6 +145,28 @@ String StringUtil::fromUTF16LE(const uint16_t* data, size_t length) { return utf8; } +bool StringUtil::ReadString(DeserializerState* state, String* value) { + auto* tokenizer = state->tokenizer(); + if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING8) { + const auto str = tokenizer->GetString8(); + *value = StringUtil::fromUTF8(str.data(), str.size()); + return true; + } + if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING16) { + const auto str = tokenizer->GetString16WireRep(); + *value = StringUtil::fromUTF16LE(reinterpret_cast(str.data()), str.size() / 2); + return true; + } + state->RegisterError(Error::BINDINGS_STRING_VALUE_EXPECTED); + return false; +} + +void StringUtil::WriteString(const String& str, std::vector* bytes) { + cbor::EncodeString8(span(StringUtil::CharactersUTF8(str), + StringUtil::CharacterCount(str)), + bytes); +} + Binary::Binary() : bytes_(new base::RefCountedBytes) {} Binary::Binary(const Binary& binary) : bytes_(binary.bytes_) {} Binary::Binary(scoped_refptr bytes) : bytes_(bytes) {} @@ -190,3 +219,31 @@ Binary Binary::fromSpan(const uint8_t* data, size_t size) { {% for namespace in config.protocol.namespace %} } // namespace {{namespace}} {% endfor %} + +namespace {{config.crdtp.namespace}} { + +// static +bool ProtocolTypeTraits::Deserialize(DeserializerState* state, Binary* value) { + auto* tokenizer = state->tokenizer(); + if (tokenizer->TokenTag() == cbor::CBORTokenTag::BINARY) { + const span bin = tokenizer->GetBinary(); + *value = Binary::fromSpan(bin.data(), bin.size()); + return true; + } + if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING8) { + const auto str_span = tokenizer->GetString8(); + String str = StringUtil::fromUTF8(str_span.data(), str_span.size()); + bool success = false; + *value = Binary::fromBase64(str, &success); + return success; + } + state->RegisterError(Error::BINDINGS_BINARY_VALUE_EXPECTED); + return false; +} + +// static +void ProtocolTypeTraits::Serialize(const Binary& value, std::vector* bytes) { + value.AppendSerialized(bytes); +} + +} // namespace {{config.crdtp.namespace}} \ No newline at end of file diff --git a/third_party/inspector_protocol/lib/base_string_adapter_h.template b/third_party/inspector_protocol/lib/base_string_adapter_h.template index ff40aba363..183ee35502 100644 --- a/third_party/inspector_protocol/lib/base_string_adapter_h.template +++ b/third_party/inspector_protocol/lib/base_string_adapter_h.template @@ -15,6 +15,7 @@ #include "base/macros.h" #include "base/memory/ref_counted_memory.h" #include "{{config.crdtp.dir}}/serializable.h" +#include "{{config.crdtp.dir}}/protocol_core.h" {% if config.lib.export_header %} #include "{{config.lib.export_header}}" @@ -24,6 +25,10 @@ namespace base { class Value; } +namespace {{config.crdtp.namespace}} { +class DeserializerState; +} + {% for namespace in config.protocol.namespace %} namespace {{namespace}} { {% endfor %} @@ -46,6 +51,9 @@ class {{config.lib.export_macro}} StringUtil { } static const uint16_t* CharactersUTF16(const String& s) { return nullptr; } static size_t CharacterCount(const String& s) { return s.size(); } + + static bool ReadString({{config.crdtp.namespace}}::DeserializerState* state, String* str); + static void WriteString(const String& str, std::vector* bytes); }; // A read-only sequence of uninterpreted bytes with reference-counted storage. @@ -81,4 +89,24 @@ std::unique_ptr toBaseValue(Value* value, int depth); } // namespace {{namespace}} {% endfor %} +namespace {{config.crdtp.namespace}} { + +template <> +struct ProtocolTypeTraits<{{"::".join(config.protocol.namespace)}}::String> { + static bool Deserialize(DeserializerState* state, {{"::".join(config.protocol.namespace)}}::String* value) { + return {{"::".join(config.protocol.namespace)}}::StringUtil::ReadString(state, value); + } + static void Serialize(const {{"::".join(config.protocol.namespace)}}::String& value, std::vector* bytes) { + {{"::".join(config.protocol.namespace)}}::StringUtil::WriteString(value, bytes); + } +}; + +template <> +struct ProtocolTypeTraits<{{"::".join(config.protocol.namespace)}}::Binary> { + static bool Deserialize(DeserializerState* state, {{"::".join(config.protocol.namespace)}}::Binary* value); + static void Serialize(const {{"::".join(config.protocol.namespace)}}::Binary& value, std::vector* bytes); +}; + +} // {{config.crdtp.namespace}} + #endif // !defined({{"_".join(config.protocol.namespace)}}_BASE_STRING_ADAPTER_H) diff --git a/third_party/inspector_protocol/roll.py b/third_party/inspector_protocol/roll.py index 85765d4350..b807257106 100755 --- a/third_party/inspector_protocol/roll.py +++ b/third_party/inspector_protocol/roll.py @@ -31,13 +31,16 @@ FILES_TO_SYNC = [ 'crdtp/find_by_first.h', 'crdtp/find_by_first_test.cc', 'crdtp/frontend_channel.h', - 'crdtp/glue.h', - 'crdtp/glue_test.cc', + 'crdtp/maybe.h', + 'crdtp/maybe_test.cc', 'crdtp/json.cc', 'crdtp/json.h', 'crdtp/json_platform.h', 'crdtp/json_test.cc', 'crdtp/parser_handler.h', + 'crdtp/protocol_core_test.cc', + 'crdtp/protocol_core.cc', + 'crdtp/protocol_core.h', 'crdtp/serializable.h', 'crdtp/serializable.cc', 'crdtp/serializable_test.cc', diff --git a/third_party/inspector_protocol/templates/Imported_h.template b/third_party/inspector_protocol/templates/Imported_h.template index bb1dcf4a20..47f27ac108 100644 --- a/third_party/inspector_protocol/templates/Imported_h.template +++ b/third_party/inspector_protocol/templates/Imported_h.template @@ -14,6 +14,53 @@ #include {{format_include(config.imported.package, domain.domain)}} {% endif %} +#ifndef {{"_".join(config.protocol.namespace)}}_imported_imported_h + +namespace {{config.crdtp.namespace}} { + +template +struct ProtocolTypeTraits< + std::unique_ptr, + typename std::enable_if< + std::is_base_of<{{"::".join(config.imported.namespace)}}::Exported, T>::value>::type> { + static bool Deserialize(DeserializerState* state, std::unique_ptr* value) { + if (state->tokenizer()->TokenTag() != cbor::CBORTokenTag::ENVELOPE) { + state->RegisterError(Error::CBOR_INVALID_ENVELOPE); + return false; + } + span env = state->tokenizer()->GetEnvelope(); + auto res = T::fromBinary(env.data(), env.size()); + if (!res) { + // TODO(caseq): properly plumb an error rather than returning a bogus code. + state->RegisterError(Error::MESSAGE_MUST_BE_AN_OBJECT); + return false; + } + *value = std::move(res); + return true; + } + static void Serialize(const std::unique_ptr& value, std::vector* bytes) { + // Use virtual method, so that outgoing protocol objects could be retained + // by a pointer to ProtocolObject. + value->AppendSerialized(bytes); + } +}; + +template +struct ProtocolTypeTraits< + T, + typename std::enable_if< + std::is_base_of<{{"::".join(config.imported.namespace)}}::Exported, T>::value>::type> { + static void Serialize(const T& value, std::vector* bytes) { + // Use virtual method, so that outgoing protocol objects could be retained + // by a pointer to ProtocolObject. + value.AppendSerialized(bytes); + } +}; + +} // namespace {{config.crdtp.namespace}} + +#endif // {{"_".join(config.protocol.namespace)}}_imported_imported_h + {% for namespace in config.protocol.namespace %} namespace {{namespace}} { {% endfor %} diff --git a/third_party/inspector_protocol/templates/TypeBuilder_cpp.template b/third_party/inspector_protocol/templates/TypeBuilder_cpp.template index f0a3d3ccaf..0139ee2f7d 100644 --- a/third_party/inspector_protocol/templates/TypeBuilder_cpp.template +++ b/third_party/inspector_protocol/templates/TypeBuilder_cpp.template @@ -10,7 +10,6 @@ #include "{{config.crdtp.dir}}/cbor.h" #include "{{config.crdtp.dir}}/find_by_first.h" -#include "{{config.crdtp.dir}}/serializer_traits.h" #include "{{config.crdtp.dir}}/span.h" {% for namespace in config.protocol.namespace %} @@ -18,6 +17,9 @@ namespace {{namespace}} { {% endfor %} namespace {{domain.domain}} { +using {{config.crdtp.namespace}}::DeserializerState; +using {{config.crdtp.namespace}}::ProtocolTypeTraits; + // ------------- Enum values from types. const char Metainfo::domainName[] = "{{domain.domain}}"; @@ -43,6 +45,7 @@ const char* {{ literal | dash_to_camelcase}} = "{{literal}}"; } // namespace API {% endif %} {% endif %} + {% for property in type.properties %} {% if "enum" in property %} @@ -52,80 +55,28 @@ const char* {{type.id}}::{{property.name | to_title_case}}Enum::{{literal | dash {% endif %} {% endfor %} {% if not (type.type == "object") or not ("properties" in type) %}{% continue %}{% endif %} - -std::unique_ptr<{{type.id}}> {{type.id}}::fromValue(protocol::Value* value, ErrorSupport* errors) -{ - if (!value || value->type() != protocol::Value::TypeObject) { - errors->AddError("object expected"); - return nullptr; - } - - std::unique_ptr<{{type.id}}> result(new {{type.id}}()); - protocol::DictionaryValue* object = DictionaryValue::cast(value); - errors->Push(); - {% for property in type.properties %} - protocol::Value* {{property.name}}Value = object->get("{{property.name}}"); +V8_CRDTP_BEGIN_DESERIALIZER({{type.id}}) + {% for property in type.properties | sort(attribute = 'name', case_sensitive=True) %} {% if property.optional %} - if ({{property.name}}Value) { - errors->SetName("{{property.name}}"); - result->m_{{property.name}} = ValueConversions<{{protocol.resolve_type(property).raw_type}}>::fromValue({{property.name}}Value, errors); - } + V8_CRDTP_DESERIALIZE_FIELD_OPT("{{property.name}}", m_{{property.name}}), {% else %} - errors->SetName("{{property.name}}"); - result->m_{{property.name}} = ValueConversions<{{protocol.resolve_type(property).raw_type}}>::fromValue({{property.name}}Value, errors); + V8_CRDTP_DESERIALIZE_FIELD("{{property.name}}", m_{{property.name}}), {% endif %} - {% endfor %} - errors->Pop(); - if (!errors->Errors().empty()) - return nullptr; - return result; -} + {% endfor %} +V8_CRDTP_END_DESERIALIZER() -std::unique_ptr {{type.id}}::toValue() const -{ - std::unique_ptr result = DictionaryValue::create(); - {% for property in type.properties %} - {% set property_type = protocol.resolve_type(property) %} - {% set property_field = "m_" + property.name %} - {% if property.optional %} - if ({{property_field}}.isJust()) - result->setValue("{{property.name}}", ValueConversions<{{property_type.raw_type}}>::toValue({{property_field}}.fromJust())); - {% else %} - result->setValue("{{property.name}}", ValueConversions<{{property_type.raw_type}}>::toValue({{property_type.to_raw_type % property_field}})); - {% endif %} - {% endfor %} - return result; -} +V8_CRDTP_BEGIN_SERIALIZER({{type.id}}) + {% for property in type.properties %} + V8_CRDTP_SERIALIZE_FIELD("{{property.name}}", m_{{property.name}}); + {% endfor %} +V8_CRDTP_END_SERIALIZER(); -void {{type.id}}::AppendSerialized(std::vector* out) const { - {{config.crdtp.namespace}}::cbor::EnvelopeEncoder envelope_encoder; - envelope_encoder.EncodeStart(out); - out->push_back({{config.crdtp.namespace}}::cbor::EncodeIndefiniteLengthMapStart()); - {% for property in type.properties %} - {% set property_field = "m_" + property.name %} - {{config.crdtp.namespace}}::SerializeField({{config.crdtp.namespace}}::SpanFrom("{{property.name}}"), {{property_field}}, out); - {% endfor %} - out->push_back({{config.crdtp.namespace}}::cbor::EncodeStop()); - envelope_encoder.EncodeStop(out); -} - -std::unique_ptr<{{type.id}}> {{type.id}}::clone() const -{ - ErrorSupport errors; - return fromValue(toValue().get(), &errors); -} {% if protocol.is_exported(domain.domain, type.id) %} - // static std::unique_ptr API::{{type.id}}::fromBinary(const uint8_t* data, size_t length) { - ErrorSupport errors; - std::unique_ptr value = Value::parseBinary(data, length); - if (!value) - return nullptr; - return protocol::{{domain.domain}}::{{type.id}}::fromValue(value.get(), &errors); + return protocol::{{domain.domain}}::{{type.id}}::FromBinary(data, length); } - {% endif %} {% endfor %} @@ -174,20 +125,11 @@ void Frontend::{{event.name | to_method_case}}( if (!frontend_channel_) return; {% if event.parameters %} - std::unique_ptr<{{event.name | to_title_case}}Notification> messageData = {{event.name | to_title_case}}Notification::{{"create" | to_method_case}}() + {{config.crdtp.namespace}}::ObjectSerializer serializer; {% for parameter in event.parameters %} - {% if not "optional" in parameter %} - .{{"set" | to_method_case}}{{parameter.name | to_title_case}}({{protocol.resolve_type(parameter).to_pass_type % parameter.name}}) - {% endif %} + serializer.AddField({{config.crdtp.namespace}}::MakeSpan("{{parameter.name}}"), {{parameter.name}}); {% endfor %} - .{{ "build" | to_method_case }}(); - {% for parameter in event.parameters %} - {% if "optional" in parameter %} - if ({{parameter.name}}.isJust()) - messageData->{{"set" | to_method_case}}{{parameter.name | to_title_case}}(std::move({{parameter.name}}).takeJust()); - {% endif %} - {% endfor %} - frontend_channel_->SendProtocolNotification({{config.crdtp.namespace}}::CreateNotification("{{domain.domain}}.{{event.name}}", std::move(messageData))); + frontend_channel_->SendProtocolNotification({{config.crdtp.namespace}}::CreateNotification("{{domain.domain}}.{{event.name}}", serializer.Finish())); {% else %} frontend_channel_->SendProtocolNotification({{config.crdtp.namespace}}::CreateNotification("{{domain.domain}}.{{event.name}}")); {% endif %} @@ -213,14 +155,14 @@ public: , m_backend(backend) {} ~DomainDispatcherImpl() override { } - using CallHandler = void (DomainDispatcherImpl::*)(const {{config.crdtp.namespace}}::Dispatchable& dispatchable, DictionaryValue* params, ErrorSupport* errors); + using CallHandler = void (DomainDispatcherImpl::*)(const {{config.crdtp.namespace}}::Dispatchable& dispatchable); std::function Dispatch({{config.crdtp.namespace}}::span command_name) override; {% for command in domain.commands %} {% if "redirect" in command %}{% continue %}{% endif %} {% if not protocol.generate_command(domain.domain, command.name) %}{% continue %}{% endif %} - void {{command.name}}(const {{config.crdtp.namespace}}::Dispatchable& dispatchable, DictionaryValue* params, ErrorSupport* errors); + void {{command.name}}(const {{config.crdtp.namespace}}::Dispatchable& dispatchable); {% endfor %} protected: Backend* m_backend; @@ -252,13 +194,9 @@ DomainDispatcherImpl::CallHandler CommandByName({{config.crdtp.namespace}}::span std::function DomainDispatcherImpl::Dispatch({{config.crdtp.namespace}}::span command_name) { CallHandler handler = CommandByName(command_name); if (!handler) return nullptr; - return [this, handler](const {{config.crdtp.namespace}}::Dispatchable& dispatchable){ - std::unique_ptr params = - DictionaryValue::cast(protocol::Value::parseBinary(dispatchable.Params().data(), - dispatchable.Params().size())); - ErrorSupport errors; - errors.Push(); - (this->*handler)(dispatchable, params.get(), &errors); + + return [this, handler](const {{config.crdtp.namespace}}::Dispatchable& dispatchable) { + (this->*handler)(dispatchable); }; } @@ -284,16 +222,11 @@ public: {%- if not loop.last -%}, {% endif -%} {%- endfor -%}) override { - std::vector result_buffer; - {{config.crdtp.namespace}}::cbor::EnvelopeEncoder envelope_encoder; - envelope_encoder.EncodeStart(&result_buffer); - result_buffer.push_back({{config.crdtp.namespace}}::cbor::EncodeIndefiniteLengthMapStart()); + {{config.crdtp.namespace}}::ObjectSerializer serializer; {% for parameter in command.returns %} - {{config.crdtp.namespace}}::SerializeField({{config.crdtp.namespace}}::SpanFrom("{{parameter.name}}"), {{parameter.name}}, &result_buffer); + serializer.AddField({{config.crdtp.namespace}}::MakeSpan("{{parameter.name}}"), {{parameter.name}}); {% endfor %} - result_buffer.push_back({{config.crdtp.namespace}}::cbor::EncodeStop()); - envelope_encoder.EncodeStop(&result_buffer); - sendIfActive({{config.crdtp.namespace}}::Serializable::From(std::move(result_buffer)), DispatchResponse::Success()); + sendIfActive(serializer.Finish(), DispatchResponse::Success()); } void fallThrough() override @@ -309,26 +242,45 @@ public: }; {% endif %} -void DomainDispatcherImpl::{{command.name}}(const {{config.crdtp.namespace}}::Dispatchable& dispatchable, DictionaryValue* params, ErrorSupport* errors) -{ +namespace { + {% if "parameters" in command %} - // Prepare input parameters. +struct {{command.name}}Params : public {{config.crdtp.namespace}}::DeserializableProtocolObject<{{command.name}}Params> { {% for parameter in command.parameters %} {% set parameter_type = protocol.resolve_type(parameter) %} - protocol::Value* {{parameter.name}}Value = params ? params->get("{{parameter.name}}") : nullptr; {% if parameter.optional %} - Maybe<{{parameter_type.raw_type}}> in_{{parameter.name}}; - if ({{parameter.name}}Value) { - errors->SetName("{{parameter.name}}"); - in_{{parameter.name}} = ValueConversions<{{parameter_type.raw_type}}>::fromValue({{parameter.name}}Value, errors); - } + Maybe<{{parameter_type.raw_type}}> {{parameter.name}}; {% else %} - errors->SetName("{{parameter.name}}"); - {{parameter_type.type}} in_{{parameter.name}} = ValueConversions<{{parameter_type.raw_type}}>::fromValue({{parameter.name}}Value, errors); + {{parameter_type.type}} {{parameter.name}}; {% endif %} {% endfor %} - if (MaybeReportInvalidParams(dispatchable, *errors)) return; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER({{command.name}}Params) + {% for parameter in command.parameters | sort(attribute = 'name', case_sensitive=True) %} + {% if parameter.optional %} + V8_CRDTP_DESERIALIZE_FIELD_OPT("{{parameter.name}}", {{parameter.name}}), + {% else %} + V8_CRDTP_DESERIALIZE_FIELD("{{parameter.name}}", {{parameter.name}}), + {% endif %} + {% endfor %} +V8_CRDTP_END_DESERIALIZER() + {% endif %} + +} // namespace + +void DomainDispatcherImpl::{{command.name}}(const {{config.crdtp.namespace}}::Dispatchable& dispatchable) +{ + // Prepare input parameters. + {% if "parameters" in command %} + auto deserializer = {{config.crdtp.namespace}}::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + {{command.name}}Params params; + {{command.name}}Params::Deserialize(&deserializer, ¶ms); + if (MaybeReportInvalidParams(dispatchable, deserializer)) + return; {% endif %} + {% if "returns" in command and not protocol.is_async_command(domain.domain, command.name) %} // Declare output parameters. {% for parameter in command.returns %} @@ -346,9 +298,9 @@ void DomainDispatcherImpl::{{command.name}}(const {{config.crdtp.namespace}}::Di {%- for parameter in command.parameters -%} {%- if not loop.first -%}, {% endif -%} {%- if "optional" in parameter -%} - std::move(in_{{parameter.name}}) + std::move(params.{{parameter.name}}) {%- else -%} - {{protocol.resolve_type(parameter).to_pass_type % ("in_" + parameter.name)}} + {{protocol.resolve_type(parameter).to_pass_type % ("params." + parameter.name)}} {%- endif -%} {%- endfor %} {%- if "returns" in command %} @@ -363,18 +315,17 @@ void DomainDispatcherImpl::{{command.name}}(const {{config.crdtp.namespace}}::Di } {% if "returns" in command %} if (weak->get()) { - std::vector result; + std::unique_ptr<{{config.crdtp.namespace}}::Serializable> result; if (response.IsSuccess()) { - {{config.crdtp.namespace}}::cbor::EnvelopeEncoder envelope_encoder; - envelope_encoder.EncodeStart(&result); - result.push_back({{config.crdtp.namespace}}::cbor::EncodeIndefiniteLengthMapStart()); + {{config.crdtp.namespace}}::ObjectSerializer serializer; {% for parameter in command.returns %} - {{config.crdtp.namespace}}::SerializeField({{config.crdtp.namespace}}::SpanFrom("{{parameter.name}}"), out_{{parameter.name}}, &result); + serializer.AddField({{config.crdtp.namespace}}::MakeSpan("{{parameter.name}}"), out_{{parameter.name}}); {% endfor %} - result.push_back({{config.crdtp.namespace}}::cbor::EncodeStop()); - envelope_encoder.EncodeStop(&result); + result = serializer.Finish(); + } else { + result = Serializable::From({}); } - weak->get()->sendResponse(dispatchable.CallId(), response, {{config.crdtp.namespace}}::Serializable::From(std::move(result))); + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); } {% else %} if (weak->get()) @@ -386,9 +337,9 @@ void DomainDispatcherImpl::{{command.name}}(const {{config.crdtp.namespace}}::Di {%- for property in command.parameters -%} {%- if not loop.first -%}, {% endif -%} {%- if "optional" in property -%} - std::move(in_{{property.name}}) + std::move(params.{{property.name}}) {%- else -%} - {{protocol.resolve_type(property).to_pass_type % ("in_" + property.name)}} + {{protocol.resolve_type(property).to_pass_type % ("params." + property.name)}} {%- endif -%} {%- endfor -%} {%- if command.parameters -%}, {% endif -%} diff --git a/third_party/inspector_protocol/templates/TypeBuilder_h.template b/third_party/inspector_protocol/templates/TypeBuilder_h.template index bc3998e4a2..c2e21a2d31 100644 --- a/third_party/inspector_protocol/templates/TypeBuilder_h.template +++ b/third_party/inspector_protocol/templates/TypeBuilder_h.template @@ -26,8 +26,6 @@ namespace {{namespace}} { {% endfor %} namespace {{domain.domain}} { - -// ------------- Forward and enum declarations. {% for type in domain.types %} {% if not protocol.generate_type(domain.domain, type.id) %}{% continue %}{% endif %} {% if type.type == "object" %} @@ -40,6 +38,8 @@ using {{type.id}} = Object; using {{type.id}} = {{protocol.resolve_type(type).type}}; {% endif %} {% endfor %} + +// ------------- Forward and enum declarations. {% for type in domain.types %} {% if not protocol.generate_type(domain.domain, type.id) %}{% continue %}{% endif %} {% if "enum" in type %} @@ -71,11 +71,9 @@ namespace {{param.name | to_title_case}}Enum { {% if not protocol.generate_type(domain.domain, type.id) %}{% continue %}{% endif %} {% if not (type.type == "object") or not ("properties" in type) %}{% continue %}{% endif %} -class {{config.protocol.export_macro}} {{type.id}} : public Serializable{% if protocol.is_exported(domain.domain, type.id) %}, public API::{{type.id}}{% endif %}{ - PROTOCOL_DISALLOW_COPY({{type.id}}); +class {{config.protocol.export_macro}} {{type.id}} : public ::{{config.crdtp.namespace}}::ProtocolObject<{{type.id}}>{% if protocol.is_exported(domain.domain, type.id) %}, + public API::{{type.id}}{% endif %} { public: - static std::unique_ptr<{{type.id}}> fromValue(protocol::Value* value, ErrorSupport* errors); - ~{{type.id}}() override { } {% for property in type.properties %} {% set property_type = protocol.resolve_type(property) %} @@ -99,10 +97,6 @@ public: void {{"set" | to_method_case}}{{property_name}}({{property_type.pass_type}} value) { {{property_field}} = {{property_type.to_rvalue % "value"}}; } {% endfor %} - std::unique_ptr toValue() const; - void AppendSerialized(std::vector* out) const override; - std::unique_ptr<{{type.id}}> clone() const; - template class {{type.id}}Builder { public: @@ -160,6 +154,8 @@ public: } private: + DECLARE_SERIALIZATION_SUPPORT(); + {{type.id}}() { {% for property in type.properties %}