1// Copyright (c) 2016 Google Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "gtest/gtest.h"
16#include "source/table.h"
17#include "spirv-tools/libspirv.h"
18
19namespace spvtools {
20namespace {
21
22// TODO(antiagainst): Use public C API for setting the consumer once exists.
23#ifndef SPIRV_TOOLS_SHAREDLIB
24void SetContextMessageConsumer(spv_context context, MessageConsumer consumer) {
25  spvtools::SetContextMessageConsumer(context, consumer);
26}
27#else
28void SetContextMessageConsumer(spv_context, MessageConsumer) {}
29#endif
30
31// The default consumer is a null std::function.
32TEST(CInterface, DefaultConsumerNullDiagnosticForValidInput) {
33  auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1);
34  const char input_text[] =
35      "OpCapability Shader\n"
36      "OpCapability Linkage\n"
37      "OpMemoryModel Logical GLSL450";
38
39  spv_binary binary = nullptr;
40  EXPECT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text,
41                                         sizeof(input_text), &binary, nullptr));
42
43  {
44    // Sadly the compiler don't allow me to feed binary directly to
45    // spvValidate().
46    spv_const_binary_t b{binary->code, binary->wordCount};
47    EXPECT_EQ(SPV_SUCCESS, spvValidate(context, &b, nullptr));
48  }
49
50  spv_text text = nullptr;
51  EXPECT_EQ(SPV_SUCCESS, spvBinaryToText(context, binary->code,
52                                         binary->wordCount, 0, &text, nullptr));
53
54  spvTextDestroy(text);
55  spvBinaryDestroy(binary);
56  spvContextDestroy(context);
57}
58
59// The default consumer is a null std::function.
60TEST(CInterface, DefaultConsumerNullDiagnosticForInvalidAssembling) {
61  auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1);
62  const char input_text[] = "%1 = OpName";
63
64  spv_binary binary = nullptr;
65  EXPECT_EQ(SPV_ERROR_INVALID_TEXT,
66            spvTextToBinary(context, input_text, sizeof(input_text), &binary,
67                            nullptr));
68  spvBinaryDestroy(binary);
69  spvContextDestroy(context);
70}
71
72// The default consumer is a null std::function.
73TEST(CInterface, DefaultConsumerNullDiagnosticForInvalidDiassembling) {
74  auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1);
75  const char input_text[] = "OpNop";
76
77  spv_binary binary = nullptr;
78  ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text,
79                                         sizeof(input_text), &binary, nullptr));
80  // Change OpNop to an invalid (wordcount|opcode) word.
81  binary->code[binary->wordCount - 1] = 0xffffffff;
82
83  spv_text text = nullptr;
84  EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
85            spvBinaryToText(context, binary->code, binary->wordCount, 0, &text,
86                            nullptr));
87
88  spvTextDestroy(text);
89  spvBinaryDestroy(binary);
90  spvContextDestroy(context);
91}
92
93// The default consumer is a null std::function.
94TEST(CInterface, DefaultConsumerNullDiagnosticForInvalidValidating) {
95  auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1);
96  const char input_text[] = "OpNop";
97
98  spv_binary binary = nullptr;
99  ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text,
100                                         sizeof(input_text), &binary, nullptr));
101
102  spv_const_binary_t b{binary->code, binary->wordCount};
103  EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, spvValidate(context, &b, nullptr));
104
105  spvBinaryDestroy(binary);
106  spvContextDestroy(context);
107}
108
109TEST(CInterface, SpecifyConsumerNullDiagnosticForAssembling) {
110  const char input_text[] = "     OpName\n";
111
112  auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1);
113  int invocation = 0;
114  SetContextMessageConsumer(
115      context,
116      [&invocation](spv_message_level_t level, const char* source,
117                    const spv_position_t& position, const char* message) {
118        ++invocation;
119        EXPECT_EQ(SPV_MSG_ERROR, level);
120        // The error happens at scanning the beginning of second line.
121        EXPECT_STREQ("input", source);
122        EXPECT_EQ(1u, position.line);
123        EXPECT_EQ(0u, position.column);
124        EXPECT_EQ(12u, position.index);
125        EXPECT_STREQ(
126            "Expected operand for OpName instruction, but found the end of the "
127            "stream.",
128            message);
129      });
130
131  spv_binary binary = nullptr;
132  EXPECT_EQ(SPV_ERROR_INVALID_TEXT,
133            spvTextToBinary(context, input_text, sizeof(input_text), &binary,
134                            nullptr));
135#ifndef SPIRV_TOOLS_SHAREDLIB
136  EXPECT_EQ(1, invocation);
137#endif
138  spvBinaryDestroy(binary);
139  spvContextDestroy(context);
140}
141
142TEST(CInterface, SpecifyConsumerNullDiagnosticForDisassembling) {
143  const char input_text[] = "OpNop";
144
145  auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1);
146  int invocation = 0;
147  SetContextMessageConsumer(
148      context,
149      [&invocation](spv_message_level_t level, const char* source,
150                    const spv_position_t& position, const char* message) {
151        ++invocation;
152        EXPECT_EQ(SPV_MSG_ERROR, level);
153        EXPECT_STREQ("input", source);
154        EXPECT_EQ(0u, position.line);
155        EXPECT_EQ(0u, position.column);
156        EXPECT_EQ(1u, position.index);
157        EXPECT_STREQ("Invalid opcode: 65535", message);
158      });
159
160  spv_binary binary = nullptr;
161  ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text,
162                                         sizeof(input_text), &binary, nullptr));
163  // Change OpNop to an invalid (wordcount|opcode) word.
164  binary->code[binary->wordCount - 1] = 0xffffffff;
165
166  spv_text text = nullptr;
167  EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
168            spvBinaryToText(context, binary->code, binary->wordCount, 0, &text,
169                            nullptr));
170#ifndef SPIRV_TOOLS_SHAREDLIB
171  EXPECT_EQ(1, invocation);
172#endif
173
174  spvTextDestroy(text);
175  spvBinaryDestroy(binary);
176  spvContextDestroy(context);
177}
178
179TEST(CInterface, SpecifyConsumerNullDiagnosticForValidating) {
180  const char input_text[] = "OpNop";
181
182  auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1);
183  int invocation = 0;
184  SetContextMessageConsumer(
185      context,
186      [&invocation](spv_message_level_t level, const char* source,
187                    const spv_position_t& position, const char* message) {
188        ++invocation;
189        EXPECT_EQ(SPV_MSG_ERROR, level);
190        EXPECT_STREQ("input", source);
191        EXPECT_EQ(0u, position.line);
192        EXPECT_EQ(0u, position.column);
193        // TODO(antiagainst): what validation reports is not a word offset here.
194        // It is inconsistent with diassembler. Should be fixed.
195        EXPECT_EQ(1u, position.index);
196        EXPECT_STREQ(
197            "Nop cannot appear before the memory model instruction\n"
198            "  OpNop\n",
199            message);
200      });
201
202  spv_binary binary = nullptr;
203  ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text,
204                                         sizeof(input_text), &binary, nullptr));
205
206  spv_const_binary_t b{binary->code, binary->wordCount};
207  EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, spvValidate(context, &b, nullptr));
208#ifndef SPIRV_TOOLS_SHAREDLIB
209  EXPECT_EQ(1, invocation);
210#endif
211
212  spvBinaryDestroy(binary);
213  spvContextDestroy(context);
214}
215
216// When having both a consumer and an diagnostic object, the diagnostic object
217// should take priority.
218TEST(CInterface, SpecifyConsumerSpecifyDiagnosticForAssembling) {
219  const char input_text[] = "     OpName";
220
221  auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1);
222  int invocation = 0;
223  SetContextMessageConsumer(
224      context,
225      [&invocation](spv_message_level_t, const char*, const spv_position_t&,
226                    const char*) { ++invocation; });
227
228  spv_binary binary = nullptr;
229  spv_diagnostic diagnostic = nullptr;
230  EXPECT_EQ(SPV_ERROR_INVALID_TEXT,
231            spvTextToBinary(context, input_text, sizeof(input_text), &binary,
232                            &diagnostic));
233  EXPECT_EQ(0, invocation);  // Consumer should not be invoked at all.
234  EXPECT_STREQ(
235      "Expected operand for OpName instruction, but found the end of the "
236      "stream.",
237      diagnostic->error);
238
239  spvDiagnosticDestroy(diagnostic);
240  spvBinaryDestroy(binary);
241  spvContextDestroy(context);
242}
243
244TEST(CInterface, SpecifyConsumerSpecifyDiagnosticForDisassembling) {
245  const char input_text[] = "OpNop";
246
247  auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1);
248  int invocation = 0;
249  SetContextMessageConsumer(
250      context,
251      [&invocation](spv_message_level_t, const char*, const spv_position_t&,
252                    const char*) { ++invocation; });
253
254  spv_binary binary = nullptr;
255  ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text,
256                                         sizeof(input_text), &binary, nullptr));
257  // Change OpNop to an invalid (wordcount|opcode) word.
258  binary->code[binary->wordCount - 1] = 0xffffffff;
259
260  spv_diagnostic diagnostic = nullptr;
261  spv_text text = nullptr;
262  EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
263            spvBinaryToText(context, binary->code, binary->wordCount, 0, &text,
264                            &diagnostic));
265
266  EXPECT_EQ(0, invocation);  // Consumer should not be invoked at all.
267  EXPECT_STREQ("Invalid opcode: 65535", diagnostic->error);
268
269  spvTextDestroy(text);
270  spvDiagnosticDestroy(diagnostic);
271  spvBinaryDestroy(binary);
272  spvContextDestroy(context);
273}
274
275TEST(CInterface, SpecifyConsumerSpecifyDiagnosticForValidating) {
276  const char input_text[] = "OpNop";
277
278  auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1);
279  int invocation = 0;
280  SetContextMessageConsumer(
281      context,
282      [&invocation](spv_message_level_t, const char*, const spv_position_t&,
283                    const char*) { ++invocation; });
284
285  spv_binary binary = nullptr;
286  ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text,
287                                         sizeof(input_text), &binary, nullptr));
288
289  spv_diagnostic diagnostic = nullptr;
290  spv_const_binary_t b{binary->code, binary->wordCount};
291  EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, spvValidate(context, &b, &diagnostic));
292
293  EXPECT_EQ(0, invocation);  // Consumer should not be invoked at all.
294  EXPECT_STREQ(
295      "Nop cannot appear before the memory model instruction\n"
296      "  OpNop\n",
297      diagnostic->error);
298
299  spvDiagnosticDestroy(diagnostic);
300  spvBinaryDestroy(binary);
301  spvContextDestroy(context);
302}
303
304}  // namespace
305}  // namespace spvtools
306