C++程序  |  893行  |  40.65 KB

// Copyright (c) 2015-2016 The Khronos Group Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <algorithm>
#include <limits>
#include <sstream>
#include <string>
#include <vector>

#include "gmock/gmock.h"
#include "source/latest_version_opencl_std_header.h"
#include "source/table.h"
#include "test/test_fixture.h"
#include "test/unit_spirv.h"

// Returns true if two spv_parsed_operand_t values are equal.
// To use this operator, this definition must appear in the same namespace
// as spv_parsed_operand_t.
static bool operator==(const spv_parsed_operand_t& a,
                       const spv_parsed_operand_t& b) {
  return a.offset == b.offset && a.num_words == b.num_words &&
         a.type == b.type && a.number_kind == b.number_kind &&
         a.number_bit_width == b.number_bit_width;
}

namespace spvtools {
namespace {

using ::spvtest::Concatenate;
using ::spvtest::MakeInstruction;
using ::spvtest::MakeVector;
using ::spvtest::ScopedContext;
using ::testing::_;
using ::testing::AnyOf;
using ::testing::Eq;
using ::testing::InSequence;
using ::testing::Return;

// An easily-constructible and comparable object for the contents of an
// spv_parsed_instruction_t.  Unlike spv_parsed_instruction_t, owns the memory
// of its components.
struct ParsedInstruction {
  explicit ParsedInstruction(const spv_parsed_instruction_t& inst)
      : words(inst.words, inst.words + inst.num_words),
        opcode(static_cast<SpvOp>(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, 0x10000,
          SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), bound, 0};
}

// Returns a parsed operand for a non-number value at the given word offset
// within an instruction.
spv_parsed_operand_t MakeSimpleOperand(uint16_t offset,
                                       spv_operand_type_t type) {
  return {offset, 1, type, SPV_NUMBER_NONE, 0};
}

// Returns a parsed operand for a literal unsigned integer value at the given
// word offset within an instruction.
spv_parsed_operand_t MakeLiteralNumberOperand(uint16_t offset) {
  return {offset, 1, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_NUMBER_UNSIGNED_INT,
          32};
}

// Returns a parsed operand for a literal string value at the given
// word offset within an instruction.
spv_parsed_operand_t MakeLiteralStringOperand(uint16_t offset,
                                              uint16_t length) {
  return {offset, length, SPV_OPERAND_TYPE_LITERAL_STRING, SPV_NUMBER_NONE, 0};
}

// Returns a ParsedInstruction for an OpTypeVoid instruction that would
// generate the given result Id.
ParsedInstruction MakeParsedVoidTypeInstruction(uint32_t result_id) {
  const auto void_inst = MakeInstruction(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:
  ~BinaryParseTest() { spvDiagnosticDestroy(diagnostic_); }

  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(ScopedContext().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, \
             0x10000, SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0),  \
             bound, 0 /*reserved*/))

static const bool kSwapEndians[] = {false, true};

TEST_F(BinaryParseTest, EmptyModuleHasValidHeaderAndNoInstructionCallbacks) {
  for (bool endian_swap : kSwapEndians) {
    const auto words = CompileSuccessfully("");
    EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
    EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
    Parse(words, SPV_SUCCESS, endian_swap);
    EXPECT_EQ(nullptr, diagnostic_);
  }
}

TEST_F(BinaryParseTest, NullDiagnosticsIsOkForGoodParse) {
  const auto words = CompileSuccessfully("");
  EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
  EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
  EXPECT_EQ(
      SPV_SUCCESS,
      spvBinaryParse(ScopedContext().context, &client_, words.data(),
                     words.size(), invoke_header, invoke_instruction, nullptr));
}

TEST_F(BinaryParseTest, NullDiagnosticsIsOkForBadParse) {
  auto words = CompileSuccessfully("");
  words.push_back(0xffffffff);  // Certainly invalid instruction header.
  EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
  EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
  EXPECT_EQ(
      SPV_ERROR_INVALID_BINARY,
      spvBinaryParse(ScopedContext().context, &client_, words.data(),
                     words.size(), invoke_header, invoke_instruction, nullptr));
}

// Make sure that we don't blow up when both the consumer and the diagnostic are
// null.
TEST_F(BinaryParseTest, NullConsumerNullDiagnosticsForBadParse) {
  auto words = CompileSuccessfully("");

  auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
  ctx.SetMessageConsumer(nullptr);

  words.push_back(0xffffffff);  // Certainly invalid instruction header.
  EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
  EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
  EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
            spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
                           invoke_header, invoke_instruction, nullptr));
}

TEST_F(BinaryParseTest, SpecifyConsumerNullDiagnosticsForGoodParse) {
  const auto words = CompileSuccessfully("");

  auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
  int invocation = 0;
  ctx.SetMessageConsumer([&invocation](spv_message_level_t, const char*,
                                       const spv_position_t&,
                                       const char*) { ++invocation; });

  EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
  EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
  EXPECT_EQ(SPV_SUCCESS,
            spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
                           invoke_header, invoke_instruction, nullptr));
  EXPECT_EQ(0, invocation);
}

