| // Copyright (c) 2015-2016 The Khronos Group Inc. | |
| // | |
| // 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 <algorithm> | |
| #include <limits> | |
| #include <sstream> | |
| #include <string> | |
| #include <vector> | |
| #include "gmock/gmock.h" | |
| #include "source/latest_version_opencl_std_header.h" | |
| #include "source/table.h" | |
| #include "source/util/string_utils.h" | |
| #include "test/test_fixture.h" | |
| #include "test/unit_spirv.h" | |
| // Returns true if two spv_parsed_operand_t values are equal. | |
| // To use this operator, this definition must appear in the same namespace | |
| // as spv_parsed_operand_t. | |
| static bool operator==(const spv_parsed_operand_t& a, | |
| const spv_parsed_operand_t& b) { | |
| return a.offset == b.offset && a.num_words == b.num_words && | |
| a.type == b.type && a.number_kind == b.number_kind && | |
| a.number_bit_width == b.number_bit_width; | |
| } | |
| namespace spvtools { | |
| namespace { | |
| using ::spvtest::Concatenate; | |
| using ::spvtest::MakeInstruction; | |
| using utils::MakeVector; | |
| using ::spvtest::ScopedContext; | |
| using ::testing::_; | |
| using ::testing::AnyOf; | |
| using ::testing::Eq; | |
| using ::testing::InSequence; | |
| using ::testing::Return; | |
| // An easily-constructible and comparable object for the contents of an | |
| // spv_parsed_instruction_t. Unlike spv_parsed_instruction_t, owns the memory | |
| // of its components. | |
| struct ParsedInstruction { | |
| explicit ParsedInstruction(const spv_parsed_instruction_t& inst) | |
| : words(inst.words, inst.words + inst.num_words), | |
| opcode(static_cast<spv::Op>(inst.opcode)), | |
| ext_inst_type(inst.ext_inst_type), | |
| type_id(inst.type_id), | |
| result_id(inst.result_id), | |
| operands(inst.operands, inst.operands + inst.num_operands) {} | |
| std::vector<uint32_t> words; | |
| spv::Op opcode; | |
| spv_ext_inst_type_t ext_inst_type; | |
| uint32_t type_id; | |
| uint32_t result_id; | |
| std::vector<spv_parsed_operand_t> operands; | |
| bool operator==(const ParsedInstruction& b) const { | |
| return words == b.words && opcode == b.opcode && | |
| ext_inst_type == b.ext_inst_type && type_id == b.type_id && | |
| result_id == b.result_id && operands == b.operands; | |
| } | |
| }; | |
| // Prints a ParsedInstruction object to the given output stream, and returns | |
| // the stream. | |
| std::ostream& operator<<(std::ostream& os, const ParsedInstruction& inst) { | |
| os << "\nParsedInstruction( {"; | |
| spvtest::PrintTo(spvtest::WordVector(inst.words), &os); | |
| os << "}, opcode: " << int(inst.opcode) | |
| << " ext_inst_type: " << int(inst.ext_inst_type) | |
| << " type_id: " << inst.type_id << " result_id: " << inst.result_id; | |
| for (const auto& operand : inst.operands) { | |
| os << " { offset: " << operand.offset << " num_words: " << operand.num_words | |
| << " type: " << int(operand.type) | |
| << " number_kind: " << int(operand.number_kind) | |
| << " number_bit_width: " << int(operand.number_bit_width) << "}"; | |
| } | |
| os << ")"; | |
| return os; | |
| } | |
| // Basic check for the equality operator on ParsedInstruction. | |
| TEST(ParsedInstruction, ZeroInitializedAreEqual) { | |
| spv_parsed_instruction_t pi = {}; | |
| ParsedInstruction a(pi); | |
| ParsedInstruction b(pi); | |
| EXPECT_THAT(a, ::testing::TypedEq<ParsedInstruction>(b)); | |
| } | |
| // Googlemock class receiving Header/Instruction calls from spvBinaryParse(). | |
| class MockParseClient { | |
| public: | |
| MOCK_METHOD6(Header, spv_result_t(spv_endianness_t endian, uint32_t magic, | |
| uint32_t version, uint32_t generator, | |
| uint32_t id_bound, uint32_t reserved)); | |
| MOCK_METHOD1(Instruction, spv_result_t(const ParsedInstruction&)); | |
| }; | |
| // Casts user_data as MockParseClient and invokes its Header(). | |
| spv_result_t invoke_header(void* user_data, spv_endianness_t endian, | |
| uint32_t magic, uint32_t version, uint32_t generator, | |
| uint32_t id_bound, uint32_t reserved) { | |
| return static_cast<MockParseClient*>(user_data)->Header( | |
| endian, magic, version, generator, id_bound, reserved); | |
| } | |
| // Casts user_data as MockParseClient and invokes its Instruction(). | |
| spv_result_t invoke_instruction( | |
| void* user_data, const spv_parsed_instruction_t* parsed_instruction) { | |
| return static_cast<MockParseClient*>(user_data)->Instruction( | |
| ParsedInstruction(*parsed_instruction)); | |
| } | |
| // The SPIR-V module header words for the Khronos Assembler generator, | |
| // for a module with an ID bound of 1. | |
| const uint32_t kHeaderForBound1[] = { | |
| spv::MagicNumber, spv::Version, | |
| SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), 1 /*bound*/, | |
| 0 /*schema*/}; | |
| // Returns the expected SPIR-V module header words for the Khronos | |
| // Assembler generator, and with a given Id bound. | |
| std::vector<uint32_t> ExpectedHeaderForBound(uint32_t bound) { | |
| return {spv::MagicNumber, 0x10000, | |
| SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), bound, 0}; | |
| } | |
| // Returns a parsed operand for a non-number value at the given word offset | |
| // within an instruction. | |
| spv_parsed_operand_t MakeSimpleOperand(uint16_t offset, | |
| spv_operand_type_t type) { | |
| return {offset, 1, type, SPV_NUMBER_NONE, 0}; | |
| } | |
| // Returns a parsed operand for a literal unsigned integer value at the given | |
| // word offset within an instruction. | |
| spv_parsed_operand_t MakeLiteralNumberOperand(uint16_t offset) { | |
| return {offset, 1, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_NUMBER_UNSIGNED_INT, | |
| 32}; | |
| } | |
| // Returns a parsed operand for a literal string value at the given | |
| // word offset within an instruction. | |
| spv_parsed_operand_t MakeLiteralStringOperand(uint16_t offset, | |
| uint16_t length) { | |
| return {offset, length, SPV_OPERAND_TYPE_LITERAL_STRING, SPV_NUMBER_NONE, 0}; | |
| } | |
| // Returns a ParsedInstruction for an OpTypeVoid instruction that would | |
| // generate the given result Id. | |
| ParsedInstruction MakeParsedVoidTypeInstruction(uint32_t result_id) { | |
| const auto void_inst = MakeInstruction(spv::Op::OpTypeVoid, {result_id}); | |
| const auto void_operands = std::vector<spv_parsed_operand_t>{ | |
| MakeSimpleOperand(1, SPV_OPERAND_TYPE_RESULT_ID)}; | |
| const spv_parsed_instruction_t parsed_void_inst = { | |
| void_inst.data(), | |
| static_cast<uint16_t>(void_inst.size()), | |
| uint16_t(spv::Op::OpTypeVoid), | |
| SPV_EXT_INST_TYPE_NONE, | |
| 0, // type id | |
| result_id, | |
| void_operands.data(), | |
| static_cast<uint16_t>(void_operands.size())}; | |
| return ParsedInstruction(parsed_void_inst); | |
| } | |
| // Returns a ParsedInstruction for an OpTypeInt instruction that generates | |
| // the given result Id for a 32-bit signed integer scalar type. | |
| ParsedInstruction MakeParsedInt32TypeInstruction(uint32_t result_id) { | |
| const auto i32_inst = MakeInstruction(spv::Op::OpTypeInt, {result_id, 32, 1}); | |
| const auto i32_operands = std::vector<spv_parsed_operand_t>{ | |
| MakeSimpleOperand(1, SPV_OPERAND_TYPE_RESULT_ID), | |
| MakeLiteralNumberOperand(2), MakeLiteralNumberOperand(3)}; | |
| spv_parsed_instruction_t parsed_i32_inst = { | |
| i32_inst.data(), | |
| static_cast<uint16_t>(i32_inst.size()), | |
| uint16_t(spv::Op::OpTypeInt), | |
| SPV_EXT_INST_TYPE_NONE, | |
| 0, // type id | |
| result_id, | |
| i32_operands.data(), | |
| static_cast<uint16_t>(i32_operands.size())}; | |
| return ParsedInstruction(parsed_i32_inst); | |
| } | |
| class BinaryParseTest : public spvtest::TextToBinaryTestBase<::testing::Test> { | |
| protected: | |
| ~BinaryParseTest() override { spvDiagnosticDestroy(diagnostic_); } | |
| void Parse(const SpirvVector& words, spv_result_t expected_result, | |
| bool flip_words = false) { | |
| SpirvVector flipped_words(words); | |
| MaybeFlipWords(flip_words, flipped_words.begin(), flipped_words.end()); | |
| EXPECT_EQ(expected_result, | |
| spvBinaryParse(ScopedContext().context, &client_, | |
| flipped_words.data(), flipped_words.size(), | |
| invoke_header, invoke_instruction, &diagnostic_)); | |
| } | |
| spv_diagnostic diagnostic_ = nullptr; | |
| MockParseClient client_; | |
| }; | |
| class CxxBinaryParseTest | |
| : public spvtest::TextToBinaryTestBase<::testing::Test> { | |
| protected: | |
| CxxBinaryParseTest() { | |
| header_parser_ = [this](const spv_endianness_t endianness, | |
| const spv_parsed_header_t& header) { | |
| return this->client_.Header(endianness, header.magic, header.version, | |
| header.generator, header.bound, | |
| header.reserved); | |
| }; | |
| instruction_parser_ = [this](const spv_parsed_instruction_t& instruction) { | |
| return this->client_.Instruction(ParsedInstruction(instruction)); | |
| }; | |
| } | |
| ~CxxBinaryParseTest() override { spvDiagnosticDestroy(diagnostic_); } | |
| void Parse(const SpirvVector& words, bool expected_result, | |
| bool flip_words = false, | |
| spv_target_env env = SPV_ENV_UNIVERSAL_1_0) { | |
| SpirvVector flipped_words(words); | |
| MaybeFlipWords(flip_words, flipped_words.begin(), flipped_words.end()); | |
| spvtools::SpirvTools tools(env); | |
| EXPECT_EQ(expected_result, tools.Parse(flipped_words, header_parser_, | |
| instruction_parser_, &diagnostic_)); | |
| } | |
| spv_diagnostic diagnostic_ = nullptr; | |
| MockParseClient client_; | |
| HeaderParser header_parser_; | |
| InstructionParser instruction_parser_; | |
| }; | |
| // Adds an EXPECT_CALL to client_->Header() with appropriate parameters, | |
| // including bound. Returns the EXPECT_CALL result. | |
| #define EXPECT_HEADER(bound) \ | |
| EXPECT_CALL(client_, \ | |
| Header(AnyOf(SPV_ENDIANNESS_LITTLE, SPV_ENDIANNESS_BIG), \ | |
| spv::MagicNumber, 0x10000, \ | |
| SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), \ | |
| bound, 0 /*reserved*/)) | |
| static const bool kSwapEndians[] = {false, true}; | |
| TEST_F(BinaryParseTest, EmptyModuleHasValidHeaderAndNoInstructionCallbacks) { | |
| for (bool endian_swap : kSwapEndians) { | |
| const auto words = CompileSuccessfully(""); | |
| EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. | |
| Parse(words, SPV_SUCCESS, endian_swap); | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| } | |
| TEST_F(CxxBinaryParseTest, EmptyModuleHasValidHeaderAndNoInstructionCallbacks) { | |
| for (bool endian_swap : kSwapEndians) { | |
| const auto words = CompileSuccessfully(""); | |
| EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. | |
| Parse(words, true, endian_swap); | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| } | |
| TEST_F(BinaryParseTest, NullDiagnosticsIsOkForGoodParse) { | |
| const auto words = CompileSuccessfully(""); | |
| EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. | |
| EXPECT_EQ( | |
| SPV_SUCCESS, | |
| spvBinaryParse(ScopedContext().context, &client_, words.data(), | |
| words.size(), invoke_header, invoke_instruction, nullptr)); | |
| } | |
| TEST_F(CxxBinaryParseTest, NullDiagnosticsIsOkForGoodParse) { | |
| const auto words = CompileSuccessfully(""); | |
| EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. | |
| spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_0); | |
| EXPECT_EQ(true, | |
| tools.Parse(words, header_parser_, instruction_parser_, nullptr)); | |
| } | |
| TEST_F(BinaryParseTest, NullDiagnosticsIsOkForBadParse) { | |
| auto words = CompileSuccessfully(""); | |
| words.push_back(0xffffffff); // Certainly invalid instruction header. | |
| EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. | |
| EXPECT_EQ( | |
| SPV_ERROR_INVALID_BINARY, | |
| spvBinaryParse(ScopedContext().context, &client_, words.data(), | |
| words.size(), invoke_header, invoke_instruction, nullptr)); | |
| } | |
| TEST_F(CxxBinaryParseTest, NullDiagnosticsIsOkForBadParse) { | |
| auto words = CompileSuccessfully(""); | |
| words.push_back(0xffffffff); // Certainly invalid instruction header. | |
| EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. | |
| spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_0); | |
| EXPECT_EQ(false, | |
| tools.Parse(words, header_parser_, instruction_parser_, nullptr)); | |
| } | |
| // Make sure that we don't blow up when both the consumer and the diagnostic are | |
| // null. | |
| TEST_F(BinaryParseTest, NullConsumerNullDiagnosticsForBadParse) { | |
| auto words = CompileSuccessfully(""); | |
| auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1); | |
| ctx.SetMessageConsumer(nullptr); | |
| words.push_back(0xffffffff); // Certainly invalid instruction header. | |
| EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. | |
| EXPECT_EQ(SPV_ERROR_INVALID_BINARY, | |
| spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(), | |
| invoke_header, invoke_instruction, nullptr)); | |
| } | |
| TEST_F(CxxBinaryParseTest, NullConsumerNullDiagnosticsForBadParse) { | |
| spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_1); | |
| tools.SetMessageConsumer(nullptr); | |
| auto words = CompileSuccessfully(""); | |
| words.push_back(0xffffffff); // Certainly invalid instruction header. | |
| EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. | |
| EXPECT_EQ(false, | |
| tools.Parse(words, header_parser_, instruction_parser_, nullptr)); | |
| } | |
| TEST_F(BinaryParseTest, SpecifyConsumerNullDiagnosticsForGoodParse) { | |
| const auto words = CompileSuccessfully(""); | |
| auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1); | |
| int invocation = 0; | |
| ctx.SetMessageConsumer([&invocation](spv_message_level_t, const char*, | |
| const spv_position_t&, | |
| const char*) { ++invocation; }); | |
| EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. | |
| EXPECT_EQ(SPV_SUCCESS, | |
| spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(), | |
| invoke_header, invoke_instruction, nullptr)); | |
| EXPECT_EQ(0, invocation); | |
| } | |
| TEST_F(CxxBinaryParseTest, SpecifyConsumerNullDiagnosticsForGoodParse) { | |
| const auto words = CompileSuccessfully(""); | |
| spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_1); | |
| int invocation = 0; | |
| tools.SetMessageConsumer([&invocation](spv_message_level_t, const char*, | |
| const spv_position_t&, | |
| const char*) { ++invocation; }); | |
| EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. | |
| EXPECT_EQ(true, | |
| tools.Parse(words, header_parser_, instruction_parser_, nullptr)); | |
| EXPECT_EQ(0, invocation); | |
| } | |
| TEST_F(BinaryParseTest, SpecifyConsumerNullDiagnosticsForBadParse) { | |
| auto words = CompileSuccessfully(""); | |
| auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1); | |
| int invocation = 0; | |
| ctx.SetMessageConsumer( | |
| [&invocation](spv_message_level_t level, const char* source, | |
| const spv_position_t& position, const char* message) { | |
| ++invocation; | |
| EXPECT_EQ(SPV_MSG_ERROR, level); | |
| EXPECT_STREQ("input", source); | |
| EXPECT_EQ(0u, position.line); | |
| EXPECT_EQ(0u, position.column); | |
| EXPECT_EQ(1u, position.index); | |
| EXPECT_STREQ("Invalid opcode: 65535", message); | |
| }); | |
| words.push_back(0xffffffff); // Certainly invalid instruction header. | |
| EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. | |
| EXPECT_EQ(SPV_ERROR_INVALID_BINARY, | |
| spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(), | |
| invoke_header, invoke_instruction, nullptr)); | |
| EXPECT_EQ(1, invocation); | |
| } | |
| TEST_F(CxxBinaryParseTest, SpecifyConsumerNullDiagnosticsForBadParse) { | |
| auto words = CompileSuccessfully(""); | |
| spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_1); | |
| int invocation = 0; | |
| tools.SetMessageConsumer( | |
| [&invocation](spv_message_level_t level, const char* source, | |
| const spv_position_t& position, const char* message) { | |
| ++invocation; | |
| EXPECT_EQ(SPV_MSG_ERROR, level); | |
| EXPECT_STREQ("input", source); | |
| EXPECT_EQ(0u, position.line); | |
| EXPECT_EQ(0u, position.column); | |
| EXPECT_EQ(1u, position.index); | |
| EXPECT_STREQ("Invalid opcode: 65535", message); | |
| }); | |
| words.push_back(0xffffffff); // Certainly invalid instruction header. | |
| EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. | |
| EXPECT_EQ(false, | |
| tools.Parse(words, header_parser_, instruction_parser_, nullptr)); | |
| EXPECT_EQ(1, invocation); | |
| } | |
| TEST_F(BinaryParseTest, SpecifyConsumerSpecifyDiagnosticsForGoodParse) { | |
| const auto words = CompileSuccessfully(""); | |
| auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1); | |
| int invocation = 0; | |
| ctx.SetMessageConsumer([&invocation](spv_message_level_t, const char*, | |
| const spv_position_t&, | |
| const char*) { ++invocation; }); | |
| EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. | |
| EXPECT_EQ(SPV_SUCCESS, | |
| spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(), | |
| invoke_header, invoke_instruction, &diagnostic_)); | |
| EXPECT_EQ(0, invocation); | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| TEST_F(CxxBinaryParseTest, SpecifyConsumerSpecifyDiagnosticsForGoodParse) { | |
| const auto words = CompileSuccessfully(""); | |
| spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_1); | |
| int invocation = 0; | |
| tools.SetMessageConsumer([&invocation](spv_message_level_t, const char*, | |
| const spv_position_t&, | |
| const char*) { ++invocation; }); | |
| EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. | |
| EXPECT_EQ(true, tools.Parse(words, header_parser_, instruction_parser_, | |
| &diagnostic_)); | |
| EXPECT_EQ(0, invocation); | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| TEST_F(BinaryParseTest, SpecifyConsumerSpecifyDiagnosticsForBadParse) { | |
| auto words = CompileSuccessfully(""); | |
| auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1); | |
| int invocation = 0; | |
| ctx.SetMessageConsumer([&invocation](spv_message_level_t, const char*, | |
| const spv_position_t&, | |
| const char*) { ++invocation; }); | |
| words.push_back(0xffffffff); // Certainly invalid instruction header. | |
| EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. | |
| EXPECT_EQ(SPV_ERROR_INVALID_BINARY, | |
| spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(), | |
| invoke_header, invoke_instruction, &diagnostic_)); | |
| EXPECT_EQ(0, invocation); | |
| EXPECT_STREQ("Invalid opcode: 65535", diagnostic_->error); | |
| } | |
| TEST_F(CxxBinaryParseTest, SpecifyConsumerSpecifyDiagnosticsForBadParse) { | |
| auto words = CompileSuccessfully(""); | |
| spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_1); | |
| int invocation = 0; | |
| tools.SetMessageConsumer([&invocation](spv_message_level_t, const char*, | |
| const spv_position_t&, | |
| const char*) { ++invocation; }); | |
| words.push_back(0xffffffff); // Certainly invalid instruction header. | |
| EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. | |
| EXPECT_EQ(false, tools.Parse(words, header_parser_, instruction_parser_, | |
| &diagnostic_)); | |
| EXPECT_EQ(0, invocation); | |
| EXPECT_STREQ("Invalid opcode: 65535", diagnostic_->error); | |
| } | |
| TEST_F(BinaryParseTest, | |
| ModuleWithSingleInstructionHasValidHeaderAndInstructionCallback) { | |
| for (bool endian_swap : kSwapEndians) { | |
| const auto words = CompileSuccessfully("%1 = OpTypeVoid"); | |
| InSequence calls_expected_in_specific_order; | |
| EXPECT_HEADER(2).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1))) | |
| .WillOnce(Return(SPV_SUCCESS)); | |
| Parse(words, SPV_SUCCESS, endian_swap); | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| } | |
| TEST_F(CxxBinaryParseTest, | |
| ModuleWithSingleInstructionHasValidHeaderAndInstructionCallback) { | |
| for (bool endian_swap : kSwapEndians) { | |
| const auto words = CompileSuccessfully("%1 = OpTypeVoid"); | |
| InSequence calls_expected_in_specific_order; | |
| EXPECT_HEADER(2).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1))) | |
| .WillOnce(Return(SPV_SUCCESS)); | |
| Parse(words, true, endian_swap); | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| } | |
| TEST_F(BinaryParseTest, NullHeaderCallbackIsIgnored) { | |
| const auto words = CompileSuccessfully("%1 = OpTypeVoid"); | |
| EXPECT_CALL(client_, Header(_, _, _, _, _, _)) | |
| .Times(0); // No header callback. | |
| EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1))) | |
| .WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_EQ(SPV_SUCCESS, spvBinaryParse(ScopedContext().context, &client_, | |
| words.data(), words.size(), nullptr, | |
| invoke_instruction, &diagnostic_)); | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| TEST_F(BinaryParseTest, NullInstructionCallbackIsIgnored) { | |
| const auto words = CompileSuccessfully("%1 = OpTypeVoid"); | |
| EXPECT_HEADER((2)).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. | |
| EXPECT_EQ(SPV_SUCCESS, | |
| spvBinaryParse(ScopedContext().context, &client_, words.data(), | |
| words.size(), invoke_header, nullptr, &diagnostic_)); | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| // Check the result of multiple instruction callbacks. | |
| // | |
| // This test exercises non-default values for the following members of the | |
| // spv_parsed_instruction_t struct: words, num_words, opcode, result_id, | |
| // operands, num_operands. | |
| TEST_F(BinaryParseTest, TwoScalarTypesGenerateTwoInstructionCallbacks) { | |
| for (bool endian_swap : kSwapEndians) { | |
| const auto words = CompileSuccessfully( | |
| "%1 = OpTypeVoid " | |
| "%2 = OpTypeInt 32 1"); | |
| InSequence calls_expected_in_specific_order; | |
| EXPECT_HEADER(3).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1))) | |
| .WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(MakeParsedInt32TypeInstruction(2))) | |
| .WillOnce(Return(SPV_SUCCESS)); | |
| Parse(words, SPV_SUCCESS, endian_swap); | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| } | |
| TEST_F(CxxBinaryParseTest, TwoScalarTypesGenerateTwoInstructionCallbacks) { | |
| for (bool endian_swap : kSwapEndians) { | |
| const auto words = CompileSuccessfully( | |
| "%1 = OpTypeVoid " | |
| "%2 = OpTypeInt 32 1"); | |
| InSequence calls_expected_in_specific_order; | |
| EXPECT_HEADER(3).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1))) | |
| .WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(MakeParsedInt32TypeInstruction(2))) | |
| .WillOnce(Return(SPV_SUCCESS)); | |
| Parse(words, true, endian_swap); | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| } | |
| TEST_F(BinaryParseTest, EarlyReturnWithZeroPassingCallbacks) { | |
| for (bool endian_swap : kSwapEndians) { | |
| const auto words = CompileSuccessfully( | |
| "%1 = OpTypeVoid " | |
| "%2 = OpTypeInt 32 1"); | |
| InSequence calls_expected_in_specific_order; | |
| EXPECT_HEADER(3).WillOnce(Return(SPV_ERROR_INVALID_BINARY)); | |
| // Early exit means no calls to Instruction(). | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); | |
| Parse(words, SPV_ERROR_INVALID_BINARY, endian_swap); | |
| // On error, the binary parser doesn't generate its own diagnostics. | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| } | |
| TEST_F(CxxBinaryParseTest, EarlyReturnWithZeroPassingCallbacks) { | |
| for (bool endian_swap : kSwapEndians) { | |
| const auto words = CompileSuccessfully( | |
| "%1 = OpTypeVoid " | |
| "%2 = OpTypeInt 32 1"); | |
| InSequence calls_expected_in_specific_order; | |
| EXPECT_HEADER(3).WillOnce(Return(SPV_ERROR_INVALID_BINARY)); | |
| // Early exit means no calls to Instruction(). | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); | |
| Parse(words, false, endian_swap); | |
| // On error, the binary parser doesn't generate its own diagnostics. | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| } | |
| TEST_F(BinaryParseTest, | |
| EarlyReturnWithZeroPassingCallbacksAndSpecifiedResultCode) { | |
| for (bool endian_swap : kSwapEndians) { | |
| const auto words = CompileSuccessfully( | |
| "%1 = OpTypeVoid " | |
| "%2 = OpTypeInt 32 1"); | |
| InSequence calls_expected_in_specific_order; | |
| EXPECT_HEADER(3).WillOnce(Return(SPV_REQUESTED_TERMINATION)); | |
| // Early exit means no calls to Instruction(). | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); | |
| Parse(words, SPV_REQUESTED_TERMINATION, endian_swap); | |
| // On early termination, the binary parser doesn't generate its own | |
| // diagnostics. | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| } | |
| TEST_F(CxxBinaryParseTest, | |
| EarlyReturnWithZeroPassingCallbacksAndSpecifiedResultCode) { | |
| for (bool endian_swap : kSwapEndians) { | |
| const auto words = CompileSuccessfully( | |
| "%1 = OpTypeVoid " | |
| "%2 = OpTypeInt 32 1"); | |
| InSequence calls_expected_in_specific_order; | |
| EXPECT_HEADER(3).WillOnce(Return(SPV_REQUESTED_TERMINATION)); | |
| // Early exit means no calls to Instruction(). | |
| EXPECT_CALL(client_, Instruction(_)).Times(0); | |
| Parse(words, false, endian_swap); | |
| // On early termination, the binary parser doesn't generate its own | |
| // diagnostics. | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| } | |
| TEST_F(BinaryParseTest, EarlyReturnWithOnePassingCallback) { | |
| for (bool endian_swap : kSwapEndians) { | |
| const auto words = CompileSuccessfully( | |
| "%1 = OpTypeVoid " | |
| "%2 = OpTypeInt 32 1 " | |
| "%3 = OpTypeFloat 32"); | |
| InSequence calls_expected_in_specific_order; | |
| EXPECT_HEADER(4).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1))) | |
| .WillOnce(Return(SPV_REQUESTED_TERMINATION)); | |
| Parse(words, SPV_REQUESTED_TERMINATION, endian_swap); | |
| // On early termination, the binary parser doesn't generate its own | |
| // diagnostics. | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| } | |
| TEST_F(CxxBinaryParseTest, EarlyReturnWithOnePassingCallback) { | |
| for (bool endian_swap : kSwapEndians) { | |
| const auto words = CompileSuccessfully( | |
| "%1 = OpTypeVoid " | |
| "%2 = OpTypeInt 32 1 " | |
| "%3 = OpTypeFloat 32"); | |
| InSequence calls_expected_in_specific_order; | |
| EXPECT_HEADER(4).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1))) | |
| .WillOnce(Return(SPV_REQUESTED_TERMINATION)); | |
| Parse(words, false, endian_swap); | |
| // On early termination, the binary parser doesn't generate its own | |
| // diagnostics. | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| } | |
| TEST_F(BinaryParseTest, EarlyReturnWithTwoPassingCallbacks) { | |
| for (bool endian_swap : kSwapEndians) { | |
| const auto words = CompileSuccessfully( | |
| "%1 = OpTypeVoid " | |
| "%2 = OpTypeInt 32 1 " | |
| "%3 = OpTypeFloat 32"); | |
| InSequence calls_expected_in_specific_order; | |
| EXPECT_HEADER(4).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1))) | |
| .WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(MakeParsedInt32TypeInstruction(2))) | |
| .WillOnce(Return(SPV_REQUESTED_TERMINATION)); | |
| Parse(words, SPV_REQUESTED_TERMINATION, endian_swap); | |
| // On early termination, the binary parser doesn't generate its own | |
| // diagnostics. | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| } | |
| TEST_F(CxxBinaryParseTest, EarlyReturnWithTwoPassingCallbacks) { | |
| for (bool endian_swap : kSwapEndians) { | |
| const auto words = CompileSuccessfully( | |
| "%1 = OpTypeVoid " | |
| "%2 = OpTypeInt 32 1 " | |
| "%3 = OpTypeFloat 32"); | |
| InSequence calls_expected_in_specific_order; | |
| EXPECT_HEADER(4).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1))) | |
| .WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(MakeParsedInt32TypeInstruction(2))) | |
| .WillOnce(Return(SPV_REQUESTED_TERMINATION)); | |
| Parse(words, false, endian_swap); | |
| // On early termination, the binary parser doesn't generate its own | |
| // diagnostics. | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| } | |
| TEST_F(BinaryParseTest, InstructionWithStringOperand) { | |
| for (bool endian_swap : kSwapEndians) { | |
| const std::string str = | |
| "the future is already here, it's just not evenly distributed"; | |
| const auto str_words = MakeVector(str); | |
| const auto instruction = MakeInstruction(spv::Op::OpName, {99}, str_words); | |
| const auto words = Concatenate({ExpectedHeaderForBound(100), instruction}); | |
| InSequence calls_expected_in_specific_order; | |
| EXPECT_HEADER(100).WillOnce(Return(SPV_SUCCESS)); | |
| const auto operands = std::vector<spv_parsed_operand_t>{ | |
| MakeSimpleOperand(1, SPV_OPERAND_TYPE_ID), | |
| MakeLiteralStringOperand(2, static_cast<uint16_t>(str_words.size()))}; | |
| EXPECT_CALL( | |
| client_, | |
| Instruction(ParsedInstruction(spv_parsed_instruction_t{ | |
| instruction.data(), static_cast<uint16_t>(instruction.size()), | |
| uint16_t(spv::Op::OpName), SPV_EXT_INST_TYPE_NONE, 0 /*type id*/, | |
| 0 /* No result id for OpName*/, operands.data(), | |
| static_cast<uint16_t>(operands.size())}))) | |
| .WillOnce(Return(SPV_SUCCESS)); | |
| Parse(words, SPV_SUCCESS, endian_swap); | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| } | |
| TEST_F(CxxBinaryParseTest, InstructionWithStringOperand) { | |
| for (bool endian_swap : kSwapEndians) { | |
| const std::string str = | |
| "the future is already here, it's just not evenly distributed"; | |
| const auto str_words = MakeVector(str); | |
| const auto instruction = MakeInstruction(spv::Op::OpName, {99}, str_words); | |
| const auto words = Concatenate({ExpectedHeaderForBound(100), instruction}); | |
| InSequence calls_expected_in_specific_order; | |
| EXPECT_HEADER(100).WillOnce(Return(SPV_SUCCESS)); | |
| const auto operands = std::vector<spv_parsed_operand_t>{ | |
| MakeSimpleOperand(1, SPV_OPERAND_TYPE_ID), | |
| MakeLiteralStringOperand(2, static_cast<uint16_t>(str_words.size()))}; | |
| EXPECT_CALL( | |
| client_, | |
| Instruction(ParsedInstruction(spv_parsed_instruction_t{ | |
| instruction.data(), static_cast<uint16_t>(instruction.size()), | |
| uint16_t(spv::Op::OpName), SPV_EXT_INST_TYPE_NONE, 0 /*type id*/, | |
| 0 /* No result id for OpName*/, operands.data(), | |
| static_cast<uint16_t>(operands.size())}))) | |
| .WillOnce(Return(SPV_SUCCESS)); | |
| Parse(words, true, endian_swap); | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| } | |
| // Checks for non-zero values for the result_id and ext_inst_type members | |
| // spv_parsed_instruction_t. | |
| TEST_F(BinaryParseTest, ExtendedInstruction) { | |
| const auto words = CompileSuccessfully( | |
| "%extcl = OpExtInstImport \"OpenCL.std\" " | |
| "%result = OpExtInst %float %extcl sqrt %x"); | |
| EXPECT_HEADER(5).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).WillOnce(Return(SPV_SUCCESS)); | |
| // We're only interested in the second call to Instruction(): | |
| const auto operands = std::vector<spv_parsed_operand_t>{ | |
| MakeSimpleOperand(1, SPV_OPERAND_TYPE_TYPE_ID), | |
| MakeSimpleOperand(2, SPV_OPERAND_TYPE_RESULT_ID), | |
| MakeSimpleOperand(3, | |
| SPV_OPERAND_TYPE_ID), // Extended instruction set Id | |
| MakeSimpleOperand(4, SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER), | |
| MakeSimpleOperand(5, SPV_OPERAND_TYPE_ID), // Id of the argument | |
| }; | |
| const auto instruction = MakeInstruction( | |
| spv::Op::OpExtInst, | |
| {2, 3, 1, static_cast<uint32_t>(OpenCLLIB::Entrypoints::Sqrt), 4}); | |
| EXPECT_CALL(client_, | |
| Instruction(ParsedInstruction(spv_parsed_instruction_t{ | |
| instruction.data(), static_cast<uint16_t>(instruction.size()), | |
| uint16_t(spv::Op::OpExtInst), SPV_EXT_INST_TYPE_OPENCL_STD, | |
| 2 /*type id*/, 3 /*result id*/, operands.data(), | |
| static_cast<uint16_t>(operands.size())}))) | |
| .WillOnce(Return(SPV_SUCCESS)); | |
| // Since we are actually checking the output, don't test the | |
| // endian-swapped version. | |
| Parse(words, SPV_SUCCESS, false); | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| TEST_F(CxxBinaryParseTest, ExtendedInstruction) { | |
| const auto words = CompileSuccessfully( | |
| "%extcl = OpExtInstImport \"OpenCL.std\" " | |
| "%result = OpExtInst %float %extcl sqrt %x"); | |
| EXPECT_HEADER(5).WillOnce(Return(SPV_SUCCESS)); | |
| EXPECT_CALL(client_, Instruction(_)).WillOnce(Return(SPV_SUCCESS)); | |
| // We're only interested in the second call to Instruction(): | |
| const auto operands = std::vector<spv_parsed_operand_t>{ | |
| MakeSimpleOperand(1, SPV_OPERAND_TYPE_TYPE_ID), | |
| MakeSimpleOperand(2, SPV_OPERAND_TYPE_RESULT_ID), | |
| MakeSimpleOperand(3, | |
| SPV_OPERAND_TYPE_ID), // Extended instruction set Id | |
| MakeSimpleOperand(4, SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER), | |
| MakeSimpleOperand(5, SPV_OPERAND_TYPE_ID), // Id of the argument | |
| }; | |
| const auto instruction = MakeInstruction( | |
| spv::Op::OpExtInst, | |
| {2, 3, 1, static_cast<uint32_t>(OpenCLLIB::Entrypoints::Sqrt), 4}); | |
| EXPECT_CALL(client_, | |
| Instruction(ParsedInstruction(spv_parsed_instruction_t{ | |
| instruction.data(), static_cast<uint16_t>(instruction.size()), | |
| uint16_t(spv::Op::OpExtInst), SPV_EXT_INST_TYPE_OPENCL_STD, | |
| 2 /*type id*/, 3 /*result id*/, operands.data(), | |
| static_cast<uint16_t>(operands.size())}))) | |
| .WillOnce(Return(SPV_SUCCESS)); | |
| // Since we are actually checking the output, don't test the | |
| // endian-swapped version. | |
| Parse(words, true, false); | |
| EXPECT_EQ(nullptr, diagnostic_); | |
| } | |
| // A binary parser diagnostic test case where we provide the words array | |
| // pointer and word count explicitly. | |
| struct WordsAndCountDiagnosticCase { | |
| const uint32_t* words; | |
| size_t num_words; | |
| std::string expected_diagnostic; | |
| }; | |
| using BinaryParseWordsAndCountDiagnosticTest = spvtest::TextToBinaryTestBase< | |
| ::testing::TestWithParam<WordsAndCountDiagnosticCase>>; | |
| TEST_P(BinaryParseWordsAndCountDiagnosticTest, WordAndCountCases) { | |
| EXPECT_EQ( | |
| SPV_ERROR_INVALID_BINARY, | |
| spvBinaryParse(ScopedContext().context, nullptr, GetParam().words, | |
| GetParam().num_words, nullptr, nullptr, &diagnostic)); | |
| ASSERT_NE(nullptr, diagnostic); | |
| EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic)); | |
| } | |
| INSTANTIATE_TEST_SUITE_P( | |
| BinaryParseDiagnostic, BinaryParseWordsAndCountDiagnosticTest, | |
| ::testing::ValuesIn(std::vector<WordsAndCountDiagnosticCase>{ | |
| {nullptr, 0, "Missing module."}, | |
| {kHeaderForBound1, 0, | |
| "Module has incomplete header: only 0 words instead of 5"}, | |
| {kHeaderForBound1, 1, | |
| "Module has incomplete header: only 1 words instead of 5"}, | |
| {kHeaderForBound1, 2, | |
| "Module has incomplete header: only 2 words instead of 5"}, | |
| {kHeaderForBound1, 3, | |
| "Module has incomplete header: only 3 words instead of 5"}, | |
| {kHeaderForBound1, 4, | |
| "Module has incomplete header: only 4 words instead of 5"}, | |
| })); | |
| // A binary parser diagnostic test case where a vector of words is | |
| // provided. We'll use this to express cases that can't be created | |
| // via the assembler. Either we want to make a malformed instruction, | |
| // or an invalid case the assembler would reject. | |
| struct WordVectorDiagnosticCase { | |
| std::vector<uint32_t> words; | |
| std::string expected_diagnostic; | |
| }; | |
| using BinaryParseWordVectorDiagnosticTest = spvtest::TextToBinaryTestBase< | |
| ::testing::TestWithParam<WordVectorDiagnosticCase>>; | |
| TEST_P(BinaryParseWordVectorDiagnosticTest, WordVectorCases) { | |
| const auto& words = GetParam().words; | |
| EXPECT_THAT(spvBinaryParse(ScopedContext().context, nullptr, words.data(), | |
| words.size(), nullptr, nullptr, &diagnostic), | |
| AnyOf(SPV_ERROR_INVALID_BINARY, SPV_ERROR_INVALID_ID)); | |
| ASSERT_NE(nullptr, diagnostic); | |
| EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic)); | |
| } | |
| INSTANTIATE_TEST_SUITE_P( | |
| BinaryParseDiagnostic, BinaryParseWordVectorDiagnosticTest, | |
| ::testing::ValuesIn(std::vector<WordVectorDiagnosticCase>{ | |
| {Concatenate({ExpectedHeaderForBound(1), | |
| {spvOpcodeMake(0, spv::Op::OpNop)}}), | |
| "Invalid instruction word count: 0"}, | |
| {Concatenate( | |
| {ExpectedHeaderForBound(1), | |
| {spvOpcodeMake(1, static_cast<spv::Op>( | |
| std::numeric_limits<uint16_t>::max()))}}), | |
| "Invalid opcode: 65535"}, | |
| {Concatenate({ExpectedHeaderForBound(1), | |
| MakeInstruction(spv::Op::OpNop, {42})}), | |
| "Invalid instruction OpNop starting at word 5: expected " | |
| "no more operands after 1 words, but stated word count is 2."}, | |
| // Supply several more unexpected words. | |
| {Concatenate({ExpectedHeaderForBound(1), | |
| MakeInstruction(spv::Op::OpNop, | |
| {42, 43, 44, 45, 46, 47})}), | |
| "Invalid instruction OpNop starting at word 5: expected " | |
| "no more operands after 1 words, but stated word count is 7."}, | |
| {Concatenate({ExpectedHeaderForBound(1), | |
| MakeInstruction(spv::Op::OpTypeVoid, {1, 2})}), | |
| "Invalid instruction OpTypeVoid starting at word 5: expected " | |
| "no more operands after 2 words, but stated word count is 3."}, | |
| {Concatenate({ExpectedHeaderForBound(1), | |
| MakeInstruction(spv::Op::OpTypeVoid, {1, 2, 5, 9, 10})}), | |
| "Invalid instruction OpTypeVoid starting at word 5: expected " | |
| "no more operands after 2 words, but stated word count is 6."}, | |
| {Concatenate({ExpectedHeaderForBound(1), | |
| MakeInstruction(spv::Op::OpTypeInt, {1, 32, 1, 9})}), | |
| "Invalid instruction OpTypeInt starting at word 5: expected " | |
| "no more operands after 4 words, but stated word count is 5."}, | |
| {Concatenate({ExpectedHeaderForBound(1), | |
| MakeInstruction(spv::Op::OpTypeInt, {1})}), | |
| "End of input reached while decoding OpTypeInt starting at word 5:" | |
| " expected more operands after 2 words."}, | |
| // Check several cases for running off the end of input. | |
| // Detect a missing single word operand. | |
| {Concatenate({ExpectedHeaderForBound(1), | |
| {spvOpcodeMake(2, spv::Op::OpTypeStruct)}}), | |
| "End of input reached while decoding OpTypeStruct starting at word" | |
| " 5: missing result ID operand at word offset 1."}, | |
| // Detect this a missing a multi-word operand to OpConstant. | |
| // We also lie and say the OpConstant instruction has 5 words when | |
| // it only has 3. Corresponds to something like this: | |
| // %1 = OpTypeInt 64 0 | |
| // %2 = OpConstant %1 <missing> | |
| {Concatenate({ExpectedHeaderForBound(3), | |
| {MakeInstruction(spv::Op::OpTypeInt, {1, 64, 0})}, | |
| {spvOpcodeMake(5, spv::Op::OpConstant), 1, 2}}), | |
| "End of input reached while decoding OpConstant starting at word" | |
| " 9: missing possibly multi-word literal number operand at word " | |
| "offset 3."}, | |
| // Detect when we provide only one word from the 64-bit literal, | |
| // and again lie about the number of words in the instruction. | |
| {Concatenate({ExpectedHeaderForBound(3), | |
| {MakeInstruction(spv::Op::OpTypeInt, {1, 64, 0})}, | |
| {spvOpcodeMake(5, spv::Op::OpConstant), 1, 2, 42}}), | |
| "End of input reached while decoding OpConstant starting at word" | |
| " 9: truncated possibly multi-word literal number operand at word " | |
| "offset 3."}, | |
| // Detect when a required string operand is missing. | |
| // Also, lie about the length of the instruction. | |
| {Concatenate({ExpectedHeaderForBound(3), | |
| {spvOpcodeMake(3, spv::Op::OpString), 1}}), | |
| "End of input reached while decoding OpString starting at word" | |
| " 5: missing literal string operand at word offset 2."}, | |
| // Detect when a required string operand is truncated: it's missing | |
| // a null terminator. Catching the error avoids a buffer overrun. | |
| {Concatenate({ExpectedHeaderForBound(3), | |
| {spvOpcodeMake(4, spv::Op::OpString), 1, 0x41414141, | |
| 0x41414141}}), | |
| "End of input reached while decoding OpString starting at word" | |
| " 5: truncated literal string operand at word offset 2."}, | |
| // Detect when an optional string operand is truncated: it's missing | |
| // a null terminator. Catching the error avoids a buffer overrun. | |
| // (It is valid for an optional string operand to be absent.) | |
| {Concatenate({ExpectedHeaderForBound(3), | |
| {spvOpcodeMake(6, spv::Op::OpSource), | |
| static_cast<uint32_t>(spv::SourceLanguage::OpenCL_C), | |
| 210, 1 /* file id */, | |
| /*start of string*/ 0x41414141, 0x41414141}}), | |
| "End of input reached while decoding OpSource starting at word" | |
| " 5: truncated literal string operand at word offset 4."}, | |
| // (End of input exhaustion test cases.) | |
| // In this case the instruction word count is too small, where | |
| // it would truncate a multi-word operand to OpConstant. | |
| {Concatenate({ExpectedHeaderForBound(3), | |
| {MakeInstruction(spv::Op::OpTypeInt, {1, 64, 0})}, | |
| {spvOpcodeMake(4, spv::Op::OpConstant), 1, 2, 44, 44}}), | |
| "Invalid word count: OpConstant starting at word 9 says it has 4" | |
| " words, but found 5 words instead."}, | |
| // Word count is to small, where it would truncate a literal string. | |
| {Concatenate({ExpectedHeaderForBound(2), | |
| {spvOpcodeMake(3, spv::Op::OpString), 1, 0x41414141, 0}}), | |
| "Invalid word count: OpString starting at word 5 says it has 3" | |
| " words, but found 4 words instead."}, | |
| // Word count is too large. The string terminates before the last | |
| // word. | |
| {Concatenate({ExpectedHeaderForBound(2), | |
| {spvOpcodeMake(4, spv::Op::OpString), 1 /* result id */}, | |
| MakeVector("abc"), | |
| {0 /* this word does not belong*/}}), | |
| "Invalid instruction OpString starting at word 5: expected no more" | |
| " operands after 3 words, but stated word count is 4."}, | |
| // Word count is too large. There are too many words after the string | |
| // literal. A linkage attribute decoration is the only case in SPIR-V | |
| // where a string operand is followed by another operand. | |
| {Concatenate( | |
| {ExpectedHeaderForBound(2), | |
| {spvOpcodeMake(6, spv::Op::OpDecorate), 1 /* target id */, | |
| static_cast<uint32_t>(spv::Decoration::LinkageAttributes)}, | |
| MakeVector("abc"), | |
| {static_cast<uint32_t>(spv::LinkageType::Import), | |
| 0 /* does not belong */}}), | |
| "Invalid instruction OpDecorate starting at word 5: expected no more" | |
| " operands after 5 words, but stated word count is 6."}, | |
| // Like the previous case, but with 5 extra words. | |
| {Concatenate( | |
| {ExpectedHeaderForBound(2), | |
| {spvOpcodeMake(10, spv::Op::OpDecorate), 1 /* target id */, | |
| static_cast<uint32_t>(spv::Decoration::LinkageAttributes)}, | |
| MakeVector("abc"), | |
| {static_cast<uint32_t>(spv::LinkageType::Import), | |
| /* don't belong */ 0, 1, 2, 3, 4}}), | |
| "Invalid instruction OpDecorate starting at word 5: expected no more" | |
| " operands after 5 words, but stated word count is 10."}, | |
| // Like the previous two cases, but with OpMemberDecorate. | |
| {Concatenate( | |
| {ExpectedHeaderForBound(2), | |
| {spvOpcodeMake(7, spv::Op::OpMemberDecorate), 1 /* target id */, | |
| 42 /* member index */, | |
| static_cast<uint32_t>(spv::Decoration::LinkageAttributes)}, | |
| MakeVector("abc"), | |
| {static_cast<uint32_t>(spv::LinkageType::Import), | |
| 0 /* does not belong */}}), | |
| "Invalid instruction OpMemberDecorate starting at word 5: expected no" | |
| " more operands after 6 words, but stated word count is 7."}, | |
| {Concatenate( | |
| {ExpectedHeaderForBound(2), | |
| {spvOpcodeMake(11, spv::Op::OpMemberDecorate), 1 /* target id */, | |
| 42 /* member index */, | |
| static_cast<uint32_t>(spv::Decoration::LinkageAttributes)}, | |
| MakeVector("abc"), | |
| {static_cast<uint32_t>(spv::LinkageType::Import), | |
| /* don't belong */ 0, 1, 2, 3, 4}}), | |
| "Invalid instruction OpMemberDecorate starting at word 5: expected no" | |
| " more operands after 6 words, but stated word count is 11."}, | |
| // Word count is too large. There should be no more words | |
| // after the RelaxedPrecision decoration. | |
| {Concatenate({ExpectedHeaderForBound(2), | |
| {spvOpcodeMake(4, spv::Op::OpDecorate), 1 /* target id */, | |
| static_cast<uint32_t>(spv::Decoration::RelaxedPrecision), | |
| 0 /* does not belong */}}), | |
| "Invalid instruction OpDecorate starting at word 5: expected no" | |
| " more operands after 3 words, but stated word count is 4."}, | |
| // Word count is too large. There should be only one word after | |
| // the SpecId decoration enum word. | |
| {Concatenate({ExpectedHeaderForBound(2), | |
| {spvOpcodeMake(5, spv::Op::OpDecorate), 1 /* target id */, | |
| static_cast<uint32_t>(spv::Decoration::SpecId), | |
| 42 /* the spec id */, 0 /* does not belong */}}), | |
| "Invalid instruction OpDecorate starting at word 5: expected no" | |
| " more operands after 4 words, but stated word count is 5."}, | |
| {Concatenate({ExpectedHeaderForBound(2), | |
| {spvOpcodeMake(2, spv::Op::OpTypeVoid), 0}}), | |
| "Error: Result Id is 0"}, | |
| {Concatenate({ | |
| ExpectedHeaderForBound(2), | |
| {spvOpcodeMake(2, spv::Op::OpTypeVoid), 1}, | |
| {spvOpcodeMake(2, spv::Op::OpTypeBool), 1}, | |
| }), | |
| "Id 1 is defined more than once"}, | |
| {Concatenate({ExpectedHeaderForBound(3), | |
| MakeInstruction(spv::Op::OpExtInst, {2, 3, 100, 4, 5})}), | |
| "OpExtInst set Id 100 does not reference an OpExtInstImport result " | |
| "Id"}, | |
| {Concatenate({ExpectedHeaderForBound(101), | |
| MakeInstruction(spv::Op::OpExtInstImport, {100}, | |
| MakeVector("OpenCL.std")), | |
| // OpenCL cos is #14 | |
| MakeInstruction(spv::Op::OpExtInst, | |
| {2, 3, 100, 14, 5, 999})}), | |
| "Invalid instruction OpExtInst starting at word 10: expected no " | |
| "more operands after 6 words, but stated word count is 7."}, | |
| // In this case, the OpSwitch selector refers to an invalid ID. | |
| {Concatenate({ExpectedHeaderForBound(3), | |
| MakeInstruction(spv::Op::OpSwitch, {1, 2, 42, 3})}), | |
| "Invalid OpSwitch: selector id 1 has no type"}, | |
| // In this case, the OpSwitch selector refers to an ID that has | |
| // no type. | |
| {Concatenate({ExpectedHeaderForBound(3), | |
| MakeInstruction(spv::Op::OpLabel, {1}), | |
| MakeInstruction(spv::Op::OpSwitch, {1, 2, 42, 3})}), | |
| "Invalid OpSwitch: selector id 1 has no type"}, | |
| {Concatenate({ExpectedHeaderForBound(3), | |
| MakeInstruction(spv::Op::OpTypeInt, {1, 32, 0}), | |
| MakeInstruction(spv::Op::OpSwitch, {1, 3, 42, 3})}), | |
| "Invalid OpSwitch: selector id 1 is a type, not a value"}, | |
| {Concatenate({ExpectedHeaderForBound(3), | |
| MakeInstruction(spv::Op::OpTypeFloat, {1, 32}), | |
| MakeInstruction(spv::Op::OpConstant, {1, 2, 0x78f00000}), | |
| MakeInstruction(spv::Op::OpSwitch, {2, 3, 42, 3})}), | |
| "Invalid OpSwitch: selector id 2 is not a scalar integer"}, | |
| {Concatenate({ExpectedHeaderForBound(3), | |
| MakeInstruction(spv::Op::OpExtInstImport, {1}, | |
| MakeVector("invalid-import"))}), | |
| "Invalid extended instruction import 'invalid-import'"}, | |
| {Concatenate({ | |
| ExpectedHeaderForBound(3), | |
| MakeInstruction(spv::Op::OpTypeInt, {1, 32, 0}), | |
| MakeInstruction(spv::Op::OpConstant, {2, 2, 42}), | |
| }), | |
| "Type Id 2 is not a type"}, | |
| {Concatenate({ | |
| ExpectedHeaderForBound(3), | |
| MakeInstruction(spv::Op::OpTypeBool, {1}), | |
| MakeInstruction(spv::Op::OpConstant, {1, 2, 42}), | |
| }), | |
| "Type Id 1 is not a scalar numeric type"}, | |
| })); | |
| // A binary parser diagnostic case generated from an assembly text input. | |
| struct AssemblyDiagnosticCase { | |
| std::string assembly; | |
| std::string expected_diagnostic; | |
| }; | |
| using BinaryParseAssemblyDiagnosticTest = spvtest::TextToBinaryTestBase< | |
| ::testing::TestWithParam<AssemblyDiagnosticCase>>; | |
| TEST_P(BinaryParseAssemblyDiagnosticTest, AssemblyCases) { | |
| auto words = CompileSuccessfully(GetParam().assembly); | |
| EXPECT_THAT(spvBinaryParse(ScopedContext().context, nullptr, words.data(), | |
| words.size(), nullptr, nullptr, &diagnostic), | |
| AnyOf(SPV_ERROR_INVALID_BINARY, SPV_ERROR_INVALID_ID)); | |
| ASSERT_NE(nullptr, diagnostic); | |
| EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic)); | |
| } | |
| INSTANTIATE_TEST_SUITE_P( | |
| BinaryParseDiagnostic, BinaryParseAssemblyDiagnosticTest, | |
| ::testing::ValuesIn(std::vector<AssemblyDiagnosticCase>{ | |
| {"%1 = OpConstant !0 42", "Error: Type Id is 0"}, | |
| // A required id is 0. | |
| {"OpName !0 \"foo\"", "Id is 0"}, | |
| // An optional id is 0, in this case the optional | |
| // initializer. | |
| {"%2 = OpVariable %1 CrossWorkgroup !0", "Id is 0"}, | |
| {"OpControlBarrier !0 %1 %2", "scope ID is 0"}, | |
| {"OpControlBarrier %1 !0 %2", "scope ID is 0"}, | |
| {"OpControlBarrier %1 %2 !0", "memory semantics ID is 0"}, | |
| {"%import = OpExtInstImport \"GLSL.std.450\" " | |
| "%result = OpExtInst %type %import !999999 %x", | |
| "Invalid extended instruction number: 999999"}, | |
| {"%2 = OpSpecConstantOp %1 !1000 %2", | |
| "Invalid OpSpecConstantOp opcode: 1000"}, | |
| {"OpCapability !9999", "Invalid capability operand: 9999"}, | |
| {"OpSource !9999 100", "Invalid source language operand: 9999"}, | |
| {"OpEntryPoint !9999", "Invalid execution model operand: 9999"}, | |
| {"OpMemoryModel !9999", "Invalid addressing model operand: 9999"}, | |
| {"OpMemoryModel Logical !9999", "Invalid memory model operand: 9999"}, | |
| {"OpExecutionMode %1 !9999", "Invalid execution mode operand: 9999"}, | |
| {"OpTypeForwardPointer %1 !9999", | |
| "Invalid storage class operand: 9999"}, | |
| {"%2 = OpTypeImage %1 !9999", "Invalid dimensionality operand: 9999"}, | |
| {"%2 = OpTypeImage %1 1D 0 0 0 0 !9999", | |
| "Invalid image format operand: 9999"}, | |
| {"OpDecorate %1 FPRoundingMode !9999", | |
| "Invalid floating-point rounding mode operand: 9999"}, | |
| {"OpDecorate %1 LinkageAttributes \"C\" !9999", | |
| "Invalid linkage type operand: 9999"}, | |
| {"%1 = OpTypePipe !9999", "Invalid access qualifier operand: 9999"}, | |
| {"OpDecorate %1 FuncParamAttr !9999", | |
| "Invalid function parameter attribute operand: 9999"}, | |
| {"OpDecorate %1 !9999", "Invalid decoration operand: 9999"}, | |
| {"OpDecorate %1 BuiltIn !9999", "Invalid built-in operand: 9999"}, | |
| {"%2 = OpGroupIAdd %1 %3 !9999", | |
| "Invalid group operation operand: 9999"}, | |
| {"OpDecorate %1 FPFastMathMode !63", | |
| "Invalid floating-point fast math mode operand: 63 has invalid mask " | |
| "component 32"}, | |
| {"%2 = OpFunction %2 !31", | |
| "Invalid function control operand: 31 has invalid mask component 16"}, | |
| {"OpLoopMerge %1 %2 !1027", | |
| "Invalid loop control operand: 1027 has invalid mask component 1024"}, | |
| {"%2 = OpImageFetch %1 %image %coord !32770", | |
| "Invalid image operand: 32770 has invalid mask component 32768"}, | |
| {"OpSelectionMerge %1 !7", | |
| "Invalid selection control operand: 7 has invalid mask component 4"}, | |
| })); | |
| } // namespace | |
| } // namespace spvtools |