| // 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 |