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