| //* Copyright 2017 The Dawn & Tint Authors |
| //* |
| //* Redistribution and use in source and binary forms, with or without |
| //* modification, are permitted provided that the following conditions are met: |
| //* |
| //* 1. Redistributions of source code must retain the above copyright notice, this |
| //* list of conditions and the following disclaimer. |
| //* |
| //* 2. Redistributions in binary form must reproduce the above copyright notice, |
| //* this list of conditions and the following disclaimer in the documentation |
| //* and/or other materials provided with the distribution. |
| //* |
| //* 3. Neither the name of the copyright holder nor the names of its |
| //* contributors may be used to endorse or promote products derived from |
| //* this software without specific prior written permission. |
| //* |
| //* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| //* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| //* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| //* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| //* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| //* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| //* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| //* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| //* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| //* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include "dawn/wire/WireCmd_autogen.h" |
| |
| #include "dawn/common/Assert.h" |
| #include "dawn/common/Log.h" |
| #include "dawn/common/Numeric.h" |
| #include "dawn/wire/BufferConsumer_impl.h" |
| #include "dawn/wire/Wire.h" |
| |
| #include <algorithm> |
| #include <cstring> |
| #include <limits> |
| |
| #if defined(__GNUC__) || defined(__clang__) |
| // error: 'offsetof' within non-standard-layout type 'wgpu::XXX' is conditionally-supported |
| #pragma GCC diagnostic ignored "-Winvalid-offsetof" |
| #endif |
| |
| //* Helper macros so that the main [de]serialization functions can be written in a generic manner. |
| |
| //* Outputs an rvalue that's the number of elements a pointer member points to. |
| {%- macro member_length(member, record_accessor) -%} |
| {%- if member.length == "constant" -%} |
| {{member.constant_length}}u |
| {%- else -%} |
| {{record_accessor}}{{as_varName(member.length.name)}} |
| {%- endif -%} |
| {%- endmacro -%} |
| |
| //* Outputs the type that will be used on the wire for the member |
| {%- macro member_transfer_type(member) -%} |
| {%- if member.type.category == "object" -%} |
| ObjectId |
| {%- elif member.type.category == "structure" -%} |
| {{as_cType(member.type.name)}}Transfer |
| {%- elif member.type.category == "bitmask" -%} |
| {{as_cType(member.type.name)}}Flags |
| {%- elif as_cType(member.type.name) == "size_t" -%} |
| {{as_cType(types["uint64_t"].name)}} |
| {%- else -%} |
| {%- do assert(member.type.is_wire_transparent, 'wire transparent') -%} |
| {{as_cType(member.type.name)}} |
| {%- endif -%} |
| {%- endmacro -%} |
| |
| //* Outputs the size of one element of the type that will be used on the wire for the member |
| {%- macro member_transfer_sizeof(member) -%} |
| sizeof({{member_transfer_type(member)}}) |
| {%- endmacro -%} |
| |
| //* Outputs the serialization code to put `in` in `out` |
| {%- macro serialize_member(member, in, out) -%} |
| {%- if member.type.category == "object" -%} |
| {%- set Optional = "Optional" if member.optional else "" -%} |
| WIRE_TRY(provider.Get{{Optional}}Id({{in}}, &{{out}})); |
| {%- elif member.type.category == "structure" -%} |
| //* Do not memcpy or we may serialize padding bytes which can leak information across a |
| //* trusted boundary. |
| {%- set Provider = ", provider" if member.type.may_have_dawn_object else "" -%} |
| WIRE_TRY({{as_cType(member.type.name)}}Serialize({{in}}, &{{out}}, buffer{{Provider}})); |
| {%- elif member.type.category == "function pointer" or member.type.name.get() == "void *" -%} |
| //* Function pointers and explicit "void *" types (i.e. userdata) cannot be serialized. |
| if ({{in}} != nullptr) return WireResult::FatalError; |
| {%- else -%} |
| {{out}} = {{in}}; |
| {%- endif -%} |
| {%- endmacro -%} |
| |
| //* Outputs the deserialization code to put `in` in `out` |
| {%- macro deserialize_member(member, in, out) -%} |
| {%- if member.type.category == "object" -%} |
| {%- set Optional = "Optional" if member.optional else "" -%} |
| WIRE_TRY(resolver.Get{{Optional}}FromId({{in}}, &{{out}})); |
| {%- elif member.type.category == "structure" %} |
| {% if member.type.is_wire_transparent %} |
| static_assert(sizeof({{out}}) == sizeof({{in}}), "Deserialize memcpy size must match."); |
| memcpy(&{{out}}, const_cast<const {{member_transfer_type(member)}}*>(&{{in}}), {{member_transfer_sizeof(member)}}); |
| {%- else %} |
| WIRE_TRY({{as_cType(member.type.name)}}Deserialize(&{{out}}, &{{in}}, deserializeBuffer, allocator |
| {%- if member.type.may_have_dawn_object -%} |
| , resolver |
| {%- endif -%} |
| )); |
| {%- endif -%} |
| {%- elif member.type.category == "function pointer" or member.type.name.get() == "void *" %} |
| //* Function pointers and explicit "void *" types (i.e. userdata) cannot be deserialized. |
| {{out}} = nullptr; |
| {%- elif member.type.name.get() == "size_t" -%} |
| //* Deserializing into size_t requires check that the uint64_t used on the wire won't narrow. |
| if ({{in}} > std::numeric_limits<size_t>::max()) return WireResult::FatalError; |
| {{out}} = checked_cast<size_t>({{in}}); |
| {%- else -%} |
| static_assert(sizeof({{out}}) >= sizeof({{in}}), "Deserialize assignment may not narrow."); |
| {{out}} = {{in}}; |
| {%- endif -%} |
| {%- endmacro -%} |
| |
| //* The main [de]serialization macro |
| //* Methods are very similar to structures that have one member corresponding to each arguments. |
| //* This macro takes advantage of the similarity to output [de]serialization code for a record |
| //* that is either a structure or a method, with some special cases for each. |
| {%- macro write_record_serialization_helpers(record, name, members, is_cmd=False, is_return_command=False) -%} |
| {%- set Return = "Return" if is_return_command else "" -%} |
| {%- set Cmd = "Cmd" if is_cmd else "" -%} |
| {%- set Inherits = " : CmdHeader" if is_cmd else "" %} |
| |
| //* Structure for the wire format of each of the records. Members that are values |
| //* are embedded directly in the structure. Other members are assumed to be in the |
| //* memory directly following the structure in the buffer. |
| struct {{Return}}{{name}}Transfer{{Inherits}} { |
| static_assert({{[is_cmd, record.extensible, record.chained].count(True)}} <= 1, |
| "Record must be at most one of is_cmd, extensible, and chained."); |
| {% if is_cmd %} |
| //* Start the transfer structure with the command ID, so that casting to WireCmd gives the ID. |
| {{Return}}WireCmd commandId; |
| {% elif record.extensible %} |
| bool hasNextInChain; |
| {% elif record.chained %} |
| WGPUChainedStructTransfer chain; |
| {% endif %} |
| |
| {% for member in members %} |
| //* Function pointers and explicit "void *" types (i.e. userdata) do not get serialized. |
| {% if member.type.category == "function pointer" or member.type.name.get() == "void *" %} |
| {% continue %} |
| {% endif %} |
| //* Value types are directly in the command, objects being replaced with their IDs. |
| {% if member.annotation == "value" %} |
| {{member_transfer_type(member)}} {{as_varName(member.name)}}; |
| {% continue %} |
| {% endif %} |
| //* Members of type "const char *" have their length embedded directly in the command. |
| {% if member.length == "strlen" %} |
| uint64_t {{as_varName(member.name)}}Strlen; |
| {% endif %} |
| //* Optional members additionally come with a boolean to indicate whether they were set. |
| {% if member.optional and member.type.category != "object" %} |
| bool has_{{as_varName(member.name)}}; |
| {% endif %} |
| {% endfor %} |
| }; |
| |
| {% if is_cmd %} |
| static_assert(offsetof({{Return}}{{name}}Transfer, commandSize) == 0); |
| static_assert(offsetof({{Return}}{{name}}Transfer, commandId) == sizeof(CmdHeader)); |
| {% endif -%} |
| |
| {% if record.chained %} |
| static_assert(offsetof({{Return}}{{name}}Transfer, chain) == 0); |
| {% endif %} |
| |
| //* Returns the required transfer size for `record` in addition to the transfer structure. |
| DAWN_DECLARE_UNUSED size_t {{Return}}{{name}}GetExtraRequiredSize([[maybe_unused]] const {{Return}}{{name}}{{Cmd}}& record) { |
| size_t result = 0; |
| |
| //* Gather how much space will be needed for the extension chain. |
| {% if record.extensible %} |
| if (record.nextInChain != nullptr) { |
| result += GetChainedStructExtraRequiredSize(record.nextInChain); |
| } |
| {% endif %} |
| //* Gather space needed for pointer members. |
| {% for member in members %} |
| {%- set memberName = as_varName(member.name) -%} |
| //* Skip size computation if we are skipping serialization. |
| {% if member.skip_serialize %} |
| {% continue %} |
| {% endif %} |
| //* Special handling of const char* that have their length embedded directly in the command. |
| {% if member.length == "strlen" %} |
| {% if member.optional %} |
| if (record.{{memberName}} != nullptr) { |
| result += Align(std::strlen(record.{{memberName}}), kWireBufferAlignment); |
| } |
| {% else %} |
| DAWN_ASSERT(record.{{memberName}} != nullptr); |
| result += Align(std::strlen(record.{{memberName}}), kWireBufferAlignment); |
| {% endif %} |
| {% continue %} |
| {% endif %} |
| //* Normal handling for pointer members and structs. |
| {% if member.annotation != "value" or member.type.category == "structure" %} |
| {% if member.type.category != "object" and member.optional %} |
| if (record.{{as_varName(member.name)}} != nullptr) { |
| {% else %} |
| { |
| {% endif %} |
| {% if member.annotation != "value" %} |
| {% do assert(member.annotation != "const*const*", "const*const* not valid here") %} |
| auto memberLength = {{member_length(member, "record.")}}; |
| auto size = WireAlignSizeofN<{{member_transfer_type(member)}}>(memberLength); |
| DAWN_ASSERT(size); |
| result += *size; |
| //* Structures might contain more pointers so we need to add their extra size as well. |
| {% if member.type.category == "structure" %} |
| for (decltype(memberLength) i = 0; i < memberLength; ++i) { |
| {% do assert(member.annotation == "const*" or member.annotation == "*", "unhandled annotation: " + member.annotation)%} |
| result += {{as_cType(member.type.name)}}GetExtraRequiredSize(record.{{as_varName(member.name)}}[i]); |
| } |
| {% endif %} |
| {% elif member.type.category == "structure" %} |
| result += {{as_cType(member.type.name)}}GetExtraRequiredSize(record.{{as_varName(member.name)}}); |
| {% endif %} |
| } |
| {% endif %} |
| {% endfor %} |
| return result; |
| } |
| // GetExtraRequiredSize isn't used for structures that are value members of other structures |
| // because we assume they cannot contain pointers themselves. |
| DAWN_UNUSED_FUNC({{Return}}{{name}}GetExtraRequiredSize); |
| |
| //* Serializes `record` into `transfer`, using `buffer` to get more space for pointed-to data |
| //* and `provider` to serialize objects. |
| DAWN_DECLARE_UNUSED WireResult {{Return}}{{name}}Serialize( |
| const {{Return}}{{name}}{{Cmd}}& record, |
| {{Return}}{{name}}Transfer* transfer, |
| [[maybe_unused]] SerializeBuffer* buffer |
| {%- if record.may_have_dawn_object -%} |
| , const ObjectIdProvider& provider |
| {%- endif -%} |
| ) { |
| //* Handle special transfer members of methods. |
| {% if is_cmd %} |
| transfer->commandId = {{Return}}WireCmd::{{name}}; |
| {% endif %} |
| |
| {% if record.extensible %} |
| if (record.nextInChain != nullptr) { |
| transfer->hasNextInChain = true; |
| WIRE_TRY(SerializeChainedStruct(record.nextInChain, buffer, provider)); |
| } else { |
| transfer->hasNextInChain = false; |
| } |
| {% endif %} |
| {% if record.chained %} |
| //* Should be set by the root descriptor's call to SerializeChainedStruct. |
| DAWN_ASSERT(transfer->chain.sType == {{as_cEnum(types["s type"].name, record.name)}}); |
| DAWN_ASSERT(transfer->chain.hasNext == (record.chain.next != nullptr)); |
| {% endif %} |
| |
| //* Iterate members, sorted in reverse on "attribute" so that "value" types are serialized first. |
| //* Note this is important because some array pointer members rely on another "value" for their |
| //* "length", but order is not always given. |
| {% for member in members | sort(reverse=true, attribute="annotation") %} |
| {% set memberName = as_varName(member.name) %} |
| //* Skip serialization for custom serialized members. |
| {% if member.skip_serialize %} |
| {% continue %} |
| {% endif %} |
| //* Value types are directly in the transfer record, objects being replaced with their IDs. |
| {% if member.annotation == "value" %} |
| {{serialize_member(member, "record." + memberName, "transfer->" + memberName)}} |
| {% continue %} |
| {% endif %} |
| //* Special handling of const char* that have their length embedded directly in the command. |
| {% if member.length == "strlen" %} |
| {% if member.optional %} |
| bool has_{{memberName}} = record.{{memberName}} != nullptr; |
| transfer->has_{{memberName}} = has_{{memberName}}; |
| if (has_{{memberName}}) { |
| {% else %} |
| { |
| {% endif %} |
| transfer->{{memberName}}Strlen = std::strlen(record.{{memberName}}); |
| |
| char* stringInBuffer; |
| WIRE_TRY(buffer->NextN(transfer->{{memberName}}Strlen, &stringInBuffer)); |
| memcpy(stringInBuffer, record.{{memberName}}, transfer->{{memberName}}Strlen); |
| } |
| {% continue %} |
| {% endif %} |
| //* Allocate space and write the non-value arguments in it. |
| {% do assert(member.annotation != "const*const*") %} |
| {% if member.type.category != "object" and member.optional %} |
| bool has_{{memberName}} = record.{{memberName}} != nullptr; |
| transfer->has_{{memberName}} = has_{{memberName}}; |
| if (has_{{memberName}}) { |
| {% else %} |
| { |
| {% endif %} |
| auto memberLength = {{member_length(member, "record.")}}; |
| |
| {{member_transfer_type(member)}}* memberBuffer; |
| WIRE_TRY(buffer->NextN(memberLength, &memberBuffer)); |
| |
| {% if member.type.is_wire_transparent %} |
| memcpy( |
| memberBuffer, record.{{memberName}}, |
| {{member_transfer_sizeof(member)}} * memberLength); |
| {% else %} |
| //* This loop cannot overflow because it iterates up to |memberLength|. Even if |
| //* memberLength were the maximum integer value, |i| would become equal to it |
| //* just before exiting the loop, but not increment past or wrap around. |
| for (decltype(memberLength) i = 0; i < memberLength; ++i) { |
| {{serialize_member(member, "record." + memberName + "[i]", "memberBuffer[i]" )}} |
| } |
| {% endif %} |
| } |
| {% endfor %} |
| |
| return WireResult::Success; |
| } |
| DAWN_UNUSED_FUNC({{Return}}{{name}}Serialize); |
| |
| //* Deserializes `transfer` into `record` getting more serialized data from `buffer` and `size` |
| //* if needed, using `allocator` to store pointed-to values and `resolver` to translate object |
| //* Ids to actual objects. |
| DAWN_DECLARE_UNUSED WireResult {{Return}}{{name}}Deserialize( |
| {{Return}}{{name}}{{Cmd}}* record, |
| const volatile {{Return}}{{name}}Transfer* transfer, |
| DeserializeBuffer* deserializeBuffer, |
| [[maybe_unused]] DeserializeAllocator* allocator |
| {%- if record.may_have_dawn_object -%} |
| , const ObjectIdResolver& resolver |
| {%- endif -%} |
| ) { |
| {% if is_cmd %} |
| DAWN_ASSERT(transfer->commandId == {{Return}}WireCmd::{{name}}); |
| {% endif %} |
| {% if record.derived_method %} |
| record->selfId = transfer->self; |
| {% endif %} |
| |
| {% if record.extensible %} |
| record->nextInChain = nullptr; |
| if (transfer->hasNextInChain) { |
| WIRE_TRY(DeserializeChainedStruct(&record->nextInChain, deserializeBuffer, allocator, resolver)); |
| } |
| {% endif %} |
| {% if record.chained %} |
| //* Should be set by the root descriptor's call to DeserializeChainedStruct. |
| //* Don't check |record->chain.next| matches because it is not set until the |
| //* next iteration inside DeserializeChainedStruct. |
| DAWN_ASSERT(record->chain.sType == {{as_cEnum(types["s type"].name, record.name)}}); |
| DAWN_ASSERT(record->chain.next == nullptr); |
| {% endif %} |
| |
| //* Iterate members, sorted in reverse on "attribute" so that "value" types are serialized first. |
| //* Note this is important because some array pointer members rely on another "value" for their |
| //* "length", but order is not always given. |
| {% for member in members | sort(reverse=true, attribute="annotation") %} |
| {% set memberName = as_varName(member.name) %} |
| //* Value types are directly in the transfer record, objects being replaced with their IDs. |
| {% if member.annotation == "value" %} |
| {{deserialize_member(member, "transfer->" + memberName, "record->" + memberName)}} |
| {% continue %} |
| {% endif %} |
| //* Special handling of const char* that have their length embedded directly in the command. |
| {% if member.length == "strlen" %} |
| {% if member.optional %} |
| bool has_{{memberName}} = transfer->has_{{memberName}}; |
| record->{{memberName}} = nullptr; |
| if (has_{{memberName}}) { |
| {% else %} |
| { |
| {% endif %} |
| uint64_t stringLength64 = transfer->{{memberName}}Strlen; |
| if (stringLength64 >= std::numeric_limits<size_t>::max()) { |
| //* Cannot allocate space for the string. It can be at most |
| //* size_t::max() - 1. We need 1 byte for the null-terminator. |
| return WireResult::FatalError; |
| } |
| size_t stringLength = static_cast<size_t>(stringLength64); |
| |
| const volatile char* stringInBuffer; |
| WIRE_TRY(deserializeBuffer->ReadN(stringLength, &stringInBuffer)); |
| |
| char* copiedString; |
| WIRE_TRY(GetSpace(allocator, stringLength + 1, &copiedString)); |
| //* We can cast away the volatile qualifier because DeserializeBuffer::ReadN already |
| //* validated that the range [stringInBuffer, stringInBuffer + stringLength) is valid. |
| //* memcpy may have an unknown access pattern, but this is fine since the string is only |
| //* data and won't affect control flow of this function. |
| memcpy(copiedString, const_cast<const char*>(stringInBuffer), stringLength); |
| copiedString[stringLength] = '\0'; |
| record->{{memberName}} = copiedString; |
| } |
| {% continue %} |
| {% endif %} |
| //* Get extra buffer data, and copy pointed to values in extra allocated space. Note that |
| //* currently, there is an implicit restriction that "skip_serialize" members must not be be |
| //* a "value" type, and that they are the last non-"value" type specified in the list in |
| //* dawn_wire.json. |
| {% do assert(member.annotation != "const*const*") %} |
| {% if member.type.category != "object" and member.optional %} |
| //* Non-constant length optional members use length=0 to denote they aren't present. |
| //* Otherwise we could have length=N and has_member=false, causing reads from an |
| //* uninitialized pointer. |
| {% do assert(member.length == "constant") %} |
| bool has_{{memberName}} = transfer->has_{{memberName}}; |
| record->{{memberName}} = nullptr; |
| if (has_{{memberName}}) { |
| {% else %} |
| { |
| {% endif %} |
| auto memberLength = {{member_length(member, "record->")}}; |
| const volatile {{member_transfer_type(member)}}* memberBuffer; |
| WIRE_TRY(deserializeBuffer->ReadN(memberLength, &memberBuffer)); |
| |
| //* For data-only members (e.g. "data" in WriteBuffer and WriteTexture), they are |
| //* not security sensitive so we can directly refer the data inside the transfer |
| //* buffer in dawn_native. For other members, as prevention of TOCTOU attacks is an |
| //* important feature of the wire, we must make sure every single value returned to |
| //* dawn_native must be a copy of what's in the wire. |
| {% if member.json_data["wire_is_data_only"] %} |
| record->{{memberName}} = |
| const_cast<const {{member_transfer_type(member)}}*>(memberBuffer); |
| |
| {% else %} |
| {{as_cType(member.type.name)}}* copiedMembers; |
| WIRE_TRY(GetSpace(allocator, memberLength, &copiedMembers)); |
| record->{{memberName}} = copiedMembers; |
| |
| {% if member.type.is_wire_transparent %} |
| //* memcpy is not allowed to copy from volatile objects. However, these |
| //* arrays are just used as plain data, and don't impact control flow. So if |
| //* the underlying data were changed while the copy was still executing, we |
| //* would get different data - but it wouldn't cause unexpected downstream |
| //* effects. |
| memcpy( |
| copiedMembers, |
| const_cast<const {{member_transfer_type(member)}}*>(memberBuffer), |
| {{member_transfer_sizeof(member)}} * memberLength); |
| {% else %} |
| //* This loop cannot overflow because it iterates up to |memberLength|. Even |
| //* if memberLength were the maximum integer value, |i| would become equal |
| //* to it just before exiting the loop, but not increment past or wrap |
| //* around. |
| for (decltype(memberLength) i = 0; i < memberLength; ++i) { |
| {{deserialize_member(member, "memberBuffer[i]", "copiedMembers[i]")}} |
| } |
| {% endif %} |
| {% endif %} |
| } |
| {% endfor %} |
| |
| return WireResult::Success; |
| } |
| DAWN_UNUSED_FUNC({{Return}}{{name}}Deserialize); |
| {%- endmacro -%} |
| |
| {%- macro write_command_serialization_methods(command, is_return) -%} |
| {% set Return = "Return" if is_return else "" %} |
| {% set Name = Return + command.name.CamelCase() %} |
| {% set Cmd = Name + "Cmd" %} |
| size_t {{Cmd}}::GetRequiredSize() const { |
| return WireAlignSizeof<{{Name}}Transfer>() + {{Name}}GetExtraRequiredSize(*this); |
| } |
| |
| {% if command.may_have_dawn_object %} |
| WireResult {{Cmd}}::Serialize( |
| size_t commandSize, |
| SerializeBuffer* serializeBuffer, |
| const ObjectIdProvider& provider) const { |
| {{Name}}Transfer* transfer; |
| WIRE_TRY(serializeBuffer->Next(&transfer)); |
| transfer->commandSize = commandSize; |
| return ({{Name}}Serialize(*this, transfer, serializeBuffer, provider)); |
| } |
| WireResult {{Cmd}}::Serialize(size_t commandSize, SerializeBuffer* serializeBuffer) const { |
| ErrorObjectIdProvider provider; |
| return Serialize(commandSize, serializeBuffer, provider); |
| } |
| |
| WireResult {{Cmd}}::Deserialize( |
| DeserializeBuffer* deserializeBuffer, |
| DeserializeAllocator* allocator, |
| const ObjectIdResolver& resolver) { |
| const volatile {{Name}}Transfer* transfer; |
| WIRE_TRY(deserializeBuffer->Read(&transfer)); |
| return {{Name}}Deserialize(this, transfer, deserializeBuffer, allocator, resolver); |
| } |
| WireResult {{Cmd}}::Deserialize(DeserializeBuffer* deserializeBuffer, DeserializeAllocator* allocator) { |
| ErrorObjectIdResolver resolver; |
| return Deserialize(deserializeBuffer, allocator, resolver); |
| } |
| {% else %} |
| WireResult {{Cmd}}::Serialize(size_t commandSize, SerializeBuffer* serializeBuffer) const { |
| {{Name}}Transfer* transfer; |
| WIRE_TRY(serializeBuffer->Next(&transfer)); |
| transfer->commandSize = commandSize; |
| return ({{Name}}Serialize(*this, transfer, serializeBuffer)); |
| } |
| WireResult {{Cmd}}::Serialize( |
| size_t commandSize, |
| SerializeBuffer* serializeBuffer, |
| const ObjectIdProvider&) const { |
| return Serialize(commandSize, serializeBuffer); |
| } |
| |
| WireResult {{Cmd}}::Deserialize(DeserializeBuffer* deserializeBuffer, DeserializeAllocator* allocator) { |
| const volatile {{Name}}Transfer* transfer; |
| WIRE_TRY(deserializeBuffer->Read(&transfer)); |
| return {{Name}}Deserialize(this, transfer, deserializeBuffer, allocator); |
| } |
| WireResult {{Cmd}}::Deserialize( |
| DeserializeBuffer* deserializeBuffer, |
| DeserializeAllocator* allocator, |
| const ObjectIdResolver&) { |
| return Deserialize(deserializeBuffer, allocator); |
| } |
| {% endif %} |
| {%- endmacro -%} |
| |
| {%- macro make_chained_struct_serialization_helpers(out=None) %} |
| {% set ChainedStructPtr = "WGPUChainedStructOut*" if out else "const WGPUChainedStruct*" %} |
| {% set ChainedStruct = "WGPUChainedStructOut" if out else "WGPUChainedStruct" %} |
| //* Generate the list of sTypes that we need to handle. |
| {% set sTypes = [] %} |
| {% for sType in types["s type"].values %} |
| {% if not sType.valid %} |
| {% continue %} |
| {% elif sType.name.CamelCase() in client_side_structures %} |
| {% continue %} |
| {% elif types[sType.name.get()].output != out %} |
| {% continue %} |
| {% endif %} |
| {% do sTypes.append(sType) %} |
| {% endfor %} |
| |
| size_t GetChainedStructExtraRequiredSize({{ChainedStructPtr}} chainedStruct) { |
| DAWN_ASSERT(chainedStruct != nullptr); |
| size_t result = 0; |
| while (chainedStruct != nullptr) { |
| switch (chainedStruct->sType) { |
| {% for sType in sTypes %} |
| case {{as_cEnum(types["s type"].name, sType.name)}}: { |
| const auto& typedStruct = *reinterpret_cast<{{as_cType(sType.name)}} const *>(chainedStruct); |
| result += WireAlignSizeof<{{as_cType(sType.name)}}Transfer>(); |
| result += {{as_cType(sType.name)}}GetExtraRequiredSize(typedStruct); |
| chainedStruct = typedStruct.chain.next; |
| break; |
| } |
| {% endfor %} |
| // Explicitly list the Invalid enum. MSVC complains about no case labels. |
| case WGPUSType_Invalid: |
| default: |
| // Invalid enum. Reserve space just for the transfer header (sType and hasNext). |
| result += WireAlignSizeof<WGPUChainedStructTransfer>(); |
| chainedStruct = chainedStruct->next; |
| break; |
| } |
| } |
| return result; |
| } |
| |
| [[nodiscard]] WireResult SerializeChainedStruct({{ChainedStructPtr}} chainedStruct, |
| SerializeBuffer* buffer, |
| const ObjectIdProvider& provider) { |
| DAWN_ASSERT(chainedStruct != nullptr); |
| DAWN_ASSERT(buffer != nullptr); |
| do { |
| switch (chainedStruct->sType) { |
| {% for sType in sTypes %} |
| {% set CType = as_cType(sType.name) %} |
| case {{as_cEnum(types["s type"].name, sType.name)}}: { |
| {{CType}}Transfer* transfer; |
| WIRE_TRY(buffer->Next(&transfer)); |
| transfer->chain.sType = chainedStruct->sType; |
| transfer->chain.hasNext = chainedStruct->next != nullptr; |
| |
| WIRE_TRY({{CType}}Serialize(*reinterpret_cast<{{CType}} const*>(chainedStruct), transfer, buffer |
| {%- if types[sType.name.get()].may_have_dawn_object -%} |
| , provider |
| {%- endif -%} |
| )); |
| |
| chainedStruct = chainedStruct->next; |
| } break; |
| {% endfor %} |
| // Explicitly list the Invalid enum. MSVC complains about no case labels. |
| case WGPUSType_Invalid: |
| default: { |
| // Invalid enum. Serialize just the transfer header with Invalid as the sType. |
| // TODO(crbug.com/dawn/369): Unknown sTypes are silently discarded. |
| if (chainedStruct->sType != WGPUSType_Invalid) { |
| dawn::WarningLog() << "Unknown sType " << chainedStruct->sType << " discarded."; |
| } |
| |
| WGPUChainedStructTransfer* transfer; |
| WIRE_TRY(buffer->Next(&transfer)); |
| transfer->sType = WGPUSType_Invalid; |
| transfer->hasNext = chainedStruct->next != nullptr; |
| |
| // Still move on in case there are valid structs after this. |
| chainedStruct = chainedStruct->next; |
| break; |
| } |
| } |
| } while (chainedStruct != nullptr); |
| return WireResult::Success; |
| } |
| |
| WireResult DeserializeChainedStruct({{ChainedStructPtr}}* outChainNext, |
| DeserializeBuffer* deserializeBuffer, |
| DeserializeAllocator* allocator, |
| const ObjectIdResolver& resolver) { |
| bool hasNext; |
| do { |
| const volatile WGPUChainedStructTransfer* header; |
| WIRE_TRY(deserializeBuffer->Peek(&header)); |
| WGPUSType sType = header->sType; |
| switch (sType) { |
| {% for sType in sTypes %} |
| {% set CType = as_cType(sType.name) %} |
| case {{as_cEnum(types["s type"].name, sType.name)}}: { |
| const volatile {{CType}}Transfer* transfer; |
| WIRE_TRY(deserializeBuffer->Read(&transfer)); |
| |
| {{CType}}* outStruct; |
| WIRE_TRY(GetSpace(allocator, 1u, &outStruct)); |
| outStruct->chain.sType = sType; |
| outStruct->chain.next = nullptr; |
| |
| *outChainNext = &outStruct->chain; |
| outChainNext = &outStruct->chain.next; |
| |
| WIRE_TRY({{CType}}Deserialize(outStruct, transfer, deserializeBuffer, allocator |
| {%- if types[sType.name.get()].may_have_dawn_object -%} |
| , resolver |
| {%- endif -%} |
| )); |
| |
| hasNext = transfer->chain.hasNext; |
| } break; |
| {% endfor %} |
| // Explicitly list the Invalid enum. MSVC complains about no case labels. |
| case WGPUSType_Invalid: |
| default: { |
| // Invalid enum. Deserialize just the transfer header with Invalid as the sType. |
| // TODO(crbug.com/dawn/369): Unknown sTypes are silently discarded. |
| if (sType != WGPUSType_Invalid) { |
| dawn::WarningLog() << "Unknown sType " << sType << " discarded."; |
| } |
| |
| const volatile WGPUChainedStructTransfer* transfer; |
| WIRE_TRY(deserializeBuffer->Read(&transfer)); |
| |
| {{ChainedStruct}}* outStruct; |
| WIRE_TRY(GetSpace(allocator, 1u, &outStruct)); |
| outStruct->sType = WGPUSType_Invalid; |
| outStruct->next = nullptr; |
| |
| // Still move on in case there are valid structs after this. |
| *outChainNext = outStruct; |
| outChainNext = &outStruct->next; |
| hasNext = transfer->hasNext; |
| break; |
| } |
| } |
| } while (hasNext); |
| return WireResult::Success; |
| } |
| {% endmacro %} |
| |
| |
| namespace dawn::wire { |
| namespace { |
| |
| // Allocates enough space from allocator to countain T[count] and return it in out. |
| // Return FatalError if the allocator couldn't allocate the memory. |
| // Always writes to |out| on success. |
| template <typename T, typename N> |
| WireResult GetSpace(DeserializeAllocator* allocator, N count, T** out) { |
| // Because we use this function extensively when `count` == 1, we can optimize the |
| // size computations a bit more for those cases via constexpr version of the |
| // alignment computation. |
| constexpr size_t kSizeofT = WireAlignSizeof<T>(); |
| size_t size = 0; |
| if (count == 1) { |
| size = kSizeofT; |
| } else { |
| auto sizeN = WireAlignSizeofN<T>(count); |
| // A size of 0 indicates an overflow, so return an error. |
| if (!sizeN) { |
| return WireResult::FatalError; |
| } |
| size = *sizeN; |
| } |
| |
| *out = static_cast<T*>(allocator->GetSpace(size)); |
| if (*out == nullptr) { |
| return WireResult::FatalError; |
| } |
| |
| return WireResult::Success; |
| } |
| |
| struct WGPUChainedStructTransfer { |
| WGPUSType sType; |
| bool hasNext; |
| }; |
| |
| size_t GetChainedStructExtraRequiredSize(const WGPUChainedStruct* chainedStruct); |
| [[nodiscard]] WireResult SerializeChainedStruct(const WGPUChainedStruct* chainedStruct, |
| SerializeBuffer* buffer, |
| const ObjectIdProvider& provider); |
| WireResult DeserializeChainedStruct(const WGPUChainedStruct** outChainNext, |
| DeserializeBuffer* deserializeBuffer, |
| DeserializeAllocator* allocator, |
| const ObjectIdResolver& resolver); |
| |
| size_t GetChainedStructExtraRequiredSize(WGPUChainedStructOut* chainedStruct); |
| [[nodiscard]] WireResult SerializeChainedStruct(WGPUChainedStructOut* chainedStruct, |
| SerializeBuffer* buffer, |
| const ObjectIdProvider& provider); |
| WireResult DeserializeChainedStruct(WGPUChainedStructOut** outChainNext, |
| DeserializeBuffer* deserializeBuffer, |
| DeserializeAllocator* allocator, |
| const ObjectIdResolver& resolver); |
| |
| //* Output structure [de]serialization first because it is used by commands. |
| {% for type in by_category["structure"] %} |
| {%- set name = as_cType(type.name) -%} |
| {% if type.name.CamelCase() not in client_side_structures -%} |
| {{write_record_serialization_helpers(type, name, type.members, is_cmd=False)}} |
| {% endif %} |
| {% endfor %} |
| |
| {{make_chained_struct_serialization_helpers(out=False)}} |
| {{make_chained_struct_serialization_helpers(out=True)}} |
| |
| //* Output [de]serialization helpers for commands |
| {% for command in cmd_records["command"] %} |
| {%- set name = command.name.CamelCase() -%} |
| {{write_record_serialization_helpers(command, name, command.members, is_cmd=True)}} |
| {% endfor %} |
| |
| //* Output [de]serialization helpers for return commands |
| {% for command in cmd_records["return command"] %} |
| {%- set name = command.name.CamelCase() -%} |
| {{write_record_serialization_helpers(command, name, command.members, |
| is_cmd=True, is_return_command=True)}} |
| {% endfor %} |
| |
| // Implementation of ObjectIdResolver that always errors. |
| // Used when the generator adds a provider argument because of a chained |
| // struct, but in practice, a chained struct in that location is invalid. |
| class ErrorObjectIdResolver final : public ObjectIdResolver { |
| public: |
| {% for type in by_category["object"] %} |
| WireResult GetFromId(ObjectId id, {{as_cType(type.name)}}* out) const override { |
| return WireResult::FatalError; |
| } |
| WireResult GetOptionalFromId(ObjectId id, {{as_cType(type.name)}}* out) const override { |
| return WireResult::FatalError; |
| } |
| {% endfor %} |
| }; |
| |
| // Implementation of ObjectIdProvider that always errors. |
| // Used when the generator adds a provider argument because of a chained |
| // struct, but in practice, a chained struct in that location is invalid. |
| class ErrorObjectIdProvider final : public ObjectIdProvider { |
| public: |
| {% for type in by_category["object"] %} |
| WireResult GetId({{as_cType(type.name)}} object, ObjectId* out) const override { |
| return WireResult::FatalError; |
| } |
| WireResult GetOptionalId({{as_cType(type.name)}} object, ObjectId* out) const override { |
| return WireResult::FatalError; |
| } |
| {% endfor %} |
| }; |
| |
| } // anonymous namespace |
| |
| {% for command in cmd_records["command"] -%} |
| {{write_command_serialization_methods(command, False)}} |
| {% endfor %} |
| |
| {% for command in cmd_records["return command"] -%} |
| {{write_command_serialization_methods(command, True)}} |
| {% endfor %} |
| |
| } // namespace dawn::wire |