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