[clang] 092a530 - [clang][dataflow] Model the behavior of non-standard optional constructors

Stanislav Gatev via cfe-commits cfe-commits at lists.llvm.org
Tue Mar 15 01:13:37 PDT 2022


Author: Stanislav Gatev
Date: 2022-03-15T08:13:13Z
New Revision: 092a530ca1878df08dc616cb43072044a39fb132

URL: https://github.com/llvm/llvm-project/commit/092a530ca1878df08dc616cb43072044a39fb132
DIFF: https://github.com/llvm/llvm-project/commit/092a530ca1878df08dc616cb43072044a39fb132.diff

LOG: [clang][dataflow] Model the behavior of non-standard optional constructors

Model nullopt, inplace, value, and conversion constructors.

Reviewed-by: ymandel, xazax.hun, gribozavr2

Differential Revision: https://reviews.llvm.org/D121602

Added: 
    

Modified: 
    clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h
    clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
    clang/lib/Analysis/FlowSensitive/Transfer.cpp
    clang/unittests/Analysis/FlowSensitive/MatchSwitchTest.cpp
    clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h b/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h
index b319360627911..2aaaf78f9cd0e 100644
--- a/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h
+++ b/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h
@@ -70,28 +70,24 @@ using MatchSwitch = std::function<void(const Stmt &, ASTContext &, State &)>;
 /// \endcode
 template <typename State> class MatchSwitchBuilder {
 public:
-  // An action is triggered by the match of a pattern against the input
-  // statement. For generality, actions take both the matched statement and the
-  // set of bindings produced by the match.
-  using Action = std::function<void(
-      const Stmt *, const ast_matchers::MatchFinder::MatchResult &, State &)>;
-
-  MatchSwitchBuilder &&CaseOf(ast_matchers::internal::Matcher<Stmt> M,
-                              Action A) && {
-    Matchers.push_back(std::move(M));
-    Actions.push_back(std::move(A));
-    return std::move(*this);
-  }
-
-  // Convenience function for the common case, where bound nodes are not
-  // needed. `Node` should be a subclass of `Stmt`.
+  /// Registers an action that will be triggered by the match of a pattern
+  /// against the input statement.
+  ///
+  /// Requirements:
+  ///
+  ///  `Node` should be a subclass of `Stmt`.
   template <typename Node>
-  MatchSwitchBuilder &&CaseOf(ast_matchers::internal::Matcher<Stmt> M,
-                              void (*Action)(const Node *, State &)) && {
+  MatchSwitchBuilder &&
+  CaseOf(ast_matchers::internal::Matcher<Stmt> M,
+         std::function<void(const Node *,
+                            const ast_matchers::MatchFinder::MatchResult &,
+                            State &)>
+             A) && {
     Matchers.push_back(std::move(M));
-    Actions.push_back([Action](const Stmt *Stmt,
-                               const ast_matchers::MatchFinder::MatchResult &,
-                               State &S) { Action(cast<Node>(Stmt), S); });
+    Actions.push_back(
+        [A = std::move(A)](const Stmt *Stmt,
+                           const ast_matchers::MatchFinder::MatchResult &R,
+                           State &S) { A(cast<Node>(Stmt), R, S); });
     return std::move(*this);
   }
 
@@ -146,7 +142,9 @@ template <typename State> class MatchSwitchBuilder {
   }
 
   std::vector<ast_matchers::internal::DynTypedMatcher> Matchers;
-  std::vector<Action> Actions;
+  std::vector<std::function<void(
+      const Stmt *, const ast_matchers::MatchFinder::MatchResult &, State &)>>
+      Actions;
 };
 } // namespace dataflow
 } // namespace clang

diff  --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
index 0963e8e452448..171b22b7e9130 100644
--- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
@@ -22,7 +22,7 @@ using namespace ::clang::ast_matchers;
 
 using LatticeTransferState = TransferState<SourceLocationsLattice>;
 
