xref: /llvm-project/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp (revision 9e0fc67683781fe4bd9bc4085084faec1126b41b)
1 //===- UncheckedOptionalAccessModelTest.cpp -------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 // FIXME: Move this to clang/unittests/Analysis/FlowSensitive/Models.
9 
10 #include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h"
11 #include "TestingSupport.h"
12 #include "clang/AST/ASTContext.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/Analysis/FlowSensitive/SourceLocationsLattice.h"
15 #include "clang/Tooling/Tooling.h"
16 #include "llvm/ADT/ArrayRef.h"
17 #include "llvm/ADT/StringExtras.h"
18 #include "llvm/Support/Error.h"
19 #include "gmock/gmock.h"
20 #include "gtest/gtest.h"
21 #include <string>
22 #include <utility>
23 #include <vector>
24 
25 using namespace clang;
26 using namespace dataflow;
27 using namespace test;
28 
29 using ::testing::Pair;
30 using ::testing::UnorderedElementsAre;
31 
32 // FIXME: Move header definitions in separate file(s).
33 static constexpr char StdTypeTraitsHeader[] = R"(
34 #ifndef TYPE_TRAITS_H
35 #define TYPE_TRAITS_H
36 
37 namespace std {
38 
39 typedef decltype(sizeof(char)) size_t;
40 
41 template <typename T, T V>
42 struct integral_constant {
43   static constexpr T value = V;
44 };
45 
46 using true_type = integral_constant<bool, true>;
47 using false_type = integral_constant<bool, false>;
48 
49 template< class T > struct remove_reference      {typedef T type;};
50 template< class T > struct remove_reference<T&>  {typedef T type;};
51 template< class T > struct remove_reference<T&&> {typedef T type;};
52 
53 template <class T>
54   using remove_reference_t = typename remove_reference<T>::type;
55 
56 template <class T>
57 struct remove_extent {
58   typedef T type;
59 };
60 
61 template <class T>
62 struct remove_extent<T[]> {
63   typedef T type;
64 };
65 
66 template <class T, size_t N>
67 struct remove_extent<T[N]> {
68   typedef T type;
69 };
70 
71 template <class T>
72 struct is_array : false_type {};
73 
74 template <class T>
75 struct is_array<T[]> : true_type {};
76 
77 template <class T, size_t N>
78 struct is_array<T[N]> : true_type {};
79 
80 template <class>
81 struct is_function : false_type {};
82 
83 template <class Ret, class... Args>
84 struct is_function<Ret(Args...)> : true_type {};
85 
86 namespace detail {
87 
88 template <class T>
89 struct type_identity {
90   using type = T;
91 };  // or use type_identity (since C++20)
92 
93 template <class T>
94 auto try_add_pointer(int) -> type_identity<typename remove_reference<T>::type*>;
95 template <class T>
96 auto try_add_pointer(...) -> type_identity<T>;
97 
98 }  // namespace detail
99 
100 template <class T>
101 struct add_pointer : decltype(detail::try_add_pointer<T>(0)) {};
102 
103 template <bool B, class T, class F>
104 struct conditional {
105   typedef T type;
106 };
107 
108 template <class T, class F>
109 struct conditional<false, T, F> {
110   typedef F type;
111 };
112 
113 template <class T>
114 struct remove_cv {
115   typedef T type;
116 };
117 template <class T>
118 struct remove_cv<const T> {
119   typedef T type;
120 };
121 template <class T>
122 struct remove_cv<volatile T> {
123   typedef T type;
124 };
125 template <class T>
126 struct remove_cv<const volatile T> {
127   typedef T type;
128 };
129 
130 template <class T>
131 struct decay {
132  private:
133   typedef typename remove_reference<T>::type U;
134 
135  public:
136   typedef typename conditional<
137       is_array<U>::value, typename remove_extent<U>::type*,
138       typename conditional<is_function<U>::value, typename add_pointer<U>::type,
139                            typename remove_cv<U>::type>::type>::type type;
140 };
141 
142 } // namespace std
143 
144 #endif // TYPE_TRAITS_H
145 )";
146 
147 static constexpr char StdUtilityHeader[] = R"(
148 #ifndef UTILITY_H
149 #define UTILITY_H
150 
151 #include "std_type_traits.h"
152 
153 namespace std {
154 
155 template <typename T>
156 constexpr remove_reference_t<T>&& move(T&& x);
157 
158 } // namespace std
159 
160 #endif // UTILITY_H
161 )";
162 
163 static constexpr char StdInitializerListHeader[] = R"(
164 #ifndef INITIALIZER_LIST_H
165 #define INITIALIZER_LIST_H
166 
167 namespace std {
168 
169 template <typename T>
170 class initializer_list {
171  public:
172   initializer_list() noexcept;
173 };
174 
175 } // namespace std
176 
177 #endif // INITIALIZER_LIST_H
178 )";
179 
180 static constexpr char StdOptionalHeader[] = R"(
181 #include "std_initializer_list.h"
182 #include "std_type_traits.h"
183 #include "std_utility.h"
184 
185 namespace std {
186 
187 template <typename T>
188 class optional {
189  public:
190   constexpr optional() noexcept;
191 
192   const T& operator*() const&;
193   T& operator*() &;
194   const T&& operator*() const&&;
195   T&& operator*() &&;
196 
197   const T* operator->() const;
198   T* operator->();
199 
200   const T& value() const&;
201   T& value() &;
202   const T&& value() const&&;
203   T&& value() &&;
204 
205   template <typename U>
206   constexpr T value_or(U&& v) const&;
207   template <typename U>
208   T value_or(U&& v) &&;
209 
210   template <typename... Args>
211   T& emplace(Args&&... args);
212 
213   template <typename U, typename... Args>
214   T& emplace(std::initializer_list<U> ilist, Args&&... args);
215 
216   void reset() noexcept;
217 
218   constexpr explicit operator bool() const noexcept;
219   constexpr bool has_value() const noexcept;
220 };
221 
222 template <typename T>
223 constexpr optional<typename std::decay<T>::type> make_optional(T&& v);
224 
225 template <typename T, typename... Args>
226 constexpr optional<T> make_optional(Args&&... args);
227 
228 template <typename T, typename U, typename... Args>
229 constexpr optional<T> make_optional(std::initializer_list<U> il,
230                                     Args&&... args);
231 
232 } // namespace std
233 )";
234 
235 static constexpr char AbslOptionalHeader[] = R"(
236 #include "std_initializer_list.h"
237 #include "std_type_traits.h"
238 #include "std_utility.h"
239 
240 namespace absl {
241 
242 template <typename T>
243 class optional {
244  public:
245   constexpr optional() noexcept;
246 
247   const T& operator*() const&;
248   T& operator*() &;
249   const T&& operator*() const&&;
250   T&& operator*() &&;
251 
252   const T* operator->() const;
253   T* operator->();
254 
255   const T& value() const&;
256   T& value() &;
257   const T&& value() const&&;
258   T&& value() &&;
259 
260   template <typename U>
261   constexpr T value_or(U&& v) const&;
262   template <typename U>
263   T value_or(U&& v) &&;
264 
265   template <typename... Args>
266   T& emplace(Args&&... args);
267 
268   template <typename U, typename... Args>
269   T& emplace(std::initializer_list<U> ilist, Args&&... args);
270 
271   void reset() noexcept;
272 
273   constexpr explicit operator bool() const noexcept;
274   constexpr bool has_value() const noexcept;
275 };
276 
277 template <typename T>
278 constexpr optional<typename std::decay<T>::type> make_optional(T&& v);
279 
280 template <typename T, typename... Args>
281 constexpr optional<T> make_optional(Args&&... args);
282 
283 template <typename T, typename U, typename... Args>
284 constexpr optional<T> make_optional(std::initializer_list<U> il,
285                                     Args&&... args);
286 
287 } // namespace absl
288 )";
289 
290 static constexpr char BaseOptionalHeader[] = R"(
291 #include "std_initializer_list.h"
292 #include "std_type_traits.h"
293 #include "std_utility.h"
294 
295 namespace base {
296 
297 template <typename T>
298 class Optional {
299  public:
300   constexpr Optional() noexcept;
301 
302   const T& operator*() const&;
303   T& operator*() &;
304   const T&& operator*() const&&;
305   T&& operator*() &&;
306 
307   const T* operator->() const;
308   T* operator->();
309 
310   const T& value() const&;
311   T& value() &;
312   const T&& value() const&&;
313   T&& value() &&;
314 
315   template <typename U>
316   constexpr T value_or(U&& v) const&;
317   template <typename U>
318   T value_or(U&& v) &&;
319 
320   template <typename... Args>
321   T& emplace(Args&&... args);
322 
323   template <typename U, typename... Args>
324   T& emplace(std::initializer_list<U> ilist, Args&&... args);
325 
326   void reset() noexcept;
327 
328   constexpr explicit operator bool() const noexcept;
329   constexpr bool has_value() const noexcept;
330 };
331 
332 template <typename T>
333 constexpr Optional<typename std::decay<T>::type> make_optional(T&& v);
334 
335 template <typename T, typename... Args>
336 constexpr Optional<T> make_optional(Args&&... args);
337 
338 template <typename T, typename U, typename... Args>
339 constexpr Optional<T> make_optional(std::initializer_list<U> il,
340                                     Args&&... args);
341 
342 } // namespace base
343 )";
344 
345 /// Converts `L` to string.
346 static std::string ConvertToString(const SourceLocationsLattice &L,
347                                    const ASTContext &Ctx) {
348   return L.getSourceLocations().empty() ? "safe"
349                                         : "unsafe: " + DebugString(L, Ctx);
350 }
351 
352 /// Replaces all occurrences of `Pattern` in `S` with `Replacement`.
353 static void ReplaceAllOccurrences(std::string &S, const std::string &Pattern,
354                                   const std::string &Replacement) {
355   size_t Pos = 0;
356   while (true) {
357     Pos = S.find(Pattern, Pos);
358     if (Pos == std::string::npos)
359       break;
360     S.replace(Pos, Pattern.size(), Replacement);
361   }
362 }
363 
364 struct OptionalTypeIdentifier {
365   std::string NamespaceName;
366   std::string TypeName;
367 };
368 
369 class UncheckedOptionalAccessTest
370     : public ::testing::TestWithParam<OptionalTypeIdentifier> {
371 protected:
372   template <typename LatticeChecksMatcher>
373   void ExpectLatticeChecksFor(std::string SourceCode,
374                               LatticeChecksMatcher MatchesLatticeChecks) {
375     ExpectLatticeChecksFor(SourceCode, ast_matchers::hasName("target"),
376                            MatchesLatticeChecks);
377   }
378 
379 private:
380   template <typename FuncDeclMatcher, typename LatticeChecksMatcher>
381   void ExpectLatticeChecksFor(std::string SourceCode,
382                               FuncDeclMatcher FuncMatcher,
383                               LatticeChecksMatcher MatchesLatticeChecks) {
384     ReplaceAllOccurrences(SourceCode, "$ns", GetParam().NamespaceName);
385     ReplaceAllOccurrences(SourceCode, "$optional", GetParam().TypeName);
386 
387     std::vector<std::pair<std::string, std::string>> Headers;
388     Headers.emplace_back("std_initializer_list.h", StdInitializerListHeader);
389     Headers.emplace_back("std_type_traits.h", StdTypeTraitsHeader);
390     Headers.emplace_back("std_utility.h", StdUtilityHeader);
391     Headers.emplace_back("std_optional.h", StdOptionalHeader);
392     Headers.emplace_back("absl_optional.h", AbslOptionalHeader);
393     Headers.emplace_back("base_optional.h", BaseOptionalHeader);
394     Headers.emplace_back("unchecked_optional_access_test.h", R"(
395       #include "absl_optional.h"
396       #include "base_optional.h"
397       #include "std_initializer_list.h"
398       #include "std_optional.h"
399       #include "std_utility.h"
400 
401       template <typename T>
402       T Make();
403     )");
404     const tooling::FileContentMappings FileContents(Headers.begin(),
405                                                     Headers.end());
406     llvm::Error Error = checkDataflow<UncheckedOptionalAccessModel>(
407         SourceCode, FuncMatcher,
408         [](ASTContext &Ctx, Environment &) {
409           return UncheckedOptionalAccessModel(Ctx);
410         },
411         [&MatchesLatticeChecks](
412             llvm::ArrayRef<std::pair<
413                 std::string, DataflowAnalysisState<SourceLocationsLattice>>>
414                 CheckToLatticeMap,
415             ASTContext &Ctx) {
416           // FIXME: Consider using a matcher instead of translating
417           // `CheckToLatticeMap` to `CheckToStringifiedLatticeMap`.
418           std::vector<std::pair<std::string, std::string>>
419               CheckToStringifiedLatticeMap;
420           for (const auto &E : CheckToLatticeMap) {
421             CheckToStringifiedLatticeMap.emplace_back(
422                 E.first, ConvertToString(E.second.Lattice, Ctx));
423           }
424           EXPECT_THAT(CheckToStringifiedLatticeMap, MatchesLatticeChecks);
425         },
426         {"-fsyntax-only", "-std=c++17", "-Wno-undefined-inline"}, FileContents);
427     if (Error)
428       FAIL() << llvm::toString(std::move(Error));
429   }
430 };
431 
432 INSTANTIATE_TEST_SUITE_P(
433     UncheckedOptionalUseTestInst, UncheckedOptionalAccessTest,
434     ::testing::Values(OptionalTypeIdentifier{"std", "optional"},
435                       OptionalTypeIdentifier{"absl", "optional"},
436                       OptionalTypeIdentifier{"base", "Optional"}),
437     [](const ::testing::TestParamInfo<OptionalTypeIdentifier> &Info) {
438       return Info.param.NamespaceName;
439     });
440 
441 TEST_P(UncheckedOptionalAccessTest, EmptyFunctionBody) {
442   ExpectLatticeChecksFor(R"(
443     void target() {
444       (void)0;
445       /*[[check]]*/
446     }
447   )",
448                          UnorderedElementsAre(Pair("check", "safe")));
449 }
450 
451 TEST_P(UncheckedOptionalAccessTest, UnwrapUsingValueNoCheck) {
452   ExpectLatticeChecksFor(
453       R"(
454     #include "unchecked_optional_access_test.h"
455 
456     void target($ns::$optional<int> opt) {
457       opt.value();
458       /*[[check]]*/
459     }
460   )",
461       UnorderedElementsAre(Pair("check", "unsafe: input.cc:5:7")));
462 
463   ExpectLatticeChecksFor(
464       R"(
465     #include "unchecked_optional_access_test.h"
466 
467     void target($ns::$optional<int> opt) {
468       std::move(opt).value();
469       /*[[check]]*/
470     }
471   )",
472       UnorderedElementsAre(Pair("check", "unsafe: input.cc:5:7")));
473 }
474 
475 TEST_P(UncheckedOptionalAccessTest, UnwrapUsingOperatorStarNoCheck) {
476   ExpectLatticeChecksFor(
477       R"(
478     #include "unchecked_optional_access_test.h"
479 
480     void target($ns::$optional<int> opt) {
481       *opt;
482       /*[[check]]*/
483     }
484   )",
485       UnorderedElementsAre(Pair("check", "unsafe: input.cc:5:8")));
486 
487   ExpectLatticeChecksFor(
488       R"(
489     #include "unchecked_optional_access_test.h"
490 
491     void target($ns::$optional<int> opt) {
492       *std::move(opt);
493       /*[[check]]*/
494     }
495   )",
496       UnorderedElementsAre(Pair("check", "unsafe: input.cc:5:8")));
497 }
498 
499 TEST_P(UncheckedOptionalAccessTest, UnwrapUsingOperatorArrowNoCheck) {
500   ExpectLatticeChecksFor(
501       R"(
502     #include "unchecked_optional_access_test.h"
503 
504     struct Foo {
505       void foo();
506     };
507 
508     void target($ns::$optional<Foo> opt) {
509       opt->foo();
510       /*[[check]]*/
511     }
512   )",
513       UnorderedElementsAre(Pair("check", "unsafe: input.cc:9:7")));
514 
515   ExpectLatticeChecksFor(
516       R"(
517     #include "unchecked_optional_access_test.h"
518 
519     struct Foo {
520       void foo();
521     };
522 
523     void target($ns::$optional<Foo> opt) {
524       std::move(opt)->foo();
525       /*[[check]]*/
526     }
527   )",
528       UnorderedElementsAre(Pair("check", "unsafe: input.cc:9:7")));
529 }
530 
531 TEST_P(UncheckedOptionalAccessTest, HasValueCheck) {
532   ExpectLatticeChecksFor(R"(
533     #include "unchecked_optional_access_test.h"
534 
535     void target($ns::$optional<int> opt) {
536       if (opt.has_value()) {
537         opt.value();
538         /*[[check]]*/
539       }
540     }
541   )",
542                          UnorderedElementsAre(Pair("check", "safe")));
543 }
544 
545 TEST_P(UncheckedOptionalAccessTest, OperatorBoolCheck) {
546   ExpectLatticeChecksFor(R"(
547     #include "unchecked_optional_access_test.h"
548 
549     void target($ns::$optional<int> opt) {
550       if (opt) {
551         opt.value();
552         /*[[check]]*/
553       }
554     }
555   )",
556                          UnorderedElementsAre(Pair("check", "safe")));
557 }
558 
559 TEST_P(UncheckedOptionalAccessTest, UnwrapFunctionCallResultNoCheck) {
560   ExpectLatticeChecksFor(
561       R"(
562     #include "unchecked_optional_access_test.h"
563 
564     void target() {
565       Make<$ns::$optional<int>>().value();
566       (void)0;
567       /*[[check]]*/
568     }
569   )",
570       UnorderedElementsAre(Pair("check", "unsafe: input.cc:5:7")));
571 
572   ExpectLatticeChecksFor(
573       R"(
574     #include "unchecked_optional_access_test.h"
575 
576     void target($ns::$optional<int> opt) {
577       std::move(opt).value();
578       /*[[check]]*/
579     }
580   )",
581       UnorderedElementsAre(Pair("check", "unsafe: input.cc:5:7")));
582 }
583 
584 TEST_P(UncheckedOptionalAccessTest, DefaultConstructor) {
585   ExpectLatticeChecksFor(
586       R"(
587     #include "unchecked_optional_access_test.h"
588 
589     void target() {
590       $ns::$optional<int> opt;
591       opt.value();
592       /*[[check]]*/
593     }
594   )",
595       UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:7")));
596 }
597 
598 TEST_P(UncheckedOptionalAccessTest, MakeOptional) {
599   ExpectLatticeChecksFor(R"(
600     #include "unchecked_optional_access_test.h"
601 
602     void target() {
603       $ns::$optional<int> opt = $ns::make_optional(0);
604       opt.value();
605       /*[[check]]*/
606     }
607   )",
608                          UnorderedElementsAre(Pair("check", "safe")));
609 
610   ExpectLatticeChecksFor(R"(
611     #include "unchecked_optional_access_test.h"
612 
613     struct Foo {
614       Foo(int, int);
615     };
616 
617     void target() {
618       $ns::$optional<Foo> opt = $ns::make_optional<Foo>(21, 22);
619       opt.value();
620       /*[[check]]*/
621     }
622   )",
623                          UnorderedElementsAre(Pair("check", "safe")));
624 
625   ExpectLatticeChecksFor(R"(
626     #include "unchecked_optional_access_test.h"
627 
628     struct Foo {
629       constexpr Foo(std::initializer_list<char>);
630     };
631 
632     void target() {
633       char a = 'a';
634       $ns::$optional<Foo> opt = $ns::make_optional<Foo>({a});
635       opt.value();
636       /*[[check]]*/
637     }
638   )",
639                          UnorderedElementsAre(Pair("check", "safe")));
640 }
641 
642 TEST_P(UncheckedOptionalAccessTest, ValueOr) {
643   ExpectLatticeChecksFor(R"(
644     #include "unchecked_optional_access_test.h"
645 
646     void target() {
647       $ns::$optional<int> opt;
648       opt.value_or(0);
649       (void)0;
650       /*[[check]]*/
651     }
652   )",
653                          UnorderedElementsAre(Pair("check", "safe")));
654 }
655 
656 TEST_P(UncheckedOptionalAccessTest, Emplace) {
657   ExpectLatticeChecksFor(R"(
658     #include "unchecked_optional_access_test.h"
659 
660     void target() {
661       $ns::$optional<int> opt;
662       opt.emplace(0);
663       opt.value();
664       /*[[check]]*/
665     }
666   )",
667                          UnorderedElementsAre(Pair("check", "safe")));
668 
669   ExpectLatticeChecksFor(R"(
670     #include "unchecked_optional_access_test.h"
671 
672     void target($ns::$optional<int> *opt) {
673       opt->emplace(0);
674       opt->value();
675       /*[[check]]*/
676     }
677   )",
678                          UnorderedElementsAre(Pair("check", "safe")));
679 
680   // FIXME: Add tests that call `emplace` in conditional branches.
681 }
682 
683 TEST_P(UncheckedOptionalAccessTest, Reset) {
684   ExpectLatticeChecksFor(
685       R"(
686     #include "unchecked_optional_access_test.h"
687 
688     void target() {
689       $ns::$optional<int> opt = $ns::make_optional(0);
690       opt.reset();
691       opt.value();
692       /*[[check]]*/
693     }
694   )",
695       UnorderedElementsAre(Pair("check", "unsafe: input.cc:7:7")));
696 
697   ExpectLatticeChecksFor(
698       R"(
699     #include "unchecked_optional_access_test.h"
700 
701     void target() {
702       $ns::$optional<int> *opt;
703       *opt = $ns::make_optional(0);
704       opt->reset();
705       opt->value();
706       /*[[check]]*/
707     }
708   )",
709       UnorderedElementsAre(Pair("check", "unsafe: input.cc:8:7")));
710 
711   // FIXME: Add tests that call `reset` in conditional branches.
712 }
713 
714 // FIXME: Add support for:
715 // - constructors (copy, move, non-standard)
716 // - assignment operators (default, copy, move, non-standard)
717 // - swap
718 // - invalidation (passing optional by non-const reference/pointer)
719 // - `value_or(nullptr) != nullptr`, `value_or(0) != 0`, `value_or("").empty()`
720 // - nested `optional` values
721