diff --git a/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h b/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h --- a/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h +++ b/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h @@ -36,10 +36,10 @@ /// A common form of state shared between the cases of a transfer function. template struct TransferState { - TransferState(LatticeT &Lattice, Environment &Env) - : Lattice(Lattice), Env(Env) {} + TransferState(ASTContext &ASTCtx, LatticeT &Lattice, Environment &Env) + : ASTCtx(ASTCtx), Lattice(Lattice), Env(Env) {} - /// Current lattice element. + ASTContext &ASTCtx; LatticeT &Lattice; Environment &Env; }; diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp --- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp +++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp @@ -50,6 +50,36 @@ hasOptionalType()); } +auto hasNulloptType() { + return hasType( + recordDecl(anyOf(hasName("std::nullopt_t"), hasName("absl::nullopt_t"), + hasName("base::nullopt_t")))); +} + +auto inPlaceClass() { + return recordDecl(anyOf(hasName("std::in_place_t"), + hasName("absl::in_place_t"), + hasName("base::in_place_t"))); +} + +auto isOptionalNulloptConstructor() { + return cxxConstructExpr(hasOptionalType(), argumentCountIs(1), + hasArgument(0, hasNulloptType())); +} + +auto isOptionalInPlaceConstructor() { + return cxxConstructExpr(hasOptionalType(), + hasArgument(0, hasType(inPlaceClass()))); +} + +auto isOptionalNonStandardConstructor() { + 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) { @@ -67,6 +97,37 @@ return nullptr; } +/// 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; + 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>`, 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(Type->getAsRecordDecl()) + ->getTemplateArgs() + .get(0) + .getAsType() + .getDesugaredType(ASTCtx)); +} + static void initializeOptionalReference(const Expr *OptionalExpr, LatticeTransferState &State) { if (auto *OptionalVal = cast_or_null( @@ -113,24 +174,39 @@ } } -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 transferNonStandardConstructor(const CXXConstructExpr *E, + LatticeTransferState &State) { + assert(E->getConstructor()->getTemplateSpecializationArgs()->size() > 0); + assert(E->getNumArgs() > 0); + + const int TemplateParamOptionalWrappersCount = countOptionalWrappers( + State.ASTCtx, stripReference(E->getConstructor() + ->getTemplateSpecializationArgs() + ->get(0) + .getAsType())); + const int ArgTypeOptionalWrappersCount = countOptionalWrappers( + State.ASTCtx, stripReference(E->getArg(0)->getType())); + auto *HasValueVal = + (TemplateParamOptionalWrappersCount == ArgTypeOptionalWrappersCount) + // This is a constructor call for optional with argument of type U + // such that T is constructible from U. + ? &State.Env.getBoolLiteralValue(true) + // This is a constructor call for optional with argument of type + // optional 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() { @@ -143,6 +219,21 @@ // make_optional .CaseOf(isMakeOptionalCall(), transferMakeOptionalCall) + // constructors: + .CaseOf( + isOptionalInPlaceConstructor(), + +[](const CXXConstructExpr *E, LatticeTransferState &State) { + assignOptionalValue(*E, State, State.Env.getBoolLiteralValue(true)); + }) + .CaseOf( + isOptionalNulloptConstructor(), + +[](const CXXConstructExpr *E, LatticeTransferState &State) { + assignOptionalValue(*E, State, + State.Env.getBoolLiteralValue(false)); + }) + .CaseOf(isOptionalNonStandardConstructor(), + transferNonStandardConstructor) + // optional::value .CaseOf( isOptionalMemberCallWithName("value"), @@ -167,10 +258,20 @@ transferOptionalHasValueCall) // optional::emplace - .CaseOf(isOptionalMemberCallWithName("emplace"), transferEmplaceCall) + .CaseOf( + isOptionalMemberCallWithName("emplace"), + +[](const CXXMemberCallExpr *E, LatticeTransferState &State) { + assignOptionalValue(*E->getImplicitObjectArgument(), State, + State.Env.getBoolLiteralValue(true)); + }) // optional::reset - .CaseOf(isOptionalMemberCallWithName("reset"), transferResetCall) + .CaseOf( + isOptionalMemberCallWithName("reset"), + +[](const CXXMemberCallExpr *E, LatticeTransferState &State) { + assignOptionalValue(*E->getImplicitObjectArgument(), State, + State.Env.getBoolLiteralValue(false)); + }) .Build(); } @@ -185,7 +286,7 @@ void UncheckedOptionalAccessModel::transfer(const Stmt *S, SourceLocationsLattice &L, Environment &Env) { - LatticeTransferState State(L, Env); + LatticeTransferState State(getASTContext(), L, Env); TransferMatchSwitch(*S, getASTContext(), State); } diff --git a/clang/lib/Analysis/FlowSensitive/Transfer.cpp b/clang/lib/Analysis/FlowSensitive/Transfer.cpp --- a/clang/lib/Analysis/FlowSensitive/Transfer.cpp +++ b/clang/lib/Analysis/FlowSensitive/Transfer.cpp @@ -204,6 +204,11 @@ 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 @@ -216,8 +221,6 @@ 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 --- a/clang/unittests/Analysis/FlowSensitive/MatchSwitchTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/MatchSwitchTest.cpp @@ -116,7 +116,7 @@ static BooleanLattice initialElement() { return BooleanLattice::bottom(); } void transfer(const Stmt *S, BooleanLattice &L, Environment &Env) { - TransferState State(L, Env); + TransferState State(getASTContext(), L, Env); TransferSwitch(*S, getASTContext(), State); } }; diff --git a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp --- a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp @@ -31,8 +31,8 @@ // 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 @@ typedef T type; }; +template +using remove_cv_t = typename remove_cv::type; + template struct decay { private: @@ -139,9 +142,196 @@ typename remove_cv::type>::type>::type type; }; +template +struct enable_if {}; + +template +struct enable_if { + typedef T type; +}; + +template +using enable_if_t = typename enable_if::type; + +template +struct is_same : false_type {}; + +template +struct is_same : true_type {}; + +template +struct is_void : is_same::type> {}; + +namespace detail { + +template +auto try_add_rvalue_reference(int) -> type_identity; +template +auto try_add_rvalue_reference(...) -> type_identity; + +} // namespace detail + +template +struct add_rvalue_reference : decltype(detail::try_add_rvalue_reference(0)) { +}; + +template +typename add_rvalue_reference::type declval() noexcept; + +namespace detail { + +template +auto test_returnable(int) + -> decltype(void(static_cast(nullptr)), true_type{}); +template +auto test_returnable(...) -> false_type; + +template +auto test_implicitly_convertible(int) + -> decltype(void(declval()(declval())), true_type{}); +template +auto test_implicitly_convertible(...) -> false_type; + +} // namespace detail + +template +struct is_convertible + : integral_constant(0))::value && + decltype(detail::test_implicitly_convertible( + 0))::value) || + (is_void::value && is_void::value)> {}; + +template +inline constexpr bool is_convertible_v = is_convertible::value; + +template +using void_t = void; + +template +struct is_constructible_ : false_type {}; + +template +struct is_constructible_()...))>, T, Args...> + : true_type {}; + +template +using is_constructible = is_constructible_, T, Args...>; + +template +inline constexpr bool is_constructible_v = is_constructible::value; + +template +struct __uncvref { + typedef typename remove_cv::type>::type type; +}; + +template +using __uncvref_t = typename __uncvref<_Tp>::type; + +template +using _BoolConstant = integral_constant; + +template +using _IsSame = _BoolConstant<__is_same(_Tp, _Up)>; + +template +using _IsNotSame = _BoolConstant; + +template +struct _MetaBase; +template <> +struct _MetaBase { + template + using _SelectImpl = _Tp; + template