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.
31static 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
38namespace spvtools {
39namespace {
40
41using ::spvtest::Concatenate;
42using ::spvtest::MakeInstruction;
43using utils::MakeVector;
44using ::spvtest::ScopedContext;
45using ::testing::_;
46using ::testing::AnyOf;
47using ::testing::Eq;
48using ::testing::InSequence;
49using ::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.
54struct ParsedInstruction {
55  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
70  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.
79std::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.
96TEST(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().
104class 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().
113spv_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().
121spv_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.
129const 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.
136std::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.
143spv_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.
150spv_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.
157spv_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.
164ParsedInstruction 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.
182ParsedInstruction 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
199class BinaryParseTest : public spvtest::TextToBinaryTestBase<::testing::Test> {
200 protected:
201  ~BinaryParseTest() override { spvDiagnosticDestroy(diagnostic_); }
202
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
217class CxxBinaryParseTest
218    : public spvtest::TextToBinaryTestBase<::testing::Test> {
219 protected:
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
233  ~CxxBinaryParseTest() override { spvDiagnosticDestroy(diagnostic_); }
234
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
260static const bool kSwapEndians[] = {false, true};
261
262TEST_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
272TEST_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
282TEST_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
292TEST_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
301TEST_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
312TEST_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.
324TEST_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
338TEST_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
350TEST_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
367TEST_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
382TEST_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
408TEST_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
432TEST_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
450TEST_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
466TEST_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
485TEST_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
502TEST_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
515TEST_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
528TEST_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
540TEST_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.
555TEST_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
571TEST_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
587TEST_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
602TEST_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
617TEST_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
634TEST_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
651TEST_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
668TEST_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
685TEST_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
704TEST_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
723TEST_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
748TEST_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.
775TEST_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
806TEST_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.
839struct WordsAndCountDiagnosticCase {
840  const uint32_t* words;
841  size_t num_words;
842  std::string expected_diagnostic;
843};
844
845using BinaryParseWordsAndCountDiagnosticTest = spvtest::TextToBinaryTestBase<
846    ::testing::TestWithParam<WordsAndCountDiagnosticCase>>;
847
848TEST_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
857INSTANTIATE_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.
877struct WordVectorDiagnosticCase {
878  std::vector<uint32_t> words;
879  std::string expected_diagnostic;
880};
881
882using BinaryParseWordVectorDiagnosticTest = spvtest::TextToBinaryTestBase<
883    ::testing::TestWithParam<WordVectorDiagnosticCase>>;
884
885TEST_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
894INSTANTIATE_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.
1122struct AssemblyDiagnosticCase {
1123  std::string assembly;
1124  std::string expected_diagnostic;
1125};
1126
1127using BinaryParseAssemblyDiagnosticTest = spvtest::TextToBinaryTestBase<
1128    ::testing::TestWithParam<AssemblyDiagnosticCase>>;
1129
1130TEST_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
1139INSTANTIATE_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