// Copyright (c) 2026 NVIDIA Corporation
//
// 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.

// Tests for SPV_BINARY_TO_TEXT_OPTION_HANDLE_UNKNOWN_OPCODES. Verifies that
// the binary parser, disassembler, and FriendlyNameMapper correctly handle
// SPIR-V binaries that contain unknown opcodes, unknown extended instruction
// numbers, or known opcodes with unknown enum operands.

#include <string>
#include <vector>

#include "gmock/gmock.h"
#include "source/binary.h"
#include "source/disassemble.h"
#include "source/name_mapper.h"
#include "spirv-tools/libspirv.h"
#include "test/unit_spirv.h"

namespace spvtools {
namespace {

using ::testing::HasSubstr;
using ::testing::Not;

// Opcode 0xFFFE is not assigned in any known SPIR-V grammar version.
constexpr uint32_t kUnknownOpcode = 0xFFFEu;

// Returns a SPIR-V module header for a module with the given ID bound.
std::vector<uint32_t> MakeHeader(uint32_t id_bound) {
  return {spv::MagicNumber, 0x00010000u,
          SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), id_bound, 0u};
}

// Returns the packed first word of a SPIR-V instruction.
uint32_t MakeFirstWord(uint32_t word_count, uint32_t opcode) {
  return (word_count << 16) | (opcode & 0xFFFFu);
}

class HandleUnknownOpcodesTest : public ::testing::Test {
 protected:
  void SetUp() override {
    context_ = spvContextCreate(SPV_ENV_UNIVERSAL_1_0);
    ASSERT_NE(nullptr, context_);
  }

  void TearDown() override { spvContextDestroy(context_); }

