1 /*
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <thrift/compiler/sema/standard_validator.h>
18
19 #include <cmath>
20 #include <limits>
21 #include <memory>
22
23 #include <folly/portability/GMock.h>
24 #include <folly/portability/GTest.h>
25 #include <thrift/compiler/ast/diagnostic.h>
26 #include <thrift/compiler/ast/diagnostic_context.h>
27 #include <thrift/compiler/ast/t_base_type.h>
28 #include <thrift/compiler/ast/t_enum.h>
29 #include <thrift/compiler/ast/t_enum_value.h>
30 #include <thrift/compiler/ast/t_exception.h>
31 #include <thrift/compiler/ast/t_field.h>
32 #include <thrift/compiler/ast/t_function.h>
33 #include <thrift/compiler/ast/t_interaction.h>
34 #include <thrift/compiler/ast/t_paramlist.h>
35 #include <thrift/compiler/ast/t_program.h>
36 #include <thrift/compiler/ast/t_service.h>
37 #include <thrift/compiler/ast/t_struct.h>
38 #include <thrift/compiler/ast/t_typedef.h>
39
40 namespace apache::thrift::compiler {
41 namespace {
42
43 using ::testing::UnorderedElementsAre;
44
45 class StandardValidatorTest : public ::testing::Test {
46 protected:
validate(diagnostic_params params=diagnostic_params::keep_all ())47 std::vector<diagnostic> validate(
48 diagnostic_params params = diagnostic_params::keep_all()) {
49 diagnostic_results results;
50 diagnostic_context ctx{results, std::move(params)};
51 ctx.start_program(&program_);
52 standard_validator()(ctx, program_);
53 return std::move(results).diagnostics();
54 }
55
inst(const t_struct * ttype,int lineno)56 std::unique_ptr<t_const> inst(const t_struct* ttype, int lineno) {
57 auto value = std::make_unique<t_const_value>();
58 value->set_map();
59 value->set_ttype(t_type_ref::from_ptr(ttype));
60 auto result =
61 std::make_unique<t_const>(&program_, ttype, "", std::move(value));
62 result->set_lineno(lineno);
63 return result;
64 }
65
failure(int lineno,const std::string & msg)66 diagnostic failure(int lineno, const std::string& msg) {
67 return {diagnostic_level::failure, msg, "/path/to/file.thrift", lineno};
68 }
69
warning(int lineno,const std::string & msg)70 diagnostic warning(int lineno, const std::string& msg) {
71 return {diagnostic_level::warning, msg, "/path/to/file.thrift", lineno};
72 }
73
74 t_program program_{"/path/to/file.thrift"};
75 };
76
TEST_F(StandardValidatorTest,InterfaceNamesUniqueNoError)77 TEST_F(StandardValidatorTest, InterfaceNamesUniqueNoError) {
78 // Create interfaces with non-overlapping functions.
79 {
80 auto service = std::make_unique<t_service>(&program_, "Service");
81 service->add_function(std::make_unique<t_function>(
82 &t_base_type::t_void(),
83 "bar",
84 std::make_unique<t_paramlist>(&program_)));
85 service->add_function(std::make_unique<t_function>(
86 &t_base_type::t_void(),
87 "baz",
88 std::make_unique<t_paramlist>(&program_)));
89 program_.add_service(std::move(service));
90 }
91
92 {
93 auto interaction =
94 std::make_unique<t_interaction>(&program_, "Interaction");
95 interaction->add_function(std::make_unique<t_function>(
96 &t_base_type::t_void(),
97 "bar",
98 std::make_unique<t_paramlist>(&program_)));
99 interaction->add_function(std::make_unique<t_function>(
100 &t_base_type::t_void(),
101 "baz",
102 std::make_unique<t_paramlist>(&program_)));
103 program_.add_interaction(std::move(interaction));
104 }
105
106 // No errors will be found
107 EXPECT_THAT(validate(), ::testing::IsEmpty());
108 }
109
TEST_F(StandardValidatorTest,ReapeatedNamesInService)110 TEST_F(StandardValidatorTest, ReapeatedNamesInService) {
111 // Create interfaces with overlapping functions.
112 {
113 auto service = std::make_unique<t_service>(&program_, "Service");
114 auto fn1 = std::make_unique<t_function>(
115 &t_base_type::t_void(),
116 "foo",
117 std::make_unique<t_paramlist>(&program_));
118 fn1->set_lineno(1);
119 auto fn2 = std::make_unique<t_function>(
120 &t_base_type::t_void(),
121 "foo",
122 std::make_unique<t_paramlist>(&program_));
123 fn2->set_lineno(2);
124 service->add_function(std::move(fn1));
125 service->add_function(std::move(fn2));
126 service->add_function(std::make_unique<t_function>(
127 &t_base_type::t_void(),
128 "bar",
129 std::make_unique<t_paramlist>(&program_)));
130 program_.add_service(std::move(service));
131 }
132
133 {
134 auto interaction =
135 std::make_unique<t_interaction>(&program_, "Interaction");
136 auto fn1 = std::make_unique<t_function>(
137 &t_base_type::t_void(),
138 "bar",
139 std::make_unique<t_paramlist>(&program_));
140 fn1->set_lineno(3);
141 auto fn2 = std::make_unique<t_function>(
142 &t_base_type::t_void(),
143 "bar",
144 std::make_unique<t_paramlist>(&program_));
145 fn2->set_lineno(4);
146
147 interaction->add_function(std::make_unique<t_function>(
148 &t_base_type::t_void(),
149 "foo",
150 std::make_unique<t_paramlist>(&program_)));
151 interaction->add_function(std::move(fn1));
152 interaction->add_function(std::move(fn2));
153 program_.add_interaction(std::move(interaction));
154 }
155
156 EXPECT_THAT(
157 validate(),
158 ::testing::UnorderedElementsAre(
159 failure(2, "Function `foo` is already defined for `Service`."),
160 failure(4, "Function `bar` is already defined for `Interaction`.")));
161 }
162
TEST_F(StandardValidatorTest,RepeatedNameInExtendedService)163 TEST_F(StandardValidatorTest, RepeatedNameInExtendedService) {
164 // Create first service with non repeated functions
165 auto base = std::make_unique<t_service>(&program_, "Base");
166 base->add_function(std::make_unique<t_function>(
167 &t_base_type::t_void(), "bar", std::make_unique<t_paramlist>(&program_)));
168 base->add_function(std::make_unique<t_function>(
169 &t_base_type::t_void(), "baz", std::make_unique<t_paramlist>(&program_)));
170 auto derived = std::make_unique<t_service>(&program_, "Derived", base.get());
171 derived->add_function(std::make_unique<t_function>(
172 &t_base_type::t_void(), "foo", std::make_unique<t_paramlist>(&program_)));
173
174 auto derived_ptr = derived.get();
175 program_.add_service(std::move(base));
176 program_.add_service(std::move(derived));
177
178 EXPECT_THAT(validate(), ::testing::IsEmpty());
179
180 // Add an overlapping function in the derived service.
181 auto dupe = std::make_unique<t_function>(
182 &t_base_type::t_void(), "baz", std::make_unique<t_paramlist>(&program_));
183 dupe->set_lineno(1);
184 derived_ptr->add_function(std::move(dupe));
185
186 // An error will be found
187 EXPECT_THAT(
188 validate(),
189 ::testing::UnorderedElementsAre(failure(
190 1, "Function `Derived.baz` redefines `service file.Base.baz`.")));
191 }
192
TEST_F(StandardValidatorTest,DuplicatedEnumValues)193 TEST_F(StandardValidatorTest, DuplicatedEnumValues) {
194 auto tenum = std::make_unique<t_enum>(&program_, "foo");
195 auto tenum_ptr = tenum.get();
196 program_.add_enum(std::move(tenum));
197
198 tenum_ptr->append(std::make_unique<t_enum_value>("bar", 1));
199 tenum_ptr->append(std::make_unique<t_enum_value>("baz", 2));
200
201 // No errors will be found.
202 EXPECT_THAT(validate(), ::testing::IsEmpty());
203
204 // Add enum_value with repeated value.
205 auto enum_value_3 = std::make_unique<t_enum_value>("foo", 1);
206 enum_value_3->set_lineno(1);
207 tenum_ptr->append(std::move(enum_value_3));
208
209 // An error will be found.
210 EXPECT_THAT(
211 validate(),
212 UnorderedElementsAre(failure(
213 1, "Duplicate value `foo=1` with value `bar` in enum `foo`.")));
214 }
215
TEST_F(StandardValidatorTest,RepeatedNamesInEnumValues)216 TEST_F(StandardValidatorTest, RepeatedNamesInEnumValues) {
217 auto tenum = std::make_unique<t_enum>(&program_, "foo");
218 auto tenum_ptr = tenum.get();
219 program_.add_enum(std::move(tenum));
220
221 tenum_ptr->append(std::make_unique<t_enum_value>("bar", 1));
222 tenum_ptr->append(std::make_unique<t_enum_value>("not_bar", 2));
223
224 // No errors will be found.
225 EXPECT_THAT(validate(), ::testing::IsEmpty());
226
227 // Add enum_value with repeated name.
228 auto enum_value_3 = std::make_unique<t_enum_value>("bar", 3);
229 enum_value_3->set_lineno(1);
230 tenum_ptr->append(std::move(enum_value_3));
231
232 // An error will be found.
233 EXPECT_THAT(
234 validate(),
235 UnorderedElementsAre(
236 failure(1, "Enum value `bar` is already defined for `foo`.")));
237 }
238
TEST_F(StandardValidatorTest,UnsetEnumValues)239 TEST_F(StandardValidatorTest, UnsetEnumValues) {
240 auto tenum = std::make_unique<t_enum>(&program_, "foo");
241 auto tenum_ptr = tenum.get();
242 program_.add_enum(std::move(tenum));
243
244 auto enum_value_1 = std::make_unique<t_enum_value>("Foo", 1);
245 auto enum_value_2 = std::make_unique<t_enum_value>("Bar");
246 auto enum_value_3 = std::make_unique<t_enum_value>("Baz");
247 enum_value_1->set_lineno(1);
248 enum_value_2->set_lineno(2);
249 enum_value_3->set_lineno(3);
250 tenum_ptr->append(std::move(enum_value_1));
251 tenum_ptr->append(std::move(enum_value_2));
252 tenum_ptr->append(std::move(enum_value_3));
253
254 // Bar and Baz will have errors.
255 EXPECT_THAT(
256 validate(),
257 UnorderedElementsAre(
258 failure(
259 2,
260 "The enum value, `Bar`, must have an explicitly assigned value."),
261 failure(
262 3,
263 "The enum value, `Baz`, must have an explicitly assigned value.")));
264 }
265
TEST_F(StandardValidatorTest,UnionFieldAttributes)266 TEST_F(StandardValidatorTest, UnionFieldAttributes) {
267 auto tstruct = std::make_unique<t_struct>(&program_, "Struct");
268 auto tunion = std::make_unique<t_union>(&program_, "Union");
269 tunion->set_lineno(1);
270 {
271 auto field = std::make_unique<t_field>(&t_base_type::t_i64(), "req", 1);
272 field->set_lineno(2);
273 field->set_qualifier(t_field_qualifier::required);
274 tunion->append(std::move(field));
275 }
276 {
277 auto field = std::make_unique<t_field>(&t_base_type::t_i64(), "op", 2);
278 field->set_lineno(3);
279 field->set_qualifier(t_field_qualifier::optional);
280 tunion->append(std::move(field));
281 }
282 {
283 auto field = std::make_unique<t_field>(&t_base_type::t_i64(), "non", 3);
284 field->set_lineno(4);
285 tunion->append(std::move(field));
286 }
287 {
288 auto field = std::make_unique<t_field>(tstruct.get(), "mixin", 4);
289 field->set_lineno(5);
290 field->set_annotation("cpp.mixin");
291 tunion->append(std::move(field));
292 }
293 program_.add_struct(std::move(tstruct));
294 program_.add_struct(std::move(tunion));
295
296 EXPECT_THAT(
297 validate(),
298 UnorderedElementsAre(
299 // Qualified fields will have errors.
300 failure(
301 2,
302 "Unions cannot contain qualified fields. Remove `required` qualifier from field `req`."),
303 failure(
304 3,
305 "Unions cannot contain qualified fields. Remove `optional` qualifier from field `op`."),
306 // Fields with cpp.mixing have errors.
307 failure(5, "Union `Union` cannot contain mixin field `mixin`.")));
308 }
309
TEST_F(StandardValidatorTest,FieldId)310 TEST_F(StandardValidatorTest, FieldId) {
311 auto tstruct = std::make_unique<t_struct>(&program_, "Struct");
312 tstruct->append(
313 std::make_unique<t_field>(t_base_type::t_i64(), "explicit_id", 1));
314 tstruct->append(
315 std::make_unique<t_field>(t_base_type::t_i64(), "zero_id", 0));
316 tstruct->append(
317 std::make_unique<t_field>(t_base_type::t_i64(), "neg_id", -1));
318 tstruct->append(std::make_unique<t_field>(
319 t_base_type::t_i64(), "implicit_id", -2, false));
320
321 program_.add_struct(std::move(tstruct));
322 EXPECT_THAT(
323 validate(),
324 UnorderedElementsAre(
325 failure(-1, "Zero value (0) not allowed as a field id for `zero_id`"),
326 warning(
327 -1,
328 "No field id specified for `implicit_id`, resulting protocol may have conflicts or not be backwards compatible!")));
329 }
330
TEST_F(StandardValidatorTest,MixinFieldType)331 TEST_F(StandardValidatorTest, MixinFieldType) {
332 auto tstruct = std::make_unique<t_struct>(&program_, "Struct");
333 auto tunion = std::make_unique<t_union>(&program_, "Union");
334 auto texception = std::make_unique<t_exception>(&program_, "Exception");
335
336 auto foo = std::make_unique<t_struct>(&program_, "Foo");
337 {
338 auto field = std::make_unique<t_field>(tstruct.get(), "struct_field", 1);
339 field->set_lineno(1);
340 field->set_annotation("cpp.mixin");
341 field->set_qualifier(t_field_qualifier::optional);
342 foo->append(std::move(field));
343 }
344 {
345 auto field = std::make_unique<t_field>(tunion.get(), "union_field", 2);
346 field->set_lineno(2);
347 field->set_annotation("cpp.mixin");
348 field->set_qualifier(t_field_qualifier::required);
349 foo->append(std::move(field));
350 }
351 {
352 auto field = std::make_unique<t_field>(texception.get(), "except_field", 3);
353 field->set_lineno(3);
354 field->set_annotation("cpp.mixin");
355 foo->append(std::move(field));
356 }
357 {
358 auto field =
359 std::make_unique<t_field>(&t_base_type::t_i32(), "other_field", 4);
360 field->set_lineno(4);
361 field->set_annotation("cpp.mixin");
362 foo->append(std::move(field));
363 }
364
365 program_.add_struct(std::move(tstruct));
366 program_.add_struct(std::move(tunion));
367 program_.add_exception(std::move(texception));
368 program_.add_struct(std::move(foo));
369
370 EXPECT_THAT(
371 validate(diagnostic_params::only_failures()),
372 UnorderedElementsAre(
373 failure(1, "Mixin field `struct_field` cannot be optional."),
374 failure(
375 3,
376 "Mixin field `except_field` type must be a struct or union. Found `Exception`."),
377 failure(
378 4,
379 "Mixin field `other_field` type must be a struct or union. Found `i32`.")));
380 }
381
TEST_F(StandardValidatorTest,RepeatedStructuredAnnotation)382 TEST_F(StandardValidatorTest, RepeatedStructuredAnnotation) {
383 auto foo = std::make_unique<t_struct>(&program_, "Foo");
384 // A different program with the same name.
385 t_program other_program("/path/to/other/file.thrift");
386 // A different foo with the same file.name
387 auto other_foo = std::make_unique<t_struct>(&other_program, "Foo");
388 EXPECT_EQ(foo->get_full_name(), other_foo->get_full_name());
389
390 t_scope scope;
391 auto bar = std::make_unique<t_typedef>(
392 &program_, &t_base_type::t_i32(), "Bar", &scope);
393 bar->add_structured_annotation(inst(other_foo.get(), 1));
394 bar->add_structured_annotation(inst(foo.get(), 2));
395 bar->add_structured_annotation(inst(foo.get(), 3));
396
397 program_.add_struct(std::move(foo));
398 program_.add_typedef(std::move(bar));
399 other_program.add_struct(std::move(other_foo));
400
401 // Only the third annotation is a duplicate.
402 EXPECT_THAT(
403 validate(diagnostic_params::only_failures()),
404 UnorderedElementsAre(failure(
405 3, "Structured annotation `Foo` is already defined for `Bar`.")));
406 }
407
TEST_F(StandardValidatorTest,CustomDefaultValue)408 TEST_F(StandardValidatorTest, CustomDefaultValue) {
409 auto foo = std::make_unique<t_struct>(&program_, "Foo");
410
411 auto custom_byte = std::make_unique<t_const_value>(
412 static_cast<int64_t>(std::numeric_limits<int8_t>::max()) + 1);
413 auto custom_short = std::make_unique<t_const_value>(
414 static_cast<int64_t>(std::numeric_limits<int16_t>::max()) + 1);
415 auto custom_integer = std::make_unique<t_const_value>(
416 static_cast<int64_t>(std::numeric_limits<int32_t>::max()) + 1);
417 auto custom_float = std::make_unique<t_const_value>();
418 custom_float->set_double(std::nextafter(
419 static_cast<double>(std::numeric_limits<float>::max()),
420 std::numeric_limits<double>::max()));
421 auto custom_float_precision_loss =
422 std::make_unique<t_const_value>(std::numeric_limits<int32_t>::max());
423
424 auto const_byte = std::make_unique<t_const>(
425 &program_, &t_base_type::t_byte(), "const_byte", std::move(custom_byte));
426 auto const_short = std::make_unique<t_const>(
427 &program_, &t_base_type::t_i16(), "const_short", std::move(custom_short));
428 auto const_integer = std::make_unique<t_const>(
429 &program_,
430 &t_base_type::t_i32(),
431 "const_integer",
432 std::move(custom_integer));
433 auto const_float = std::make_unique<t_const>(
434 &program_,
435 &t_base_type::t_float(),
436 "const_float",
437 std::move(custom_float));
438 auto const_float_precision_loss = std::make_unique<t_const>(
439 &program_,
440 &t_base_type::t_float(),
441 "const_float_precision_loss",
442 std::move(custom_float_precision_loss));
443
444 program_.add_const(std::move(const_byte));
445 program_.add_const(std::move(const_short));
446 program_.add_const(std::move(const_integer));
447 program_.add_const(std::move(const_float));
448 program_.add_const(std::move(const_float_precision_loss));
449
450 EXPECT_THAT(
451 validate(),
452 UnorderedElementsAre(
453 failure(
454 -1,
455 "value error: const `const_byte` has an invalid custom default value."),
456 failure(
457 -1,
458 "value error: const `const_short` has an invalid custom default value."),
459 failure(
460 -1,
461 "value error: const `const_integer` has an invalid custom default value."),
462 failure(
463 -1,
464 "value error: const `const_float` has an invalid custom default value."),
465 failure(
466 -1,
467 "value error: const `const_float_precision_loss` cannot be represented precisely as `float` or `double`.")));
468 }
469
470 } // namespace
471 } // namespace apache::thrift::compiler
472