mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-12-27 10:20:14 +00:00
3b69d05086
When we were swapping endian-ness in the binary parser, we would write outside of the bounds of our vector.
772 lines
36 KiB
C++
772 lines
36 KiB
C++
// Copyright (c) 2015-2016 The Khronos Group Inc.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and/or associated documentation files (the
|
|
// "Materials"), to deal in the Materials without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Materials, and to
|
|
// permit persons to whom the Materials are furnished to do so, subject to
|
|
// the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included
|
|
// in all copies or substantial portions of the Materials.
|
|
//
|
|
// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
|
|
// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
|
|
// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
|
|
// https://www.khronos.org/registry/
|
|
//
|
|
// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
|
|
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "gmock/gmock.h"
|
|
|
|
#include "TestFixture.h"
|
|
#include "UnitSPIRV.h"
|
|
|
|
// Returns true if two spv_parsed_operand_t values are equal.
|
|
// To use this operator, this definition must appear in the same namespace
|
|
// as spv_parsed_operand_t.
|
|
static bool operator==(const spv_parsed_operand_t& a,
|
|
const spv_parsed_operand_t& b) {
|
|
return a.offset == b.offset && a.num_words == b.num_words &&
|
|
a.type == b.type && a.number_kind == b.number_kind &&
|
|
a.number_bit_width == b.number_bit_width;
|
|
}
|
|
|
|
namespace {
|
|
|
|
using ::spvtest::Concatenate;
|
|
using ::spvtest::MakeInstruction;
|
|
using ::spvtest::MakeVector;
|
|
using ::testing::AnyOf;
|
|
using ::testing::Eq;
|
|
using ::testing::InSequence;
|
|
using ::testing::Return;
|
|
using ::testing::_;
|
|
|
|
// An easily-constructible and comparable object for the contents of an
|
|
// spv_parsed_instruction_t. Unlike spv_parsed_instruction_t, owns the memory
|
|
// of its components.
|
|
struct ParsedInstruction {
|
|
explicit ParsedInstruction(const spv_parsed_instruction_t& inst)
|
|
: words(inst.words, inst.words + inst.num_words),
|
|
opcode(inst.opcode),
|
|
ext_inst_type(inst.ext_inst_type),
|
|
type_id(inst.type_id),
|
|
result_id(inst.result_id),
|
|
operands(inst.operands, inst.operands + inst.num_operands) {}
|
|
|
|
std::vector<uint32_t> words;
|
|
SpvOp opcode;
|
|
spv_ext_inst_type_t ext_inst_type;
|
|
uint32_t type_id;
|
|
uint32_t result_id;
|
|
std::vector<spv_parsed_operand_t> operands;
|
|
|
|
bool operator==(const ParsedInstruction& b) const {
|
|
return words == b.words && opcode == b.opcode &&
|
|
ext_inst_type == b.ext_inst_type && type_id == b.type_id &&
|
|
result_id == b.result_id && operands == b.operands;
|
|
}
|
|
};
|
|
|
|
// Prints a ParsedInstruction object to the given output stream, and returns
|
|
// the stream.
|
|
std::ostream& operator<<(std::ostream& os, const ParsedInstruction& inst) {
|
|
os << "\nParsedInstruction( {";
|
|
spvtest::PrintTo(spvtest::WordVector(inst.words), &os);
|
|
os << "}, opcode: " << int(inst.opcode)
|
|
<< " ext_inst_type: " << int(inst.ext_inst_type)
|
|
<< " type_id: " << inst.type_id << " result_id: " << inst.result_id;
|
|
for (const auto& operand : inst.operands) {
|
|
os << " { offset: " << operand.offset << " num_words: " << operand.num_words
|
|
<< " type: " << int(operand.type)
|
|
<< " number_kind: " << int(operand.number_kind)
|
|
<< " number_bit_width: " << int(operand.number_bit_width) << "}";
|
|
}
|
|
os << ")";
|
|
return os;
|
|
}
|
|
|
|
// Sanity check for the equality operator on ParsedInstruction.
|
|
TEST(ParsedInstruction, ZeroInitializedAreEqual) {
|
|
spv_parsed_instruction_t pi = {};
|
|
ParsedInstruction a(pi);
|
|
ParsedInstruction b(pi);
|
|
EXPECT_THAT(a, ::testing::TypedEq<ParsedInstruction>(b));
|
|
}
|
|
|
|
// Googlemock class receiving Header/Instruction calls from spvBinaryParse().
|
|
class MockParseClient {
|
|
public:
|
|
MOCK_METHOD6(Header, spv_result_t(spv_endianness_t endian, uint32_t magic,
|
|
uint32_t version, uint32_t generator,
|
|
uint32_t id_bound, uint32_t reserved));
|
|
MOCK_METHOD1(Instruction, spv_result_t(const ParsedInstruction&));
|
|
};
|
|
|
|
// Casts user_data as MockParseClient and invokes its Header().
|
|
spv_result_t invoke_header(void* user_data, spv_endianness_t endian,
|
|
uint32_t magic, uint32_t version, uint32_t generator,
|
|
uint32_t id_bound, uint32_t reserved) {
|
|
return static_cast<MockParseClient*>(user_data)->Header(
|
|
endian, magic, version, generator, id_bound, reserved);
|
|
}
|
|
|
|
// Casts user_data as MockParseClient and invokes its Instruction().
|
|
spv_result_t invoke_instruction(
|
|
void* user_data, const spv_parsed_instruction_t* parsed_instruction) {
|
|
return static_cast<MockParseClient*>(user_data)->Instruction(
|
|
ParsedInstruction(*parsed_instruction));
|
|
}
|
|
|
|
// The SPIR-V module header words for the Khronos Assembler generator,
|
|
// for a module with an ID bound of 1.
|
|
const uint32_t kHeaderForBound1[] = {
|
|
SpvMagicNumber, SpvVersion,
|
|
SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), 1 /*bound*/,
|
|
0 /*schema*/};
|
|
|
|
// Returns the expected SPIR-V module header words for the Khronos
|
|
// Assembler generator, and with a given Id bound.
|
|
std::vector<uint32_t> ExpectedHeaderForBound(uint32_t bound) {
|
|
return {SpvMagicNumber, SpvVersion,
|
|
SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), bound, 0};
|
|
}
|
|
|
|
// Returns a parsed operand for a non-number value at the given word offset
|
|
// within an instruction.
|
|
spv_parsed_operand_t MakeSimpleOperand(uint16_t offset,
|
|
spv_operand_type_t type) {
|
|
return {offset, 1, type, SPV_NUMBER_NONE, 0};
|
|
}
|
|
|
|
// Returns a parsed operand for a literal unsigned integer value at the given
|
|
// word offset within an instruction.
|
|
spv_parsed_operand_t MakeLiteralNumberOperand(uint16_t offset) {
|
|
return {offset, 1, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_NUMBER_UNSIGNED_INT,
|
|
32};
|
|
}
|
|
|
|
// Returns a parsed operand for a literal string value at the given
|
|
// word offset within an instruction.
|
|
spv_parsed_operand_t MakeLiteralStringOperand(uint16_t offset,
|
|
uint16_t length) {
|
|
return {offset, length, SPV_OPERAND_TYPE_LITERAL_STRING, SPV_NUMBER_NONE, 0};
|
|
}
|
|
|
|
// Returns a ParsedInstruction for an OpTypeVoid instruction that would
|
|
// generate the given result Id.
|
|
ParsedInstruction MakeParsedVoidTypeInstruction(uint32_t result_id) {
|
|
const auto void_inst = MakeInstruction(SpvOpTypeVoid, {result_id});
|
|
const auto void_operands = std::vector<spv_parsed_operand_t>{
|
|
MakeSimpleOperand(1, SPV_OPERAND_TYPE_RESULT_ID)};
|
|
const spv_parsed_instruction_t parsed_void_inst = {
|
|
void_inst.data(),
|
|
static_cast<uint16_t>(void_inst.size()),
|
|
SpvOpTypeVoid,
|
|
SPV_EXT_INST_TYPE_NONE,
|
|
0, // type id
|
|
result_id,
|
|
void_operands.data(),
|
|
static_cast<uint16_t>(void_operands.size())};
|
|
return ParsedInstruction(parsed_void_inst);
|
|
}
|
|
|
|
// Returns a ParsedInstruction for an OpTypeInt instruction that generates
|
|
// the given result Id for a 32-bit signed integer scalar type.
|
|
ParsedInstruction MakeParsedInt32TypeInstruction(uint32_t result_id) {
|
|
const auto i32_inst = MakeInstruction(SpvOpTypeInt, {result_id, 32, 1});
|
|
const auto i32_operands = std::vector<spv_parsed_operand_t>{
|
|
MakeSimpleOperand(1, SPV_OPERAND_TYPE_RESULT_ID),
|
|
MakeLiteralNumberOperand(2), MakeLiteralNumberOperand(3)};
|
|
spv_parsed_instruction_t parsed_i32_inst = {
|
|
i32_inst.data(),
|
|
static_cast<uint16_t>(i32_inst.size()),
|
|
SpvOpTypeInt,
|
|
SPV_EXT_INST_TYPE_NONE,
|
|
0, // type id
|
|
result_id,
|
|
i32_operands.data(),
|
|
static_cast<uint16_t>(i32_operands.size())};
|
|
return ParsedInstruction(parsed_i32_inst);
|
|
}
|
|
|
|
class BinaryParseTest : public spvtest::TextToBinaryTestBase<::testing::Test> {
|
|
protected:
|
|
void Parse(const SpirvVector& words, spv_result_t expected_result,
|
|
bool flip_words = false) {
|
|
SpirvVector flipped_words(words);
|
|
SCOPED_TRACE(flip_words ? "Flipped Endianness" : "Normal Endianness");
|
|
if (flip_words) {
|
|
std::transform(flipped_words.begin(), flipped_words.end(),
|
|
flipped_words.begin(), [](const uint32_t raw_word) {
|
|
return spvFixWord(raw_word,
|
|
I32_ENDIAN_HOST == I32_ENDIAN_BIG
|
|
? SPV_ENDIANNESS_LITTLE
|
|
: SPV_ENDIANNESS_BIG);
|
|
});
|
|
}
|
|
EXPECT_EQ(expected_result,
|
|
spvBinaryParse(context, &client_, flipped_words.data(),
|
|
flipped_words.size(), invoke_header,
|
|
invoke_instruction, &diagnostic_));
|
|
}
|
|
|
|
spv_diagnostic diagnostic_ = nullptr;
|
|
MockParseClient client_;
|
|
};
|
|
|
|
// Adds an EXPECT_CALL to client_->Header() with appropriate parameters,
|
|
// including bound. Returns the EXPECT_CALL result.
|
|
#define EXPECT_HEADER(bound) \
|
|
EXPECT_CALL(client_, \
|
|
Header(AnyOf(SPV_ENDIANNESS_LITTLE, SPV_ENDIANNESS_BIG), \
|
|
SpvMagicNumber, SpvVersion, \
|
|
SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), \
|
|
bound, 0 /*reserved*/))
|
|
|
|
static const bool kSwapEndians[] = {false, true};
|
|
|
|
TEST_F(BinaryParseTest, EmptyModuleHasValidHeaderAndNoInstructionCallbacks) {
|
|
for (bool endian_swap : kSwapEndians) {
|
|
const auto words = CompileSuccessfully("");
|
|
EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
|
|
EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
|
|
Parse(words, SPV_SUCCESS, endian_swap);
|
|
EXPECT_EQ(nullptr, diagnostic_);
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryParseTest,
|
|
ModuleWithSingleInstructionHasValidHeaderAndInstructionCallback) {
|
|
for (bool endian_swap : kSwapEndians) {
|
|
const auto words = CompileSuccessfully("%1 = OpTypeVoid");
|
|
InSequence calls_expected_in_specific_order;
|
|
EXPECT_HEADER(2).WillOnce(Return(SPV_SUCCESS));
|
|
EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
|
|
.WillOnce(Return(SPV_SUCCESS));
|
|
Parse(words, SPV_SUCCESS, endian_swap);
|
|
EXPECT_EQ(nullptr, diagnostic_);
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryParseTest, NullHeaderCallbackIsIgnored) {
|
|
const auto words = CompileSuccessfully("%1 = OpTypeVoid");
|
|
EXPECT_CALL(client_, Header(_, _, _, _, _, _))
|
|
.Times(0); // No header callback.
|
|
EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
|
|
.WillOnce(Return(SPV_SUCCESS));
|
|
EXPECT_EQ(SPV_SUCCESS,
|
|
spvBinaryParse(context, &client_, words.data(), words.size(),
|
|
nullptr, invoke_instruction, &diagnostic_));
|
|
EXPECT_EQ(nullptr, diagnostic_);
|
|
}
|
|
|
|
TEST_F(BinaryParseTest, NullInstructionCallbackIsIgnored) {
|
|
const auto words = CompileSuccessfully("%1 = OpTypeVoid");
|
|
EXPECT_HEADER((2)).WillOnce(Return(SPV_SUCCESS));
|
|
EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
|
|
EXPECT_EQ(SPV_SUCCESS,
|
|
spvBinaryParse(context, &client_, words.data(), words.size(),
|
|
invoke_header, nullptr, &diagnostic_));
|
|
EXPECT_EQ(nullptr, diagnostic_);
|
|
}
|
|
|
|
// Check the result of multiple instruction callbacks.
|
|
//
|
|
// This test exercises non-default values for the following members of the
|
|
// spv_parsed_instruction_t struct: words, num_words, opcode, result_id,
|
|
// operands, num_operands.
|
|
TEST_F(BinaryParseTest, TwoScalarTypesGenerateTwoInstructionCallbacks) {
|
|
for (bool endian_swap : kSwapEndians) {
|
|
const auto words = CompileSuccessfully(
|
|
"%1 = OpTypeVoid "
|
|
"%2 = OpTypeInt 32 1");
|
|
InSequence calls_expected_in_specific_order;
|
|
EXPECT_HEADER(3).WillOnce(Return(SPV_SUCCESS));
|
|
EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
|
|
.WillOnce(Return(SPV_SUCCESS));
|
|
EXPECT_CALL(client_, Instruction(MakeParsedInt32TypeInstruction(2)))
|
|
.WillOnce(Return(SPV_SUCCESS));
|
|
Parse(words, SPV_SUCCESS, endian_swap);
|
|
EXPECT_EQ(nullptr, diagnostic_);
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryParseTest, EarlyReturnWithZeroPassingCallbacks) {
|
|
for (bool endian_swap : kSwapEndians) {
|
|
const auto words = CompileSuccessfully(
|
|
"%1 = OpTypeVoid "
|
|
"%2 = OpTypeInt 32 1");
|
|
InSequence calls_expected_in_specific_order;
|
|
EXPECT_HEADER(3).WillOnce(Return(SPV_ERROR_INVALID_BINARY));
|
|
// Early exit means no calls to Instruction().
|
|
EXPECT_CALL(client_, Instruction(_)).Times(0);
|
|
Parse(words, SPV_ERROR_INVALID_BINARY, endian_swap);
|
|
// On error, the binary parser doesn't generate its own diagnostics.
|
|
EXPECT_EQ(nullptr, diagnostic_);
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryParseTest,
|
|
EarlyReturnWithZeroPassingCallbacksAndSpecifiedResultCode) {
|
|
for (bool endian_swap : kSwapEndians) {
|
|
const auto words = CompileSuccessfully(
|
|
"%1 = OpTypeVoid "
|
|
"%2 = OpTypeInt 32 1");
|
|
InSequence calls_expected_in_specific_order;
|
|
EXPECT_HEADER(3).WillOnce(Return(SPV_REQUESTED_TERMINATION));
|
|
// Early exit means no calls to Instruction().
|
|
EXPECT_CALL(client_, Instruction(_)).Times(0);
|
|
Parse(words, SPV_REQUESTED_TERMINATION, endian_swap);
|
|
// On early termination, the binary parser doesn't generate its own
|
|
// diagnostics.
|
|
EXPECT_EQ(nullptr, diagnostic_);
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryParseTest, EarlyReturnWithOnePassingCallback) {
|
|
for (bool endian_swap : kSwapEndians) {
|
|
const auto words = CompileSuccessfully(
|
|
"%1 = OpTypeVoid "
|
|
"%2 = OpTypeInt 32 1 "
|
|
"%3 = OpTypeFloat 32");
|
|
InSequence calls_expected_in_specific_order;
|
|
EXPECT_HEADER(4).WillOnce(Return(SPV_SUCCESS));
|
|
EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
|
|
.WillOnce(Return(SPV_REQUESTED_TERMINATION));
|
|
Parse(words, SPV_REQUESTED_TERMINATION, endian_swap);
|
|
// On early termination, the binary parser doesn't generate its own
|
|
// diagnostics.
|
|
EXPECT_EQ(nullptr, diagnostic_);
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryParseTest, EarlyReturnWithTwoPassingCallbacks) {
|
|
for (bool endian_swap : kSwapEndians) {
|
|
const auto words = CompileSuccessfully(
|
|
"%1 = OpTypeVoid "
|
|
"%2 = OpTypeInt 32 1 "
|
|
"%3 = OpTypeFloat 32");
|
|
InSequence calls_expected_in_specific_order;
|
|
EXPECT_HEADER(4).WillOnce(Return(SPV_SUCCESS));
|
|
EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
|
|
.WillOnce(Return(SPV_SUCCESS));
|
|
EXPECT_CALL(client_, Instruction(MakeParsedInt32TypeInstruction(2)))
|
|
.WillOnce(Return(SPV_REQUESTED_TERMINATION));
|
|
Parse(words, SPV_REQUESTED_TERMINATION, endian_swap);
|
|
// On early termination, the binary parser doesn't generate its own
|
|
// diagnostics.
|
|
EXPECT_EQ(nullptr, diagnostic_);
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryParseTest, InstructionWithStringOperand) {
|
|
const std::string str =
|
|
"the future is already here, it's just not evenly distributed";
|
|
const auto str_words = MakeVector(str);
|
|
const auto instruction = MakeInstruction(SpvOpName, {99}, str_words);
|
|
const auto words = Concatenate({ExpectedHeaderForBound(100), instruction});
|
|
InSequence calls_expected_in_specific_order;
|
|
EXPECT_HEADER(100).WillOnce(Return(SPV_SUCCESS));
|
|
const auto operands = std::vector<spv_parsed_operand_t>{
|
|
MakeSimpleOperand(1, SPV_OPERAND_TYPE_ID),
|
|
MakeLiteralStringOperand(2, static_cast<uint16_t>(str_words.size()))};
|
|
EXPECT_CALL(client_,
|
|
Instruction(ParsedInstruction(spv_parsed_instruction_t{
|
|
instruction.data(), static_cast<uint16_t>(instruction.size()),
|
|
SpvOpName, SPV_EXT_INST_TYPE_NONE, 0 /*type id*/,
|
|
0 /* No result id for OpName*/, operands.data(),
|
|
static_cast<uint16_t>(operands.size())})))
|
|
.WillOnce(Return(SPV_SUCCESS));
|
|
// Since we are actually checking the output, don't test the
|
|
// endian-swapped version.
|
|
Parse(words, SPV_SUCCESS, false);
|
|
EXPECT_EQ(nullptr, diagnostic_);
|
|
}
|
|
|
|
// Checks for non-zero values for the result_id and ext_inst_type members
|
|
// spv_parsed_instruction_t.
|
|
TEST_F(BinaryParseTest, ExtendedInstruction) {
|
|
const auto words = CompileSuccessfully(
|
|
"%extcl = OpExtInstImport \"OpenCL.std\" "
|
|
"%result = OpExtInst %float %extcl sqrt %x");
|
|
EXPECT_HEADER(5).WillOnce(Return(SPV_SUCCESS));
|
|
EXPECT_CALL(client_, Instruction(_)).WillOnce(Return(SPV_SUCCESS));
|
|
// We're only interested in the second call to Instruction():
|
|
const auto operands = std::vector<spv_parsed_operand_t>{
|
|
MakeSimpleOperand(1, SPV_OPERAND_TYPE_TYPE_ID),
|
|
MakeSimpleOperand(2, SPV_OPERAND_TYPE_RESULT_ID),
|
|
MakeSimpleOperand(3,
|
|
SPV_OPERAND_TYPE_ID), // Extended instruction set Id
|
|
MakeSimpleOperand(4, SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER),
|
|
MakeSimpleOperand(5, SPV_OPERAND_TYPE_ID), // Id of the argument
|
|
};
|
|
const auto instruction = MakeInstruction(
|
|
SpvOpExtInst,
|
|
{2, 3, 1, static_cast<uint32_t>(OpenCLLIB::Entrypoints::Sqrt), 4});
|
|
EXPECT_CALL(client_,
|
|
Instruction(ParsedInstruction(spv_parsed_instruction_t{
|
|
instruction.data(), static_cast<uint16_t>(instruction.size()),
|
|
SpvOpExtInst, SPV_EXT_INST_TYPE_OPENCL_STD, 2 /*type id*/,
|
|
3 /*result id*/, operands.data(),
|
|
static_cast<uint16_t>(operands.size())})))
|
|
.WillOnce(Return(SPV_SUCCESS));
|
|
// Since we are actually checking the output, don't test the
|
|
// endian-swapped version.
|
|
Parse(words, SPV_SUCCESS, false);
|
|
EXPECT_EQ(nullptr, diagnostic_);
|
|
}
|
|
|
|
// A binary parser diagnostic test case where we provide the words array
|
|
// pointer and word count explicitly.
|
|
struct WordsAndCountDiagnosticCase {
|
|
const uint32_t* words;
|
|
size_t num_words;
|
|
std::string expected_diagnostic;
|
|
};
|
|
|
|
using BinaryParseWordsAndCountDiagnosticTest = spvtest::TextToBinaryTestBase<
|
|
::testing::TestWithParam<WordsAndCountDiagnosticCase>>;
|
|
|
|
TEST_P(BinaryParseWordsAndCountDiagnosticTest, WordAndCountCases) {
|
|
EXPECT_EQ(
|
|
SPV_ERROR_INVALID_BINARY,
|
|
spvBinaryParse(context, nullptr, GetParam().words, GetParam().num_words,
|
|
nullptr, nullptr, &diagnostic));
|
|
ASSERT_NE(nullptr, diagnostic);
|
|
EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
BinaryParseDiagnostic, BinaryParseWordsAndCountDiagnosticTest,
|
|
::testing::ValuesIn(std::vector<WordsAndCountDiagnosticCase>{
|
|
{nullptr, 0, "Missing module."},
|
|
{kHeaderForBound1, 0,
|
|
"Module has incomplete header: only 0 words instead of 5"},
|
|
{kHeaderForBound1, 1,
|
|
"Module has incomplete header: only 1 words instead of 5"},
|
|
{kHeaderForBound1, 2,
|
|
"Module has incomplete header: only 2 words instead of 5"},
|
|
{kHeaderForBound1, 3,
|
|
"Module has incomplete header: only 3 words instead of 5"},
|
|
{kHeaderForBound1, 4,
|
|
"Module has incomplete header: only 4 words instead of 5"},
|
|
}));
|
|
|
|
// A binary parser diagnostic test case where a vector of words is
|
|
// provided. We'll use this to express cases that can't be created
|
|
// via the assembler. Either we want to make a malformed instruction,
|
|
// or an invalid case the assembler would reject.
|
|
struct WordVectorDiagnosticCase {
|
|
std::vector<uint32_t> words;
|
|
std::string expected_diagnostic;
|
|
};
|
|
|
|
using BinaryParseWordVectorDiagnosticTest = spvtest::TextToBinaryTestBase<
|
|
::testing::TestWithParam<WordVectorDiagnosticCase>>;
|
|
|
|
TEST_P(BinaryParseWordVectorDiagnosticTest, WordVectorCases) {
|
|
const auto& words = GetParam().words;
|
|
EXPECT_THAT(spvBinaryParse(context, nullptr, words.data(), words.size(),
|
|
nullptr, nullptr, &diagnostic),
|
|
AnyOf(SPV_ERROR_INVALID_BINARY, SPV_ERROR_INVALID_ID));
|
|
ASSERT_NE(nullptr, diagnostic);
|
|
EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
BinaryParseDiagnostic, BinaryParseWordVectorDiagnosticTest,
|
|
::testing::ValuesIn(std::vector<WordVectorDiagnosticCase>{
|
|
{Concatenate({ExpectedHeaderForBound(1), {spvOpcodeMake(0, SpvOpNop)}}),
|
|
"Invalid instruction word count: 0"},
|
|
{Concatenate(
|
|
{ExpectedHeaderForBound(1),
|
|
{spvOpcodeMake(1, static_cast<SpvOp>(
|
|
std::numeric_limits<uint16_t>::max()))}}),
|
|
"Invalid opcode: 65535"},
|
|
{Concatenate({ExpectedHeaderForBound(1),
|
|
MakeInstruction(SpvOpNop, {42})}),
|
|
"Invalid instruction OpNop starting at word 5: expected "
|
|
"no more operands after 1 words, but stated word count is 2."},
|
|
// Supply several more unexpectd words.
|
|
{Concatenate({ExpectedHeaderForBound(1),
|
|
MakeInstruction(SpvOpNop, {42, 43, 44, 45, 46, 47})}),
|
|
"Invalid instruction OpNop starting at word 5: expected "
|
|
"no more operands after 1 words, but stated word count is 7."},
|
|
{Concatenate({ExpectedHeaderForBound(1),
|
|
MakeInstruction(SpvOpTypeVoid, {1, 2})}),
|
|
"Invalid instruction OpTypeVoid starting at word 5: expected "
|
|
"no more operands after 2 words, but stated word count is 3."},
|
|
{Concatenate({ExpectedHeaderForBound(1),
|
|
MakeInstruction(SpvOpTypeVoid, {1, 2, 5, 9, 10})}),
|
|
"Invalid instruction OpTypeVoid starting at word 5: expected "
|
|
"no more operands after 2 words, but stated word count is 6."},
|
|
{Concatenate({ExpectedHeaderForBound(1),
|
|
MakeInstruction(SpvOpTypeInt, {1, 32, 1, 9})}),
|
|
"Invalid instruction OpTypeInt starting at word 5: expected "
|
|
"no more operands after 4 words, but stated word count is 5."},
|
|
{Concatenate({ExpectedHeaderForBound(1),
|
|
MakeInstruction(SpvOpTypeInt, {1})}),
|
|
"End of input reached while decoding OpTypeInt starting at word 5:"
|
|
" expected more operands after 2 words."},
|
|
|
|
// Check several cases for running off the end of input.
|
|
|
|
// Detect a missing single word operand.
|
|
{Concatenate({ExpectedHeaderForBound(1),
|
|
{spvOpcodeMake(2, SpvOpTypeStruct)}}),
|
|
"End of input reached while decoding OpTypeStruct starting at word"
|
|
" 5: missing result ID operand at word offset 1."},
|
|
// Detect this a missing a multi-word operand to OpConstant.
|
|
// We also lie and say the OpConstant instruction has 5 words when
|
|
// it only has 3. Corresponds to something like this:
|
|
// %1 = OpTypeInt 64 0
|
|
// %2 = OpConstant %1 <missing>
|
|
{Concatenate({ExpectedHeaderForBound(3),
|
|
{MakeInstruction(SpvOpTypeInt, {1, 64, 0})},
|
|
{spvOpcodeMake(5, SpvOpConstant), 1, 2}}),
|
|
"End of input reached while decoding OpConstant starting at word"
|
|
" 9: missing possibly multi-word literal number operand at word "
|
|
"offset 3."},
|
|
// Detect when we provide only one word from the 64-bit literal,
|
|
// and again lie about the number of words in the instruction.
|
|
{Concatenate({ExpectedHeaderForBound(3),
|
|
{MakeInstruction(SpvOpTypeInt, {1, 64, 0})},
|
|
{spvOpcodeMake(5, SpvOpConstant), 1, 2, 42}}),
|
|
"End of input reached while decoding OpConstant starting at word"
|
|
" 9: truncated possibly multi-word literal number operand at word "
|
|
"offset 3."},
|
|
// Detect when a required string operand is missing.
|
|
// Also, lie about the length of the instruction.
|
|
{Concatenate({ExpectedHeaderForBound(3),
|
|
{spvOpcodeMake(3, SpvOpString), 1}}),
|
|
"End of input reached while decoding OpString starting at word"
|
|
" 5: missing literal string operand at word offset 2."},
|
|
// Detect when a required string operand is truncated: it's missing
|
|
// a null terminator. Catching the error avoids a buffer overrun.
|
|
{Concatenate({ExpectedHeaderForBound(3),
|
|
{spvOpcodeMake(4, SpvOpString), 1, 0x41414141,
|
|
0x41414141}}),
|
|
"End of input reached while decoding OpString starting at word"
|
|
" 5: truncated literal string operand at word offset 2."},
|
|
// Detect when an optional string operand is truncated: it's missing
|
|
// a null terminator. Catching the error avoids a buffer overrun.
|
|
// (It is valid for an optional string operand to be absent.)
|
|
{Concatenate({ExpectedHeaderForBound(3),
|
|
{spvOpcodeMake(6, SpvOpSource),
|
|
static_cast<uint32_t>(SpvSourceLanguageOpenCL_C), 210,
|
|
1 /* file id */,
|
|
/*start of string*/ 0x41414141, 0x41414141}}),
|
|
"End of input reached while decoding OpSource starting at word"
|
|
" 5: truncated literal string operand at word offset 4."},
|
|
|
|
// (End of input exhaustion test cases.)
|
|
|
|
// In this case the instruction word count is too small, where
|
|
// it would truncate a multi-word operand to OpConstant.
|
|
{Concatenate({ExpectedHeaderForBound(3),
|
|
{MakeInstruction(SpvOpTypeInt, {1, 64, 0})},
|
|
{spvOpcodeMake(4, SpvOpConstant), 1, 2, 44, 44}}),
|
|
"Invalid word count: OpConstant starting at word 9 says it has 4"
|
|
" words, but found 5 words instead."},
|
|
// Word count is to small, where it would truncate a literal string.
|
|
{Concatenate({ExpectedHeaderForBound(2),
|
|
{spvOpcodeMake(3, SpvOpString), 1, 0x41414141, 0}}),
|
|
"Invalid word count: OpString starting at word 5 says it has 3"
|
|
" words, but found 4 words instead."},
|
|
// Word count is too large. The string terminates before the last
|
|
// word.
|
|
{Concatenate({ExpectedHeaderForBound(2),
|
|
{spvOpcodeMake(4, SpvOpString), 1 /* result id */},
|
|
MakeVector("abc"),
|
|
{0 /* this word does not belong*/}}),
|
|
"Invalid instruction OpString starting at word 5: expected no more"
|
|
" operands after 3 words, but stated word count is 4."},
|
|
// Word count is too large. There are too many words after the string
|
|
// literal. A linkage attribute decoration is the only case in SPIR-V
|
|
// where a string operand is followed by another operand.
|
|
{Concatenate({ExpectedHeaderForBound(2),
|
|
{spvOpcodeMake(6, SpvOpDecorate), 1 /* target id */,
|
|
static_cast<uint32_t>(SpvDecorationLinkageAttributes)},
|
|
MakeVector("abc"),
|
|
{static_cast<uint32_t>(SpvLinkageTypeImport),
|
|
0 /* does not belong */}}),
|
|
"Invalid instruction OpDecorate starting at word 5: expected no more"
|
|
" operands after 5 words, but stated word count is 6."},
|
|
// Like the previous case, but with 5 extra words.
|
|
{Concatenate({ExpectedHeaderForBound(2),
|
|
{spvOpcodeMake(10, SpvOpDecorate), 1 /* target id */,
|
|
static_cast<uint32_t>(SpvDecorationLinkageAttributes)},
|
|
MakeVector("abc"),
|
|
{static_cast<uint32_t>(SpvLinkageTypeImport),
|
|
/* don't belong */ 0, 1, 2, 3, 4}}),
|
|
"Invalid instruction OpDecorate starting at word 5: expected no more"
|
|
" operands after 5 words, but stated word count is 10."},
|
|
// Like the previous two cases, but with OpMemberDecorate.
|
|
{Concatenate({ExpectedHeaderForBound(2),
|
|
{spvOpcodeMake(7, SpvOpMemberDecorate), 1 /* target id */,
|
|
42 /* member index */,
|
|
static_cast<uint32_t>(SpvDecorationLinkageAttributes)},
|
|
MakeVector("abc"),
|
|
{static_cast<uint32_t>(SpvLinkageTypeImport),
|
|
0 /* does not belong */}}),
|
|
"Invalid instruction OpMemberDecorate starting at word 5: expected no"
|
|
" more operands after 6 words, but stated word count is 7."},
|
|
{Concatenate({ExpectedHeaderForBound(2),
|
|
{spvOpcodeMake(11, SpvOpMemberDecorate),
|
|
1 /* target id */, 42 /* member index */,
|
|
static_cast<uint32_t>(SpvDecorationLinkageAttributes)},
|
|
MakeVector("abc"),
|
|
{static_cast<uint32_t>(SpvLinkageTypeImport),
|
|
/* don't belong */ 0, 1, 2, 3, 4}}),
|
|
"Invalid instruction OpMemberDecorate starting at word 5: expected no"
|
|
" more operands after 6 words, but stated word count is 11."},
|
|
// Word count is too large. There should be no more words
|
|
// after the RelaxedPrecision decoration.
|
|
{Concatenate({ExpectedHeaderForBound(2),
|
|
{spvOpcodeMake(4, SpvOpDecorate), 1 /* target id */,
|
|
static_cast<uint32_t>(SpvDecorationRelaxedPrecision),
|
|
0 /* does not belong */}}),
|
|
"Invalid instruction OpDecorate starting at word 5: expected no"
|
|
" more operands after 3 words, but stated word count is 4."},
|
|
// Word count is too large. There should be only one word after
|
|
// the SpecId decoration enum word.
|
|
{Concatenate({ExpectedHeaderForBound(2),
|
|
{spvOpcodeMake(5, SpvOpDecorate), 1 /* target id */,
|
|
static_cast<uint32_t>(SpvDecorationSpecId),
|
|
42 /* the spec id */, 0 /* does not belong */}}),
|
|
"Invalid instruction OpDecorate starting at word 5: expected no"
|
|
" more operands after 4 words, but stated word count is 5."},
|
|
{Concatenate({ExpectedHeaderForBound(2),
|
|
{spvOpcodeMake(2, SpvOpTypeVoid), 0}}),
|
|
"Error: Result Id is 0"},
|
|
{Concatenate({
|
|
ExpectedHeaderForBound(2),
|
|
{spvOpcodeMake(2, SpvOpTypeVoid), 1},
|
|
{spvOpcodeMake(2, SpvOpTypeBool), 1},
|
|
}),
|
|
"Id 1 is defined more than once"},
|
|
{Concatenate({ExpectedHeaderForBound(3),
|
|
MakeInstruction(SpvOpExtInst, {2, 3, 100, 4, 5})}),
|
|
"OpExtInst set Id 100 does not reference an OpExtInstImport result "
|
|
"Id"},
|
|
// In this case, the OpSwitch selector refers to an invalid ID.
|
|
{Concatenate({ExpectedHeaderForBound(3),
|
|
MakeInstruction(SpvOpSwitch, {1, 2, 42, 3})}),
|
|
"Invalid OpSwitch: selector id 1 has no type"},
|
|
// In this case, the OpSwitch selector refers to an ID that has
|
|
// no type.
|
|
{Concatenate({ExpectedHeaderForBound(3),
|
|
MakeInstruction(SpvOpLabel, {1}),
|
|
MakeInstruction(SpvOpSwitch, {1, 2, 42, 3})}),
|
|
"Invalid OpSwitch: selector id 1 has no type"},
|
|
{Concatenate({ExpectedHeaderForBound(3),
|
|
MakeInstruction(SpvOpTypeInt, {1, 32, 0}),
|
|
MakeInstruction(SpvOpSwitch, {1, 3, 42, 3})}),
|
|
"Invalid OpSwitch: selector id 1 is a type, not a value"},
|
|
{Concatenate({ExpectedHeaderForBound(3),
|
|
MakeInstruction(SpvOpTypeFloat, {1, 32}),
|
|
MakeInstruction(SpvOpConstant, {1, 2, 0x78f00000}),
|
|
MakeInstruction(SpvOpSwitch, {2, 3, 42, 3})}),
|
|
"Invalid OpSwitch: selector id 2 is not a scalar integer"},
|
|
{Concatenate({ExpectedHeaderForBound(3),
|
|
MakeInstruction(SpvOpExtInstImport, {1},
|
|
MakeVector("invalid-import"))}),
|
|
"Invalid extended instruction import 'invalid-import'"},
|
|
{Concatenate({
|
|
ExpectedHeaderForBound(3),
|
|
MakeInstruction(SpvOpTypeInt, {1, 32, 0}),
|
|
MakeInstruction(SpvOpConstant, {2, 2, 42}),
|
|
}),
|
|
"Type Id 2 is not a type"},
|
|
{Concatenate({
|
|
ExpectedHeaderForBound(3), MakeInstruction(SpvOpTypeBool, {1}),
|
|
MakeInstruction(SpvOpConstant, {1, 2, 42}),
|
|
}),
|
|
"Type Id 1 is not a scalar numeric type"},
|
|
}));
|
|
|
|
// A binary parser diagnostic case generated from an assembly text input.
|
|
struct AssemblyDiagnosticCase {
|
|
std::string assembly;
|
|
std::string expected_diagnostic;
|
|
};
|
|
|
|
using BinaryParseAssemblyDiagnosticTest = spvtest::TextToBinaryTestBase<
|
|
::testing::TestWithParam<AssemblyDiagnosticCase>>;
|
|
|
|
TEST_P(BinaryParseAssemblyDiagnosticTest, AssemblyCases) {
|
|
auto words = CompileSuccessfully(GetParam().assembly);
|
|
EXPECT_THAT(spvBinaryParse(context, nullptr, words.data(), words.size(),
|
|
nullptr, nullptr, &diagnostic),
|
|
AnyOf(SPV_ERROR_INVALID_BINARY, SPV_ERROR_INVALID_ID));
|
|
ASSERT_NE(nullptr, diagnostic);
|
|
EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
BinaryParseDiagnostic, BinaryParseAssemblyDiagnosticTest,
|
|
::testing::ValuesIn(std::vector<AssemblyDiagnosticCase>{
|
|
{"%1 = OpConstant !0 42", "Error: Type Id is 0"},
|
|
// A required id is 0.
|
|
{"OpName !0 \"foo\"", "Id is 0"},
|
|
// An optional id is 0, in this case the optional
|
|
// initializer.
|
|
{"%2 = OpVariable %1 CrossWorkgroup !0", "Id is 0"},
|
|
{"OpControlBarrier !0 %1 %2", "scope ID is 0"},
|
|
{"OpControlBarrier %1 !0 %2", "scope ID is 0"},
|
|
{"OpControlBarrier %1 %2 !0", "memory semantics ID is 0"},
|
|
{"%import = OpExtInstImport \"GLSL.std.450\" "
|
|
"%result = OpExtInst %type %import !999999 %x",
|
|
"Invalid extended instruction number: 999999"},
|
|
{"%2 = OpSpecConstantOp %1 !1000 %2",
|
|
"Invalid OpSpecConstantOp opcode: 1000"},
|
|
{"OpCapability !9999", "Invalid capability operand: 9999"},
|
|
{"OpSource !9999 100", "Invalid source language operand: 9999"},
|
|
{"OpEntryPoint !9999", "Invalid execution model operand: 9999"},
|
|
{"OpMemoryModel !9999", "Invalid addressing model operand: 9999"},
|
|
{"OpMemoryModel Logical !9999", "Invalid memory model operand: 9999"},
|
|
{"OpExecutionMode %1 !9999", "Invalid execution mode operand: 9999"},
|
|
{"OpTypeForwardPointer %1 !9999",
|
|
"Invalid storage class operand: 9999"},
|
|
{"%2 = OpTypeImage %1 !9999", "Invalid dimensionality operand: 9999"},
|
|
{"%2 = OpTypeImage %1 1D 0 0 0 0 !9999",
|
|
"Invalid image format operand: 9999"},
|
|
{"OpDecorate %1 FPRoundingMode !9999",
|
|
"Invalid floating-point rounding mode operand: 9999"},
|
|
{"OpDecorate %1 LinkageAttributes \"C\" !9999",
|
|
"Invalid linkage type operand: 9999"},
|
|
{"%1 = OpTypePipe !9999", "Invalid access qualifier operand: 9999"},
|
|
{"OpDecorate %1 FuncParamAttr !9999",
|
|
"Invalid function parameter attribute operand: 9999"},
|
|
{"OpDecorate %1 !9999", "Invalid decoration operand: 9999"},
|
|
{"OpDecorate %1 BuiltIn !9999", "Invalid built-in operand: 9999"},
|
|
{"%2 = OpGroupIAdd %1 %3 !9999",
|
|
"Invalid group operation operand: 9999"},
|
|
{"OpDecorate %1 FPFastMathMode !63",
|
|
"Invalid floating-point fast math mode operand: 63 has invalid mask "
|
|
"component 32"},
|
|
{"%2 = OpFunction %2 !31",
|
|
"Invalid function control operand: 31 has invalid mask component 16"},
|
|
{"OpLoopMerge %1 %2 !7",
|
|
"Invalid loop control operand: 7 has invalid mask component 4"},
|
|
{"%2 = OpImageFetch %1 %image %coord !511",
|
|
"Invalid image operand: 511 has invalid mask component 256"},
|
|
{"OpSelectionMerge %1 !7",
|
|
"Invalid selection control operand: 7 has invalid mask component 4"},
|
|
}));
|
|
|
|
} // anonymous namespace
|