diff --git a/dawn_wire.json b/dawn_wire.json index 96e284f915..e5cf4f6070 100644 --- a/dawn_wire.json +++ b/dawn_wire.json @@ -19,21 +19,21 @@ "commands": { "buffer map async": [ - { "name": "buffer id", "type": "ObjectId" }, + { "name": "buffer id", "type": "ObjectId", "id_type": "buffer" }, { "name": "request serial", "type": "uint64_t" }, { "name": "mode", "type": "map mode" }, { "name": "offset", "type": "uint64_t"}, { "name": "size", "type": "uint64_t"} ], "buffer update mapped data": [ - { "name": "buffer id", "type": "ObjectId" }, + { "name": "buffer id", "type": "ObjectId", "id_type": "buffer" }, { "name": "write data update info length", "type": "uint64_t" }, { "name": "write data update info", "type": "uint8_t", "annotation": "const*", "length": "write data update info length", "skip_serialize": true}, { "name": "offset", "type": "uint64_t"}, { "name": "size", "type": "uint64_t"} ], "device create buffer": [ - { "name": "device id", "type": "ObjectId" }, + { "name": "device id", "type": "ObjectId", "id_type": "device" }, { "name": "descriptor", "type": "buffer descriptor", "annotation": "const*" }, { "name": "result", "type": "ObjectHandle", "handle_type": "buffer" }, { "name": "read handle create info length", "type": "uint64_t" }, @@ -42,19 +42,19 @@ { "name": "write handle create info", "type": "uint8_t", "annotation": "const*", "length": "write handle create info length", "skip_serialize": true} ], "device create compute pipeline async": [ - { "name": "device id", "type": "ObjectId" }, + { "name": "device id", "type": "ObjectId", "id_type": "device"}, { "name": "request serial", "type": "uint64_t" }, { "name": "pipeline object handle", "type": "ObjectHandle", "handle_type": "compute pipeline"}, { "name": "descriptor", "type": "compute pipeline descriptor", "annotation": "const*"} ], "device create render pipeline async": [ - { "name": "device id", "type": "ObjectId" }, + { "name": "device id", "type": "ObjectId", "id_type": "device" }, { "name": "request serial", "type": "uint64_t" }, { "name": "pipeline object handle", "type": "ObjectHandle", "handle_type": "render pipeline"}, { "name": "descriptor", "type": "render pipeline descriptor", "annotation": "const*"} ], "device pop error scope": [ - { "name": "device id", "type": "ObjectId" }, + { "name": "device id", "type": "ObjectId", "id_type": "device" }, { "name": "request serial", "type": "uint64_t" } ], "destroy object": [ @@ -62,19 +62,19 @@ { "name": "object id", "type": "ObjectId" } ], "queue on submitted work done": [ - { "name": "queue id", "type": "ObjectId" }, + { "name": "queue id", "type": "ObjectId", "id_type": "queue" }, { "name": "signal value", "type": "uint64_t" }, { "name": "request serial", "type": "uint64_t" } ], "queue write buffer": [ - {"name": "queue id", "type": "ObjectId" }, - {"name": "buffer id", "type": "ObjectId" }, + {"name": "queue id", "type": "ObjectId", "id_type": "queue" }, + {"name": "buffer id", "type": "ObjectId", "id_type": "buffer" }, {"name": "buffer offset", "type": "uint64_t"}, {"name": "data", "type": "uint8_t", "annotation": "const*", "length": "size", "wire_is_data_only": true}, {"name": "size", "type": "uint64_t"} ], "queue write texture": [ - {"name": "queue id", "type": "ObjectId" }, + {"name": "queue id", "type": "ObjectId", "id_type": "queue" }, {"name": "destination", "type": "image copy texture", "annotation": "const*"}, {"name": "data", "type": "uint8_t", "annotation": "const*", "length": "data size", "wire_is_data_only": true}, {"name": "data size", "type": "uint64_t"}, @@ -82,17 +82,17 @@ {"name": "writeSize", "type": "extent 3D", "annotation": "const*"} ], "shader module get compilation info": [ - { "name": "shader module id", "type": "ObjectId" }, + { "name": "shader module id", "type": "ObjectId", "id_type": "shader module" }, { "name": "request serial", "type": "uint64_t" } ], "instance request adapter": [ - { "name": "instance id", "type": "ObjectId" }, + { "name": "instance id", "type": "ObjectId", "id_type": "instance" }, { "name": "request serial", "type": "uint64_t" }, { "name": "adapter object handle", "type": "ObjectHandle", "handle_type": "adapter"}, { "name": "options", "type": "request adapter options", "annotation": "const*" } ], "adapter request device": [ - { "name": "adapter id", "type": "ObjectId" }, + { "name": "adapter id", "type": "ObjectId", "id_type": "adapter" }, { "name": "request serial", "type": "uint64_t" }, { "name": "device object handle", "type": "ObjectHandle", "handle_type": "device"}, { "name": "descriptor", "type": "device descriptor", "annotation": "const*" } diff --git a/generator/dawn_json_generator.py b/generator/dawn_json_generator.py index ac077c2227..16207fcbe6 100644 --- a/generator/dawn_json_generator.py +++ b/generator/dawn_json_generator.py @@ -181,6 +181,7 @@ class RecordMember: self.optional = optional self.is_return_value = is_return_value self.handle_type = None + self.id_type = None self.default_value = default_value self.skip_serialize = skip_serialize @@ -188,6 +189,10 @@ class RecordMember: assert self.type.dict_name == "ObjectHandle" self.handle_type = handle_type + def set_id_type(self, id_type): + assert self.type.dict_name == "ObjectId" + self.id_type = id_type + Method = namedtuple( 'Method', ['name', 'return_type', 'arguments', 'autolock', 'json_data']) @@ -308,6 +313,9 @@ def linked_record_members(json_data, types): handle_type = m.get('handle_type') if handle_type: member.set_handle_type(types[handle_type]) + id_type = m.get('id_type') + if id_type: + member.set_id_type(types[id_type]) members.append(member) members_by_name[member.name.canonical_case()] = member @@ -331,6 +339,18 @@ def linked_record_members(json_data, types): return members +def mark_lengths_non_serializable_lpm(record_members): + # Remove member length values from command metadata, + # these are set to the length of the protobuf array. + for record_member in record_members: + lengths = set() + for member in record_member.members: + lengths.add(member.length) + + for member in record_member.members: + if member in lengths: + member.skip_serialize = True + ############################################################ # PARSE ############################################################ @@ -376,7 +396,6 @@ def link_function(function, types): function.arguments = linked_record_members(function.json_data['args'], types) - # Sort structures so that if struct A has struct B as a member, then B is # listed before A. # @@ -601,10 +620,21 @@ def compute_lpm_params(api_and_wire_params, lpm_json): generated_commands.append(command) all_commands.append(command) + # Set all fields that are marked as the "length" of another field to + # skip_serialize. The values passed by libprotobuf-mutator will cause + # an instant crash during serialization if these don't match the length + # of the data they are passing. These values aren't used in + # deserialization. + mark_lengths_non_serializable_lpm( + api_and_wire_params['cmd_records']['command']) + mark_lengths_non_serializable_lpm( + api_and_wire_params['by_category']['structure']) + lpm_params['cmd_records'] = { 'proto_generated_commands': generated_commands, 'proto_all_commands': all_commands, - 'cpp_generated_commands': generated_commands + 'cpp_generated_commands': generated_commands, + 'lpm_info': lpm_json.get("lpm_info") } return lpm_params @@ -636,16 +666,28 @@ def as_protobufTypeLPM(member): return member.type.name.CamelCase() +# Helper that generates names for protobuf grammars from contents +# of dawn*.json like files. example: membera def as_protobufNameLPM(*names): - # `descriptor` is a reserved keyword in lpm + # `descriptor` is a reserved keyword in lib-protobuf-mutator if (names[0].concatcase() == "descriptor"): return "desc" return as_varName(*names) +# Helper to generate member accesses within C++ of protobuf objects +# example: cmd.membera().memberb() +def as_protobufMemberNameLPM(*names): + # `descriptor` is a reserved keyword in lib-protobuf-mutator + if (names[0].concatcase() == "descriptor"): + return "desc" + return ''.join([name.concatcase().lower() for name in names]) + + def unreachable_code(): assert False + ############################################################# # Generator ############################################################# @@ -1152,8 +1194,10 @@ class MultiGeneratorFromDawnJSON(Generator): fuzzer_params = compute_lpm_params(api_and_wire_params, lpm_json) lpm_params = [ - RENDER_PARAMS_BASE, params_dawn_wire, {}, api_and_wire_params, - fuzzer_params + RENDER_PARAMS_BASE, params_dawn_wire, { + 'as_protobufMemberName': as_protobufMemberNameLPM, + 'unreachable_code': unreachable_code + }, api_and_wire_params, fuzzer_params ] renders.append( diff --git a/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMConstants.h b/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMConstants.h index f8cc150709..0b777e18db 100644 --- a/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMConstants.h +++ b/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMConstants.h @@ -12,8 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + namespace DawnLPMFuzzer { + static constexpr int kInstanceObjectId = 1; +static constexpr uint32_t kInvalidObjectId = {{ cmd_records["lpm_info"]["invalid object id"] }}; + +{% for type in by_category["object"] %} + {% if type.name.get() in cmd_records["lpm_info"]["limits"] %} + static constexpr int k{{ type.name.CamelCase() }}Limit = {{ cmd_records["lpm_info"]["limits"][type.name.get()] }}; + {% else %} + static constexpr int k{{ type.name.CamelCase() }}Limit = {{ cmd_records["lpm_info"]["limits"]["default"] }}; + {% endif %} +{% endfor %} } // namespace DawnLPMFuzzer diff --git a/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMSerializer.cpp b/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMSerializer.cpp index 8736044967..f379807ec2 100644 --- a/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMSerializer.cpp +++ b/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMSerializer.cpp @@ -12,31 +12,246 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "dawn/fuzzers/lpmfuzz/DawnLPMSerializer_autogen.h" +#include "dawn/fuzzers/lpmfuzz/DawnLPMConstants_autogen.h" #include "dawn/fuzzers/lpmfuzz/DawnLPMFuzzer.h" +#include "dawn/fuzzers/lpmfuzz/DawnLPMObjectStore.h" +#include "dawn/fuzzers/lpmfuzz/DawnLPMSerializer_autogen.h" +#include "dawn/webgpu.h" +#include "dawn/wire/BufferConsumer_impl.h" +#include "dawn/wire/ObjectHandle.h" #include "dawn/wire/Wire.h" #include "dawn/wire/WireClient.h" #include "dawn/wire/WireCmd_autogen.h" +#include "dawn/wire/WireResult.h" #include "dawn/wire/client/ApiObjects_autogen.h" -#include "dawn/webgpu.h" -#include "dawn/wire/client/Client.h" - namespace dawn::wire { -void SerializedData(const fuzzing::Program& program, dawn::wire::ChunkedCommandSerializer serializer) { +//* Outputs an rvalue that's the number of elements a pointer member points to. +{% macro member_length(member, proto_accessor) -%} + {%- if member.length == "constant" -%} + {{member.constant_length}}u + {%- else -%} + {{proto_accessor}}().size() + {%- endif -%} +{%- endmacro %} + +//* Outputs the type that will be used on the wire for the member +{% macro member_type(member) -%} + {{ assert(as_cType(member.type.name) != "size_t") }} + {{as_cType(member.type.name)}} +{%- endmacro %} + +//* Outputs the conversion code to put `in` in `out` +{% macro convert_member(member, in, out, in_access="") %} + {% if member.type in by_category["structure"] %} + {{ convert_structure(member, in, out, in_access) }} + {% elif member.type in by_category["bitmask"] %} + {{ convert_bitmask(member, in, out, in_access) }} + {% elif member.type in by_category["enum"] %} + {{ convert_enum(member, in, out, in_access) }} + {% elif member.type in by_category["object"] %} + {{ convert_object(member, in, out, in_access) }} + {% elif member.type.name.get() == "ObjectId" %} + {{ convert_objectid(member, in, out, access) }} + {% elif member.type.name.get() == "ObjectHandle" %} + {{ convert_objecthandle(member, in, out, in_access) }} + {% else %} + {{out}} = {{in}}({{in_access}}); + {% endif %} +{% endmacro %} + +//* Helper functions for converting protobufs to specific types +{% macro convert_enum(member, in, out, in_access) %} + {{out}} = static_cast<{{ member_type(member) }}>( + {{in}}({{in_access}}) + ); +{% endmacro %} + +{% macro convert_object(member, in, out, in_access) -%} + {{ out }} = reinterpret_cast<{{ as_cType(member.type.name) }}>( + objectStores[ObjectType::{{ member.type.name.CamelCase() }}].Lookup( + static_cast( + {{in}}({{in_access}}) + ) + ) + ); +{%- endmacro %} + +{% macro convert_objectid(member, in, out, in_access) -%} + {{ out }} = objectStores[ObjectType::{{ member.id_type.name.CamelCase() }}].Lookup( + static_cast( + {{in}}({{ in_access}}) + ) + ); +{%- endmacro %} + +{% macro convert_objecthandle(member, in, out, in_access) -%} + if (objectStores[ObjectType::{{ member.handle_type.name.CamelCase() }}].Size() < DawnLPMFuzzer::k{{ member.handle_type.name.CamelCase() }}Limit) { + {{ out }} = objectStores[ObjectType::{{ member.handle_type.name.CamelCase() }}].ReserveHandle(); + } else { + {{ out }} = {0, 0}; + } +{%- endmacro %} + +{% macro convert_bitmask(member, in, out, in_access) -%} + {{ out }} = 0; + for (size_t bm = 0; bm < static_cast({{ in }}().size()); bm++) { + {{ out }} |= + static_cast<{{ member_type(member) }}>( + {{ in }}(bm) + ); + } +{%- endmacro %} + +{% macro convert_structure(member, in, out, in_access) -%} + // Serializing a Structure Recursively + WIRE_TRY({{member_type(member)}}ProtoConvert({{in}}({{in_access}}), &{{out}}, serializeBuffer, objectStores)); +{%- endmacro %} + +{% macro write_record_conversion_helpers(record, name, members, is_cmd) %} + {% set overrides = cmd_records["lpm_info"]["overrides"] %} + {% set overrides_key = record.name.canonical_case() %} + {% set name = record.name.CamelCase() %} + {% set Cmd = "Cmd" if is_cmd else "" %} + {% set WGPU = "WGPU" if not is_cmd else "" %} + + WireResult {{WGPU}}{{name}}ProtoConvert(fuzzing::{{ name }} proto_record, {{WGPU}}{{ name }}{{ Cmd }} const *record, SerializeBuffer* serializeBuffer, PerObjectType &objectStores) { + + {{WGPU}}{{ name }}{{ Cmd }} *mutable_record = const_cast<{{WGPU}}{{ name }}{{ Cmd }} *>(record); + + //* Some commands don't set any members. + DAWN_UNUSED(mutable_record); + + //* Zero out any non-serializable values as they won't be set + {% for member in members if member.skip_serialize %} + {% set memberName = as_varName(member.name) %} + memset(&mutable_record->{{ memberName }}, 0, sizeof(mutable_record->{{ memberName }})); + {% endfor %} + + //* Pass by Value handling. This mirrors WireCmd with some differences between + //* convert_member and serialize_member + {% for member in members if member.annotation == "value" if not member.skip_serialize %} + {% set memberName = as_varName(member.name) %} + {% set protoMember = as_protobufMemberName(member.name) %} + + //* Major WireCmd Divergence: Some member values are hardcoded in dawn_lpm.json + {% if overrides_key in overrides and member.name.canonical_case() in overrides[overrides_key] %} + mutable_record->{{ memberName }} = {{ overrides[overrides_key][member.name.canonical_case()] }}; + {% else %} + {{ convert_member(member, 'proto_record.' + protoMember, "mutable_record->" + memberName) }} + {% endif %} + {% endfor %} + + //* Chained structures are currently not supported. + {% if record.extensible %} + mutable_record->nextInChain = nullptr; + {% endif %} + + //* Special handling for strings for now. + {% for member in members if member.length == "strlen" %} + {% set memberName = as_varName(member.name) %} + { + mutable_record->{{ memberName }} = "main"; + } + {% endfor %} + + //* Pass by Pointer handling. This mirrors WireCmd with some divergences when handling + //* byte arrays. + {% for member in members if member.annotation != "value" and member.length != "strlen" and not member.skip_serialize %} + {% set memberName = as_varName(member.name) %} + {% set protoMember = as_protobufMemberName(member.name) %} + {% set protoAccess = "i" if member.length != "constant" or member.constant_length > 1 else "" %} + + //* Major WireCmd Divergence: DawnLPM handles raw byte arrays uniquely + //* as they don't lead to new coverage, lead to OOMs when allocated with + //* an arbitrary size, and are difficult to work with in protobuf. + {% if member.type.name.get() == 'uint8_t' %} + { + const size_t kDataBufferLength = 128; + auto memberLength = kDataBufferLength; + + {{member_type(member)}}* memberBuffer; + WIRE_TRY(serializeBuffer->NextN(memberLength, &memberBuffer)); + memset(memberBuffer, 0, kDataBufferLength); + mutable_record->{{ memberName }} = memberBuffer; + + {% if member.length != "constant" -%} + mutable_record->{{ member.length.name.camelCase() }} = memberLength; + {%- endif %} + } + {% else %} + { + auto memberLength = static_cast({{member_length(member, "proto_record." + protoMember)}}); + + //* Needed for the edge cases in "external texture descriptor" + //* where we want to fuzzer to fill the fixed-length float arrays + //* with values, but the length of the protobuf buffer might not + //* be large enough for "src transfer function parameters". + {% if member.length == "constant" and member.constant_length > 1 %} + memberLength = std::min(memberLength, static_cast({{"proto_record." + protoMember}}().size())); + {% endif %} + + {{member_type(member)}}* memberBuffer; + WIRE_TRY(serializeBuffer->NextN(memberLength, &memberBuffer)); + + for (decltype(memberLength) i = 0; i < memberLength; ++i) { + {{convert_member(member, "proto_record." + protoMember, "memberBuffer[i]", protoAccess )}} + } + + mutable_record->{{ memberName }} = memberBuffer; + + //* Major WireCmd Divergence: Within the serializer the length member is + //* set by using record.length. Here we aren't receiving any data + //* and set it to the number of protobuf objects in proto_record. + {% if member.length != "constant" -%} + mutable_record->{{ member.length.name.camelCase() }} = memberLength; + {%- endif %} + } + {% endif %} + {% endfor %} + + return WireResult::Success; + } +{% endmacro %} + +//* Output structure conversion 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_conversion_helpers(type, name, type.members, False) }} + {% endif %} +{% endfor %} + +//* Output command conversion functions. +{% for command in cmd_records["cpp_generated_commands"] %} + {% set name = command.name.CamelCase() %} + {{ write_record_conversion_helpers(command, name, command.members, True) }} +{% endfor %} + +WireResult SerializedData(const fuzzing::Program& program, dawn::wire::ChunkedCommandSerializer serializer) { DawnLPMObjectIdProvider provider; + PerObjectType objectStores, &objectStoresRef = objectStores; + + // Allocate a scoped buffer allocation + const size_t kMaxSerializeBufferSize = 65536; + std::unique_ptr allocatedBuffer( + new char[kMaxSerializeBufferSize]() + ); for (const fuzzing::Command& command : program.commands()) { switch (command.command_case()) { {% for command in cmd_records["cpp_generated_commands"] %} - case fuzzing::Command::k{{command.name.CamelCase()}}: { - {{ command.name.CamelCase() }}Cmd {{ 'cmd' }}; - // TODO(chromium:1374747): Populate command buffer with serialized code from generated - // protobuf structures. Currently, this will nullptr-deref constantly. - memset(&{{ 'cmd' }}, 0, sizeof({{ command.name.CamelCase() }}Cmd)); - serializer.SerializeCommand(cmd, provider); + {% set name = command.name.CamelCase() %} + case fuzzing::Command::k{{name}}: { + SerializeBuffer serializeBuffer(allocatedBuffer.get(), kMaxSerializeBufferSize); + {{ name }}Cmd *cmd = nullptr; + WIRE_TRY(serializeBuffer.Next(&cmd)); + + WIRE_TRY({{name}}ProtoConvert(command.{{ command.name.concatcase() }}(), cmd, &serializeBuffer, objectStoresRef)); + + serializer.SerializeCommand(*cmd, provider); break; } {% endfor %} @@ -45,6 +260,8 @@ void SerializedData(const fuzzing::Program& program, dawn::wire::ChunkedCommandS } } } + + return WireResult::Success; } } // namespace dawn::wire diff --git a/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMSerializer.h b/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMSerializer.h index f5d0ef5982..172bcc9295 100644 --- a/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMSerializer.h +++ b/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMSerializer.h @@ -18,6 +18,7 @@ #include "dawn/fuzzers/lpmfuzz/dawn_lpm_autogen.pb.h" #include "dawn/wire/ChunkedCommandSerializer.h" #include "dawn/wire/WireCmd_autogen.h" +#include "dawn/wire/WireResult.h" namespace dawn::wire { @@ -38,7 +39,7 @@ class DawnLPMObjectIdProvider : public ObjectIdProvider { }; -void SerializedData(const fuzzing::Program& program, +WireResult SerializedData(const fuzzing::Program& program, dawn::wire::ChunkedCommandSerializer serializer); } // namespace dawn::wire diff --git a/generator/templates/dawn/fuzzers/lpmfuzz/dawn_lpm.proto b/generator/templates/dawn/fuzzers/lpmfuzz/dawn_lpm.proto index 8cd8fea12a..df75543cbe 100644 --- a/generator/templates/dawn/fuzzers/lpmfuzz/dawn_lpm.proto +++ b/generator/templates/dawn/fuzzers/lpmfuzz/dawn_lpm.proto @@ -17,6 +17,25 @@ package fuzzing; import "third_party/dawn/src/dawn/fuzzers/lpmfuzz/dawn_custom_lpm.proto"; + +// These are hardcoded limits for Dawn Object allocations based on type to help +// guide the fuzzer towards reusing existing objects. +{% for type in by_category["object"] %} + {% set type_key = type.name.canonical_case() %} + enum {{ type.name.CamelCase() }}Id { + {% if type_key in cmd_records["lpm_info"]["limits"] %} + {% for n in range(cmd_records["lpm_info"]["limits"][type_key]) %} + {{ type.name.SNAKE_CASE() }}_{{ loop.index }} = {{ loop.index }}; + {% endfor %} + {% else %} + {% for n in range(cmd_records["lpm_info"]["limits"]["default"]) %} + {{ type.name.SNAKE_CASE() }}_{{ loop.index }} = {{ loop.index }}; + {% endfor %} + {% endif %} + INVALID_{{ type.name.SNAKE_CASE() }} = {{ cmd_records["lpm_info"]["invalid object id"] }}; + }; +{% endfor %} + {% for type in by_category["enum"] %} enum {{as_cppType(type.name)}} { {% for value in type.values %} @@ -38,23 +57,33 @@ import "third_party/dawn/src/dawn/fuzzers/lpmfuzz/dawn_custom_lpm.proto"; {% macro lift_string_proto_member(member, count) -%} required string {{ as_protobufNameLPM(member.name) }} = {{ count.value }}; {% set count.value = count.value + 1 %} + {%- endmacro %} + {% macro lift_float_array_proto_member(member, count) -%} repeated float {{ as_varName(member.name) }} = {{ count.value }}; {% set count.value = count.value + 1 %} {%- endmacro %} +{% macro lift_object_member(member, count) %} + {{ member.type.name.CamelCase() }}Id {{ as_protobufNameLPM(member.name) }} +{% endmacro %} + + +{% macro lift_objectid_member(member, count) %} + {{ member.id_type.name.CamelCase() }}Id {{ as_protobufNameLPM(member.name) }} +{% endmacro %} + + {% macro lift_varlength_proto_member(member, count) -%} {% if member.type in by_category["object"] %} - repeated uint32 {{ as_protobufNameLPM(member.name) }} = {{ count.value }}; + repeated {{ lift_object_member(member, count) }} = {{ count.value }}; {% set count.value = count.value + 1 %} {% elif member.type.name.get() == "object id" %} - repeated uint32 {{ as_protobufNameLPM(member.name) }} = {{ count.value }}; + repeated {{ lift_objectid_member(member, count) }} = {{ count.value }}; {% set count.value = count.value + 1 %} - {% elif member.type.name.get() == "uint8_t" %} - // Skip over byte arrays in protobuf, handled by DawnSerializer {% else %} repeated {{ as_protobufTypeLPM(member) }} {{ as_protobufNameLPM(member.name) }} = {{ count.value }}; {% set count.value = count.value + 1 %} @@ -73,10 +102,10 @@ import "third_party/dawn/src/dawn/fuzzers/lpmfuzz/dawn_custom_lpm.proto"; required {{ as_protobufTypeLPM(member) }} {{ as_protobufNameLPM(member.name) }} = {{ count.value }}; {% set count.value = count.value + 1 %} {% elif member.type in by_category["object"] %} - required uint32 {{ as_protobufNameLPM(member.name) }} = {{ count.value }}; + required {{ lift_object_member(member, count) }} = {{ count.value }}; {% set count.value = count.value + 1 %} {% elif member.type.name.get() == "ObjectId" %} - required uint32 {{ as_protobufNameLPM(member.name) }} = {{ count.value }}; + required {{ lift_objectid_member(member, count) }} = {{ count.value }}; {% set count.value = count.value + 1 %} {% elif member.type.name.get() == "ObjectHandle" %} // Skips object handles while lifting dawn.json to protobuf because @@ -105,7 +134,7 @@ import "third_party/dawn/src/dawn/fuzzers/lpmfuzz/dawn_custom_lpm.proto"; {% else %} // There shouldn't be any other pass-by-pointer types in // dawn*.json, if any are added we would like to know at compile time - {{ unreachable_code }} + {{ unreachable_code() }} {% endif %} {% endmacro %} diff --git a/generator/templates/dawn/fuzzers/lpmfuzz/dawn_object_types_lpm.proto b/generator/templates/dawn/fuzzers/lpmfuzz/dawn_object_types_lpm.proto index a0b5741d9e..8943030fa6 100644 --- a/generator/templates/dawn/fuzzers/lpmfuzz/dawn_object_types_lpm.proto +++ b/generator/templates/dawn/fuzzers/lpmfuzz/dawn_object_types_lpm.proto @@ -19,4 +19,4 @@ enum ObjectType { {% for type in by_category["object"] %} {{ type.name.CamelCase() }} = {{ loop.index - 1}}; {% endfor %} -}; \ No newline at end of file +}; diff --git a/src/dawn/fuzzers/BUILD.gn b/src/dawn/fuzzers/BUILD.gn index 47426a9f01..5d8ffa1b97 100644 --- a/src/dawn/fuzzers/BUILD.gn +++ b/src/dawn/fuzzers/BUILD.gn @@ -175,6 +175,8 @@ if (is_dawn_lpm_fuzzer && build_with_chromium && dawn_use_swiftshader) { "lpmfuzz/DawnLPMFuzzer.cpp", "lpmfuzz/DawnLPMFuzzer.h", "lpmfuzz/DawnLPMFuzzerAndVulkanBackend.cpp", + "lpmfuzz/DawnLPMObjectStore.cpp", + "lpmfuzz/DawnLPMObjectStore.h", ] deps = [ diff --git a/src/dawn/fuzzers/lpmfuzz/DawnLPMFuzzer.cpp b/src/dawn/fuzzers/lpmfuzz/DawnLPMFuzzer.cpp index 41080fc204..d26eeecefd 100644 --- a/src/dawn/fuzzers/lpmfuzz/DawnLPMFuzzer.cpp +++ b/src/dawn/fuzzers/lpmfuzz/DawnLPMFuzzer.cpp @@ -30,6 +30,7 @@ #include "dawn/webgpu_cpp.h" #include "dawn/wire/ChunkedCommandSerializer.h" #include "dawn/wire/WireClient.h" +#include "dawn/wire/WireResult.h" #include "dawn/wire/WireServer.h" #include "testing/libfuzzer/libfuzzer_exports.h" @@ -106,14 +107,14 @@ int Run(const fuzzing::Program& program, bool (*AdapterSupported)(const dawn::na dawn::wire::ChunkedCommandSerializer(mCommandBuffer); mCommandBuffer->SetHandler(wireServer.get()); - dawn::wire::SerializedData(program, mSerializer); + dawn::wire::WireResult result = dawn::wire::SerializedData(program, mSerializer); mCommandBuffer->Flush(); // Note: Deleting the server will release all created objects. // Deleted devices will wait for idle on destruction. wireServer = nullptr; - return 0; + return result == dawn::wire::WireResult::FatalError; } } // namespace DawnLPMFuzzer diff --git a/src/dawn/fuzzers/lpmfuzz/DawnLPMObjectStore.cpp b/src/dawn/fuzzers/lpmfuzz/DawnLPMObjectStore.cpp new file mode 100644 index 0000000000..2e1353cadd --- /dev/null +++ b/src/dawn/fuzzers/lpmfuzz/DawnLPMObjectStore.cpp @@ -0,0 +1,100 @@ +// Copyright 2023 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include "dawn/fuzzers/lpmfuzz/DawnLPMConstants_autogen.h" +#include "dawn/fuzzers/lpmfuzz/DawnLPMObjectStore.h" +#include "dawn/fuzzers/lpmfuzz/DawnLPMSerializer_autogen.h" +#include "dawn/wire/ObjectHandle.h" + +namespace dawn::wire { + +DawnLPMObjectStore::DawnLPMObjectStore() { + mCurrentId = 1; +} + +ObjectHandle DawnLPMObjectStore::ReserveHandle() { + if (mFreeHandles.empty()) { + Insert(mCurrentId); + return {mCurrentId++, 0}; + } + ObjectHandle handle = mFreeHandles.back(); + mFreeHandles.pop_back(); + Insert(handle.id); + return handle; +} + +void DawnLPMObjectStore::Insert(ObjectId id) { + std::vector::iterator iter = + std::lower_bound(mObjects.begin(), mObjects.end(), id, std::greater()); + mObjects.insert(iter, id); +} + +void DawnLPMObjectStore::Free(ObjectId id) { + if (id == DawnLPMFuzzer::kInvalidObjectId) { + return; + } + + for (size_t i = 0; i < mObjects.size(); i++) { + if (mObjects[i] == id) { + mFreeHandles.push_back({id, 0}); + mObjects.erase(mObjects.begin() + i); + } + } +} + +/* + * Consistent hashing inspired map for fuzzer state. + * If we store the Dawn objects in a hash table mapping FuzzInt -> ObjectId + * then it would be highly unlikely that any subsequence DestroyObject command + * would come up with an ID that would correspond to a valid ObjectId in the + * hash table. + * + * One solution is to modulo the FuzzInt with the length of the hash table, but + * it does not work well with libfuzzer's minimization techniques because + * deleting a single ObjectId from the hash table changes the index of every + * entry from then on. + * + * So we use consistent hashing. we take the entry in the table that + * has the next highest id (wrapping when there is no higher entry). + */ +ObjectId DawnLPMObjectStore::Lookup(uint32_t id) const { + // CreateBindGroup relies on sending invalid object ids + if (id == DawnLPMFuzzer::kInvalidObjectId) { + return 0; + } + + auto iter = std::lower_bound(mObjects.begin(), mObjects.end(), id, std::greater()); + if (iter != mObjects.end()) { + return *iter; + } + + // Wrap to 0 + iter = std::lower_bound(mObjects.begin(), mObjects.end(), 0, std::greater()); + if (iter != mObjects.end()) { + return *iter; + } + + return 0; +} + +size_t DawnLPMObjectStore::Size() const { + return mObjects.size(); +} + +} // namespace dawn::wire diff --git a/src/dawn/fuzzers/lpmfuzz/DawnLPMObjectStore.h b/src/dawn/fuzzers/lpmfuzz/DawnLPMObjectStore.h new file mode 100644 index 0000000000..c096ca31bb --- /dev/null +++ b/src/dawn/fuzzers/lpmfuzz/DawnLPMObjectStore.h @@ -0,0 +1,43 @@ +// Copyright 2023 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SRC_DAWN_WIRE_CLIENT_DAWNLPMOBJECTSTORE_H_ +#define SRC_DAWN_WIRE_CLIENT_DAWNLPMOBJECTSTORE_H_ + +#include + +#include "dawn/wire/client/ObjectBase.h" + +namespace dawn::wire { + +class DawnLPMObjectStore { + public: + DawnLPMObjectStore(); + + ObjectHandle ReserveHandle(); + void Free(ObjectId handle); + void Insert(ObjectId handle); + ObjectId Lookup(ObjectId id) const; + size_t Size() const; + + uint32_t mCurrentId; + std::vector mFreeHandles; + + // TODO(tiszka): refactor into a vector of ObjectHandles + std::vector mObjects; +}; + +} // namespace dawn::wire + +#endif // SRC_DAWN_WIRE_CLIENT_DAWNLPMOBJECTSTORE_H_ diff --git a/src/dawn/fuzzers/lpmfuzz/dawn_lpm.json b/src/dawn/fuzzers/lpmfuzz/dawn_lpm.json index 4b657ef5ef..216c9eec5d 100644 --- a/src/dawn/fuzzers/lpmfuzz/dawn_lpm.json +++ b/src/dawn/fuzzers/lpmfuzz/dawn_lpm.json @@ -23,10 +23,26 @@ ], "blocklisted_cmds": [ - "device create render pipeline", - "device create render pipeline async", "surface descriptor from windows core window", "surface descriptor from windows swap chain panel", "surface descriptor from canvas html selector" - ] + ], + + "lpm_info": { + "overrides": { + "instance request adapter": { + "instance id": 1 + } + }, + + "limits": { + "adapter": 2, + "bind group": 512, + "bind group layout": 512, + "device": 2, + "default": 16 + }, + + "invalid object id": 2147483647 + } } \ No newline at end of file