-static auto optionalClass() {
+auto optionalClass() {
   return classTemplateSpecializationDecl(
       anyOf(hasName("std::optional"), hasName("std::__optional_storage_base"),
             hasName("__optional_destruct_base"), hasName("absl::optional"),
@@ -30,26 +30,54 @@ static auto optionalClass() {
       hasTemplateArgument(0, refersToType(type().bind("T"))));
 }
 
-static auto hasOptionalType() { return hasType(optionalClass()); }
+auto hasOptionalType() { return hasType(optionalClass()); }
 
-static auto isOptionalMemberCallWithName(llvm::StringRef MemberName) {
+auto isOptionalMemberCallWithName(llvm::StringRef MemberName) {
   return cxxMemberCallExpr(
       on(expr(unless(cxxThisExpr()))),
       callee(cxxMethodDecl(hasName(MemberName), ofClass(optionalClass()))));
 }
 
-static auto isOptionalOperatorCallWithName(llvm::StringRef OperatorName) {
+auto isOptionalOperatorCallWithName(llvm::StringRef OperatorName) {
   return cxxOperatorCallExpr(hasOverloadedOperatorName(OperatorName),
                              callee(cxxMethodDecl(ofClass(optionalClass()))));
 }
 
-static auto isMakeOptionalCall() {
+auto isMakeOptionalCall() {
   return callExpr(
       callee(functionDecl(hasAnyName(
           "std::make_optional", "base::make_optional", "absl::make_optional"))),
       hasOptionalType());
 }
 
+auto hasNulloptType() {
+  return hasType(namedDecl(
+      hasAnyName("std::nullopt_t", "absl::nullopt_t", "base::nullopt_t")));
+}
+
+auto inPlaceClass() {
+  return recordDecl(
+      hasAnyName("std::in_place_t", "absl::in_place_t", "base::in_place_t"));
+}
+
+auto isOptionalNulloptConstructor() {
+  return cxxConstructExpr(hasOptionalType(), argumentCountIs(1),
+                          hasArgument(0, hasNulloptType()));
+}
+
+auto isOptionalInPlaceConstructor() {
+  return cxxConstructExpr(hasOptionalType(),
+                          hasArgument(0, hasType(inPlaceClass())));
+}
+
+auto isOptionalValueOrConversionConstructor() {
+  return cxxConstructExpr(
+      hasOptionalType(),
+      unless(hasDeclaration(
+          cxxConstructorDecl(anyOf(isCopyConstructor(), isMoveConstructor())))),
+      argumentCountIs(1), hasArgument(0, unless(hasNulloptType())));
+}
+
 /// Creates a symbolic value for an `optional` value using `HasValueVal` as the
 /// symbolic value of its "has_value" property.
 StructValue &createOptionalValue(Environment &Env, BoolValue &HasValueVal) {
@@ -60,15 +88,48 @@ StructValue &createOptionalValue(Environment &Env, BoolValue &HasValueVal) {
 
 /// Returns the symbolic value that represents the "has_value" property of the
 /// optional value `Val`. Returns null if `Val` is null.
-static BoolValue *getHasValue(Value *Val) {
+BoolValue *getHasValue(Value *Val) {
   if (auto *OptionalVal = cast_or_null<StructValue>(Val)) {
     return cast<BoolValue>(OptionalVal->getProperty("has_value"));
   }
   return nullptr;
 }
 
-static void initializeOptionalReference(const Expr *OptionalExpr,
-                                        LatticeTransferState &State) {
+/// If `Type` is a reference type, returns the type of its pointee. Otherwise,
+/// returns `Type` itself.
+QualType stripReference(QualType Type) {
+  return Type->isReferenceType() ? Type->getPointeeType() : Type;
+}
+
+/// Returns true if and only if `Type` is an optional type.
+bool IsOptionalType(QualType Type) {
+  if (!Type->isRecordType())
+    return false;
+  // FIXME: Optimize this by avoiding the `getQualifiedNameAsString` call.
+  auto TypeName = Type->getAsCXXRecordDecl()->getQualifiedNameAsString();
+  return TypeName == "std::optional" || TypeName == "absl::optional" ||
+         TypeName == "base::Optional";
+}
+
+/// Returns the number of optional wrappers in `Type`.
+///
+/// For example, if `Type` is `optional<optional<int>>`, the result of this
+/// function will be 2.
+int countOptionalWrappers(const ASTContext &ASTCtx, QualType Type) {
+  if (!IsOptionalType(Type))
+    return 0;
+  return 1 + countOptionalWrappers(
+                 ASTCtx,
+                 cast<ClassTemplateSpecializationDecl>(Type->getAsRecordDecl())
+                     ->getTemplateArgs()
+                     .get(0)
+                     .getAsType()
+                     .getDesugaredType(ASTCtx));
+}
+
+void initializeOptionalReference(const Expr *OptionalExpr,
+                                 const MatchFinder::MatchResult &,
+                                 LatticeTransferState &State) {
   if (auto *OptionalVal = cast_or_null<StructValue>(
           State.Env.getValue(*OptionalExpr, SkipPast::Reference))) {
     if (OptionalVal->getProperty("has_value") == nullptr) {
@@ -77,8 +138,8 @@ static void initializeOptionalReference(const Expr *OptionalExpr,
   }
 }
 
-static void transferUnwrapCall(const Expr *UnwrapExpr, const Expr *ObjectExpr,
-                               LatticeTransferState &State) {
+void transferUnwrapCall(const Expr *UnwrapExpr, const Expr *ObjectExpr,
+                        LatticeTransferState &State) {
   if (auto *OptionalVal = cast_or_null<StructValue>(
           State.Env.getValue(*ObjectExpr, SkipPast::ReferenceThenPointer))) {
     auto *HasValueVal = getHasValue(OptionalVal);
@@ -92,15 +153,18 @@ static void transferUnwrapCall(const Expr *UnwrapExpr, const Expr *ObjectExpr,
   State.Lattice.getSourceLocations().insert(ObjectExpr->getBeginLoc());
 }
 
-void transferMakeOptionalCall(const CallExpr *E, LatticeTransferState &State) {
+void transferMakeOptionalCall(const CallExpr *E,
+                              const MatchFinder::MatchResult &,
+                              LatticeTransferState &State) {
   auto &Loc = State.Env.createStorageLocation(*E);
   State.Env.setStorageLocation(*E, Loc);
   State.Env.setValue(
       Loc, createOptionalValue(State.Env, State.Env.getBoolLiteralValue(true)));
 }
 
-static void transferOptionalHasValueCall(const CXXMemberCallExpr *CallExpr,
-                                         LatticeTransferState &State) {
+void transferOptionalHasValueCall(const CXXMemberCallExpr *CallExpr,
+                                  const MatchFinder::MatchResult &,
+                                  LatticeTransferState &State) {
   if (auto *OptionalVal = cast_or_null<StructValue>(
           State.Env.getValue(*CallExpr->getImplicitObjectArgument(),
                              SkipPast::ReferenceThenPointer))) {
@@ -113,64 +177,110 @@ static void transferOptionalHasValueCall(const CXXMemberCallExpr *CallExpr,
   }
 }
 
-void transferEmplaceCall(const CXXMemberCallExpr *E,
-                         LatticeTransferState &State) {
-  if (auto *OptionalLoc = State.Env.getStorageLocation(
-          *E->getImplicitObjectArgument(), SkipPast::ReferenceThenPointer)) {
-    State.Env.setValue(
-        *OptionalLoc,
-        createOptionalValue(State.Env, State.Env.getBoolLiteralValue(true)));
+void assignOptionalValue(const Expr &E, LatticeTransferState &State,
+                         BoolValue &HasValueVal) {
+  if (auto *OptionalLoc =
+          State.Env.getStorageLocation(E, SkipPast::ReferenceThenPointer)) {
+    State.Env.setValue(*OptionalLoc,
+                       createOptionalValue(State.Env, HasValueVal));
   }
 }
 
-void transferResetCall(const CXXMemberCallExpr *E,
-                       LatticeTransferState &State) {
-  if (auto *OptionalLoc = State.Env.getStorageLocation(
-          *E->getImplicitObjectArgument(), SkipPast::ReferenceThenPointer)) {
-    State.Env.setValue(
-        *OptionalLoc,
-        createOptionalValue(State.Env, State.Env.getBoolLiteralValue(false)));
-  }
+void transferValueOrConversionConstructor(
+    const CXXConstructExpr *E, const MatchFinder::MatchResult &MatchRes,
+    LatticeTransferState &State) {
+  assert(E->getConstructor()->getTemplateSpecializationArgs()->size() > 0);
+  assert(E->getNumArgs() > 0);
+
+  const int TemplateParamOptionalWrappersCount = countOptionalWrappers(
+      *MatchRes.Context, stripReference(E->getConstructor()
+                                            ->getTemplateSpecializationArgs()
+                                            ->get(0)
+                                            .getAsType()));
+  const int ArgTypeOptionalWrappersCount = countOptionalWrappers(
+      *MatchRes.Context, stripReference(E->getArg(0)->getType()));
+  auto *HasValueVal =
+      (TemplateParamOptionalWrappersCount == ArgTypeOptionalWrappersCount)
+          // This is a constructor call for optional<T> with argument of type U
+          // such that T is constructible from U.
+          ? &State.Env.getBoolLiteralValue(true)
+          // This is a constructor call for optional<T> with argument of type
+          // optional<U> such that T is constructible from U.
+          : getHasValue(State.Env.getValue(*E->getArg(0), SkipPast::Reference));
+  if (HasValueVal == nullptr)
+    HasValueVal = &State.Env.makeAtomicBoolValue();
+
+  assignOptionalValue(*E, State, *HasValueVal);
 }
 
-static auto buildTransferMatchSwitch() {
+auto buildTransferMatchSwitch() {
   return MatchSwitchBuilder<LatticeTransferState>()
       // Attach a symbolic "has_value" state to optional values that we see for
       // the first time.
-      .CaseOf(expr(anyOf(declRefExpr(), memberExpr()), hasOptionalType()),
-              initializeOptionalReference)
+      .CaseOf<Expr>(expr(anyOf(declRefExpr(), memberExpr()), hasOptionalType()),
+                    initializeOptionalReference)
 
       // make_optional
-      .CaseOf(isMakeOptionalCall(), transferMakeOptionalCall)
+      .CaseOf<CallExpr>(isMakeOptionalCall(), transferMakeOptionalCall)
+
+      // constructors:
+      .CaseOf<CXXConstructExpr>(
+          isOptionalInPlaceConstructor(),
+          [](const CXXConstructExpr *E, const MatchFinder::MatchResult &,
+             LatticeTransferState &State) {
+            assignOptionalValue(*E, State, State.Env.getBoolLiteralValue(true));
+          })
+      .CaseOf<CXXConstructExpr>(
+          isOptionalNulloptConstructor(),
+          [](const CXXConstructExpr *E, const MatchFinder::MatchResult &,
+             LatticeTransferState &State) {
+            assignOptionalValue(*E, State,
+                                State.Env.getBoolLiteralValue(false));
+          })
+      .CaseOf<CXXConstructExpr>(isOptionalValueOrConversionConstructor(),
+                                transferValueOrConversionConstructor)
 
       // optional::value
-      .CaseOf(
+      .CaseOf<CXXMemberCallExpr>(
           isOptionalMemberCallWithName("value"),
-          +[](const CXXMemberCallExpr *E, LatticeTransferState &State) {
+          [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
+             LatticeTransferState &State) {
             transferUnwrapCall(E, E->getImplicitObjectArgument(), State);
           })
 
       // optional::operator*, optional::operator->
-      .CaseOf(
-          expr(anyOf(isOptionalOperatorCallWithName("*"),
-                     isOptionalOperatorCallWithName("->"))),
-          +[](const CallExpr *E, LatticeTransferState &State) {
-            transferUnwrapCall(E, E->getArg(0), State);
-          })
+      .CaseOf<CallExpr>(expr(anyOf(isOptionalOperatorCallWithName("*"),
+                                   isOptionalOperatorCallWithName("->"))),
+                        [](const CallExpr *E, const MatchFinder::MatchResult &,
+                           LatticeTransferState &State) {
+                          transferUnwrapCall(E, E->getArg(0), State);
+                        })
 
       // optional::has_value
-      .CaseOf(isOptionalMemberCallWithName("has_value"),
-              transferOptionalHasValueCall)
+      .CaseOf<CXXMemberCallExpr>(isOptionalMemberCallWithName("has_value"),
+                                 transferOptionalHasValueCall)
 
       // optional::operator bool
-      .CaseOf(isOptionalMemberCallWithName("operator bool"),
-              transferOptionalHasValueCall)
+      .CaseOf<CXXMemberCallExpr>(isOptionalMemberCallWithName("operator bool"),
+                                 transferOptionalHasValueCall)
 
       // optional::emplace
-      .CaseOf(isOptionalMemberCallWithName("emplace"), transferEmplaceCall)
+      .CaseOf<CXXMemberCallExpr>(
+          isOptionalMemberCallWithName("emplace"),
+          [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
+             LatticeTransferState &State) {
+            assignOptionalValue(*E->getImplicitObjectArgument(), State,
+                                State.Env.getBoolLiteralValue(true));
+          })
 
       // optional::reset
-      .CaseOf(isOptionalMemberCallWithName("reset"), transferResetCall)
+      .CaseOf<CXXMemberCallExpr>(
+          isOptionalMemberCallWithName("reset"),
+          [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
+             LatticeTransferState &State) {
+            assignOptionalValue(*E->getImplicitObjectArgument(), State,
+                                State.Env.getBoolLiteralValue(false));
+          })
 
       .Build();
 }

diff  --git a/clang/lib/Analysis/FlowSensitive/Transfer.cpp b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
index 0430bc3c6eb18..f89f1f2705f65 100644
--- a/clang/lib/Analysis/FlowSensitive/Transfer.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
@@ -179,6 +179,11 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
       Env.setValue(ExprLoc, *SubExprVal);
       break;
     }
+    case CK_UncheckedDerivedToBase:
+    case CK_ConstructorConversion:
+    case CK_UserDefinedConversion:
+      // FIXME: Add tests that excercise CK_UncheckedDerivedToBase,
+      // CK_ConstructorConversion, and CK_UserDefinedConversion.
     case CK_NoOp: {
       // FIXME: Consider making `Environment::getStorageLocation` skip noop
       // expressions (this and other similar expressions in the file) instead of
@@ -191,8 +196,6 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
       break;
     }
     default:
-      // FIXME: Add support for CK_UserDefinedConversion,
-      // CK_ConstructorConversion, CK_UncheckedDerivedToBase.
       break;
     }
   }

diff  --git a/clang/unittests/Analysis/FlowSensitive/MatchSwitchTest.cpp b/clang/unittests/Analysis/FlowSensitive/MatchSwitchTest.cpp
index b99e5c6e1c0b0..bd2ae0e96c01e 100644
--- a/clang/unittests/Analysis/FlowSensitive/MatchSwitchTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/MatchSwitchTest.cpp
@@ -88,6 +88,7 @@ MATCHER_P(Holds, m,
 }
 
 void TransferSetTrue(const DeclRefExpr *,
+                     const ast_matchers::MatchFinder::MatchResult &,
                      TransferState<BooleanLattice> &State) {
   State.Lattice = BooleanLattice(true);
 }
@@ -107,9 +108,10 @@ class TestAnalysis : public DataflowAnalysis<TestAnalysis, BooleanLattice> {
     using namespace ast_matchers;
     TransferSwitch =
         MatchSwitchBuilder<TransferState<BooleanLattice>>()
-            .CaseOf(declRefExpr(to(varDecl(hasName("X")))), TransferSetTrue)
-            .CaseOf(callExpr(callee(functionDecl(hasName("Foo")))),
-                    TransferSetFalse)
+            .CaseOf<DeclRefExpr>(declRefExpr(to(varDecl(hasName("X")))),
+                                 TransferSetTrue)
+            .CaseOf<Stmt>(callExpr(callee(functionDecl(hasName("Foo")))),
+                          TransferSetFalse)
             .Build();
   }
 

diff  --git a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
index 3ea39c6be9993..943ceb6460e53 100644
--- a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
@@ -31,8 +31,8 @@ using ::testing::UnorderedElementsAre;
 
 // FIXME: Move header definitions in separate file(s).
 static constexpr char StdTypeTraitsHeader[] = R"(
-#ifndef TYPE_TRAITS_H
-#define TYPE_TRAITS_H
+#ifndef STD_TYPE_TRAITS_H
+#define STD_TYPE_TRAITS_H
 
 namespace std {
 
@@ -127,6 +127,9 @@ struct remove_cv<const volatile T> {
   typedef T type;
 };
 
+template <class T>
+using remove_cv_t = typename remove_cv<T>::type;
+
 template <class T>
 struct decay {
  private:
@@ -139,9 +142,196 @@ struct decay {
                            typename remove_cv<U>::type>::type>::type type;
 };
 
+template <bool B, class T = void>
+struct enable_if {};
+
+template <class T>
+struct enable_if<true, T> {
+  typedef T type;
+};
+
+template <bool B, class T = void>
+using enable_if_t = typename enable_if<B, T>::type;
+
+template <class T, class U>
+struct is_same : false_type {};
+
+template <class T>
+struct is_same<T, T> : true_type {};
+
+template <class T>
+struct is_void : is_same<void, typename remove_cv<T>::type> {};
+
+namespace detail {
+
+template <class T>
+auto try_add_rvalue_reference(int) -> type_identity<T&&>;
+template <class T>
+auto try_add_rvalue_reference(...) -> type_identity<T>;
+
+}  // namespace detail
+
+template <class T>
+struct add_rvalue_reference : decltype(detail::try_add_rvalue_reference<T>(0)) {
+};
+
+template <class T>
+typename add_rvalue_reference<T>::type declval() noexcept;
+
+namespace detail {
+
+template <class T>
+auto test_returnable(int)
+    -> decltype(void(static_cast<T (*)()>(nullptr)), true_type{});
+template <class>
+auto test_returnable(...) -> false_type;
+
+template <class From, class To>
+auto test_implicitly_convertible(int)
+    -> decltype(void(declval<void (&)(To)>()(declval<From>())), true_type{});
+template <class, class>
+auto test_implicitly_convertible(...) -> false_type;
+
+}  // namespace detail
+
+template <class From, class To>
+struct is_convertible
+    : integral_constant<bool,
+                        (decltype(detail::test_returnable<To>(0))::value &&
+                         decltype(detail::test_implicitly_convertible<From, To>(
+                             0))::value) ||
+                            (is_void<From>::value && is_void<To>::value)> {};
+
+template <class From, class To>
+inline constexpr bool is_convertible_v = is_convertible<From, To>::value;
+
+template <class...>
+using void_t = void;
+
+template <class, class T, class... Args>
+struct is_constructible_ : false_type {};
+
+template <class T, class... Args>
+struct is_constructible_<void_t<decltype(T(declval<Args>()...))>, T, Args...>
+    : true_type {};
+
+template <class T, class... Args>
+using is_constructible = is_constructible_<void_t<>, T, Args...>;
+
+template <class T, class... Args>
+inline constexpr bool is_constructible_v = is_constructible<T, Args...>::value;
+
+template <class _Tp>
+struct __uncvref {
+  typedef typename remove_cv<typename remove_reference<_Tp>::type>::type type;
+};
+
+template <class _Tp>
+using __uncvref_t = typename __uncvref<_Tp>::type;
+
+template <bool _Val>
+using _BoolConstant = integral_constant<bool, _Val>;
+
+template <class _Tp, class _Up>
+using _IsSame = _BoolConstant<__is_same(_Tp, _Up)>;
+
+template <class _Tp, class _Up>
+using _IsNotSame = _BoolConstant<!__is_same(_Tp, _Up)>;
+
+template <bool>
+struct _MetaBase;
+template <>
+struct _MetaBase<true> {
+  template <class _Tp, class _Up>
+  using _SelectImpl = _Tp;
+  template <template <class...> class _FirstFn, template <class...> class,
+            class... _Args>
+  using _SelectApplyImpl = _FirstFn<_Args...>;
+  template <class _First, class...>
+  using _FirstImpl = _First;
+  template <class, class _Second, class...>
+  using _SecondImpl = _Second;
+  template <class _Result, class _First, class... _Rest>
+  using _OrImpl =
+      typename _MetaBase<_First::value != true && sizeof...(_Rest) != 0>::
+          template _OrImpl<_First, _Rest...>;
+};
+
+template <>
+struct _MetaBase<false> {
+  template <class _Tp, class _Up>
+  using _SelectImpl = _Up;
+  template <template <class...> class, template <class...> class _SecondFn,
+            class... _Args>
+  using _SelectApplyImpl = _SecondFn<_Args...>;
+  template <class _Result, class...>
+  using _OrImpl = _Result;
+};
+
+template <bool _Cond, class _IfRes, class _ElseRes>
+using _If = typename _MetaBase<_Cond>::template _SelectImpl<_IfRes, _ElseRes>;
+
+template <class... _Rest>
+using _Or = typename _MetaBase<sizeof...(_Rest) !=
+                               0>::template _OrImpl<false_type, _Rest...>;
+
+template <bool _Bp, class _Tp = void>
+using __enable_if_t = typename enable_if<_Bp, _Tp>::type;
+
+template <class...>
+using __expand_to_true = true_type;
+template <class... _Pred>
+__expand_to_true<__enable_if_t<_Pred::value>...> __and_helper(int);
+template <class...>
+false_type __and_helper(...);
+template <class... _Pred>
+using _And = decltype(__and_helper<_Pred...>(0));
+
+struct __check_tuple_constructor_fail {
+  static constexpr bool __enable_explicit_default() { return false; }
+  static constexpr bool __enable_implicit_default() { return false; }
+  template <class...>
+  static constexpr bool __enable_explicit() {
+    return false;
+  }
+  template <class...>
+  static constexpr bool __enable_implicit() {
+    return false;
+  }
+};
+
 } // namespace std
 
-#endif // TYPE_TRAITS_H
+#endif // STD_TYPE_TRAITS_H
+)";
+
+static constexpr char AbslTypeTraitsHeader[] = R"(
+#ifndef ABSL_TYPE_TRAITS_H
+#define ABSL_TYPE_TRAITS_H
+
+#include "std_type_traits.h"
+
+namespace absl {
+
+template <typename... Ts>
+struct conjunction : std::true_type {};
+
+template <typename T, typename... Ts>
+struct conjunction<T, Ts...>
+    : std::conditional<T::value, conjunction<Ts...>, T>::type {};
+
+template <typename T>
+struct conjunction<T> : T {};
+
+template <typename T>
+struct negation : std::integral_constant<bool, !T::value> {};
+
+template <bool B, typename T = void>
+using enable_if_t = typename std::enable_if<B, T>::type;
+
+} // namespace absl
+
+#endif // ABSL_TYPE_TRAITS_H
 )";
 
 static constexpr char StdUtilityHeader[] = R"(
@@ -184,39 +374,152 @@ static constexpr char StdOptionalHeader[] = R"(
 
 namespace std {
 
-template <typename T>
-class optional {
+struct in_place_t {};
+constexpr in_place_t in_place;
+
+struct nullopt_t {
+  constexpr explicit nullopt_t() {}
+};
+constexpr nullopt_t nullopt;
+
+template <class _Tp>
+struct __optional_destruct_base {
+  constexpr void reset() noexcept;
+};
+
+template <class _Tp>
+struct __optional_storage_base : __optional_destruct_base<_Tp> {
+  constexpr bool has_value() const noexcept;
+};
+
+template <typename _Tp>
+class optional : private __optional_storage_base<_Tp> {
+  using __base = __optional_storage_base<_Tp>;
+
  public:
-  constexpr optional() noexcept;
+  using value_type = _Tp;
 
-  const T& operator*() const&;
-  T& operator*() &;
-  const T&& operator*() const&&;
-  T&& operator*() &&;
+ private:
+  struct _CheckOptionalArgsConstructor {
+    template <class _Up>
+    static constexpr bool __enable_implicit() {
+      return is_constructible_v<_Tp, _Up&&> && is_convertible_v<_Up&&, _Tp>;
+    }
 
-  const T* operator->() const;
-  T* operator->();
+    template <class _Up>
+    static constexpr bool __enable_explicit() {
+      return is_constructible_v<_Tp, _Up&&> && !is_convertible_v<_Up&&, _Tp>;
+    }
+  };
+  template <class _Up>
+  using _CheckOptionalArgsCtor =
+      _If<_IsNotSame<__uncvref_t<_Up>, in_place_t>::value &&
+              _IsNotSame<__uncvref_t<_Up>, optional>::value,
+          _CheckOptionalArgsConstructor, __check_tuple_constructor_fail>;
+  template <class _QualUp>
+  struct _CheckOptionalLikeConstructor {
+    template <class _Up, class _Opt = optional<_Up>>
+    using __check_constructible_from_opt =
+        _Or<is_constructible<_Tp, _Opt&>, is_constructible<_Tp, _Opt const&>,
+            is_constructible<_Tp, _Opt&&>, is_constructible<_Tp, _Opt const&&>,
+            is_convertible<_Opt&, _Tp>, is_convertible<_Opt const&, _Tp>,
+            is_convertible<_Opt&&, _Tp>, is_convertible<_Opt const&&, _Tp>>;
+    template <class _Up, class _QUp = _QualUp>
+    static constexpr bool __enable_implicit() {
+      return is_convertible<_QUp, _Tp>::value &&
+             !__check_constructible_from_opt<_Up>::value;
+    }
+    template <class _Up, class _QUp = _QualUp>
+    static constexpr bool __enable_explicit() {
+      return !is_convertible<_QUp, _Tp>::value &&
+             !__check_constructible_from_opt<_Up>::value;
+    }
+  };
 
-  const T& value() const&;
-  T& value() &;
-  const T&& value() const&&;
-  T&& value() &&;
+  template <class _Up, class _QualUp>
+  using _CheckOptionalLikeCtor =
+      _If<_And<_IsNotSame<_Up, _Tp>, is_constructible<_Tp, _QualUp>>::value,
+          _CheckOptionalLikeConstructor<_QualUp>,
+          __check_tuple_constructor_fail>;
+
+ public:
+  constexpr optional() noexcept {}
+  constexpr optional(const optional&) = default;
+  constexpr optional(optional&&) = default;
+  constexpr optional(nullopt_t) noexcept {}
+
+  template <
+      class _InPlaceT, class... _Args,
+      class = enable_if_t<_And<_IsSame<_InPlaceT, in_place_t>,
+                             is_constructible<value_type, _Args...>>::value>>
+  constexpr explicit optional(_InPlaceT, _Args&&... __args);
+
+  template <class _Up, class... _Args,
+            class = enable_if_t<is_constructible_v<
+                value_type, initializer_list<_Up>&, _Args...>>>
+  constexpr explicit optional(in_place_t, initializer_list<_Up> __il,
+                              _Args&&... __args);
+
+  template <
+      class _Up = value_type,
+      enable_if_t<_CheckOptionalArgsCtor<_Up>::template __enable_implicit<_Up>(),
+                int> = 0>
+  constexpr optional(_Up&& __v);
+
+  template <
+      class _Up,
+      enable_if_t<_CheckOptionalArgsCtor<_Up>::template __enable_explicit<_Up>(),
+                int> = 0>
+  constexpr explicit optional(_Up&& __v);
+
+  template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up const&>::
+                                     template __enable_implicit<_Up>(),
+                                 int> = 0>
+  constexpr optional(const optional<_Up>& __v);
+
+  template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up const&>::
+                                     template __enable_explicit<_Up>(),
+                                 int> = 0>
+  constexpr explicit optional(const optional<_Up>& __v);
+
+  template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up&&>::
+                                     template __enable_implicit<_Up>(),
+                                 int> = 0>
+  constexpr optional(optional<_Up>&& __v);
+
+  template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up&&>::
+                                     template __enable_explicit<_Up>(),
+                                 int> = 0>
+  constexpr explicit optional(optional<_Up>&& __v);
+
+  const _Tp& operator*() const&;
+  _Tp& operator*() &;
+  const _Tp&& operator*() const&&;
+  _Tp&& operator*() &&;
+
+  const _Tp* operator->() const;
+  _Tp* operator->();
+
+  const _Tp& value() const&;
+  _Tp& value() &;
+  const _Tp&& value() const&&;
+  _Tp&& value() &&;
 
   template <typename U>
-  constexpr T value_or(U&& v) const&;
+  constexpr _Tp value_or(U&& v) const&;
   template <typename U>
-  T value_or(U&& v) &&;
+  _Tp value_or(U&& v) &&;
 
   template <typename... Args>
-  T& emplace(Args&&... args);
+  _Tp& emplace(Args&&... args);
 
   template <typename U, typename... Args>
-  T& emplace(std::initializer_list<U> ilist, Args&&... args);
+  _Tp& emplace(std::initializer_list<U> ilist, Args&&... args);
 
-  void reset() noexcept;
+  using __base::reset;
 
   constexpr explicit operator bool() const noexcept;
-  constexpr bool has_value() const noexcept;
+  using __base::has_value;
 };
 
 template <typename T>
@@ -233,17 +536,136 @@ constexpr optional<T> make_optional(std::initializer_list<U> il,
 )";
 
 static constexpr char AbslOptionalHeader[] = R"(
+#include "absl_type_traits.h"
 #include "std_initializer_list.h"
 #include "std_type_traits.h"
 #include "std_utility.h"
 
 namespace absl {
 
+struct nullopt_t {
+  constexpr explicit nullopt_t() {}
+};
+constexpr nullopt_t nullopt;
+
+struct in_place_t {};
+constexpr in_place_t in_place;
+
+template <typename T>
+class optional;
+
+namespace optional_internal {
+
+// Whether T is constructible or convertible from optional<U>.
+template <typename T, typename U>
+struct is_constructible_convertible_from_optional
+    : std::integral_constant<
+          bool, std::is_constructible<T, optional<U>&>::value ||
+                    std::is_constructible<T, optional<U>&&>::value ||
+                    std::is_constructible<T, const optional<U>&>::value ||
+                    std::is_constructible<T, const optional<U>&&>::value ||
+                    std::is_convertible<optional<U>&, T>::value ||
+                    std::is_convertible<optional<U>&&, T>::value ||
+                    std::is_convertible<const optional<U>&, T>::value ||
+                    std::is_convertible<const optional<U>&&, T>::value> {};
+
+}  // namespace optional_internal
+
 template <typename T>
 class optional {
  public:
   constexpr optional() noexcept;
 
+  constexpr optional(nullopt_t) noexcept;
+
+  optional(const optional&) = default;
+
+  optional(optional&&) = default;
+
+  template <typename InPlaceT, typename... Args,
+            absl::enable_if_t<absl::conjunction<
+                std::is_same<InPlaceT, in_place_t>,
+                std::is_constructible<T, Args&&...>>::value>* = nullptr>
+  constexpr explicit optional(InPlaceT, Args&&... args);
+
+  template <typename U, typename... Args,
+            typename = typename std::enable_if<std::is_constructible<
+                T, std::initializer_list<U>&, Args&&...>::value>::type>
+  constexpr explicit optional(in_place_t, std::initializer_list<U> il,
+                              Args&&... args);
+
+  template <
+      typename U = T,
+      typename std::enable_if<
+          absl::conjunction<absl::negation<std::is_same<
+                                in_place_t, typename std::decay<U>::type>>,
+                            absl::negation<std::is_same<
+                                optional<T>, typename std::decay<U>::type>>,
+                            std::is_convertible<U&&, T>,
+                            std::is_constructible<T, U&&>>::value,
+          bool>::type = false>
+  constexpr optional(U&& v);
+
+  template <
+      typename U = T,
+      typename std::enable_if<
+          absl::conjunction<absl::negation<std::is_same<
+                                in_place_t, typename std::decay<U>::type>>,
+                            absl::negation<std::is_same<
+                                optional<T>, typename std::decay<U>::type>>,
+                            absl::negation<std::is_convertible<U&&, T>>,
+                            std::is_constructible<T, U&&>>::value,
+          bool>::type = false>
+  explicit constexpr optional(U&& v);
+
+  template <typename U,
+            typename std::enable_if<
+                absl::conjunction<
+                    absl::negation<std::is_same<T, U>>,
+                    std::is_constructible<T, const U&>,
+                    absl::negation<
+                        optional_internal::
+                            is_constructible_convertible_from_optional<T, U>>,
+                    std::is_convertible<const U&, T>>::value,
+                bool>::type = false>
+  optional(const optional<U>& rhs);
+
+  template <typename U,
+            typename std::enable_if<
+                absl::conjunction<
+                    absl::negation<std::is_same<T, U>>,
+                    std::is_constructible<T, const U&>,
+                    absl::negation<
+                        optional_internal::
+                            is_constructible_convertible_from_optional<T, U>>,
+                    absl::negation<std::is_convertible<const U&, T>>>::value,
+                bool>::type = false>
+  explicit optional(const optional<U>& rhs);
+
+  template <
+      typename U,
+      typename std::enable_if<
+          absl::conjunction<
+              absl::negation<std::is_same<T, U>>, std::is_constructible<T, U&&>,
+              absl::negation<
+                  optional_internal::is_constructible_convertible_from_optional<
+                      T, U>>,
+              std::is_convertible<U&&, T>>::value,
+          bool>::type = false>
+  optional(optional<U>&& rhs);
+
+  template <
+      typename U,
+      typename std::enable_if<
+          absl::conjunction<
+              absl::negation<std::is_same<T, U>>, std::is_constructible<T, U&&>,
+              absl::negation<
+                  optional_internal::is_constructible_convertible_from_optional<
+                      T, U>>,
+              absl::negation<std::is_convertible<U&&, T>>>::value,
+          bool>::type = false>
+  explicit optional(optional<U>&& rhs);
+
   const T& operator*() const&;
   T& operator*() &;
   const T&& operator*() const&&;
@@ -294,10 +716,107 @@ static constexpr char BaseOptionalHeader[] = R"(
 
 namespace base {
 
+struct in_place_t {};
+constexpr in_place_t in_place;
+
+struct nullopt_t {
+  constexpr explicit nullopt_t() {}
+};
+constexpr nullopt_t nullopt;
+
+template <typename T>
+class Optional;
+
+namespace internal {
+
+template <typename T>
+using RemoveCvRefT = std::remove_cv_t<std::remove_reference_t<T>>;
+
+template <typename T, typename U>
+struct IsConvertibleFromOptional
+    : std::integral_constant<
+          bool, std::is_constructible<T, Optional<U>&>::value ||
+                    std::is_constructible<T, const Optional<U>&>::value ||
+                    std::is_constructible<T, Optional<U>&&>::value ||
+                    std::is_constructible<T, const Optional<U>&&>::value ||
+                    std::is_convertible<Optional<U>&, T>::value ||
+                    std::is_convertible<const Optional<U>&, T>::value ||
+                    std::is_convertible<Optional<U>&&, T>::value ||
+                    std::is_convertible<const Optional<U>&&, T>::value> {};
+
+}  // namespace internal
+
 template <typename T>
 class Optional {
  public:
-  constexpr Optional() noexcept;
+  using value_type = T;
+
+  constexpr Optional() = default;
+  constexpr Optional(const Optional& other) noexcept = default;
+  constexpr Optional(Optional&& other) noexcept = default;
+
+  constexpr Optional(nullopt_t);
+
+  template <typename U,
+            typename std::enable_if<
+                std::is_constructible<T, const U&>::value &&
+                    !internal::IsConvertibleFromOptional<T, U>::value &&
+                    std::is_convertible<const U&, T>::value,
+                bool>::type = false>
+  Optional(const Optional<U>& other) noexcept;
+
+  template <typename U,
+            typename std::enable_if<
+                std::is_constructible<T, const U&>::value &&
+                    !internal::IsConvertibleFromOptional<T, U>::value &&
+                    !std::is_convertible<const U&, T>::value,
+                bool>::type = false>
+  explicit Optional(const Optional<U>& other) noexcept;
+
+  template <typename U,
+            typename std::enable_if<
+                std::is_constructible<T, U&&>::value &&
+                    !internal::IsConvertibleFromOptional<T, U>::value &&
+                    std::is_convertible<U&&, T>::value,
+                bool>::type = false>
+  Optional(Optional<U>&& other) noexcept;
+
+  template <typename U,
+            typename std::enable_if<
+                std::is_constructible<T, U&&>::value &&
+                    !internal::IsConvertibleFromOptional<T, U>::value &&
+                    !std::is_convertible<U&&, T>::value,
+                bool>::type = false>
+  explicit Optional(Optional<U>&& other) noexcept;
+
+  template <class... Args>
+  constexpr explicit Optional(in_place_t, Args&&... args);
+
+  template <class U, class... Args,
+            class = typename std::enable_if<std::is_constructible<
+                value_type, std::initializer_list<U>&, Args...>::value>::type>
+  constexpr explicit Optional(in_place_t, std::initializer_list<U> il,
+                              Args&&... args);
+
+  template <
+      typename U = value_type,
+      typename std::enable_if<
+          std::is_constructible<T, U&&>::value &&
+              !std::is_same<internal::RemoveCvRefT<U>, in_place_t>::value &&
+              !std::is_same<internal::RemoveCvRefT<U>, Optional<T>>::value &&
+              std::is_convertible<U&&, T>::value,
+          bool>::type = false>
+  constexpr Optional(U&& value);
+
+  template <
+      typename U = value_type,
+      typename std::enable_if<
+          std::is_constructible<T, U&&>::value &&
+              !std::is_same<internal::RemoveCvRefT<U>, in_place_t>::value &&
+              !std::is_same<internal::RemoveCvRefT<U>, Optional<T>>::value &&
+              !std::is_convertible<U&&, T>::value,
+          bool>::type = false>
+  constexpr explicit Optional(U&& value);
 
   const T& operator*() const&;
   T& operator*() &;
@@ -389,6 +908,7 @@ class UncheckedOptionalAccessTest
     Headers.emplace_back("std_type_traits.h", StdTypeTraitsHeader);
     Headers.emplace_back("std_utility.h", StdUtilityHeader);
     Headers.emplace_back("std_optional.h", StdOptionalHeader);
+    Headers.emplace_back("absl_type_traits.h", AbslTypeTraitsHeader);
     Headers.emplace_back("absl_optional.h", AbslOptionalHeader);
     Headers.emplace_back("base_optional.h", BaseOptionalHeader);
     Headers.emplace_back("unchecked_optional_access_test.h", R"(
@@ -595,6 +1115,250 @@ TEST_P(UncheckedOptionalAccessTest, DefaultConstructor) {
       UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:7")));
 }
 
+TEST_P(UncheckedOptionalAccessTest, NulloptConstructor) {
+  ExpectLatticeChecksFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt($ns::nullopt);
+      opt.value();
+      /*[[check]]*/
+    }
+  )",
+      UnorderedElementsAre(Pair("check", "unsafe: input.cc:6:7")));
+}
+
+TEST_P(UncheckedOptionalAccessTest, InPlaceConstructor) {
+  ExpectLatticeChecksFor(R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt($ns::in_place, 3);
+      opt.value();
+      /*[[check]]*/
+    }
+  )",
+                         UnorderedElementsAre(Pair("check", "safe")));
+
+  ExpectLatticeChecksFor(R"(
+    #include "unchecked_optional_access_test.h"
+
+    struct Foo {};
+
+    void target() {
+      $ns::$optional<Foo> opt($ns::in_place);
+      opt.value();
+      /*[[check]]*/
+    }
+  )",
+                         UnorderedElementsAre(Pair("check", "safe")));
+
+  ExpectLatticeChecksFor(R"(
+    #include "unchecked_optional_access_test.h"
+
+    struct Foo {
+      explicit Foo(int, bool);
+    };
+
+    void target() {
+      $ns::$optional<Foo> opt($ns::in_place, 3, false);
+      opt.value();
+      /*[[check]]*/
+    }
+  )",
+                         UnorderedElementsAre(Pair("check", "safe")));
+
+  ExpectLatticeChecksFor(R"(
+    #include "unchecked_optional_access_test.h"
+
+    struct Foo {
+      explicit Foo(std::initializer_list<int>);
+    };
+
+    void target() {
+      $ns::$optional<Foo> opt($ns::in_place, {3});
+      opt.value();
+      /*[[check]]*/
+    }
+  )",
+                         UnorderedElementsAre(Pair("check", "safe")));
+}
+
+TEST_P(UncheckedOptionalAccessTest, ValueConstructor) {
+  ExpectLatticeChecksFor(R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt(21);
+      opt.value();
+      /*[[check]]*/
+    }
+  )",
+                         UnorderedElementsAre(Pair("check", "safe")));
+
+  ExpectLatticeChecksFor(R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<int> opt = $ns::$optional<int>(21);
+      opt.value();
+      /*[[check]]*/
+    }
+  )",
+                         UnorderedElementsAre(Pair("check", "safe")));
+  ExpectLatticeChecksFor(R"(
+    #include "unchecked_optional_access_test.h"
+
+    void target() {
+      $ns::$optional<$ns::$optional<int>> opt(Make<$ns::$optional<int>>());
+      opt.value();
+      /*[[check]]*/
+    }
+  )",
+                         UnorderedElementsAre(Pair("check", "safe")));
+
+  ExpectLatticeChecksFor(R"(
+    #include "unchecked_optional_access_test.h"
+
+    struct MyString {
+      MyString(const char*);
+    };
+
+    void target() {
+      $ns::$optional<MyString> opt("foo");
+      opt.value();
+      /*[[check]]*/
+    }
+  )",
+                         UnorderedElementsAre(Pair("check", "safe")));
+
+  ExpectLatticeChecksFor(R"(
+    #include "unchecked_optional_access_test.h"
+
+    struct Foo {};
+
+    struct Bar {
+      Bar(const Foo&);
+    };
+
+    void target() {
+      $ns::$optional<Bar> opt(Make<Foo>());
+      opt.value();
+      /*[[check]]*/
+    }
+  )",
+                         UnorderedElementsAre(Pair("check", "safe")));
+
+  ExpectLatticeChecksFor(R"(
+    #include "unchecked_optional_access_test.h"
+
+    struct Foo {
+      explicit Foo(int);
+    };
+
+    void target() {
+      $ns::$optional<Foo> opt(3);
+      opt.value();
+      /*[[check]]*/
+    }
+  )",
+                         UnorderedElementsAre(Pair("check", "safe")));
+}
+
+TEST_P(UncheckedOptionalAccessTest, ConvertibleOptionalConstructor) {
+  ExpectLatticeChecksFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    struct Foo {};
+
+    struct Bar {
+      Bar(const Foo&);
+    };
+
+    void target() {
+      $ns::$optional<Bar> opt(Make<$ns::$optional<Foo>>());
+      opt.value();
+      /*[[check]]*/
+    }
+  )",
+      UnorderedElementsAre(Pair("check", "unsafe: input.cc:12:7")));
+
+  ExpectLatticeChecksFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    struct Foo {};
+
+    struct Bar {
+      explicit Bar(const Foo&);
+    };
+
+    void target() {
+      $ns::$optional<Bar> opt(Make<$ns::$optional<Foo>>());
+      opt.value();
+      /*[[check]]*/
+    }
+  )",
+      UnorderedElementsAre(Pair("check", "unsafe: input.cc:12:7")));
+
+  ExpectLatticeChecksFor(
+      R"(
+    #include "unchecked_optional_access_test.h"
+
+    struct Foo {};
+
+    struct Bar {
+      Bar(const Foo&);
+    };
+
+    void target() {
+      $ns::$optional<Foo> opt1 = $ns::nullopt;
+      $ns::$optional<Bar> opt2(opt1);
+      opt2.value();
+      /*[[check]]*/
+    }
+  )",
+      UnorderedElementsAre(Pair("check", "unsafe: input.cc:13:7")));
+
+  ExpectLatticeChecksFor(R"(
+    #include "unchecked_optional_access_test.h"
+
+    struct Foo {};
+
+    struct Bar {
+      Bar(const Foo&);
+    };
+
+    void target() {
+      $ns::$optional<Foo> opt1(Make<Foo>());
+      $ns::$optional<Bar> opt2(opt1);
+      opt2.value();
+      /*[[check]]*/
+    }
+  )",
+                         UnorderedElementsAre(Pair("check", "safe")));
+
+  ExpectLatticeChecksFor(R"(
+    #include "unchecked_optional_access_test.h"
+
+    struct Foo {};
+
+    struct Bar {
+      explicit Bar(const Foo&);
+    };
+
+    void target() {
+      $ns::$optional<Foo> opt1(Make<Foo>());
+      $ns::$optional<Bar> opt2(opt1);
+      opt2.value();
+      /*[[check]]*/
+    }
+  )",
+                         UnorderedElementsAre(Pair("check", "safe")));
+}
+
 TEST_P(UncheckedOptionalAccessTest, MakeOptional) {
   ExpectLatticeChecksFor(R"(
     #include "unchecked_optional_access_test.h"
@@ -698,21 +1462,21 @@ TEST_P(UncheckedOptionalAccessTest, Reset) {
       R"(
     #include "unchecked_optional_access_test.h"
 
-    void target() {
-      $ns::$optional<int> *opt;
-      *opt = $ns::make_optional(0);
-      opt->reset();
-      opt->value();
-      /*[[check]]*/
+    void target($ns::$optional<int> &opt) {
+      if (opt.has_value()) {
+        opt.reset();
+        opt.value();
+        /*[[check]]*/
+      }
     }
   )",
-      UnorderedElementsAre(Pair("check", "unsafe: input.cc:8:7")));
+      UnorderedElementsAre(Pair("check", "unsafe: input.cc:7:9")));
 
   // FIXME: Add tests that call `reset` in conditional branches.
 }
 
 // FIXME: Add support for:
-// - constructors (copy, move, non-standard)
+// - constructors (copy, move)
 // - assignment operators (default, copy, move, non-standard)
 // - swap
 // - invalidation (passing optional by non-const reference/pointer)


        


More information about the cfe-commits mailing list