  spv_context context_ = nullptr;
};

// Binary parser: unknown opcode without the flag -> error.
TEST_F(HandleUnknownOpcodesTest, ParseUnknownOpcodeWithoutFlagFails) {
  std::vector<uint32_t> binary = spvtest::Concatenate({
      MakeHeader(1),
      {MakeFirstWord(1, kUnknownOpcode)},
  });
  spv_diagnostic diag = nullptr;
  const spv_result_t result =
      spvBinaryParseWithOptions(context_, nullptr, binary.data(), binary.size(),
                                nullptr, nullptr, &diag, 0u);
  EXPECT_NE(SPV_SUCCESS, result);
  spvDiagnosticDestroy(diag);
}

// Binary parser: unknown opcode with the flag -> success.
TEST_F(HandleUnknownOpcodesTest, ParseUnknownOpcodeWithFlagSucceeds) {
  std::vector<uint32_t> binary = spvtest::Concatenate({
      MakeHeader(1),
      {MakeFirstWord(3, kUnknownOpcode), 42u, 99u},
  });
  spv_diagnostic diag = nullptr;
  const spv_result_t result = spvBinaryParseWithOptions(
      context_, nullptr, binary.data(), binary.size(), nullptr, nullptr, &diag,
      SPV_BINARY_TO_TEXT_OPTION_HANDLE_UNKNOWN_OPCODES);
  EXPECT_EQ(SPV_SUCCESS, result);
  spvDiagnosticDestroy(diag);
}

// Disassembler: unknown opcode without the flag -> error.
TEST_F(HandleUnknownOpcodesTest, DisassembleUnknownOpcodeWithoutFlagFails) {
  std::vector<uint32_t> binary = spvtest::Concatenate({
      MakeHeader(1),
      {MakeFirstWord(3, kUnknownOpcode), 42u, 99u},
  });
  spv_text text = nullptr;
  spv_diagnostic diag = nullptr;
  const spv_result_t result =
      spvBinaryToText(context_, binary.data(), binary.size(), 0u, &text, &diag);
  EXPECT_NE(SPV_SUCCESS, result);
  spvTextDestroy(text);
  spvDiagnosticDestroy(diag);
}

// Disassembler: unknown opcode with the flag -> emits OpUnknown.
TEST_F(HandleUnknownOpcodesTest,
       DisassembleUnknownOpcodeWithFlagEmitsOpUnknown) {
  // 3-word instruction: [opcode+wc, 42, 99]
  std::vector<uint32_t> binary = spvtest::Concatenate({
      MakeHeader(1),
      {MakeFirstWord(3, kUnknownOpcode), 42u, 99u},
  });
  spv_text text = nullptr;
  spv_diagnostic diag = nullptr;
  const spv_result_t result = spvBinaryToText(
      context_, binary.data(), binary.size(),
      SPV_BINARY_TO_TEXT_OPTION_HANDLE_UNKNOWN_OPCODES, &text, &diag);
  ASSERT_EQ(SPV_SUCCESS, result) << (diag ? diag->error : "(no diagnostic)");
  const std::string output(text->str, text->length);
  // kUnknownOpcode=0xFFFE=65534, word_count=3, operands=42 99
  EXPECT_THAT(output, HasSubstr("OpUnknown(65534, 3) 42 99"));
  EXPECT_THAT(output,
              HasSubstr("; note: ID bound may be incorrect after reassembly"));
  spvTextDestroy(text);
  spvDiagnosticDestroy(diag);
}

// Disassembler: known opcode with the flag -> normal output (flag is a no-op
// for known opcodes).
TEST_F(HandleUnknownOpcodesTest, DisassembleKnownOpcodeWithFlagIsNoOp) {
  // OpCapability Shader: opcode=17, word_count=2, operand=1 (Shader)
  std::vector<uint32_t> binary = spvtest::Concatenate({
      MakeHeader(1),
      {MakeFirstWord(2, 17u), 1u},
  });
  spv_text text = nullptr;
  spv_diagnostic diag = nullptr;
  const spv_result_t result = spvBinaryToText(
      context_, binary.data(), binary.size(),
      SPV_BINARY_TO_TEXT_OPTION_HANDLE_UNKNOWN_OPCODES, &text, &diag);
  ASSERT_EQ(SPV_SUCCESS, result) << (diag ? diag->error : "(no diagnostic)");
  const std::string output(text->str, text->length);
  EXPECT_THAT(output, HasSubstr("OpCapability Shader"));
  EXPECT_THAT(output, Not(HasSubstr("OpUnknown")));
  spvTextDestroy(text);
  spvDiagnosticDestroy(diag);
}

// Disassembler: OpExtInst with unknown instruction number in a semantic set,
// without the flag -> error.
TEST_F(HandleUnknownOpcodesTest, DisassembleUnknownExtInstWithoutFlagFails) {
  // OpExtInstImport %1 "GLSL.std.450" (6 words):
  //   opcode=11 word_count=6, result_id=1, "GLSL.std.450\0" (4 words)
  // OpExtInst %2 %3 %1 0xFFFF (5 words):
  //   opcode=12 word_count=5, result_type=2, result_id=3, set_id=1,
  //   inst_number=0xFFFF
  std::vector<uint32_t> binary = spvtest::Concatenate({
      MakeHeader(4),
      {0x0006000Bu, 1u, 0x4C534C47u, 0x6474732Eu, 0x3035342Eu, 0x00000000u},
      {MakeFirstWord(5, 12u), 2u, 3u, 1u, 0xFFFFu},
  });
  spv_text text = nullptr;
  spv_diagnostic diag = nullptr;
  const spv_result_t result =
      spvBinaryToText(context_, binary.data(), binary.size(), 0u, &text, &diag);
  EXPECT_NE(SPV_SUCCESS, result);
  spvTextDestroy(text);
  spvDiagnosticDestroy(diag);
}

// Disassembler: OpExtInst with unknown instruction number in a semantic set,
// with the flag -> emits OpUnknown with all instruction words.
TEST_F(HandleUnknownOpcodesTest,
       DisassembleUnknownExtInstWithFlagEmitsOpUnknown) {
  // See DisassembleUnknownExtInstWithoutFlagFails for binary layout.
  std::vector<uint32_t> binary = spvtest::Concatenate({
      MakeHeader(4),
      {0x0006000Bu, 1u, 0x4C534C47u, 0x6474732Eu, 0x3035342Eu, 0x00000000u},
      {MakeFirstWord(5, 12u), 2u, 3u, 1u, 0xFFFFu},
  });
  spv_text text = nullptr;
  spv_diagnostic diag = nullptr;
  const spv_result_t result = spvBinaryToText(
      context_, binary.data(), binary.size(),
      SPV_BINARY_TO_TEXT_OPTION_HANDLE_UNKNOWN_OPCODES, &text, &diag);
  ASSERT_EQ(SPV_SUCCESS, result) << (diag ? diag->error : "(no diagnostic)");
  const std::string output(text->str, text->length);
  // OpExtInst opcode=12, word_count=5; operands: result_type=2, result_id=3,
  // set_id=1, inst_number=0xFFFF=65535.
  EXPECT_THAT(output, HasSubstr("OpUnknown(12, 5) 2 3 1 65535"));
  spvTextDestroy(text);
  spvDiagnosticDestroy(diag);
}

// Disassembler: OpExtInst with an unknown instruction number in a non-semantic
// extended instruction set disassembles normally (not as OpUnknown) both with
// and without the flag. Non-semantic sets handle unknown instruction numbers
// gracefully regardless of the flag; setting the flag must not change that.
TEST_F(HandleUnknownOpcodesTest,
       NonSemanticExtInstUnknownNumberNotEmittedAsOpUnknown) {
  // OpExtInstImport %1 "NonSemantic.DebugPrintf" (8 words):
  //   opcode=11, word_count=8, result_id=1
  //   "NonSemantic.DebugPrintf\0" packed into 6 32-bit words:
  //     "NonS" = 0x536E6F4E, "eman" = 0x6E616D65, "tic." = 0x2E636974,
  //     "Debu" = 0x75626544, "gPri" = 0x69725067, "ntf\0" = 0x0066746E
  // OpExtInst %2 %3 %1 0xFFFF (5 words):
  //   opcode=12, word_count=5, result_type=2, result_id=3, set_id=1,
  //   inst_number=0xFFFF (not present in the NonSemantic.DebugPrintf grammar)
  const std::vector<uint32_t> binary = spvtest::Concatenate({
      MakeHeader(4),
      {0x0008000Bu, 1u, 0x536E6F4Eu, 0x6E616D65u, 0x2E636974u, 0x75626544u,
       0x69725067u, 0x0066746Eu},
      {MakeFirstWord(5, 12u), 2u, 3u, 1u, 0xFFFFu},
  });

  // Without the flag: non-semantic sets already handle unknown instruction
  // numbers gracefully; parsing and disassembly succeed.
  {
    spv_text text = nullptr;
    spv_diagnostic diag = nullptr;
    EXPECT_EQ(SPV_SUCCESS, spvBinaryToText(context_, binary.data(),
                                           binary.size(), 0u, &text, &diag))
        << (diag ? diag->error : "(no diagnostic)");
    spvTextDestroy(text);
    spvDiagnosticDestroy(diag);
  }

  // With the flag: the non-semantic graceful path is unchanged.  The
  // instruction must not be emitted as OpUnknown.
  {
    spv_text text = nullptr;
    spv_diagnostic diag = nullptr;
    ASSERT_EQ(SPV_SUCCESS,
              spvBinaryToText(context_, binary.data(), binary.size(),
                              SPV_BINARY_TO_TEXT_OPTION_HANDLE_UNKNOWN_OPCODES,
                              &text, &diag))
        << (diag ? diag->error : "(no diagnostic)");
    const std::string output(text->str, text->length);
    EXPECT_THAT(output, Not(HasSubstr("OpUnknown")));
    spvTextDestroy(text);
    spvDiagnosticDestroy(diag);
  }
}

// FriendlyNameMapper: when a binary contains an unknown opcode, IDs defined
// after the unknown opcode get friendly names only when the flag is set.
TEST_F(HandleUnknownOpcodesTest, FriendlyNameMapperContinuesPastUnknownOpcode) {
  // OpTypeVoid %1    (opcode=19, word_count=2)
  // Unknown opcode 0xFFFE  (word_count=1)
  // OpTypeBool %2    (opcode=20, word_count=2)
  std::vector<uint32_t> binary = spvtest::Concatenate({
      MakeHeader(3),
      {MakeFirstWord(2, 19u), 1u},
      {MakeFirstWord(1, kUnknownOpcode)},
      {MakeFirstWord(2, 20u), 2u},
  });

  // With the flag, parsing continues past the unknown opcode so %2 is named.
  FriendlyNameMapper mapper_with_flag(
      context_, binary.data(), binary.size(),
      SPV_BINARY_TO_TEXT_OPTION_HANDLE_UNKNOWN_OPCODES);
  EXPECT_EQ("void", mapper_with_flag.NameForId(1));
  EXPECT_EQ("bool", mapper_with_flag.NameForId(2));

  // Without the flag, parsing stops at the unknown opcode so %2 falls back to
  // its trivial numeric name.
  FriendlyNameMapper mapper_without_flag(context_, binary.data(),
                                         binary.size());
  EXPECT_EQ("void", mapper_without_flag.NameForId(1));
  EXPECT_EQ("2", mapper_without_flag.NameForId(2));
}

// Binary parser: unknown opcode that claims more words than remain in the
// binary -> error, even with the flag set.
TEST_F(HandleUnknownOpcodesTest, ParseTruncatedUnknownOpcodeFails) {
  // Instruction claims 3 words but only 1 is present.
  std::vector<uint32_t> binary = spvtest::Concatenate({
      MakeHeader(1),
      {MakeFirstWord(3, kUnknownOpcode)},
  });
  spv_diagnostic diag = nullptr;
  const spv_result_t result = spvBinaryParseWithOptions(
      context_, nullptr, binary.data(), binary.size(), nullptr, nullptr, &diag,
      SPV_BINARY_TO_TEXT_OPTION_HANDLE_UNKNOWN_OPCODES);
  EXPECT_NE(SPV_SUCCESS, result);
  spvDiagnosticDestroy(diag);
}

// Disassembler: known opcode with an unknown flat enum operand, without the
// flag -> error.
TEST_F(HandleUnknownOpcodesTest, DisassembleUnknownFlatEnumWithoutFlagFails) {
  // OpCapability 0xFFFF: opcode=17, word_count=2, capability=0xFFFF (unknown).
  std::vector<uint32_t> binary = spvtest::Concatenate({
      MakeHeader(1),
      {MakeFirstWord(2, 17u), 0xFFFFu},
  });
  spv_text text = nullptr;
  spv_diagnostic diag = nullptr;
  const spv_result_t result =
      spvBinaryToText(context_, binary.data(), binary.size(), 0u, &text, &diag);
  EXPECT_NE(SPV_SUCCESS, result);
  spvTextDestroy(text);
  spvDiagnosticDestroy(diag);
}

// Disassembler: known opcode with an unknown flat enum operand, with the flag
// -> emits the whole instruction as OpUnknown.
TEST_F(HandleUnknownOpcodesTest,
       DisassembleUnknownFlatEnumWithFlagEmitsOpUnknown) {
  // OpCapability 0xFFFF: opcode=17, word_count=2, capability=0xFFFF (unknown).
  std::vector<uint32_t> binary = spvtest::Concatenate({
      MakeHeader(1),
      {MakeFirstWord(2, 17u), 0xFFFFu},
  });
  spv_text text = nullptr;
  spv_diagnostic diag = nullptr;
  const spv_result_t result = spvBinaryToText(
      context_, binary.data(), binary.size(),
      SPV_BINARY_TO_TEXT_OPTION_HANDLE_UNKNOWN_OPCODES, &text, &diag);
  ASSERT_EQ(SPV_SUCCESS, result) << (diag ? diag->error : "(no diagnostic)");
  const std::string output(text->str, text->length);
  // OpCapability opcode=17, word_count=2; operand=0xFFFF=65535.
  EXPECT_THAT(output, HasSubstr("OpUnknown(17, 2) 65535"));
  spvTextDestroy(text);
  spvDiagnosticDestroy(diag);
}

// Disassembler: known opcode with an unknown mask enum operand, without the
// flag -> error.
TEST_F(HandleUnknownOpcodesTest, DisassembleUnknownMaskEnumWithoutFlagFails) {
  // OpFunction %1 %2 FunctionControl(0x80000000) %3:
  //   opcode=54, word_count=5, result_type=1, result_id=2,
  //   function_control=0x80000000 (unknown bit), function_type=3.
  std::vector<uint32_t> binary = spvtest::Concatenate({
      MakeHeader(4),
      {MakeFirstWord(5, 54u), 1u, 2u, 0x80000000u, 3u},
  });
  spv_text text = nullptr;
  spv_diagnostic diag = nullptr;
  const spv_result_t result =
      spvBinaryToText(context_, binary.data(), binary.size(), 0u, &text, &diag);
  EXPECT_NE(SPV_SUCCESS, result);
  spvTextDestroy(text);
  spvDiagnosticDestroy(diag);
}

// Disassembler: known opcode with an unknown mask enum operand, with the flag
// -> emits the whole instruction as OpUnknown.
TEST_F(HandleUnknownOpcodesTest,
       DisassembleUnknownMaskEnumWithFlagEmitsOpUnknown) {
  // OpFunction %1 %2 FunctionControl(0x80000000) %3:
  //   opcode=54, word_count=5, result_type=1, result_id=2,
  //   function_control=0x80000000 (unknown bit), function_type=3.
  std::vector<uint32_t> binary = spvtest::Concatenate({
      MakeHeader(4),
      {MakeFirstWord(5, 54u), 1u, 2u, 0x80000000u, 3u},
  });
  spv_text text = nullptr;
  spv_diagnostic diag = nullptr;
  const spv_result_t result = spvBinaryToText(
      context_, binary.data(), binary.size(),
      SPV_BINARY_TO_TEXT_OPTION_HANDLE_UNKNOWN_OPCODES, &text, &diag);
  ASSERT_EQ(SPV_SUCCESS, result) << (diag ? diag->error : "(no diagnostic)");
  const std::string output(text->str, text->length);
  // OpFunction opcode=54, word_count=5; operands: 1, 2, 2147483648, 3.
  EXPECT_THAT(output, HasSubstr("OpUnknown(54, 5) 1 2 2147483648 3"));
  spvTextDestroy(text);
  spvDiagnosticDestroy(diag);
}

// Round-trip: disassemble with the flag, reassemble, and verify the
// instruction words are preserved byte-for-byte.
TEST_F(HandleUnknownOpcodesTest, RoundTripUnknownOpcode) {
  // 3-word instruction: [opcode+wc, 42, 99]
  const std::vector<uint32_t> inst_words = {MakeFirstWord(3, kUnknownOpcode),
                                            42u, 99u};
  std::vector<uint32_t> original =
      spvtest::Concatenate({MakeHeader(1), inst_words});

  // Disassemble to text with the flag.
  spv_text text = nullptr;
  spv_diagnostic dis_diag = nullptr;
  ASSERT_EQ(SPV_SUCCESS,
            spvBinaryToText(context_, original.data(), original.size(),
                            SPV_BINARY_TO_TEXT_OPTION_HANDLE_UNKNOWN_OPCODES,
                            &text, &dis_diag))
      << (dis_diag ? dis_diag->error : "(no diagnostic)");

  // Reassemble the text back to binary.
  spv_binary reassembled = nullptr;
  spv_diagnostic asm_diag = nullptr;
  ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context_, text->str, text->length,
                                         &reassembled, &asm_diag))
      << (asm_diag ? asm_diag->error : "(no diagnostic)");

  // The instruction words start after the 5-word SPIR-V module header and
  // must be byte-for-byte identical to the original instruction.
  ASSERT_GE(reassembled->wordCount, 5u + inst_words.size());
  for (size_t i = 0; i < inst_words.size(); i++) {
    EXPECT_EQ(inst_words[i], reassembled->code[5 + i])
        << "Word mismatch at instruction word " << i;
  }

  spvTextDestroy(text);
  spvBinaryDestroy(reassembled);
  spvDiagnosticDestroy(dis_diag);
  spvDiagnosticDestroy(asm_diag);
}