TEST_F(BinaryParseTest, SpecifyConsumerNullDiagnosticsForBadParse) {
  auto words = CompileSuccessfully("");

  auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
  int invocation = 0;
  ctx.SetMessageConsumer(
      [&invocation](spv_message_level_t level, const char* source,
                    const spv_position_t& position, const char* message) {
        ++invocation;
        EXPECT_EQ(SPV_MSG_ERROR, level);
        EXPECT_STREQ("input", source);
        EXPECT_EQ(0u, position.line);
        EXPECT_EQ(0u, position.column);
        EXPECT_EQ(1u, position.index);
        EXPECT_STREQ("Invalid opcode: 65535", message);
      });

  words.push_back(0xffffffff);  // Certainly invalid instruction header.
  EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
  EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
  EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
            spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
                           invoke_header, invoke_instruction, nullptr));
  EXPECT_EQ(1, invocation);
}

TEST_F(BinaryParseTest, SpecifyConsumerSpecifyDiagnosticsForGoodParse) {
  const auto words = CompileSuccessfully("");

  auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
  int invocation = 0;
  ctx.SetMessageConsumer([&invocation](spv_message_level_t, const char*,
                                       const spv_position_t&,
                                       const char*) { ++invocation; });

  EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
  EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
  EXPECT_EQ(SPV_SUCCESS,
            spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
                           invoke_header, invoke_instruction, &diagnostic_));
  EXPECT_EQ(0, invocation);
  EXPECT_EQ(nullptr, diagnostic_);
}

TEST_F(BinaryParseTest, SpecifyConsumerSpecifyDiagnosticsForBadParse) {
  auto words = CompileSuccessfully("");

  auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
  int invocation = 0;
  ctx.SetMessageConsumer([&invocation](spv_message_level_t, const char*,
                                       const spv_position_t&,
                                       const char*) { ++invocation; });

  words.push_back(0xffffffff);  // Certainly invalid instruction header.
  EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
  EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
  EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
            spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
                           invoke_header, invoke_instruction, &diagnostic_));
  EXPECT_EQ(0, invocation);
  EXPECT_STREQ("Invalid opcode: 65535", diagnostic_->error);
}

TEST_F(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(ScopedContext().context, &client_,
                                        words.data(), words.size(), nullptr,
                                        invoke_instruction, &diagnostic_));
  EXPECT_EQ(nullptr, diagnostic_);
}

TEST_F(BinaryParseTest, NullInstructionCallbackIsIgnored) {
  const auto words = CompileSuccessfully("%1 = OpTypeVoid");
  EXPECT_HEADER((2)).WillOnce(Return(SPV_SUCCESS));
  EXPECT_CALL(client_, Instruction(_)).Times(0);  // No instruction callback.
  EXPECT_EQ(SPV_SUCCESS,
            spvBinaryParse(ScopedContext().context, &client_, words.data(),
                           words.size(), invoke_header, nullptr, &diagnostic_));
  EXPECT_EQ(nullptr, diagnostic_);
}

// Check the result of multiple instruction callbacks.
//
// This test exercises non-default values for the following members of the
// spv_parsed_instruction_t struct: words, num_words, opcode, result_id,
// operands, num_operands.
TEST_F(BinaryParseTest, TwoScalarTypesGenerateTwoInstructionCallbacks) {
  for (bool endian_swap : kSwapEndians) {
    const auto words = CompileSuccessfully(
        "%1 = OpTypeVoid "
        "%2 = OpTypeInt 32 1");
    InSequence calls_expected_in_specific_order;
    EXPECT_HEADER(3).WillOnce(Return(SPV_SUCCESS));
    EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
        .WillOnce(Return(SPV_SUCCESS));
    EXPECT_CALL(client_, Instruction(MakeParsedInt32TypeInstruction(2)))
        .WillOnce(Return(SPV_SUCCESS));
    Parse(words, SPV_SUCCESS, endian_swap);
    EXPECT_EQ(nullptr, diagnostic_);
  }
}

TEST_F(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(ScopedContext().context, nullptr, GetParam().words,
                     GetParam().num_words, nullptr, nullptr, &diagnostic));
  ASSERT_NE(nullptr, diagnostic);
  EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
}

INSTANTIATE_TEST_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(ScopedContext().context, nullptr, words.data(),
                             words.size(), nullptr, nullptr, &diagnostic),
              AnyOf(SPV_ERROR_INVALID_BINARY, SPV_ERROR_INVALID_ID));
  ASSERT_NE(nullptr, diagnostic);
  EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
}

INSTANTIATE_TEST_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"},
        {Concatenate({ExpectedHeaderForBound(101),
                      MakeInstruction(SpvOpExtInstImport, {100},
                                      MakeVector("OpenCL.std")),
                      // OpenCL cos is #14
                      MakeInstruction(SpvOpExtInst, {2, 3, 100, 14, 5, 999})}),
         "Invalid instruction OpExtInst starting at word 10: expected no "
         "more operands after 6 words, but stated word count is 7."},
        // In this case, the OpSwitch selector refers to an invalid ID.
        {Concatenate({ExpectedHeaderForBound(3),
                      MakeInstruction(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(ScopedContext().context, nullptr, words.data(),
                             words.size(), nullptr, nullptr, &diagnostic),
              AnyOf(SPV_ERROR_INVALID_BINARY, SPV_ERROR_INVALID_ID));
  ASSERT_NE(nullptr, diagnostic);
  EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
}

INSTANTIATE_TEST_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 !1027",
         "Invalid loop control operand: 1027 has invalid mask component 1024"},
        {"%2 = OpImageFetch %1 %image %coord !32770",
         "Invalid image operand: 32770 has invalid mask component 32768"},
        {"OpSelectionMerge %1 !7",
         "Invalid selection control operand: 7 has invalid mask component 4"},
    }), );

}  // namespace
}  // namespace spvtools