// Copyright (c) 2016 Google 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 "gtest/gtest.h" #include "source/message.h" #include "source/table.h" #include "spirv-tools/libspirv.h" namespace spvtools { namespace { // TODO(antiagainst): Use public C API for setting the consumer once exists. #ifndef SPIRV_TOOLS_SHAREDLIB void SetContextMessageConsumer(spv_context context, MessageConsumer consumer) { spvtools::SetContextMessageConsumer(context, consumer); } #else void SetContextMessageConsumer(spv_context, MessageConsumer) {} #endif // The default consumer is a null std::function. TEST(CInterface, DefaultConsumerNullDiagnosticForValidInput) { auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); const char input_text[] = "OpCapability Shader\n" "OpCapability Linkage\n" "OpMemoryModel Logical GLSL450"; spv_binary binary = nullptr; EXPECT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text, sizeof(input_text), &binary, nullptr)); { // Sadly the compiler don't allow me to feed binary directly to // spvValidate(). spv_const_binary_t b{binary->code, binary->wordCount}; EXPECT_EQ(SPV_SUCCESS, spvValidate(context, &b, nullptr)); } spv_text text = nullptr; EXPECT_EQ(SPV_SUCCESS, spvBinaryToText(context, binary->code, binary->wordCount, 0, &text, nullptr)); spvTextDestroy(text); spvBinaryDestroy(binary); spvContextDestroy(context); } // The default consumer is a null std::function. TEST(CInterface, DefaultConsumerNullDiagnosticForInvalidAssembling) { auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); const char input_text[] = "%1 = OpName"; spv_binary binary = nullptr; EXPECT_EQ(SPV_ERROR_INVALID_TEXT, spvTextToBinary(context, input_text, sizeof(input_text), &binary, nullptr)); spvBinaryDestroy(binary); spvContextDestroy(context); } // The default consumer is a null std::function. TEST(CInterface, DefaultConsumerNullDiagnosticForInvalidDiassembling) { auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); const char input_text[] = "OpNop"; spv_binary binary = nullptr; ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text, sizeof(input_text), &binary, nullptr)); // Change OpNop to an invalid (wordcount|opcode) word. binary->code[binary->wordCount - 1] = 0xffffffff; spv_text text = nullptr; EXPECT_EQ(SPV_ERROR_INVALID_BINARY, spvBinaryToText(context, binary->code, binary->wordCount, 0, &text, nullptr)); spvTextDestroy(text); spvBinaryDestroy(binary); spvContextDestroy(context); } // The default consumer is a null std::function. TEST(CInterface, DefaultConsumerNullDiagnosticForInvalidValidating) { auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); const char input_text[] = "OpNop"; spv_binary binary = nullptr; ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text, sizeof(input_text), &binary, nullptr)); spv_const_binary_t b{binary->code, binary->wordCount}; EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, spvValidate(context, &b, nullptr)); spvBinaryDestroy(binary); spvContextDestroy(context); } TEST(CInterface, SpecifyConsumerNullDiagnosticForAssembling) { const char input_text[] = "%1 = OpName\n"; auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); int invocation = 0; SetContextMessageConsumer( context, [&invocation](spv_message_level_t level, const char* source, const spv_position_t& position, const char* message) { ++invocation; EXPECT_EQ(SPV_MSG_ERROR, level); // The error happens at scanning the begining of second line. EXPECT_STREQ("input", source); EXPECT_EQ(1u, position.line); EXPECT_EQ(0u, position.column); EXPECT_EQ(12u, position.index); EXPECT_STREQ("Expected operand, found end of stream.", message); }); spv_binary binary = nullptr; EXPECT_EQ(SPV_ERROR_INVALID_TEXT, spvTextToBinary(context, input_text, sizeof(input_text), &binary, nullptr)); #ifndef SPIRV_TOOLS_SHAREDLIB EXPECT_EQ(1, invocation); #endif spvBinaryDestroy(binary); spvContextDestroy(context); } TEST(CInterface, SpecifyConsumerNullDiagnosticForDisassembling) { const char input_text[] = "OpNop"; auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); int invocation = 0; SetContextMessageConsumer( context, [&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(5u, position.index); EXPECT_STREQ("Invalid opcode: 65535", message); }); spv_binary binary = nullptr; ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text, sizeof(input_text), &binary, nullptr)); // Change OpNop to an invalid (wordcount|opcode) word. binary->code[binary->wordCount - 1] = 0xffffffff; spv_text text = nullptr; EXPECT_EQ(SPV_ERROR_INVALID_BINARY, spvBinaryToText(context, binary->code, binary->wordCount, 0, &text, nullptr)); #ifndef SPIRV_TOOLS_SHAREDLIB EXPECT_EQ(1, invocation); #endif spvTextDestroy(text); spvBinaryDestroy(binary); spvContextDestroy(context); } TEST(CInterface, SpecifyConsumerNullDiagnosticForValidating) { const char input_text[] = "OpNop"; auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); int invocation = 0; SetContextMessageConsumer( context, [&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); // TODO(antiagainst): what validation reports is not a word offset here. // It is inconsistent with diassembler. Should be fixed. EXPECT_EQ(1u, position.index); EXPECT_STREQ( "Nop cannot appear before the memory model instruction\n" " OpNop\n", message); }); spv_binary binary = nullptr; ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text, sizeof(input_text), &binary, nullptr)); spv_const_binary_t b{binary->code, binary->wordCount}; EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, spvValidate(context, &b, nullptr)); #ifndef SPIRV_TOOLS_SHAREDLIB EXPECT_EQ(1, invocation); #endif spvBinaryDestroy(binary); spvContextDestroy(context); } // When having both a consumer and an diagnostic object, the diagnostic object // should take priority. TEST(CInterface, SpecifyConsumerSpecifyDiagnosticForAssembling) { const char input_text[] = "%1 = OpName"; auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); int invocation = 0; SetContextMessageConsumer( context, [&invocation](spv_message_level_t, const char*, const spv_position_t&, const char*) { ++invocation; }); spv_binary binary = nullptr; spv_diagnostic diagnostic = nullptr; EXPECT_EQ(SPV_ERROR_INVALID_TEXT, spvTextToBinary(context, input_text, sizeof(input_text), &binary, &diagnostic)); EXPECT_EQ(0, invocation); // Consumer should not be invoked at all. EXPECT_STREQ("Expected operand, found end of stream.", diagnostic->error); spvDiagnosticDestroy(diagnostic); spvBinaryDestroy(binary); spvContextDestroy(context); } TEST(CInterface, SpecifyConsumerSpecifyDiagnosticForDisassembling) { const char input_text[] = "OpNop"; auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); int invocation = 0; SetContextMessageConsumer( context, [&invocation](spv_message_level_t, const char*, const spv_position_t&, const char*) { ++invocation; }); spv_binary binary = nullptr; ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text, sizeof(input_text), &binary, nullptr)); // Change OpNop to an invalid (wordcount|opcode) word. binary->code[binary->wordCount - 1] = 0xffffffff; spv_diagnostic diagnostic = nullptr; spv_text text = nullptr; EXPECT_EQ(SPV_ERROR_INVALID_BINARY, spvBinaryToText(context, binary->code, binary->wordCount, 0, &text, &diagnostic)); EXPECT_EQ(0, invocation); // Consumer should not be invoked at all. EXPECT_STREQ("Invalid opcode: 65535", diagnostic->error); spvTextDestroy(text); spvDiagnosticDestroy(diagnostic); spvBinaryDestroy(binary); spvContextDestroy(context); } TEST(CInterface, SpecifyConsumerSpecifyDiagnosticForValidating) { const char input_text[] = "OpNop"; auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); int invocation = 0; SetContextMessageConsumer( context, [&invocation](spv_message_level_t, const char*, const spv_position_t&, const char*) { ++invocation; }); spv_binary binary = nullptr; ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text, sizeof(input_text), &binary, nullptr)); spv_diagnostic diagnostic = nullptr; spv_const_binary_t b{binary->code, binary->wordCount}; EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, spvValidate(context, &b, &diagnostic)); EXPECT_EQ(0, invocation); // Consumer should not be invoked at all. EXPECT_STREQ( "Nop cannot appear before the memory model instruction\n" " OpNop\n", diagnostic->error); spvDiagnosticDestroy(diagnostic); spvBinaryDestroy(binary); spvContextDestroy(context); } } // namespace } // namespace spvtools