// FriendlyNameMapper: when a known instruction has an unknown enum operand and
// the flag is set, the instruction is retried as unknown.  inst.result_id is
// not decoded (stays 0) so the real result ID falls back to its trivial numeric
// name.  IDs defined by preceding and following instructions are still named.
TEST_F(HandleUnknownOpcodesTest,
       FriendlyNameMapperUnknownEnumOperandDropsResultName) {
  // OpTypeVoid %1        (opcode=19, word_count=2)
  // OpCapability 0xFFFF  (opcode=17, word_count=2; unknown Capability value)
  // OpTypeBool %2        (opcode=20, word_count=2)
  std::vector<uint32_t> binary = spvtest::Concatenate({
      MakeHeader(3),
      {MakeFirstWord(2, 19u), 1u},
      {MakeFirstWord(2, 17u), 0xFFFFu},
      {MakeFirstWord(2, 20u), 2u},
  });

  // With the flag, parsing continues past the unknown-enum instruction.
  // %1 and %2 are both named; the OpCapability result (none for that opcode)
  // does not affect naming.
  FriendlyNameMapper mapper_with_flag(
      context_, binary.data(), binary.size(),
      SPV_BINARY_TO_TEXT_OPTION_HANDLE_UNKNOWN_OPCODES);
  EXPECT_EQ("void", mapper_with_flag.NameForId(1));
  EXPECT_EQ("bool", mapper_with_flag.NameForId(2));

  // Without the flag, parsing stops at OpCapability 0xFFFF so %2 is unnamed.
  FriendlyNameMapper mapper_without_flag(context_, binary.data(),
                                         binary.size());
  EXPECT_EQ("void", mapper_without_flag.NameForId(1));
  EXPECT_EQ("2", mapper_without_flag.NameForId(2));
}

