xref: /aosp_15_r20/external/swiftshader/third_party/SPIRV-Tools/test/binary_parse_test.cpp (revision 03ce13f70fcc45d86ee91b7ee4cab1936a95046e)
1 // Copyright (c) 2015-2016 The Khronos Group Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include <algorithm>
16 #include <limits>
17 #include <sstream>
18 #include <string>
19 #include <vector>
20 
21 #include "gmock/gmock.h"
22 #include "source/latest_version_opencl_std_header.h"
23 #include "source/table.h"
24 #include "source/util/string_utils.h"
25 #include "test/test_fixture.h"
26 #include "test/unit_spirv.h"
27 
28 // Returns true if two spv_parsed_operand_t values are equal.
29 // To use this operator, this definition must appear in the same namespace
30 // as spv_parsed_operand_t.
operator ==(const spv_parsed_operand_t & a,const spv_parsed_operand_t & b)31 static bool operator==(const spv_parsed_operand_t& a,
32                        const spv_parsed_operand_t& b) {
33   return a.offset == b.offset && a.num_words == b.num_words &&
34          a.type == b.type && a.number_kind == b.number_kind &&
35          a.number_bit_width == b.number_bit_width;
36 }
37 
38 namespace spvtools {
39 namespace {
40 
41 using ::spvtest::Concatenate;
42 using ::spvtest::MakeInstruction;
43 using utils::MakeVector;
44 using ::spvtest::ScopedContext;
45 using ::testing::_;
46 using ::testing::AnyOf;
47 using ::testing::Eq;
48 using ::testing::InSequence;
49 using ::testing::Return;
50 
51 // An easily-constructible and comparable object for the contents of an
52 // spv_parsed_instruction_t.  Unlike spv_parsed_instruction_t, owns the memory
53 // of its components.
54 struct ParsedInstruction {
ParsedInstructionspvtools::__anon522c80920111::ParsedInstruction55   explicit ParsedInstruction(const spv_parsed_instruction_t& inst)
56       : words(inst.words, inst.words + inst.num_words),
57         opcode(static_cast<spv::Op>(inst.opcode)),
58         ext_inst_type(inst.ext_inst_type),
59         type_id(inst.type_id),
60         result_id(inst.result_id),
61         operands(inst.operands, inst.operands + inst.num_operands) {}
62 
63   std::vector<uint32_t> words;
64   spv::Op opcode;
65   spv_ext_inst_type_t ext_inst_type;
66   uint32_t type_id;
67   uint32_t result_id;
68   std::vector<spv_parsed_operand_t> operands;
69 
operator ==spvtools::__anon522c80920111::ParsedInstruction70   bool operator==(const ParsedInstruction& b) const {
71     return words == b.words && opcode == b.opcode &&
72            ext_inst_type == b.ext_inst_type && type_id == b.type_id &&
73            result_id == b.result_id && operands == b.operands;
74   }
75 };
76 
77 // Prints a ParsedInstruction object to the given output stream, and returns
78 // the stream.
operator <<(std::ostream & os,const ParsedInstruction & inst)79 std::ostream& operator<<(std::ostream& os, const ParsedInstruction& inst) {
80   os << "\nParsedInstruction( {";
81   spvtest::PrintTo(spvtest::WordVector(inst.words), &os);
82   os << "}, opcode: " << int(inst.opcode)
83      << " ext_inst_type: " << int(inst.ext_inst_type)
84      << " type_id: " << inst.type_id << " result_id: " << inst.result_id;
85   for (const auto& operand : inst.operands) {
86     os << " { offset: " << operand.offset << " num_words: " << operand.num_words
87        << " type: " << int(operand.type)
88        << " number_kind: " << int(operand.number_kind)
89        << " number_bit_width: " << int(operand.number_bit_width) << "}";
90   }
91   os << ")";
92   return os;
93 }
94 
95 // Basic check for the equality operator on ParsedInstruction.
TEST(ParsedInstruction,ZeroInitializedAreEqual)96 TEST(ParsedInstruction, ZeroInitializedAreEqual) {
97   spv_parsed_instruction_t pi = {};
98   ParsedInstruction a(pi);
99   ParsedInstruction b(pi);
100   EXPECT_THAT(a, ::testing::TypedEq<ParsedInstruction>(b));
101 }
102 
103 // Googlemock class receiving Header/Instruction calls from spvBinaryParse().
104 class MockParseClient {
105  public:
106   MOCK_METHOD6(Header, spv_result_t(spv_endianness_t endian, uint32_t magic,
107                                     uint32_t version, uint32_t generator,
108                                     uint32_t id_bound, uint32_t reserved));
109   MOCK_METHOD1(Instruction, spv_result_t(const ParsedInstruction&));
110 };
111 
112 // Casts user_data as MockParseClient and invokes its Header().
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)113 spv_result_t invoke_header(void* user_data, spv_endianness_t endian,
114                            uint32_t magic, uint32_t version, uint32_t generator,
115                            uint32_t id_bound, uint32_t reserved) {
116   return static_cast<MockParseClient*>(user_data)->Header(
117       endian, magic, version, generator, id_bound, reserved);
118 }
119 
120 // Casts user_data as MockParseClient and invokes its Instruction().
invoke_instruction(void * user_data,const spv_parsed_instruction_t * parsed_instruction)121 spv_result_t invoke_instruction(
122     void* user_data, const spv_parsed_instruction_t* parsed_instruction) {
123   return static_cast<MockParseClient*>(user_data)->Instruction(
124       ParsedInstruction(*parsed_instruction));
125 }
126 
127 // The SPIR-V module header words for the Khronos Assembler generator,
128 // for a module with an ID bound of 1.
129 const uint32_t kHeaderForBound1[] = {
130     spv::MagicNumber, spv::Version,
131     SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), 1 /*bound*/,
132     0 /*schema*/};
133 
134 // Returns the expected SPIR-V module header words for the Khronos
135 // Assembler generator, and with a given Id bound.
ExpectedHeaderForBound(uint32_t bound)136 std::vector<uint32_t> ExpectedHeaderForBound(uint32_t bound) {
137   return {spv::MagicNumber, 0x10000,
138           SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), bound, 0};
139 }
140 
141 // Returns a parsed operand for a non-number value at the given word offset
142 // within an instruction.
MakeSimpleOperand(uint16_t offset,spv_operand_type_t type)143 spv_parsed_operand_t MakeSimpleOperand(uint16_t offset,
144                                        spv_operand_type_t type) {
145   return {offset, 1, type, SPV_NUMBER_NONE, 0};
146 }
147 
148 // Returns a parsed operand for a literal unsigned integer value at the given
149 // word offset within an instruction.
MakeLiteralNumberOperand(uint16_t offset)150 spv_parsed_operand_t MakeLiteralNumberOperand(uint16_t offset) {
151   return {offset, 1, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_NUMBER_UNSIGNED_INT,
152           32};
153 }
154 
155 // Returns a parsed operand for a literal string value at the given
156 // word offset within an instruction.
MakeLiteralStringOperand(uint16_t offset,uint16_t length)157 spv_parsed_operand_t MakeLiteralStringOperand(uint16_t offset,
158                                               uint16_t length) {
159   return {offset, length, SPV_OPERAND_TYPE_LITERAL_STRING, SPV_NUMBER_NONE, 0};
160 }
161 
162 // Returns a ParsedInstruction for an OpTypeVoid instruction that would
163 // generate the given result Id.
MakeParsedVoidTypeInstruction(uint32_t result_id)164 ParsedInstruction MakeParsedVoidTypeInstruction(uint32_t result_id) {
165   const auto void_inst = MakeInstruction(spv::Op::OpTypeVoid, {result_id});
166   const auto void_operands = std::vector<spv_parsed_operand_t>{
167       MakeSimpleOperand(1, SPV_OPERAND_TYPE_RESULT_ID)};
168   const spv_parsed_instruction_t parsed_void_inst = {
169       void_inst.data(),
170       static_cast<uint16_t>(void_inst.size()),
171       uint16_t(spv::Op::OpTypeVoid),
172       SPV_EXT_INST_TYPE_NONE,
173       0,  // type id
174       result_id,
175       void_operands.data(),
176       static_cast<uint16_t>(void_operands.size())};
177   return ParsedInstruction(parsed_void_inst);
178 }
179 
180 // Returns a ParsedInstruction for an OpTypeInt instruction that generates
181 // the given result Id for a 32-bit signed integer scalar type.
MakeParsedInt32TypeInstruction(uint32_t result_id)182 ParsedInstruction MakeParsedInt32TypeInstruction(uint32_t result_id) {
183   const auto i32_inst = MakeInstruction(spv::Op::OpTypeInt, {result_id, 32, 1});
184   const auto i32_operands = std::vector<spv_parsed_operand_t>{
185       MakeSimpleOperand(1, SPV_OPERAND_TYPE_RESULT_ID),
186       MakeLiteralNumberOperand(2), MakeLiteralNumberOperand(3)};
187   spv_parsed_instruction_t parsed_i32_inst = {
188       i32_inst.data(),
189       static_cast<uint16_t>(i32_inst.size()),
190       uint16_t(spv::Op::OpTypeInt),
191       SPV_EXT_INST_TYPE_NONE,
192       0,  // type id
193       result_id,
194       i32_operands.data(),
195       static_cast<uint16_t>(i32_operands.size())};
196   return ParsedInstruction(parsed_i32_inst);
197 }
198 
199 class BinaryParseTest : public spvtest::TextToBinaryTestBase<::testing::Test> {
200  protected:
~BinaryParseTest()201   ~BinaryParseTest() override { spvDiagnosticDestroy(diagnostic_); }
202 
Parse(const SpirvVector & words,spv_result_t expected_result,bool flip_words=false)203   void Parse(const SpirvVector& words, spv_result_t expected_result,
204              bool flip_words = false) {
205     SpirvVector flipped_words(words);
206     MaybeFlipWords(flip_words, flipped_words.begin(), flipped_words.end());
207     EXPECT_EQ(expected_result,
208               spvBinaryParse(ScopedContext().context, &client_,
209                              flipped_words.data(), flipped_words.size(),
210                              invoke_header, invoke_instruction, &diagnostic_));
211   }
212 
213   spv_diagnostic diagnostic_ = nullptr;
214   MockParseClient client_;
215 };
216 
217 class CxxBinaryParseTest
218     : public spvtest::TextToBinaryTestBase<::testing::Test> {
219  protected:
CxxBinaryParseTest()220   CxxBinaryParseTest() {
221     header_parser_ = [this](const spv_endianness_t endianness,
222                             const spv_parsed_header_t& header) {
223       return this->client_.Header(endianness, header.magic, header.version,
224                                   header.generator, header.bound,
225                                   header.reserved);
226     };
227 
228     instruction_parser_ = [this](const spv_parsed_instruction_t& instruction) {
229       return this->client_.Instruction(ParsedInstruction(instruction));
230     };
231   }
232 
~CxxBinaryParseTest()233   ~CxxBinaryParseTest() override { spvDiagnosticDestroy(diagnostic_); }
234 
Parse(const SpirvVector & words,bool expected_result,bool flip_words=false,spv_target_env env=SPV_ENV_UNIVERSAL_1_0)235   void Parse(const SpirvVector& words, bool expected_result,
236              bool flip_words = false,
237              spv_target_env env = SPV_ENV_UNIVERSAL_1_0) {
238     SpirvVector flipped_words(words);
239     MaybeFlipWords(flip_words, flipped_words.begin(), flipped_words.end());
240     spvtools::SpirvTools tools(env);
241     EXPECT_EQ(expected_result, tools.Parse(flipped_words, header_parser_,
242                                            instruction_parser_, &diagnostic_));
243   }
244 
245   spv_diagnostic diagnostic_ = nullptr;
246   MockParseClient client_;
247   HeaderParser header_parser_;
248   InstructionParser instruction_parser_;
249 };
250 
251 // Adds an EXPECT_CALL to client_->Header() with appropriate parameters,
252 // including bound.  Returns the EXPECT_CALL result.
253 #define EXPECT_HEADER(bound)                                                 \
254   EXPECT_CALL(client_,                                                       \
255               Header(AnyOf(SPV_ENDIANNESS_LITTLE, SPV_ENDIANNESS_BIG),       \
256                      spv::MagicNumber, 0x10000,                              \
257                      SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), \
258                      bound, 0 /*reserved*/))
259 
260 static const bool kSwapEndians[] = {false, true};
261 
TEST_F(BinaryParseTest,EmptyModuleHasValidHeaderAndNoInstructionCallbacks)262 TEST_F(BinaryParseTest, EmptyModuleHasValidHeaderAndNoInstructionCallbacks) {
263   for (bool endian_swap : kSwapEndians) {
264     const auto words = CompileSuccessfully("");
265     EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
266     EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
267     Parse(words, SPV_SUCCESS, endian_swap);
268     EXPECT_EQ(nullptr, diagnostic_);
269   }
270 }
271 
TEST_F(CxxBinaryParseTest,EmptyModuleHasValidHeaderAndNoInstructionCallbacks)272 TEST_F(CxxBinaryParseTest, EmptyModuleHasValidHeaderAndNoInstructionCallbacks) {
273   for (bool endian_swap : kSwapEndians) {
274     const auto words = CompileSuccessfully("");
275     EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
276     EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
277     Parse(words, true, endian_swap);
278     EXPECT_EQ(nullptr, diagnostic_);
279   }
280 }
281 
TEST_F(BinaryParseTest,NullDiagnosticsIsOkForGoodParse)282 TEST_F(BinaryParseTest, NullDiagnosticsIsOkForGoodParse) {
283   const auto words = CompileSuccessfully("");
284   EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
285   EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
286   EXPECT_EQ(
287       SPV_SUCCESS,
288       spvBinaryParse(ScopedContext().context, &client_, words.data(),
289                      words.size(), invoke_header, invoke_instruction, nullptr));
290 }
291 
TEST_F(CxxBinaryParseTest,NullDiagnosticsIsOkForGoodParse)292 TEST_F(CxxBinaryParseTest, NullDiagnosticsIsOkForGoodParse) {
293   const auto words = CompileSuccessfully("");
294   EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
295   EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
296   spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
297   EXPECT_EQ(true,
298             tools.Parse(words, header_parser_, instruction_parser_, nullptr));
299 }
300 
TEST_F(BinaryParseTest,NullDiagnosticsIsOkForBadParse)301 TEST_F(BinaryParseTest, NullDiagnosticsIsOkForBadParse) {
302   auto words = CompileSuccessfully("");
303   words.push_back(0xffffffff);  // Certainly invalid instruction header.
304   EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
305   EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
306   EXPECT_EQ(
307       SPV_ERROR_INVALID_BINARY,
308       spvBinaryParse(ScopedContext().context, &client_, words.data(),
309                      words.size(), invoke_header, invoke_instruction, nullptr));
310 }
311 
TEST_F(CxxBinaryParseTest,NullDiagnosticsIsOkForBadParse)312 TEST_F(CxxBinaryParseTest, NullDiagnosticsIsOkForBadParse) {
313   auto words = CompileSuccessfully("");
314   words.push_back(0xffffffff);  // Certainly invalid instruction header.
315   EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
316   EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
317   spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
318   EXPECT_EQ(false,
319             tools.Parse(words, header_parser_, instruction_parser_, nullptr));
320 }
321 
322 // Make sure that we don't blow up when both the consumer and the diagnostic are
323 // null.
TEST_F(BinaryParseTest,NullConsumerNullDiagnosticsForBadParse)324 TEST_F(BinaryParseTest, NullConsumerNullDiagnosticsForBadParse) {
325   auto words = CompileSuccessfully("");
326 
327   auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
328   ctx.SetMessageConsumer(nullptr);
329 
330   words.push_back(0xffffffff);  // Certainly invalid instruction header.
331   EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
332   EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
333   EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
334             spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
335                            invoke_header, invoke_instruction, nullptr));
336 }
337 
TEST_F(CxxBinaryParseTest,NullConsumerNullDiagnosticsForBadParse)338 TEST_F(CxxBinaryParseTest, NullConsumerNullDiagnosticsForBadParse) {
339   spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_1);
340   tools.SetMessageConsumer(nullptr);
341 
342   auto words = CompileSuccessfully("");
343   words.push_back(0xffffffff);  // Certainly invalid instruction header.
344   EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
345   EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
346   EXPECT_EQ(false,
347             tools.Parse(words, header_parser_, instruction_parser_, nullptr));
348 }
349 
TEST_F(BinaryParseTest,SpecifyConsumerNullDiagnosticsForGoodParse)350 TEST_F(BinaryParseTest, SpecifyConsumerNullDiagnosticsForGoodParse) {
351   const auto words = CompileSuccessfully("");
352 
353   auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
354   int invocation = 0;
355   ctx.SetMessageConsumer([&invocation](spv_message_level_t, const char*,
356                                        const spv_position_t&,
357                                        const char*) { ++invocation; });
358 
359   EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
360   EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
361   EXPECT_EQ(SPV_SUCCESS,
362             spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
363                            invoke_header, invoke_instruction, nullptr));
364   EXPECT_EQ(0, invocation);
365 }
366 
TEST_F(CxxBinaryParseTest,SpecifyConsumerNullDiagnosticsForGoodParse)367 TEST_F(CxxBinaryParseTest, SpecifyConsumerNullDiagnosticsForGoodParse) {
368   const auto words = CompileSuccessfully("");
369   spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_1);
370   int invocation = 0;
371   tools.SetMessageConsumer([&invocation](spv_message_level_t, const char*,
372                                          const spv_position_t&,
373                                          const char*) { ++invocation; });
374 
375   EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
376   EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
377   EXPECT_EQ(true,
378             tools.Parse(words, header_parser_, instruction_parser_, nullptr));
379   EXPECT_EQ(0, invocation);
380 }
381 
TEST_F(BinaryParseTest,SpecifyConsumerNullDiagnosticsForBadParse)382 TEST_F(BinaryParseTest, SpecifyConsumerNullDiagnosticsForBadParse) {
383   auto words = CompileSuccessfully("");
384 
385   auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
386   int invocation = 0;
387   ctx.SetMessageConsumer(
388       [&invocation](spv_message_level_t level, const char* source,
389                     const spv_position_t& position, const char* message) {
390         ++invocation;
391         EXPECT_EQ(SPV_MSG_ERROR, level);
392         EXPECT_STREQ("input", source);
393         EXPECT_EQ(0u, position.line);
394         EXPECT_EQ(0u, position.column);
395         EXPECT_EQ(1u, position.index);
396         EXPECT_STREQ("Invalid opcode: 65535", message);
397       });
398 
399   words.push_back(0xffffffff);  // Certainly invalid instruction header.
400   EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
401   EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
402   EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
403             spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
404                            invoke_header, invoke_instruction, nullptr));
405   EXPECT_EQ(1, invocation);
406 }
407 
TEST_F(CxxBinaryParseTest,SpecifyConsumerNullDiagnosticsForBadParse)408 TEST_F(CxxBinaryParseTest, SpecifyConsumerNullDiagnosticsForBadParse) {
409   auto words = CompileSuccessfully("");
410   spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_1);
411   int invocation = 0;
412   tools.SetMessageConsumer(
413       [&invocation](spv_message_level_t level, const char* source,
414                     const spv_position_t& position, const char* message) {
415         ++invocation;
416         EXPECT_EQ(SPV_MSG_ERROR, level);
417         EXPECT_STREQ("input", source);
418         EXPECT_EQ(0u, position.line);
419         EXPECT_EQ(0u, position.column);
420         EXPECT_EQ(1u, position.index);
421         EXPECT_STREQ("Invalid opcode: 65535", message);
422       });
423 
424   words.push_back(0xffffffff);  // Certainly invalid instruction header.
425   EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
426   EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
427   EXPECT_EQ(false,
428             tools.Parse(words, header_parser_, instruction_parser_, nullptr));
429   EXPECT_EQ(1, invocation);
430 }
431 
TEST_F(BinaryParseTest,SpecifyConsumerSpecifyDiagnosticsForGoodParse)432 TEST_F(BinaryParseTest, SpecifyConsumerSpecifyDiagnosticsForGoodParse) {
433   const auto words = CompileSuccessfully("");
434 
435   auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
436   int invocation = 0;
437   ctx.SetMessageConsumer([&invocation](spv_message_level_t, const char*,
438                                        const spv_position_t&,
439                                        const char*) { ++invocation; });
440 
441   EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
442   EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
443   EXPECT_EQ(SPV_SUCCESS,
444             spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
445                            invoke_header, invoke_instruction, &diagnostic_));
446   EXPECT_EQ(0, invocation);
447   EXPECT_EQ(nullptr, diagnostic_);
448 }
449 
TEST_F(CxxBinaryParseTest,SpecifyConsumerSpecifyDiagnosticsForGoodParse)450 TEST_F(CxxBinaryParseTest, SpecifyConsumerSpecifyDiagnosticsForGoodParse) {
451   const auto words = CompileSuccessfully("");
452   spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_1);
453   int invocation = 0;
454   tools.SetMessageConsumer([&invocation](spv_message_level_t, const char*,
455                                          const spv_position_t&,
456                                          const char*) { ++invocation; });
457 
458   EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
459   EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
460   EXPECT_EQ(true, tools.Parse(words, header_parser_, instruction_parser_,
461                               &diagnostic_));
462   EXPECT_EQ(0, invocation);
463   EXPECT_EQ(nullptr, diagnostic_);
464 }
465 
TEST_F(BinaryParseTest,SpecifyConsumerSpecifyDiagnosticsForBadParse)466 TEST_F(BinaryParseTest, SpecifyConsumerSpecifyDiagnosticsForBadParse) {
467   auto words = CompileSuccessfully("");
468 
469   auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
470   int invocation = 0;
471   ctx.SetMessageConsumer([&invocation](spv_message_level_t, const char*,
472                                        const spv_position_t&,
473                                        const char*) { ++invocation; });
474 
475   words.push_back(0xffffffff);  // Certainly invalid instruction header.
476   EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
477   EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
478   EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
479             spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
480                            invoke_header, invoke_instruction, &diagnostic_));
481   EXPECT_EQ(0, invocation);
482   EXPECT_STREQ("Invalid opcode: 65535", diagnostic_->error);
483 }
484 
TEST_F(CxxBinaryParseTest,SpecifyConsumerSpecifyDiagnosticsForBadParse)485 TEST_F(CxxBinaryParseTest, SpecifyConsumerSpecifyDiagnosticsForBadParse) {
486   auto words = CompileSuccessfully("");
487   spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_1);
488   int invocation = 0;
489   tools.SetMessageConsumer([&invocation](spv_message_level_t, const char*,
490                                          const spv_position_t&,
491                                          const char*) { ++invocation; });
492 
493   words.push_back(0xffffffff);  // Certainly invalid instruction header.
494   EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
495   EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
496   EXPECT_EQ(false, tools.Parse(words, header_parser_, instruction_parser_,
497                                &diagnostic_));
498   EXPECT_EQ(0, invocation);
499   EXPECT_STREQ("Invalid opcode: 65535", diagnostic_->error);
500 }
501 
TEST_F(BinaryParseTest,ModuleWithSingleInstructionHasValidHeaderAndInstructionCallback)502 TEST_F(BinaryParseTest,
503        ModuleWithSingleInstructionHasValidHeaderAndInstructionCallback) {
504   for (bool endian_swap : kSwapEndians) {
505     const auto words = CompileSuccessfully("%1 = OpTypeVoid");
506     InSequence calls_expected_in_specific_order;
507     EXPECT_HEADER(2).WillOnce(Return(SPV_SUCCESS));
508     EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
509         .WillOnce(Return(SPV_SUCCESS));
510     Parse(words, SPV_SUCCESS, endian_swap);
511     EXPECT_EQ(nullptr, diagnostic_);
512   }
513 }
514 
TEST_F(CxxBinaryParseTest,ModuleWithSingleInstructionHasValidHeaderAndInstructionCallback)515 TEST_F(CxxBinaryParseTest,
516        ModuleWithSingleInstructionHasValidHeaderAndInstructionCallback) {
517   for (bool endian_swap : kSwapEndians) {
518     const auto words = CompileSuccessfully("%1 = OpTypeVoid");
519     InSequence calls_expected_in_specific_order;
520     EXPECT_HEADER(2).WillOnce(Return(SPV_SUCCESS));
521     EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
522         .WillOnce(Return(SPV_SUCCESS));
523     Parse(words, true, endian_swap);
524     EXPECT_EQ(nullptr, diagnostic_);
525   }
526 }
527 
TEST_F(BinaryParseTest,NullHeaderCallbackIsIgnored)528 TEST_F(BinaryParseTest, NullHeaderCallbackIsIgnored) {
529   const auto words = CompileSuccessfully("%1 = OpTypeVoid");
530   EXPECT_CALL(client_, Header(_, _, _, _, _, _))
531       .Times(0);  // No header callback.
532   EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
533       .WillOnce(Return(SPV_SUCCESS));
534   EXPECT_EQ(SPV_SUCCESS, spvBinaryParse(ScopedContext().context, &client_,
535                                         words.data(), words.size(), nullptr,
536                                         invoke_instruction, &diagnostic_));
537   EXPECT_EQ(nullptr, diagnostic_);
538 }
539 
TEST_F(BinaryParseTest,NullInstructionCallbackIsIgnored)540 TEST_F(BinaryParseTest, NullInstructionCallbackIsIgnored) {
541   const auto words = CompileSuccessfully("%1 = OpTypeVoid");
542   EXPECT_HEADER((2)).WillOnce(Return(SPV_SUCCESS));
543   EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
544   EXPECT_EQ(SPV_SUCCESS,
545             spvBinaryParse(ScopedContext().context, &client_, words.data(),
546                            words.size(), invoke_header, nullptr, &diagnostic_));
547   EXPECT_EQ(nullptr, diagnostic_);
548 }
549 
550 // Check the result of multiple instruction callbacks.
551 //
552 // This test exercises non-default values for the following members of the
553 // spv_parsed_instruction_t struct: words, num_words, opcode, result_id,
554 // operands, num_operands.
TEST_F(BinaryParseTest,TwoScalarTypesGenerateTwoInstructionCallbacks)555 TEST_F(BinaryParseTest, TwoScalarTypesGenerateTwoInstructionCallbacks) {
556   for (bool endian_swap : kSwapEndians) {
557     const auto words = CompileSuccessfully(
558         "%1 = OpTypeVoid "
559         "%2 = OpTypeInt 32 1");
560     InSequence calls_expected_in_specific_order;
561     EXPECT_HEADER(3).WillOnce(Return(SPV_SUCCESS));
562     EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
563         .WillOnce(Return(SPV_SUCCESS));
564     EXPECT_CALL(client_, Instruction(MakeParsedInt32TypeInstruction(2)))
565         .WillOnce(Return(SPV_SUCCESS));
566     Parse(words, SPV_SUCCESS, endian_swap);
567     EXPECT_EQ(nullptr, diagnostic_);
568   }
569 }
570 
TEST_F(CxxBinaryParseTest,TwoScalarTypesGenerateTwoInstructionCallbacks)571 TEST_F(CxxBinaryParseTest, TwoScalarTypesGenerateTwoInstructionCallbacks) {
572   for (bool endian_swap : kSwapEndians) {
573     const auto words = CompileSuccessfully(
574         "%1 = OpTypeVoid "
575         "%2 = OpTypeInt 32 1");
576     InSequence calls_expected_in_specific_order;
577     EXPECT_HEADER(3).WillOnce(Return(SPV_SUCCESS));
578     EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
579         .WillOnce(Return(SPV_SUCCESS));
580     EXPECT_CALL(client_, Instruction(MakeParsedInt32TypeInstruction(2)))
581         .WillOnce(Return(SPV_SUCCESS));
582     Parse(words, true, endian_swap);
583     EXPECT_EQ(nullptr, diagnostic_);
584   }
585 }
586 
TEST_F(BinaryParseTest,EarlyReturnWithZeroPassingCallbacks)587 TEST_F(BinaryParseTest, EarlyReturnWithZeroPassingCallbacks) {
588   for (bool endian_swap : kSwapEndians) {
589     const auto words = CompileSuccessfully(
590         "%1 = OpTypeVoid "
591         "%2 = OpTypeInt 32 1");
592     InSequence calls_expected_in_specific_order;
593     EXPECT_HEADER(3).WillOnce(Return(SPV_ERROR_INVALID_BINARY));
594     // Early exit means no calls to Instruction().
595     EXPECT_CALL(client_, Instruction(_)).Times(0);
596     Parse(words, SPV_ERROR_INVALID_BINARY, endian_swap);
597     // On error, the binary parser doesn't generate its own diagnostics.
598     EXPECT_EQ(nullptr, diagnostic_);
599   }
600 }
601 
TEST_F(CxxBinaryParseTest,EarlyReturnWithZeroPassingCallbacks)602 TEST_F(CxxBinaryParseTest, EarlyReturnWithZeroPassingCallbacks) {
603   for (bool endian_swap : kSwapEndians) {
604     const auto words = CompileSuccessfully(
605         "%1 = OpTypeVoid "
606         "%2 = OpTypeInt 32 1");
607     InSequence calls_expected_in_specific_order;
608     EXPECT_HEADER(3).WillOnce(Return(SPV_ERROR_INVALID_BINARY));
609     // Early exit means no calls to Instruction().
610     EXPECT_CALL(client_, Instruction(_)).Times(0);
611     Parse(words, false, endian_swap);
612     // On error, the binary parser doesn't generate its own diagnostics.
613     EXPECT_EQ(nullptr, diagnostic_);
614   }
615 }
616 
TEST_F(BinaryParseTest,EarlyReturnWithZeroPassingCallbacksAndSpecifiedResultCode)617 TEST_F(BinaryParseTest,
618        EarlyReturnWithZeroPassingCallbacksAndSpecifiedResultCode) {
619   for (bool endian_swap : kSwapEndians) {
620     const auto words = CompileSuccessfully(
621         "%1 = OpTypeVoid "
622         "%2 = OpTypeInt 32 1");
623     InSequence calls_expected_in_specific_order;
624     EXPECT_HEADER(3).WillOnce(Return(SPV_REQUESTED_TERMINATION));
625     // Early exit means no calls to Instruction().
626     EXPECT_CALL(client_, Instruction(_)).Times(0);
627     Parse(words, SPV_REQUESTED_TERMINATION, endian_swap);
628     // On early termination, the binary parser doesn't generate its own
629     // diagnostics.
630     EXPECT_EQ(nullptr, diagnostic_);
631   }
632 }
633 
TEST_F(CxxBinaryParseTest,EarlyReturnWithZeroPassingCallbacksAndSpecifiedResultCode)634 TEST_F(CxxBinaryParseTest,
635        EarlyReturnWithZeroPassingCallbacksAndSpecifiedResultCode) {
636   for (bool endian_swap : kSwapEndians) {
637     const auto words = CompileSuccessfully(
638         "%1 = OpTypeVoid "
639         "%2 = OpTypeInt 32 1");
640     InSequence calls_expected_in_specific_order;
641     EXPECT_HEADER(3).WillOnce(Return(SPV_REQUESTED_TERMINATION));
642     // Early exit means no calls to Instruction().
643     EXPECT_CALL(client_, Instruction(_)).Times(0);
644     Parse(words, false, endian_swap);
645     // On early termination, the binary parser doesn't generate its own
646     // diagnostics.
647     EXPECT_EQ(nullptr, diagnostic_);
648   }
649 }
650 
TEST_F(BinaryParseTest,EarlyReturnWithOnePassingCallback)651 TEST_F(BinaryParseTest, EarlyReturnWithOnePassingCallback) {
652   for (bool endian_swap : kSwapEndians) {
653     const auto words = CompileSuccessfully(
654         "%1 = OpTypeVoid "
655         "%2 = OpTypeInt 32 1 "
656         "%3 = OpTypeFloat 32");
657     InSequence calls_expected_in_specific_order;
658     EXPECT_HEADER(4).WillOnce(Return(SPV_SUCCESS));
659     EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
660         .WillOnce(Return(SPV_REQUESTED_TERMINATION));
661     Parse(words, SPV_REQUESTED_TERMINATION, endian_swap);
662     // On early termination, the binary parser doesn't generate its own
663     // diagnostics.
664     EXPECT_EQ(nullptr, diagnostic_);
665   }
666 }
667 
TEST_F(CxxBinaryParseTest,EarlyReturnWithOnePassingCallback)668 TEST_F(CxxBinaryParseTest, EarlyReturnWithOnePassingCallback) {
669   for (bool endian_swap : kSwapEndians) {
670     const auto words = CompileSuccessfully(
671         "%1 = OpTypeVoid "
672         "%2 = OpTypeInt 32 1 "
673         "%3 = OpTypeFloat 32");
674     InSequence calls_expected_in_specific_order;
675     EXPECT_HEADER(4).WillOnce(Return(SPV_SUCCESS));
676     EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
677         .WillOnce(Return(SPV_REQUESTED_TERMINATION));
678     Parse(words, false, endian_swap);
679     // On early termination, the binary parser doesn't generate its own
680     // diagnostics.
681     EXPECT_EQ(nullptr, diagnostic_);
682   }
683 }
684 
TEST_F(BinaryParseTest,EarlyReturnWithTwoPassingCallbacks)685 TEST_F(BinaryParseTest, EarlyReturnWithTwoPassingCallbacks) {
686   for (bool endian_swap : kSwapEndians) {
687     const auto words = CompileSuccessfully(
688         "%1 = OpTypeVoid "
689         "%2 = OpTypeInt 32 1 "
690         "%3 = OpTypeFloat 32");
691     InSequence calls_expected_in_specific_order;
692     EXPECT_HEADER(4).WillOnce(Return(SPV_SUCCESS));
693     EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
694         .WillOnce(Return(SPV_SUCCESS));
695     EXPECT_CALL(client_, Instruction(MakeParsedInt32TypeInstruction(2)))
696         .WillOnce(Return(SPV_REQUESTED_TERMINATION));
697     Parse(words, SPV_REQUESTED_TERMINATION, endian_swap);
698     // On early termination, the binary parser doesn't generate its own
699     // diagnostics.
700     EXPECT_EQ(nullptr, diagnostic_);
701   }
702 }
703 
TEST_F(CxxBinaryParseTest,EarlyReturnWithTwoPassingCallbacks)704 TEST_F(CxxBinaryParseTest, EarlyReturnWithTwoPassingCallbacks) {
705   for (bool endian_swap : kSwapEndians) {
706     const auto words = CompileSuccessfully(
707         "%1 = OpTypeVoid "
708         "%2 = OpTypeInt 32 1 "
709         "%3 = OpTypeFloat 32");
710     InSequence calls_expected_in_specific_order;
711     EXPECT_HEADER(4).WillOnce(Return(SPV_SUCCESS));
712     EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
713         .WillOnce(Return(SPV_SUCCESS));
714     EXPECT_CALL(client_, Instruction(MakeParsedInt32TypeInstruction(2)))
715         .WillOnce(Return(SPV_REQUESTED_TERMINATION));
716     Parse(words, false, endian_swap);
717     // On early termination, the binary parser doesn't generate its own
718     // diagnostics.
719     EXPECT_EQ(nullptr, diagnostic_);
720   }
721 }
722 
TEST_F(BinaryParseTest,InstructionWithStringOperand)723 TEST_F(BinaryParseTest, InstructionWithStringOperand) {
724   for (bool endian_swap : kSwapEndians) {
725     const std::string str =
726         "the future is already here, it's just not evenly distributed";
727     const auto str_words = MakeVector(str);
728     const auto instruction = MakeInstruction(spv::Op::OpName, {99}, str_words);
729     const auto words = Concatenate({ExpectedHeaderForBound(100), instruction});
730     InSequence calls_expected_in_specific_order;
731     EXPECT_HEADER(100).WillOnce(Return(SPV_SUCCESS));
732     const auto operands = std::vector<spv_parsed_operand_t>{
733         MakeSimpleOperand(1, SPV_OPERAND_TYPE_ID),
734         MakeLiteralStringOperand(2, static_cast<uint16_t>(str_words.size()))};
735     EXPECT_CALL(
736         client_,
737         Instruction(ParsedInstruction(spv_parsed_instruction_t{
738             instruction.data(), static_cast<uint16_t>(instruction.size()),
739             uint16_t(spv::Op::OpName), SPV_EXT_INST_TYPE_NONE, 0 /*type id*/,
740             0 /* No result id for OpName*/, operands.data(),
741             static_cast<uint16_t>(operands.size())})))
742         .WillOnce(Return(SPV_SUCCESS));
743     Parse(words, SPV_SUCCESS, endian_swap);
744     EXPECT_EQ(nullptr, diagnostic_);
745   }
746 }
747 
TEST_F(CxxBinaryParseTest,InstructionWithStringOperand)748 TEST_F(CxxBinaryParseTest, InstructionWithStringOperand) {
749   for (bool endian_swap : kSwapEndians) {
750     const std::string str =
751         "the future is already here, it's just not evenly distributed";
752     const auto str_words = MakeVector(str);
753     const auto instruction = MakeInstruction(spv::Op::OpName, {99}, str_words);
754     const auto words = Concatenate({ExpectedHeaderForBound(100), instruction});
755     InSequence calls_expected_in_specific_order;
756     EXPECT_HEADER(100).WillOnce(Return(SPV_SUCCESS));
757     const auto operands = std::vector<spv_parsed_operand_t>{
758         MakeSimpleOperand(1, SPV_OPERAND_TYPE_ID),
759         MakeLiteralStringOperand(2, static_cast<uint16_t>(str_words.size()))};
760     EXPECT_CALL(
761         client_,
762         Instruction(ParsedInstruction(spv_parsed_instruction_t{
763             instruction.data(), static_cast<uint16_t>(instruction.size()),
764             uint16_t(spv::Op::OpName), SPV_EXT_INST_TYPE_NONE, 0 /*type id*/,
765             0 /* No result id for OpName*/, operands.data(),
766             static_cast<uint16_t>(operands.size())})))
767         .WillOnce(Return(SPV_SUCCESS));
768     Parse(words, true, endian_swap);
769     EXPECT_EQ(nullptr, diagnostic_);
770   }
771 }
772 
773 // Checks for non-zero values for the result_id and ext_inst_type members
774 // spv_parsed_instruction_t.
TEST_F(BinaryParseTest,ExtendedInstruction)775 TEST_F(BinaryParseTest, ExtendedInstruction) {
776   const auto words = CompileSuccessfully(
777       "%extcl = OpExtInstImport \"OpenCL.std\" "
778       "%result = OpExtInst %float %extcl sqrt %x");
779   EXPECT_HEADER(5).WillOnce(Return(SPV_SUCCESS));
780   EXPECT_CALL(client_, Instruction(_)).WillOnce(Return(SPV_SUCCESS));
781   // We're only interested in the second call to Instruction():
782   const auto operands = std::vector<spv_parsed_operand_t>{
783       MakeSimpleOperand(1, SPV_OPERAND_TYPE_TYPE_ID),
784       MakeSimpleOperand(2, SPV_OPERAND_TYPE_RESULT_ID),
785       MakeSimpleOperand(3,
786                         SPV_OPERAND_TYPE_ID),  // Extended instruction set Id
787       MakeSimpleOperand(4, SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER),
788       MakeSimpleOperand(5, SPV_OPERAND_TYPE_ID),  // Id of the argument
789   };
790   const auto instruction = MakeInstruction(
791       spv::Op::OpExtInst,
792       {2, 3, 1, static_cast<uint32_t>(OpenCLLIB::Entrypoints::Sqrt), 4});
793   EXPECT_CALL(client_,
794               Instruction(ParsedInstruction(spv_parsed_instruction_t{
795                   instruction.data(), static_cast<uint16_t>(instruction.size()),
796                   uint16_t(spv::Op::OpExtInst), SPV_EXT_INST_TYPE_OPENCL_STD,
797                   2 /*type id*/, 3 /*result id*/, operands.data(),
798                   static_cast<uint16_t>(operands.size())})))
799       .WillOnce(Return(SPV_SUCCESS));
800   // Since we are actually checking the output, don't test the
801   // endian-swapped version.
802   Parse(words, SPV_SUCCESS, false);
803   EXPECT_EQ(nullptr, diagnostic_);
804 }
805 
TEST_F(CxxBinaryParseTest,ExtendedInstruction)806 TEST_F(CxxBinaryParseTest, ExtendedInstruction) {
807   const auto words = CompileSuccessfully(
808       "%extcl = OpExtInstImport \"OpenCL.std\" "
809       "%result = OpExtInst %float %extcl sqrt %x");
810   EXPECT_HEADER(5).WillOnce(Return(SPV_SUCCESS));
811   EXPECT_CALL(client_, Instruction(_)).WillOnce(Return(SPV_SUCCESS));
812   // We're only interested in the second call to Instruction():
813   const auto operands = std::vector<spv_parsed_operand_t>{
814       MakeSimpleOperand(1, SPV_OPERAND_TYPE_TYPE_ID),
815       MakeSimpleOperand(2, SPV_OPERAND_TYPE_RESULT_ID),
816       MakeSimpleOperand(3,
817                         SPV_OPERAND_TYPE_ID),  // Extended instruction set Id
818       MakeSimpleOperand(4, SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER),
819       MakeSimpleOperand(5, SPV_OPERAND_TYPE_ID),  // Id of the argument
820   };
821   const auto instruction = MakeInstruction(
822       spv::Op::OpExtInst,
823       {2, 3, 1, static_cast<uint32_t>(OpenCLLIB::Entrypoints::Sqrt), 4});
824   EXPECT_CALL(client_,
825               Instruction(ParsedInstruction(spv_parsed_instruction_t{
826                   instruction.data(), static_cast<uint16_t>(instruction.size()),
827                   uint16_t(spv::Op::OpExtInst), SPV_EXT_INST_TYPE_OPENCL_STD,
828                   2 /*type id*/, 3 /*result id*/, operands.data(),
829                   static_cast<uint16_t>(operands.size())})))
830       .WillOnce(Return(SPV_SUCCESS));
831   // Since we are actually checking the output, don't test the
832   // endian-swapped version.
833   Parse(words, true, false);
834   EXPECT_EQ(nullptr, diagnostic_);
835 }
836 
837 // A binary parser diagnostic test case where we provide the words array
838 // pointer and word count explicitly.
839 struct WordsAndCountDiagnosticCase {
840   const uint32_t* words;
841   size_t num_words;
842   std::string expected_diagnostic;
843 };
844 
845 using BinaryParseWordsAndCountDiagnosticTest = spvtest::TextToBinaryTestBase<
846     ::testing::TestWithParam<WordsAndCountDiagnosticCase>>;
847 
TEST_P(BinaryParseWordsAndCountDiagnosticTest,WordAndCountCases)848 TEST_P(BinaryParseWordsAndCountDiagnosticTest, WordAndCountCases) {
849   EXPECT_EQ(
850       SPV_ERROR_INVALID_BINARY,
851       spvBinaryParse(ScopedContext().context, nullptr, GetParam().words,
852                      GetParam().num_words, nullptr, nullptr, &diagnostic));
853   ASSERT_NE(nullptr, diagnostic);
854   EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
855 }
856 
857 INSTANTIATE_TEST_SUITE_P(
858     BinaryParseDiagnostic, BinaryParseWordsAndCountDiagnosticTest,
859     ::testing::ValuesIn(std::vector<WordsAndCountDiagnosticCase>{
860         {nullptr, 0, "Missing module."},
861         {kHeaderForBound1, 0,
862          "Module has incomplete header: only 0 words instead of 5"},
863         {kHeaderForBound1, 1,
864          "Module has incomplete header: only 1 words instead of 5"},
865         {kHeaderForBound1, 2,
866          "Module has incomplete header: only 2 words instead of 5"},
867         {kHeaderForBound1, 3,
868          "Module has incomplete header: only 3 words instead of 5"},
869         {kHeaderForBound1, 4,
870          "Module has incomplete header: only 4 words instead of 5"},
871     }));
872 
873 // A binary parser diagnostic test case where a vector of words is
874 // provided.  We'll use this to express cases that can't be created
875 // via the assembler.  Either we want to make a malformed instruction,
876 // or an invalid case the assembler would reject.
877 struct WordVectorDiagnosticCase {
878   std::vector<uint32_t> words;
879   std::string expected_diagnostic;
880 };
881 
882 using BinaryParseWordVectorDiagnosticTest = spvtest::TextToBinaryTestBase<
883     ::testing::TestWithParam<WordVectorDiagnosticCase>>;
884 
TEST_P(BinaryParseWordVectorDiagnosticTest,WordVectorCases)885 TEST_P(BinaryParseWordVectorDiagnosticTest, WordVectorCases) {
886   const auto& words = GetParam().words;
887   EXPECT_THAT(spvBinaryParse(ScopedContext().context, nullptr, words.data(),
888                              words.size(), nullptr, nullptr, &diagnostic),
889               AnyOf(SPV_ERROR_INVALID_BINARY, SPV_ERROR_INVALID_ID));
890   ASSERT_NE(nullptr, diagnostic);
891   EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
892 }
893 
894 INSTANTIATE_TEST_SUITE_P(
895     BinaryParseDiagnostic, BinaryParseWordVectorDiagnosticTest,
896     ::testing::ValuesIn(std::vector<WordVectorDiagnosticCase>{
897         {Concatenate({ExpectedHeaderForBound(1),
898                       {spvOpcodeMake(0, spv::Op::OpNop)}}),
899          "Invalid instruction word count: 0"},
900         {Concatenate(
901              {ExpectedHeaderForBound(1),
902               {spvOpcodeMake(1, static_cast<spv::Op>(
903                                     std::numeric_limits<uint16_t>::max()))}}),
904          "Invalid opcode: 65535"},
905         {Concatenate({ExpectedHeaderForBound(1),
906                       MakeInstruction(spv::Op::OpNop, {42})}),
907          "Invalid instruction OpNop starting at word 5: expected "
908          "no more operands after 1 words, but stated word count is 2."},
909         // Supply several more unexpected words.
910         {Concatenate({ExpectedHeaderForBound(1),
911                       MakeInstruction(spv::Op::OpNop,
912                                       {42, 43, 44, 45, 46, 47})}),
913          "Invalid instruction OpNop starting at word 5: expected "
914          "no more operands after 1 words, but stated word count is 7."},
915         {Concatenate({ExpectedHeaderForBound(1),
916                       MakeInstruction(spv::Op::OpTypeVoid, {1, 2})}),
917          "Invalid instruction OpTypeVoid starting at word 5: expected "
918          "no more operands after 2 words, but stated word count is 3."},
919         {Concatenate({ExpectedHeaderForBound(1),
920                       MakeInstruction(spv::Op::OpTypeVoid, {1, 2, 5, 9, 10})}),
921          "Invalid instruction OpTypeVoid starting at word 5: expected "
922          "no more operands after 2 words, but stated word count is 6."},
923         {Concatenate({ExpectedHeaderForBound(1),
924                       MakeInstruction(spv::Op::OpTypeInt, {1, 32, 1, 9})}),
925          "Invalid instruction OpTypeInt starting at word 5: expected "
926          "no more operands after 4 words, but stated word count is 5."},
927         {Concatenate({ExpectedHeaderForBound(1),
928                       MakeInstruction(spv::Op::OpTypeInt, {1})}),
929          "End of input reached while decoding OpTypeInt starting at word 5:"
930          " expected more operands after 2 words."},
931 
932         // Check several cases for running off the end of input.
933 
934         // Detect a missing single word operand.
935         {Concatenate({ExpectedHeaderForBound(1),
936                       {spvOpcodeMake(2, spv::Op::OpTypeStruct)}}),
937          "End of input reached while decoding OpTypeStruct starting at word"
938          " 5: missing result ID operand at word offset 1."},
939         // Detect this a missing a multi-word operand to OpConstant.
940         // We also lie and say the OpConstant instruction has 5 words when
941         // it only has 3.  Corresponds to something like this:
942         //    %1 = OpTypeInt 64 0
943         //    %2 = OpConstant %1 <missing>
944         {Concatenate({ExpectedHeaderForBound(3),
945                       {MakeInstruction(spv::Op::OpTypeInt, {1, 64, 0})},
946                       {spvOpcodeMake(5, spv::Op::OpConstant), 1, 2}}),
947          "End of input reached while decoding OpConstant starting at word"
948          " 9: missing possibly multi-word literal number operand at word "
949          "offset 3."},
950         // Detect when we provide only one word from the 64-bit literal,
951         // and again lie about the number of words in the instruction.
952         {Concatenate({ExpectedHeaderForBound(3),
953                       {MakeInstruction(spv::Op::OpTypeInt, {1, 64, 0})},
954                       {spvOpcodeMake(5, spv::Op::OpConstant), 1, 2, 42}}),
955          "End of input reached while decoding OpConstant starting at word"
956          " 9: truncated possibly multi-word literal number operand at word "
957          "offset 3."},
958         // Detect when a required string operand is missing.
959         // Also, lie about the length of the instruction.
960         {Concatenate({ExpectedHeaderForBound(3),
961                       {spvOpcodeMake(3, spv::Op::OpString), 1}}),
962          "End of input reached while decoding OpString starting at word"
963          " 5: missing literal string operand at word offset 2."},
964         // Detect when a required string operand is truncated: it's missing
965         // a null terminator.  Catching the error avoids a buffer overrun.
966         {Concatenate({ExpectedHeaderForBound(3),
967                       {spvOpcodeMake(4, spv::Op::OpString), 1, 0x41414141,
968                        0x41414141}}),
969          "End of input reached while decoding OpString starting at word"
970          " 5: truncated literal string operand at word offset 2."},
971         // Detect when an optional string operand is truncated: it's missing
972         // a null terminator.  Catching the error avoids a buffer overrun.
973         // (It is valid for an optional string operand to be absent.)
974         {Concatenate({ExpectedHeaderForBound(3),
975                       {spvOpcodeMake(6, spv::Op::OpSource),
976                        static_cast<uint32_t>(spv::SourceLanguage::OpenCL_C),
977                        210, 1 /* file id */,
978                        /*start of string*/ 0x41414141, 0x41414141}}),
979          "End of input reached while decoding OpSource starting at word"
980          " 5: truncated literal string operand at word offset 4."},
981 
982         // (End of input exhaustion test cases.)
983 
984         // In this case the instruction word count is too small, where
985         // it would truncate a multi-word operand to OpConstant.
986         {Concatenate({ExpectedHeaderForBound(3),
987                       {MakeInstruction(spv::Op::OpTypeInt, {1, 64, 0})},
988                       {spvOpcodeMake(4, spv::Op::OpConstant), 1, 2, 44, 44}}),
989          "Invalid word count: OpConstant starting at word 9 says it has 4"
990          " words, but found 5 words instead."},
991         // Word count is to small, where it would truncate a literal string.
992         {Concatenate({ExpectedHeaderForBound(2),
993                       {spvOpcodeMake(3, spv::Op::OpString), 1, 0x41414141, 0}}),
994          "Invalid word count: OpString starting at word 5 says it has 3"
995          " words, but found 4 words instead."},
996         // Word count is too large.  The string terminates before the last
997         // word.
998         {Concatenate({ExpectedHeaderForBound(2),
999                       {spvOpcodeMake(4, spv::Op::OpString), 1 /* result id */},
1000                       MakeVector("abc"),
1001                       {0 /* this word does not belong*/}}),
1002          "Invalid instruction OpString starting at word 5: expected no more"
1003          " operands after 3 words, but stated word count is 4."},
1004         // Word count is too large.  There are too many words after the string
1005         // literal.  A linkage attribute decoration is the only case in SPIR-V
1006         // where a string operand is followed by another operand.
1007         {Concatenate(
1008              {ExpectedHeaderForBound(2),
1009               {spvOpcodeMake(6, spv::Op::OpDecorate), 1 /* target id */,
1010                static_cast<uint32_t>(spv::Decoration::LinkageAttributes)},
1011               MakeVector("abc"),
1012               {static_cast<uint32_t>(spv::LinkageType::Import),
1013                0 /* does not belong */}}),
1014          "Invalid instruction OpDecorate starting at word 5: expected no more"
1015          " operands after 5 words, but stated word count is 6."},
1016         // Like the previous case, but with 5 extra words.
1017         {Concatenate(
1018              {ExpectedHeaderForBound(2),
1019               {spvOpcodeMake(10, spv::Op::OpDecorate), 1 /* target id */,
1020                static_cast<uint32_t>(spv::Decoration::LinkageAttributes)},
1021               MakeVector("abc"),
1022               {static_cast<uint32_t>(spv::LinkageType::Import),
1023                /* don't belong */ 0, 1, 2, 3, 4}}),
1024          "Invalid instruction OpDecorate starting at word 5: expected no more"
1025          " operands after 5 words, but stated word count is 10."},
1026         // Like the previous two cases, but with OpMemberDecorate.
1027         {Concatenate(
1028              {ExpectedHeaderForBound(2),
1029               {spvOpcodeMake(7, spv::Op::OpMemberDecorate), 1 /* target id */,
1030                42 /* member index */,
1031                static_cast<uint32_t>(spv::Decoration::LinkageAttributes)},
1032               MakeVector("abc"),
1033               {static_cast<uint32_t>(spv::LinkageType::Import),
1034                0 /* does not belong */}}),
1035          "Invalid instruction OpMemberDecorate starting at word 5: expected no"
1036          " more operands after 6 words, but stated word count is 7."},
1037         {Concatenate(
1038              {ExpectedHeaderForBound(2),
1039               {spvOpcodeMake(11, spv::Op::OpMemberDecorate), 1 /* target id */,
1040                42 /* member index */,
1041                static_cast<uint32_t>(spv::Decoration::LinkageAttributes)},
1042               MakeVector("abc"),
1043               {static_cast<uint32_t>(spv::LinkageType::Import),
1044                /* don't belong */ 0, 1, 2, 3, 4}}),
1045          "Invalid instruction OpMemberDecorate starting at word 5: expected no"
1046          " more operands after 6 words, but stated word count is 11."},
1047         // Word count is too large.  There should be no more words
1048         // after the RelaxedPrecision decoration.
1049         {Concatenate({ExpectedHeaderForBound(2),
1050                       {spvOpcodeMake(4, spv::Op::OpDecorate), 1 /* target id */,
1051                        static_cast<uint32_t>(spv::Decoration::RelaxedPrecision),
1052                        0 /* does not belong */}}),
1053          "Invalid instruction OpDecorate starting at word 5: expected no"
1054          " more operands after 3 words, but stated word count is 4."},
1055         // Word count is too large.  There should be only one word after
1056         // the SpecId decoration enum word.
1057         {Concatenate({ExpectedHeaderForBound(2),
1058                       {spvOpcodeMake(5, spv::Op::OpDecorate), 1 /* target id */,
1059                        static_cast<uint32_t>(spv::Decoration::SpecId),
1060                        42 /* the spec id */, 0 /* does not belong */}}),
1061          "Invalid instruction OpDecorate starting at word 5: expected no"
1062          " more operands after 4 words, but stated word count is 5."},
1063         {Concatenate({ExpectedHeaderForBound(2),
1064                       {spvOpcodeMake(2, spv::Op::OpTypeVoid), 0}}),
1065          "Error: Result Id is 0"},
1066         {Concatenate({
1067              ExpectedHeaderForBound(2),
1068              {spvOpcodeMake(2, spv::Op::OpTypeVoid), 1},
1069              {spvOpcodeMake(2, spv::Op::OpTypeBool), 1},
1070          }),
1071          "Id 1 is defined more than once"},
1072         {Concatenate({ExpectedHeaderForBound(3),
1073                       MakeInstruction(spv::Op::OpExtInst, {2, 3, 100, 4, 5})}),
1074          "OpExtInst set Id 100 does not reference an OpExtInstImport result "
1075          "Id"},
1076         {Concatenate({ExpectedHeaderForBound(101),
1077                       MakeInstruction(spv::Op::OpExtInstImport, {100},
1078                                       MakeVector("OpenCL.std")),
1079                       // OpenCL cos is #14
1080                       MakeInstruction(spv::Op::OpExtInst,
1081                                       {2, 3, 100, 14, 5, 999})}),
1082          "Invalid instruction OpExtInst starting at word 10: expected no "
1083          "more operands after 6 words, but stated word count is 7."},
1084         // In this case, the OpSwitch selector refers to an invalid ID.
1085         {Concatenate({ExpectedHeaderForBound(3),
1086                       MakeInstruction(spv::Op::OpSwitch, {1, 2, 42, 3})}),
1087          "Invalid OpSwitch: selector id 1 has no type"},
1088         // In this case, the OpSwitch selector refers to an ID that has
1089         // no type.
1090         {Concatenate({ExpectedHeaderForBound(3),
1091                       MakeInstruction(spv::Op::OpLabel, {1}),
1092                       MakeInstruction(spv::Op::OpSwitch, {1, 2, 42, 3})}),
1093          "Invalid OpSwitch: selector id 1 has no type"},
1094         {Concatenate({ExpectedHeaderForBound(3),
1095                       MakeInstruction(spv::Op::OpTypeInt, {1, 32, 0}),
1096                       MakeInstruction(spv::Op::OpSwitch, {1, 3, 42, 3})}),
1097          "Invalid OpSwitch: selector id 1 is a type, not a value"},
1098         {Concatenate({ExpectedHeaderForBound(3),
1099                       MakeInstruction(spv::Op::OpTypeFloat, {1, 32}),
1100                       MakeInstruction(spv::Op::OpConstant, {1, 2, 0x78f00000}),
1101                       MakeInstruction(spv::Op::OpSwitch, {2, 3, 42, 3})}),
1102          "Invalid OpSwitch: selector id 2 is not a scalar integer"},
1103         {Concatenate({ExpectedHeaderForBound(3),
1104                       MakeInstruction(spv::Op::OpExtInstImport, {1},
1105                                       MakeVector("invalid-import"))}),
1106          "Invalid extended instruction import 'invalid-import'"},
1107         {Concatenate({
1108              ExpectedHeaderForBound(3),
1109              MakeInstruction(spv::Op::OpTypeInt, {1, 32, 0}),
1110              MakeInstruction(spv::Op::OpConstant, {2, 2, 42}),
1111          }),
1112          "Type Id 2 is not a type"},
1113         {Concatenate({
1114              ExpectedHeaderForBound(3),
1115              MakeInstruction(spv::Op::OpTypeBool, {1}),
1116              MakeInstruction(spv::Op::OpConstant, {1, 2, 42}),
1117          }),
1118          "Type Id 1 is not a scalar numeric type"},
1119     }));
1120 
1121 // A binary parser diagnostic case generated from an assembly text input.
1122 struct AssemblyDiagnosticCase {
1123   std::string assembly;
1124   std::string expected_diagnostic;
1125 };
1126 
1127 using BinaryParseAssemblyDiagnosticTest = spvtest::TextToBinaryTestBase<
1128     ::testing::TestWithParam<AssemblyDiagnosticCase>>;
1129 
TEST_P(BinaryParseAssemblyDiagnosticTest,AssemblyCases)1130 TEST_P(BinaryParseAssemblyDiagnosticTest, AssemblyCases) {
1131   auto words = CompileSuccessfully(GetParam().assembly);
1132   EXPECT_THAT(spvBinaryParse(ScopedContext().context, nullptr, words.data(),
1133                              words.size(), nullptr, nullptr, &diagnostic),
1134               AnyOf(SPV_ERROR_INVALID_BINARY, SPV_ERROR_INVALID_ID));
1135   ASSERT_NE(nullptr, diagnostic);
1136   EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
1137 }
1138 
1139 INSTANTIATE_TEST_SUITE_P(
1140     BinaryParseDiagnostic, BinaryParseAssemblyDiagnosticTest,
1141     ::testing::ValuesIn(std::vector<AssemblyDiagnosticCase>{
1142         {"%1 = OpConstant !0 42", "Error: Type Id is 0"},
1143         // A required id is 0.
1144         {"OpName !0 \"foo\"", "Id is 0"},
1145         // An optional id is 0, in this case the optional
1146         // initializer.
1147         {"%2 = OpVariable %1 CrossWorkgroup !0", "Id is 0"},
1148         {"OpControlBarrier !0 %1 %2", "scope ID is 0"},
1149         {"OpControlBarrier %1 !0 %2", "scope ID is 0"},
1150         {"OpControlBarrier %1 %2 !0", "memory semantics ID is 0"},
1151         {"%import = OpExtInstImport \"GLSL.std.450\" "
1152          "%result = OpExtInst %type %import !999999 %x",
1153          "Invalid extended instruction number: 999999"},
1154         {"%2 = OpSpecConstantOp %1 !1000 %2",
1155          "Invalid OpSpecConstantOp opcode: 1000"},
1156         {"OpCapability !9999", "Invalid capability operand: 9999"},
1157         {"OpSource !9999 100",
1158          "Invalid source language operand: 9999, if you are creating a new "
1159          "source language please use value 0 (Unknown) and when ready, add "
1160          "your source language to SPRIV-Headers"},
1161         {"OpEntryPoint !9999", "Invalid execution model operand: 9999"},
1162         {"OpMemoryModel !9999", "Invalid addressing model operand: 9999"},
1163         {"OpMemoryModel Logical !9999", "Invalid memory model operand: 9999"},
1164         {"OpExecutionMode %1 !9999", "Invalid execution mode operand: 9999"},
1165         {"OpTypeForwardPointer %1 !9999",
1166          "Invalid storage class operand: 9999"},
1167         {"%2 = OpTypeImage %1 !9999", "Invalid dimensionality operand: 9999"},
1168         {"%2 = OpTypeImage %1 1D 0 0 0 0 !9999",
1169          "Invalid image format operand: 9999"},
1170         {"OpDecorate %1 FPRoundingMode !9999",
1171          "Invalid floating-point rounding mode operand: 9999"},
1172         {"OpDecorate %1 LinkageAttributes \"C\" !9999",
1173          "Invalid linkage type operand: 9999"},
1174         {"%1 = OpTypePipe !9999", "Invalid access qualifier operand: 9999"},
1175         {"OpDecorate %1 FuncParamAttr !9999",
1176          "Invalid function parameter attribute operand: 9999"},
1177         {"OpDecorate %1 !9999", "Invalid decoration operand: 9999"},
1178         {"OpDecorate %1 BuiltIn !9999", "Invalid built-in operand: 9999"},
1179         {"%2 = OpGroupIAdd %1 %3 !9999",
1180          "Invalid group operation operand: 9999"},
1181         {"OpDecorate %1 FPFastMathMode !63",
1182          "Invalid floating-point fast math mode operand: 63 has invalid mask "
1183          "component 32"},
1184         {"%2 = OpFunction %2 !31",
1185          "Invalid function control operand: 31 has invalid mask component 16"},
1186         {"OpLoopMerge %1 %2 !1027",
1187          "Invalid loop control operand: 1027 has invalid mask component 1024"},
1188         {"%2 = OpImageFetch %1 %image %coord !32770",
1189          "Invalid image operand: 32770 has invalid mask component 32768"},
1190         {"OpSelectionMerge %1 !7",
1191          "Invalid selection control operand: 7 has invalid mask component 4"},
1192     }));
1193 
1194 }  // namespace
1195 }  // namespace spvtools
1196