xref: /llvm-project/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp (revision af98b0af6705df3859ee3c98525b57025d40d9e8)
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 static constexpr char StdTypeTraitsHeader[] = R"(
33 namespace std {
34 
35 template< class T > struct remove_reference      {typedef T type;};
36 template< class T > struct remove_reference<T&>  {typedef T type;};
37 template< class T > struct remove_reference<T&&> {typedef T type;};
38 
39 template <class T>
40   using remove_reference_t = typename remove_reference<T>::type;
41 
42 } // namespace std
43 )";
44 
45 static constexpr char StdUtilityHeader[] = R"(
46 #include "std_type_traits.h"
47 
48 namespace std {
49 
50 template <typename T>
51 constexpr std::remove_reference_t<T>&& move(T&& x);
52 
53 } // namespace std
54 )";
55 
56 static constexpr char StdOptionalHeader[] = R"(
57 namespace std {
58 
59 template <typename T>
60 class optional {
61  public:
62   constexpr optional() noexcept;
63 
64   const T& operator*() const&;
65   T& operator*() &;
66   const T&& operator*() const&&;
67   T&& operator*() &&;
68 
69   const T* operator->() const;
70   T* operator->();
71 
72   const T& value() const&;
73   T& value() &;
74   const T&& value() const&&;
75   T&& value() &&;
76 
77   constexpr bool has_value() const noexcept;
78 };
79 
80 } // namespace std
81 )";
82 
83 static constexpr char AbslOptionalHeader[] = R"(
84 namespace absl {
85 
86 template <typename T>
87 class optional {
88  public:
89   constexpr optional() noexcept;
90 
91   const T& operator*() const&;
92   T& operator*() &;
93   const T&& operator*() const&&;
94   T&& operator*() &&;
95 
96   const T* operator->() const;
97   T* operator->();
98 
99   const T& value() const&;
100   T& value() &;
101   const T&& value() const&&;
102   T&& value() &&;
103 
104   constexpr bool has_value() const noexcept;
105 };
106 
107 } // namespace absl
108 )";
109 
110 static constexpr char BaseOptionalHeader[] = R"(
111 namespace base {
112 
113 template <typename T>
114 class Optional {
115  public:
116   constexpr Optional() noexcept;
117 
118   const T& operator*() const&;
119   T& operator*() &;
120   const T&& operator*() const&&;
121   T&& operator*() &&;
122 
123   const T* operator->() const;
124   T* operator->();
125 
126   const T& value() const&;
127   T& value() &;
128   const T&& value() const&&;
129   T&& value() &&;
130 
131   constexpr bool has_value() const noexcept;
132 };
133 
134 } // namespace base
135 )";
136 
137 /// Converts `L` to string.
138 static std::string ConvertToString(const SourceLocationsLattice &L,
139                                    const ASTContext &Ctx) {
140   return L.getSourceLocations().empty() ? "safe"
141                                         : "unsafe: " + DebugString(L, Ctx);
142 }
143 
144 /// Replaces all occurrences of `Pattern` in `S` with `Replacement`.
145 static void ReplaceAllOccurrences(std::string &S, const std::string &Pattern,
146                                   const std::string &Replacement) {
147   size_t Pos = 0;
148   while (true) {
149     Pos = S.find(Pattern, Pos);
150     if (Pos == std::string::npos)
151       break;
152     S.replace(Pos, Pattern.size(), Replacement);
153   }
154 }
155 
156 struct OptionalTypeIdentifier {
157   std::string NamespaceName;
158   std::string TypeName;
159 };
160 
161 class UncheckedOptionalAccessTest
162     : public ::testing::TestWithParam<OptionalTypeIdentifier> {
163 protected:
164   template <typename LatticeChecksMatcher>
165   void ExpectLatticeChecksFor(std::string SourceCode,
166                               LatticeChecksMatcher MatchesLatticeChecks) {
167     ExpectLatticeChecksFor(SourceCode, ast_matchers::hasName("target"),
168                            MatchesLatticeChecks);
169   }
170 
171 private:
172   template <typename FuncDeclMatcher, typename LatticeChecksMatcher>
173   void ExpectLatticeChecksFor(std::string SourceCode,
174                               FuncDeclMatcher FuncMatcher,
175                               LatticeChecksMatcher MatchesLatticeChecks) {
176     ReplaceAllOccurrences(SourceCode, "$ns", GetParam().NamespaceName);
177     ReplaceAllOccurrences(SourceCode, "$optional", GetParam().TypeName);
178 
179     std::vector<std::pair<std::string, std::string>> Headers;
180     Headers.emplace_back("std_type_traits.h", StdTypeTraitsHeader);
181     Headers.emplace_back("std_utility.h", StdUtilityHeader);
182     Headers.emplace_back("std_optional.h", StdOptionalHeader);
183     Headers.emplace_back("absl_optional.h", AbslOptionalHeader);
184     Headers.emplace_back("base_optional.h", BaseOptionalHeader);
185     Headers.emplace_back("unchecked_optional_access_test.h", R"(
186       #include "absl_optional.h"
187       #include "base_optional.h"
188       #include "std_optional.h"
189       #include "std_utility.h"
190     )");
191     const tooling::FileContentMappings FileContents(Headers.begin(),
192                                                     Headers.end());
193     llvm::Error Error = checkDataflow<UncheckedOptionalAccessModel>(
194         SourceCode, FuncMatcher,
195         [](ASTContext &Ctx, Environment &) {
196           return UncheckedOptionalAccessModel(Ctx);
197         },
198         [&MatchesLatticeChecks](
199             llvm::ArrayRef<std::pair<
200                 std::string, DataflowAnalysisState<SourceLocationsLattice>>>
201                 CheckToLatticeMap,
202             ASTContext &Ctx) {
203           // FIXME: Consider using a matcher instead of translating
204           // `CheckToLatticeMap` to `CheckToStringifiedLatticeMap`.
205           std::vector<std::pair<std::string, std::string>>
206               CheckToStringifiedLatticeMap;
207           for (const auto &E : CheckToLatticeMap) {
208             CheckToStringifiedLatticeMap.emplace_back(
209                 E.first, ConvertToString(E.second.Lattice, Ctx));
210           }
211           EXPECT_THAT(CheckToStringifiedLatticeMap, MatchesLatticeChecks);
212         },
213         {"-fsyntax-only", "-std=c++17", "-Wno-undefined-inline"}, FileContents);
214     if (Error)
215       FAIL() << llvm::toString(std::move(Error));
216   }
217 };
218 
219 INSTANTIATE_TEST_SUITE_P(
220     UncheckedOptionalUseTestInst, UncheckedOptionalAccessTest,
221     ::testing::Values(OptionalTypeIdentifier{"std", "optional"},
222                       OptionalTypeIdentifier{"absl", "optional"},
223                       OptionalTypeIdentifier{"base", "Optional"}),
224     [](const ::testing::TestParamInfo<OptionalTypeIdentifier> &Info) {
225       return Info.param.NamespaceName;
226     });
227 
228 TEST_P(UncheckedOptionalAccessTest, EmptyFunctionBody) {
229   ExpectLatticeChecksFor(R"(
230     void target() {
231       (void)0;
232       /*[[check]]*/
233     }
234   )",
235                          UnorderedElementsAre(Pair("check", "safe")));
236 }
237 
238 TEST_P(UncheckedOptionalAccessTest, UnwrapUsingValueNoCheck) {
239   ExpectLatticeChecksFor(
240       R"(
241     #include "unchecked_optional_access_test.h"
242 
243     void target($ns::$optional<int> opt) {
244       opt.value();
245       /*[[check]]*/
246     }
247   )",
248       UnorderedElementsAre(Pair("check", "unsafe: input.cc:5:7")));
249 
250   ExpectLatticeChecksFor(
251       R"(
252     #include "unchecked_optional_access_test.h"
253 
254     void target($ns::$optional<int> opt) {
255       std::move(opt).value();
256       /*[[check]]*/
257     }
258   )",
259       UnorderedElementsAre(Pair("check", "unsafe: input.cc:5:7")));
260 }
261 
262 TEST_P(UncheckedOptionalAccessTest, UnwrapUsingOperatorStarNoCheck) {
263   ExpectLatticeChecksFor(
264       R"(
265     #include "unchecked_optional_access_test.h"
266 
267     void target($ns::$optional<int> opt) {
268       *opt;
269       /*[[check]]*/
270     }
271   )",
272       UnorderedElementsAre(Pair("check", "unsafe: input.cc:5:8")));
273 
274   ExpectLatticeChecksFor(
275       R"(
276     #include "unchecked_optional_access_test.h"
277 
278     void target($ns::$optional<int> opt) {
279       *std::move(opt);
280       /*[[check]]*/
281     }
282   )",
283       UnorderedElementsAre(Pair("check", "unsafe: input.cc:5:8")));
284 }
285 
286 TEST_P(UncheckedOptionalAccessTest, UnwrapUsingOperatorArrowNoCheck) {
287   ExpectLatticeChecksFor(
288       R"(
289     #include "unchecked_optional_access_test.h"
290 
291     struct Foo {
292       void foo();
293     };
294 
295     void target($ns::$optional<Foo> opt) {
296       opt->foo();
297       /*[[check]]*/
298     }
299   )",
300       UnorderedElementsAre(Pair("check", "unsafe: input.cc:9:7")));
301 
302   ExpectLatticeChecksFor(
303       R"(
304     #include "unchecked_optional_access_test.h"
305 
306     struct Foo {
307       void foo();
308     };
309 
310     void target($ns::$optional<Foo> opt) {
311       std::move(opt)->foo();
312       /*[[check]]*/
313     }
314   )",
315       UnorderedElementsAre(Pair("check", "unsafe: input.cc:9:7")));
316 }
317 
318 TEST_P(UncheckedOptionalAccessTest, UnwrapWithCheck) {
319   ExpectLatticeChecksFor(R"(
320     #include "unchecked_optional_access_test.h"
321 
322     void target($ns::$optional<int> opt) {
323       if (opt.has_value()) {
324         opt.value();
325         /*[[check]]*/
326       }
327     }
328   )",
329                          UnorderedElementsAre(Pair("check", "safe")));
330 }
331 
332 // FIXME: Add support for:
333 // - constructors (default, copy, move, non-standard)
334 // - assignment operators (default, copy, move, non-standard)
335 // - operator bool
336 // - emplace
337 // - reset
338 // - value_or
339 // - swap
340 // - make_optional
341 // - invalidation (passing optional by non-const reference/pointer)
342