// Disassembler: a binary with a valid instruction before and after an unknown
// opcode disassembles all three instructions correctly.
TEST_F(HandleUnknownOpcodesTest, DisassembleContinuesPastUnknownOpcode) {
  // OpTypeVoid %1 (opcode=19, word_count=2)
  // Unknown opcode 0xFFFE (word_count=1)
  // OpTypeBool %2 (opcode=20, word_count=2)
  std::vector<uint32_t> binary = spvtest::Concatenate({
      MakeHeader(3),
      {MakeFirstWord(2, 19u), 1u},
      {MakeFirstWord(1, kUnknownOpcode)},
      {MakeFirstWord(2, 20u), 2u},
  });
  spv_text text = nullptr;
  spv_diagnostic diag = nullptr;
  ASSERT_EQ(SPV_SUCCESS,
            spvBinaryToText(context_, binary.data(), binary.size(),
                            SPV_BINARY_TO_TEXT_OPTION_HANDLE_UNKNOWN_OPCODES,
                            &text, &diag))
      << (diag ? diag->error : "(no diagnostic)");
  const std::string output(text->str, text->length);
  EXPECT_THAT(output, HasSubstr("OpTypeVoid"));
  EXPECT_THAT(output, HasSubstr("OpUnknown(65534, 1)"));
  EXPECT_THAT(output, HasSubstr("OpTypeBool"));
  spvTextDestroy(text);
  spvDiagnosticDestroy(diag);
}

