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 <string>
20 #include <unordered_map>
21
22 #include <thrift/compiler/ast/name_index.h>
23 #include <thrift/compiler/ast/t_enum.h>
24 #include <thrift/compiler/ast/t_enum_value.h>
25 #include <thrift/compiler/ast/t_field.h>
26 #include <thrift/compiler/ast/t_function.h>
27 #include <thrift/compiler/ast/t_interface.h>
28 #include <thrift/compiler/ast/t_named.h>
29 #include <thrift/compiler/ast/t_node.h>
30 #include <thrift/compiler/ast/t_service.h>
31 #include <thrift/compiler/ast/t_struct.h>
32 #include <thrift/compiler/ast/t_structured.h>
33 #include <thrift/compiler/ast/t_throws.h>
34 #include <thrift/compiler/ast/t_type.h>
35 #include <thrift/compiler/ast/t_union.h>
36 #include <thrift/compiler/gen/cpp/reference_type.h>
37 #include <thrift/compiler/lib/cpp2/util.h>
38 #include <thrift/compiler/sema/const_checker.h>
39 #include <thrift/compiler/sema/scope_validator.h>
40
41 namespace apache {
42 namespace thrift {
43 namespace compiler {
44
45 namespace {
46
get_mixin_type(const t_field & field)47 const t_structured* get_mixin_type(const t_field& field) {
48 if (cpp2::is_mixin(field)) {
49 return dynamic_cast<const t_structured*>(field.type()->get_true_type());
50 }
51 return nullptr;
52 }
53
has_lazy_field(const t_structured & node)54 bool has_lazy_field(const t_structured& node) {
55 for (const auto& field : node.fields()) {
56 if (cpp2::is_lazy(&field)) {
57 return true;
58 }
59 }
60 return false;
61 }
62
63 // Reports an existing name was redefined within the given parent node.
report_redef_failure(diagnostic_context & ctx,const char * kind,const std::string & name,const std::string & path,const t_named & parent,const t_node & child,const t_node &)64 void report_redef_failure(
65 diagnostic_context& ctx,
66 const char* kind,
67 const std::string& name,
68 const std::string& path,
69 const t_named& parent,
70 const t_node& child,
71 const t_node& /*existing*/) {
72 // TODO(afuller): Use `existing` to provide more detail in the
73 // diagnostic.
74 ctx.failure(child, path, [&](auto& o) {
75 o << kind << " `" << name << "` is already defined for `" << parent.name()
76 << "`.";
77 });
78 }
79
report_redef_failure(diagnostic_context & ctx,const char * kind,const std::string & name,const t_named & parent,const t_node & child,const t_node & existing)80 void report_redef_failure(
81 diagnostic_context& ctx,
82 const char* kind,
83 const std::string& name,
84 const t_named& parent,
85 const t_node& child,
86 const t_node& existing) {
87 report_redef_failure(
88 ctx, kind, name, ctx.program()->path(), parent, child, existing);
89 }
90
91 // Helper for checking for the redefinition of a name in the context of a node.
92 class redef_checker {
93 public:
redef_checker(diagnostic_context & ctx,const char * kind,const t_named & parent)94 redef_checker(
95 diagnostic_context& ctx, const char* kind, const t_named& parent)
96 : ctx_(ctx), kind_(kind), parent_(parent) {}
97
98 // Checks if the given `name`, derived from `node` via `child`, has already
99 // been defined.
100 //
101 // For example, a mixin field causes all fields of the mixin type to be
102 // inherited. In this case 'node' wold be the mixin type, from which `name`
103 // was derived, while `child` is the mixin field that caused the name to be
104 // inherited.
check(const std::string & name,const t_named & node,const t_node & child)105 void check(
106 const std::string& name, const t_named& node, const t_node& child) {
107 if (const auto* existing = seen_.put(name, node)) {
108 if (&node == &parent_ && existing == &parent_) {
109 // The degenerate case where parent_ is conflicting with itself.
110 report_redef_failure(ctx_, kind_, name, parent_, child, *existing);
111 } else {
112 ctx_.failure(child, [&](auto& o) {
113 o << kind_ << " `" << node.name() << "." << name << "` and `"
114 << existing->name() << "." << name
115 << "` can not have same name in `" << parent_.name() << "`.";
116 });
117 }
118 }
119 }
120 void check(std::string&&, const t_named&, const t_node&) = delete;
121 void check(const std::string&, t_named&&, const t_node&) = delete;
122
123 // Helpers for the common case where the names are from child t_nameds of
124 // the parent.
125 //
126 // For example, all functions in an interface.
check(const t_named & child)127 void check(const t_named& child) {
128 if (const auto* existing = seen_.put(child)) {
129 report_redef_failure(
130 ctx_, kind_, child.name(), parent_, child, *existing);
131 }
132 }
133 void check(t_named&& child) = delete;
134
135 template <typename Cs>
check_all(const Cs & children)136 void check_all(const Cs& children) {
137 for (const t_named& child : children) {
138 check(child);
139 }
140 }
141
142 private:
143 diagnostic_context& ctx_;
144 const char* kind_;
145 const t_named& parent_;
146
147 name_index<t_named> seen_;
148 };
149
150 struct service_metadata {
151 name_index<t_service> function_name_to_service;
152
service_metadataapache::thrift::compiler::__anone0818b6a0111::service_metadata153 service_metadata(node_metadata_cache& cache, const t_service& node) {
154 if (node.extends() != nullptr) {
155 // Add all the inherited functions.
156 function_name_to_service =
157 cache.get<service_metadata>(*node.extends()).function_name_to_service;
158 }
159 // Add all the directly defined functions.
160 for (const auto& function : node.functions()) {
161 function_name_to_service.put(function.name(), node);
162 }
163 }
164 };
165
166 struct structured_metadata {
167 name_index<t_structured> field_name_to_parent;
168
structured_metadataapache::thrift::compiler::__anone0818b6a0111::structured_metadata169 structured_metadata(node_metadata_cache& cache, const t_structured& node) {
170 for (const auto& field : node.fields()) {
171 if (const auto* mixin = get_mixin_type(field)) {
172 // Add all the inherited mixin fields from field.
173 auto mixin_metadata = cache.get<structured_metadata>(*mixin);
174 field_name_to_parent.put_all(mixin_metadata.field_name_to_parent);
175 }
176 // Add the directly defined field.
177 field_name_to_parent.put(field.name(), node);
178 }
179 }
180 };
181
validate_interface_function_name_uniqueness(diagnostic_context & ctx,const t_interface & node)182 void validate_interface_function_name_uniqueness(
183 diagnostic_context& ctx, const t_interface& node) {
184 // Check for a redefinition of a function in the same interface.
185 redef_checker(ctx, "Function", node).check_all(node.functions());
186 }
187
188 // Checks for a redefinition of an inherited function.
validate_extends_service_function_name_uniqueness(diagnostic_context & ctx,const t_service & node)189 void validate_extends_service_function_name_uniqueness(
190 diagnostic_context& ctx, const t_service& node) {
191 if (node.extends() == nullptr) {
192 return;
193 }
194
195 const auto& extends_metadata =
196 ctx.cache().get<service_metadata>(*node.extends());
197 for (const auto& function : node.functions()) {
198 if (const auto* existing_service =
199 extends_metadata.function_name_to_service.find(function.name())) {
200 ctx.failure(function, [&](auto& o) {
201 o << "Function `" << node.name() << "." << function.name()
202 << "` redefines `" << existing_service->get_full_name() << "."
203 << function.name() << "`.";
204 });
205 }
206 }
207 }
208
validate_throws_exceptions(diagnostic_context & ctx,const t_throws & node)209 void validate_throws_exceptions(diagnostic_context& ctx, const t_throws& node) {
210 for (const auto& except : node.fields()) {
211 auto except_type = except.type()->get_true_type();
212 if (dynamic_cast<const t_exception*>(except_type) == nullptr) {
213 ctx.failure(except, [&](auto& o) {
214 o << "Non-exception type, `" << except_type->name() << "`, in throws.";
215 });
216 }
217 }
218 }
219
220 // Checks for a redefinition of a field in the same t_structured, including
221 // those inherited via mixin fields.
validate_field_names_uniqueness(diagnostic_context & ctx,const t_structured & node)222 void validate_field_names_uniqueness(
223 diagnostic_context& ctx, const t_structured& node) {
224 redef_checker checker(ctx, "Field", node);
225 for (const auto& field : node.fields()) {
226 // Check the directly defined field.
227 checker.check(field.name(), node, field);
228
229 // Check any transtively defined fields via a mixin annotation.
230 if (const auto* mixin = get_mixin_type(field)) {
231 const auto& mixin_metadata = ctx.cache().get<structured_metadata>(*mixin);
232 mixin_metadata.field_name_to_parent.for_each(
233 [&](const std::string& name, const t_structured& parent) {
234 checker.check(name, parent, field);
235 });
236 }
237 }
238 }
239
240 // Checks the attributes of fields in a union.
validate_union_field_attributes(diagnostic_context & ctx,const t_union & node)241 void validate_union_field_attributes(
242 diagnostic_context& ctx, const t_union& node) {
243 for (const auto& field : node.fields()) {
244 if (field.qualifier() != t_field_qualifier::unspecified) {
245 auto qual = field.qualifier() == t_field_qualifier::required ? "required"
246 : "optional";
247 ctx.failure(field, [&](auto& o) {
248 o << "Unions cannot contain qualified fields. Remove `" << qual
249 << "` qualifier from field `" << field.name() << "`.";
250 });
251 }
252 }
253 }
254
validate_boxed_field_attributes(diagnostic_context & ctx,const t_field & node)255 void validate_boxed_field_attributes(
256 diagnostic_context& ctx, const t_field& node) {
257 if (gen::cpp::find_ref_type(node) != gen::cpp::reference_type::boxed) {
258 return;
259 }
260
261 if (!dynamic_cast<const t_union*>(ctx.parent()) &&
262 node.qualifier() != t_field_qualifier::optional) {
263 ctx.failure([&](auto& o) {
264 o << "The `cpp.box` annotation can only be used with optional fields. Make sure `"
265 << node.name() << "` is optional.";
266 });
267 }
268
269 if (node.has_annotation({
270 "cpp.ref",
271 "cpp2.ref",
272 "cpp.ref_type",
273 "cpp2.ref_type",
274 })) {
275 ctx.failure([&](auto& o) {
276 o << "The `cpp.box` annotation cannot be combined with the `cpp.ref` or `cpp.ref_type` annotations. Remove one of the annotations from `"
277 << node.name() << "`.";
278 });
279 }
280 }
281
282 // Checks the attributes of a mixin field.
validate_mixin_field_attributes(diagnostic_context & ctx,const t_field & node)283 void validate_mixin_field_attributes(
284 diagnostic_context& ctx, const t_field& node) {
285 if (!cpp2::is_mixin(node)) {
286 return;
287 }
288
289 auto* ttype = node.type()->get_true_type();
290 if (typeid(*ttype) != typeid(t_struct) && typeid(*ttype) != typeid(t_union)) {
291 ctx.failure([&](auto& o) {
292 o << "Mixin field `" << node.name()
293 << "` type must be a struct or union. Found `" << ttype->get_name()
294 << "`.";
295 });
296 }
297
298 if (const auto* parent = dynamic_cast<const t_union*>(ctx.parent())) {
299 ctx.failure([&](auto& o) {
300 o << "Union `" << parent->name() << "` cannot contain mixin field `"
301 << node.name() << "`.";
302 });
303 } else if (node.qualifier() == t_field_qualifier::optional) {
304 // Nothing technically stops us from marking optional field mixin.
305 // However, this will bring surprising behavior. e.g. `foo.bar_ref()`
306 // might throw `bad_field_access` if `bar` is inside optional mixin
307 // field.
308 ctx.failure([&](auto& o) {
309 o << "Mixin field `" << node.name() << "` cannot be optional.";
310 });
311 }
312 }
313
314 /*
315 * Validates whether all fields which have annotations cpp.ref or cpp2.ref are
316 * also optional.
317 */
validate_ref_field_attributes(diagnostic_context & ctx,const t_field & node)318 void validate_ref_field_attributes(
319 diagnostic_context& ctx, const t_field& node) {
320 if (!cpp2::has_ref_annotation(node)) {
321 return;
322 }
323
324 if (node.qualifier() != t_field_qualifier::optional &&
325 dynamic_cast<const t_union*>(ctx.parent()) == nullptr) {
326 ctx.warning([&](auto& o) {
327 o << "`cpp.ref` field `" << node.name()
328 << "` must be optional if it is recursive.";
329 });
330 }
331 }
332
validate_enum_value_name_uniqueness(diagnostic_context & ctx,const t_enum & node)333 void validate_enum_value_name_uniqueness(
334 diagnostic_context& ctx, const t_enum& node) {
335 redef_checker(ctx, "Enum value", node).check_all(node.values());
336 }
337
validate_enum_value_uniqueness(diagnostic_context & ctx,const t_enum & node)338 void validate_enum_value_uniqueness(
339 diagnostic_context& ctx, const t_enum& node) {
340 std::unordered_map<int32_t, const t_enum_value*> values;
341 for (const auto& value : node.values()) {
342 auto prev = values.emplace(value.get_value(), &value);
343 if (!prev.second) {
344 ctx.failure(value, [&](auto& o) {
345 o << "Duplicate value `" << value.name() << "=" << value.get_value()
346 << "` with value `" << prev.first->second->name() << "` in enum `"
347 << node.name() << "`.";
348 });
349 }
350 }
351 }
352
validate_enum_value(diagnostic_context & ctx,const t_enum_value & node)353 void validate_enum_value(diagnostic_context& ctx, const t_enum_value& node) {
354 if (!node.has_value()) {
355 ctx.failure([&](auto& o) {
356 o << "The enum value, `" << node.name()
357 << "`, must have an explicitly assigned value.";
358 });
359 } else if (node.get_value() < 0 && !ctx.params().allow_neg_enum_vals) {
360 ctx.warning([&](auto& o) {
361 o << "Negative value supplied for enum value `" << node.name() << "`.";
362 });
363 }
364 }
365
validate_const_type_and_value(diagnostic_context & ctx,const t_const & c)366 void validate_const_type_and_value(diagnostic_context& ctx, const t_const& c) {
367 check_const_rec(ctx, c, &c.type().deref(), c.value());
368 }
369
validate_field_default_value(diagnostic_context & ctx,const t_field & field)370 void validate_field_default_value(
371 diagnostic_context& ctx, const t_field& field) {
372 if (field.get_default_value() != nullptr) {
373 check_const_rec(ctx, field, &field.type().deref(), field.default_value());
374 }
375 }
376
validate_structured_annotation(diagnostic_context & ctx,const t_named & node)377 void validate_structured_annotation(
378 diagnostic_context& ctx, const t_named& node) {
379 std::unordered_map<const t_type*, const t_const*> seen;
380 for (const auto* annot : node.structured_annotations()) {
381 auto result = seen.emplace(&annot->type().deref(), annot);
382 if (!result.second) {
383 report_redef_failure(
384 ctx,
385 "Structured annotation",
386 result.first->first->name(),
387 node,
388 *annot,
389 *result.first->second);
390 }
391 validate_const_type_and_value(ctx, *annot);
392 }
393 }
394
validate_uri_uniqueness(diagnostic_context & ctx,const t_program & prog)395 void validate_uri_uniqueness(diagnostic_context& ctx, const t_program& prog) {
396 // TODO: use string_view as map key
397 std::unordered_map<std::string, const t_named*> uri_to_node;
398 basic_ast_visitor<true, const std::string&> visit;
399 visit.add_definition_visitor(
400 [&](const std::string& path, const t_named& node) {
401 const auto& uri = node.uri();
402 if (uri.empty()) {
403 return;
404 }
405 auto result = uri_to_node.emplace(uri, &node);
406 if (!result.second) {
407 report_redef_failure(
408 ctx, "thrift.uri", uri, path, node, node, *result.first->second);
409 }
410 });
411 for (const auto* p : prog.get_included_programs()) {
412 visit(p->path(), *p);
413 }
414 visit(prog.path(), prog);
415 }
416
validate_field_id(diagnostic_context & ctx,const t_field & node)417 void validate_field_id(diagnostic_context& ctx, const t_field& node) {
418 if (!node.has_explicit_id()) {
419 ctx.warning([&](auto& o) {
420 o << "No field id specified for `" << node.name()
421 << "`, resulting protocol may"
422 << " have conflicts or not be backwards compatible!";
423 });
424 }
425
426 if (node.id() == 0 &&
427 !node.has_annotation("cpp.deprecated_allow_zero_as_field_id")) {
428 ctx.failure([&](auto& o) {
429 o << "Zero value (0) not allowed as a field id for `" << node.get_name()
430 << "`";
431 });
432 }
433
434 if (node.id() < t_field::min_id) {
435 ctx.failure([&](auto& o) {
436 o << "Reserved field id (" << node.id() << ") cannot be used for `"
437 << node.name() << "`.";
438 });
439 }
440 }
441
validate_compatibility_with_lazy_field(diagnostic_context & ctx,const t_structured & node)442 void validate_compatibility_with_lazy_field(
443 diagnostic_context& ctx, const t_structured& node) {
444 if (!has_lazy_field(node)) {
445 return;
446 }
447
448 if (node.has_annotation("cpp.methods")) {
449 ctx.failure([&](auto& o) {
450 o << "cpp.methods is incompatible with lazy deserialization in struct `"
451 << node.get_name() << "`";
452 });
453 }
454 }
455
validate_ref_annotation(diagnostic_context & ctx,const t_field & node)456 void validate_ref_annotation(diagnostic_context& ctx, const t_field& node) {
457 if (node.find_structured_annotation_or_null(
458 "facebook.com/thrift/annotation/cpp/Ref") &&
459 node.has_annotation(
460 {"cpp.ref", "cpp2.ref", "cpp.ref_type", "cpp2.ref_type"})) {
461 ctx.failure([&](auto& o) {
462 o << "The @cpp.Ref annotation cannot be combined with the `cpp.ref` or `cpp.ref_type` annotations. Remove one of the annotations from `"
463 << node.name() << "`.";
464 });
465 }
466 }
467
validate_adapter_annotation(diagnostic_context & ctx,const t_field & node)468 void validate_adapter_annotation(diagnostic_context& ctx, const t_field& node) {
469 const t_const* adapter_annotation = nullptr;
470 for (const t_const* annotation : node.structured_annotations()) {
471 if (annotation->type()->uri() ==
472 "facebook.com/thrift/annotation/cpp/Adapter") {
473 adapter_annotation = annotation;
474 break;
475 }
476 }
477
478 if (adapter_annotation &&
479 t_typedef::get_first_annotation_or_null(&*node.type(), {"cpp.adapter"})) {
480 ctx.failure([&](auto& o) {
481 o << "`@cpp.Adapter` cannot be combined with `cpp_adapter` in `"
482 << node.name() << "`.";
483 });
484 }
485 }
486
validate_hack_adapter_annotation(diagnostic_context & ctx,const t_field & node)487 void validate_hack_adapter_annotation(
488 diagnostic_context& ctx, const t_field& node) {
489 const t_const* field_adapter_annotation = nullptr;
490 for (const t_const* annotation : node.structured_annotations()) {
491 if (annotation->type()->uri() ==
492 "facebook.com/thrift/annotation/hack/ExperimentalAdapter") {
493 field_adapter_annotation = annotation;
494 break;
495 }
496 }
497
498 if (field_adapter_annotation &&
499 t_typedef::get_first_annotation_or_null(
500 &*node.type(), {"hack.adapter"})) {
501 ctx.failure([&](auto& o) {
502 o << "`@hack.ExperimentalAdapter` cannot be combined with "
503 "`hack_adapter` in `"
504 << node.name() << "`.";
505 });
506 }
507 }
508
validate_box_annotation(diagnostic_context & ctx,const t_field & node)509 void validate_box_annotation(diagnostic_context& ctx, const t_field& node) {
510 if (node.has_annotation("cpp.box")) {
511 ctx.warning([&](auto& o) {
512 o << "Cpp.box is deprecated. Please use thrift.box annotation instead in `"
513 << node.name() << "`.";
514 });
515 }
516 }
validate_box_annotation_in_struct(diagnostic_context & ctx,const t_struct & node)517 void validate_box_annotation_in_struct(
518 diagnostic_context& ctx, const t_struct& node) {
519 if (node.is_struct()) {
520 for (const t_field& f : node.fields()) {
521 validate_box_annotation(ctx, f);
522 }
523 }
524 }
525
validate_ref_unique_and_box_annotation(diagnostic_context & ctx,const t_field & node)526 void validate_ref_unique_and_box_annotation(
527 diagnostic_context& ctx, const t_field& node) {
528 if (cpp2::is_unique_ref(&node)) {
529 if (node.has_annotation({"cpp.ref", "cpp2.ref"})) {
530 ctx.warning([&](auto& o) {
531 o << "cpp.ref, cpp2.ref "
532 << "are deprecated. Please use thrift.box annotation instead in `"
533 << node.name() << "`.";
534 });
535 }
536 if (node.find_annotation_or_null({"cpp.ref_type", "cpp2.ref_type"})) {
537 ctx.warning([&](auto& o) {
538 o << "cpp.ref_type = `unique`, cpp2.ref_type = `unique` "
539 << "are deprecated. Please use thrift.box annotation instead in `"
540 << node.name() << "`.";
541 });
542 }
543 if (node.find_structured_annotation_or_null(
544 "facebook.com/thrift/annotation/cpp/Ref")) {
545 ctx.warning([&](auto& o) {
546 o << "@cpp.Ref{type = cpp.RefType.Unique} "
547 << "is deprecated. Please use thrift.box annotation instead in `"
548 << node.name() << "`.";
549 });
550 }
551 }
552 }
553 } // namespace
554
standard_validator()555 ast_validator standard_validator() {
556 ast_validator validator;
557 validator.add_interface_visitor(&validate_interface_function_name_uniqueness);
558 validator.add_service_visitor(
559 &validate_extends_service_function_name_uniqueness);
560 validator.add_throws_visitor(&validate_throws_exceptions);
561
562 validator.add_structured_definition_visitor(&validate_field_names_uniqueness);
563 validator.add_structured_definition_visitor(
564 &validate_compatibility_with_lazy_field);
565 validator.add_structured_definition_visitor(
566 &validate_box_annotation_in_struct);
567 validator.add_union_visitor(&validate_union_field_attributes);
568 validator.add_field_visitor(&validate_field_id);
569 validator.add_field_visitor(&validate_mixin_field_attributes);
570 validator.add_field_visitor(&validate_boxed_field_attributes);
571 validator.add_field_visitor(&validate_ref_field_attributes);
572 validator.add_field_visitor(&validate_field_default_value);
573 validator.add_field_visitor(&validate_ref_annotation);
574 validator.add_field_visitor(&validate_adapter_annotation);
575 validator.add_field_visitor(&validate_hack_adapter_annotation);
576 validator.add_field_visitor(&validate_ref_unique_and_box_annotation);
577
578 validator.add_enum_visitor(&validate_enum_value_name_uniqueness);
579 validator.add_enum_visitor(&validate_enum_value_uniqueness);
580 validator.add_enum_value_visitor(&validate_enum_value);
581
582 validator.add_definition_visitor(&validate_structured_annotation);
583 validator.add_definition_visitor(&validate_annotation_scopes);
584
585 validator.add_const_visitor(&validate_const_type_and_value);
586 validator.add_program_visitor(&validate_uri_uniqueness);
587 return validator;
588 }
589
590 } // namespace compiler
591 } // namespace thrift
592 } // namespace apache
593