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