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::__anon9b04da180111::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::__anon9b04da180111::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