// Disassembler: FRIENDLY_NAMES and HANDLE_UNKNOWN_OPCODES work together.
// Known instructions use friendly names; the unknown opcode emits OpUnknown.
TEST_F(HandleUnknownOpcodesTest, DisassembleFriendlyNamesWithUnknownOpcode) {
  // OpTypeVoid %1 (opcode=19, word_count=2)
  // Unknown opcode 0xFFFE (word_count=1)
  // OpTypeBool %2 (opcode=20, word_count=2)
  std::vector<uint32_t> binary = spvtest::Concatenate({
      MakeHeader(3),
      {MakeFirstWord(2, 19u), 1u},
      {MakeFirstWord(1, kUnknownOpcode)},
      {MakeFirstWord(2, 20u), 2u},
  });
  spv_text text = nullptr;
  spv_diagnostic diag = nullptr;
  ASSERT_EQ(SPV_SUCCESS,
            spvBinaryToText(context_, binary.data(), binary.size(),
                            SPV_BINARY_TO_TEXT_OPTION_HANDLE_UNKNOWN_OPCODES |
                                SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES,
                            &text, &diag))
      << (diag ? diag->error : "(no diagnostic)");
  const std::string output(text->str, text->length);
  EXPECT_THAT(output, HasSubstr("%void"));
  EXPECT_THAT(output, HasSubstr("%bool"));
  EXPECT_THAT(output, HasSubstr("OpUnknown(65534, 1)"));
  spvTextDestroy(text);
  spvDiagnosticDestroy(diag);
}

// spvInstructionBinaryToText: unknown opcode with the flag emits OpUnknown.
TEST_F(HandleUnknownOpcodesTest, InstructionBinaryToTextUnknownOpcode) {
  const std::vector<uint32_t> inst_words = {MakeFirstWord(3, kUnknownOpcode),
                                            42u, 99u};
  std::vector<uint32_t> binary =
      spvtest::Concatenate({MakeHeader(1), inst_words});
  const std::string output = spvInstructionBinaryToText(
      SPV_ENV_UNIVERSAL_1_0, inst_words.data(), inst_words.size(),
      binary.data(), binary.size(),
      SPV_BINARY_TO_TEXT_OPTION_HANDLE_UNKNOWN_OPCODES);
  EXPECT_THAT(output, HasSubstr("OpUnknown(65534, 3) 42 99"));
}

}  // namespace
}  // namespace spvtools
