diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 9015872c7d94f..7590a6d2b7522 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -318,6 +318,10 @@ def C11LockChecker : Checker<"C11Lock">, Dependencies<[PthreadLockBase]>, Documentation; +def StdVariantChecker : Checker<"StdVariant">, + HelpText<"Check for bad type access for std::variant.">, + Documentation; + } // end "alpha.core" //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h index 8129ebc8fdc69..0d36587484bf9 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h @@ -78,7 +78,7 @@ enum CallEventKind { class CallEvent; -template +template class CallEventRef : public IntrusiveRefCntPtr { public: CallEventRef(const T *Call) : IntrusiveRefCntPtr(Call) {} @@ -94,8 +94,7 @@ class CallEventRef : public IntrusiveRefCntPtr { // Allow implicit conversions to a superclass type, since CallEventRef // behaves like a pointer-to-const. - template - operator CallEventRef () const { + template operator CallEventRef() const { return this->get(); } }; @@ -124,9 +123,9 @@ class RuntimeDefinition { public: RuntimeDefinition() = default; - RuntimeDefinition(const Decl *InD): D(InD) {} + RuntimeDefinition(const Decl *InD) : D(InD) {} RuntimeDefinition(const Decl *InD, bool Foreign) : D(InD), Foreign(Foreign) {} - RuntimeDefinition(const Decl *InD, const MemRegion *InR): D(InD), R(InR) {} + RuntimeDefinition(const Decl *InD, const MemRegion *InR) : D(InD), R(InR) {} const Decl *getDecl() { return D; } bool isForeign() const { return Foreign; } @@ -207,8 +206,9 @@ class CallEvent { /// Used to specify non-argument regions that will be invalidated as a /// result of this call. - virtual void getExtraInvalidatedValues(ValueList &Values, - RegionAndSymbolInvalidationTraits *ETraits) const {} + virtual void + getExtraInvalidatedValues(ValueList &Values, + RegionAndSymbolInvalidationTraits *ETraits) const {} public: CallEvent &operator=(const CallEvent &) = delete; @@ -231,14 +231,10 @@ class CallEvent { void setForeign(bool B) const { Foreign = B; } /// The state in which the call is being evaluated. - const ProgramStateRef &getState() const { - return State; - } + const ProgramStateRef &getState() const { return State; } /// The context in which the call is being evaluated. - const LocationContext *getLocationContext() const { - return LCtx; - } + const LocationContext *getLocationContext() const { return LCtx; } const CFGBlock::ConstCFGElementRef &getCFGElementRef() const { return ElemRef; @@ -270,7 +266,7 @@ class CallEvent { SourceLocation Loc = D->getLocation(); if (Loc.isValid()) { const SourceManager &SM = - getState()->getStateManager().getContext().getSourceManager(); + getState()->getStateManager().getContext().getSourceManager(); return SM.isInSystemHeader(D->getLocation()); } @@ -324,9 +320,7 @@ class CallEvent { // NOTE: The exact semantics of this are still being defined! // We don't really want a list of hardcoded exceptions in the long run, // but we don't want duplicated lists of known APIs in the short term either. - virtual bool argumentsMayEscape() const { - return hasNonZeroCallbackArg(); - } + virtual bool argumentsMayEscape() const { return hasNonZeroCallbackArg(); } /// Returns true if the callee is an externally-visible function in the /// top-level namespace, such as \c malloc. @@ -456,6 +450,14 @@ class CallEvent { /// can be retrieved from its construction context. std::optional getReturnValueUnderConstruction() const; + // Returns the CallEvent representing the caller of this function + const CallEventRef<> getCaller() const; + + // Returns true if the function was called from a standard library function. + // If not or could not get the caller (it may be a top level function) + // returns false. + bool isCalledFromSystemHeader() const; + // Iterator access to formal parameters and their types. private: struct GetTypeFn { @@ -579,8 +581,9 @@ class BlockCall : public CallEvent { void cloneTo(void *Dest) const override { new (Dest) BlockCall(*this); } - void getExtraInvalidatedValues(ValueList &Values, - RegionAndSymbolInvalidationTraits *ETraits) const override; + void getExtraInvalidatedValues( + ValueList &Values, + RegionAndSymbolInvalidationTraits *ETraits) const override; public: const CallExpr *getOriginExpr() const override { @@ -650,14 +653,12 @@ class BlockCall : public CallEvent { // the block body and analyze the operator() method on the captured lambda. const VarDecl *LambdaVD = getRegionStoringCapturedLambda()->getDecl(); const CXXRecordDecl *LambdaDecl = LambdaVD->getType()->getAsCXXRecordDecl(); - CXXMethodDecl* LambdaCallOperator = LambdaDecl->getLambdaCallOperator(); + CXXMethodDecl *LambdaCallOperator = LambdaDecl->getLambdaCallOperator(); return RuntimeDefinition(LambdaCallOperator); } - bool argumentsMayEscape() const override { - return true; - } + bool argumentsMayEscape() const override { return true; } void getInitialStackFrameContents(const StackFrameContext *CalleeCtx, BindingsTy &Bindings) const override; @@ -684,8 +685,9 @@ class CXXInstanceCall : public AnyFunctionCall { : AnyFunctionCall(D, St, LCtx, ElemRef) {} CXXInstanceCall(const CXXInstanceCall &Other) = default; - void getExtraInvalidatedValues(ValueList &Values, - RegionAndSymbolInvalidationTraits *ETraits) const override; + void getExtraInvalidatedValues( + ValueList &Values, + RegionAndSymbolInvalidationTraits *ETraits) const override; public: /// Returns the expression representing the implicit 'this' object. @@ -843,7 +845,9 @@ class CXXDestructorCall : public CXXInstanceCall { CXXDestructorCall(const CXXDestructorCall &Other) = default; - void cloneTo(void *Dest) const override {new (Dest) CXXDestructorCall(*this);} + void cloneTo(void *Dest) const override { + new (Dest) CXXDestructorCall(*this); + } public: SourceRange getSourceRange() const override { return Location; } @@ -880,8 +884,9 @@ class AnyCXXConstructorCall : public AnyFunctionCall { Data = Target; } - void getExtraInvalidatedValues(ValueList &Values, - RegionAndSymbolInvalidationTraits *ETraits) const override; + void getExtraInvalidatedValues( + ValueList &Values, + RegionAndSymbolInvalidationTraits *ETraits) const override; void getInitialStackFrameContents(const StackFrameContext *CalleeCtx, BindingsTy &Bindings) const override; @@ -921,7 +926,9 @@ class CXXConstructorCall : public AnyCXXConstructorCall { CXXConstructorCall(const CXXConstructorCall &Other) = default; - void cloneTo(void *Dest) const override { new (Dest) CXXConstructorCall(*this); } + void cloneTo(void *Dest) const override { + new (Dest) CXXConstructorCall(*this); + } public: const CXXConstructExpr *getOriginExpr() const override { @@ -1040,7 +1047,9 @@ class CXXAllocatorCall : public AnyFunctionCall { : AnyFunctionCall(E, St, LCtx, ElemRef) {} CXXAllocatorCall(const CXXAllocatorCall &Other) = default; - void cloneTo(void *Dest) const override { new (Dest) CXXAllocatorCall(*this); } + void cloneTo(void *Dest) const override { + new (Dest) CXXAllocatorCall(*this); + } public: const CXXNewExpr *getOriginExpr() const override { @@ -1154,11 +1163,7 @@ class CXXDeallocatorCall : public AnyFunctionCall { // // Note to maintainers: OCM_Message should always be last, since it does not // need to fit in the Data field's low bits. -enum ObjCMessageKind { - OCM_PropertyAccess, - OCM_Subscript, - OCM_Message -}; +enum ObjCMessageKind { OCM_PropertyAccess, OCM_Subscript, OCM_Message }; /// Represents any expression that calls an Objective-C method. /// @@ -1180,8 +1185,9 @@ class ObjCMethodCall : public CallEvent { void cloneTo(void *Dest) const override { new (Dest) ObjCMethodCall(*this); } - void getExtraInvalidatedValues(ValueList &Values, - RegionAndSymbolInvalidationTraits *ETraits) const override; + void getExtraInvalidatedValues( + ValueList &Values, + RegionAndSymbolInvalidationTraits *ETraits) const override; /// Check if the selector may have multiple definitions (may have overrides). virtual bool canBeOverridenInSubclass(ObjCInterfaceDecl *IDecl, @@ -1196,9 +1202,7 @@ class ObjCMethodCall : public CallEvent { return getOriginExpr()->getMethodDecl(); } - unsigned getNumArgs() const override { - return getOriginExpr()->getNumArgs(); - } + unsigned getNumArgs() const override { return getOriginExpr()->getNumArgs(); } const Expr *getArgExpr(unsigned Index) const override { return getOriginExpr()->getArg(Index); @@ -1212,9 +1216,7 @@ class ObjCMethodCall : public CallEvent { return getOriginExpr()->getMethodFamily(); } - Selector getSelector() const { - return getOriginExpr()->getSelector(); - } + Selector getSelector() const { return getOriginExpr()->getSelector(); } SourceRange getSourceRange() const override; @@ -1262,7 +1264,7 @@ class ObjCMethodCall : public CallEvent { void getInitialStackFrameContents(const StackFrameContext *CalleeCtx, BindingsTy &Bindings) const override; - ArrayRef parameters() const override; + ArrayRef parameters() const override; Kind getKind() const override { return CE_ObjCMessage; } StringRef getKindAsString() const override { return "ObjCMethodCall"; } @@ -1336,8 +1338,8 @@ class CallEventManager { CallEventManager(llvm::BumpPtrAllocator &alloc) : Alloc(alloc) {} /// Gets an outside caller given a callee context. - CallEventRef<> - getCaller(const StackFrameContext *CalleeCtx, ProgramStateRef State); + CallEventRef<> getCaller(const StackFrameContext *CalleeCtx, + ProgramStateRef State); /// Gets a call event for a function call, Objective-C method call, /// a 'new', or a 'delete' call. @@ -1433,11 +1435,10 @@ inline void CallEvent::Release() const { namespace llvm { // Support isa<>, cast<>, and dyn_cast<> for CallEventRef. -template struct simplify_type< clang::ento::CallEventRef> { +template struct simplify_type> { using SimpleType = const T *; - static SimpleType - getSimplifiedValue(clang::ento::CallEventRef Val) { + static SimpleType getSimplifiedValue(clang::ento::CallEventRef Val) { return Val.get(); } }; diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index d849649c96a0d..4443ffd092938 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -108,6 +108,7 @@ add_clang_library(clangStaticAnalyzerCheckers SmartPtrModeling.cpp StackAddrEscapeChecker.cpp StdLibraryFunctionsChecker.cpp + StdVariantChecker.cpp STLAlgorithmModeling.cpp StreamChecker.cpp StringChecker.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp new file mode 100644 index 0000000000000..f7b7befe28ee7 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp @@ -0,0 +1,298 @@ +//===- StdVariantChecker.cpp -------------------------------------*- C++ -*-==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/Type.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "llvm/ADT/FoldingSet.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include +#include + +#include "TaggedUnionModeling.h" + +using namespace clang; +using namespace ento; +using namespace tagged_union_modeling; + +REGISTER_MAP_WITH_PROGRAMSTATE(VariantHeldTypeMap, const MemRegion *, QualType) + +namespace clang::ento::tagged_union_modeling { + +const CXXConstructorDecl * +getConstructorDeclarationForCall(const CallEvent &Call) { + const auto *ConstructorCall = dyn_cast(&Call); + if (!ConstructorCall) + return nullptr; + + return ConstructorCall->getDecl(); +} + +bool isCopyConstructorCall(const CallEvent &Call) { + if (const CXXConstructorDecl *ConstructorDecl = + getConstructorDeclarationForCall(Call)) + return ConstructorDecl->isCopyConstructor(); + return false; +} + +bool isCopyAssignmentCall(const CallEvent &Call) { + const Decl *CopyAssignmentDecl = Call.getDecl(); + + if (const auto *AsMethodDecl = + dyn_cast_or_null(CopyAssignmentDecl)) + return AsMethodDecl->isCopyAssignmentOperator(); + return false; +} + +bool isMoveConstructorCall(const CallEvent &Call) { + const CXXConstructorDecl *ConstructorDecl = + getConstructorDeclarationForCall(Call); + if (!ConstructorDecl) + return false; + + return ConstructorDecl->isMoveConstructor(); +} + +bool isMoveAssignmentCall(const CallEvent &Call) { + const Decl *CopyAssignmentDecl = Call.getDecl(); + + const auto *AsMethodDecl = + dyn_cast_or_null(CopyAssignmentDecl); + if (!AsMethodDecl) + return false; + + return AsMethodDecl->isMoveAssignmentOperator(); +} + +bool isStdType(const Type *Type, llvm::StringRef TypeName) { + auto *Decl = Type->getAsRecordDecl(); + if (!Decl) + return false; + return (Decl->getName() == TypeName) && Decl->isInStdNamespace(); +} + +bool isStdVariant(const Type *Type) { + return isStdType(Type, llvm::StringLiteral("variant")); +} + +} // end of namespace clang::ento::tagged_union_modeling + +static std::optional> +getTemplateArgsFromVariant(const Type *VariantType) { + const auto *TempSpecType = VariantType->getAs(); + if (!TempSpecType) + return {}; + + return TempSpecType->template_arguments(); +} + +static std::optional +getNthTemplateTypeArgFromVariant(const Type *varType, unsigned i) { + std::optional> VariantTemplates = + getTemplateArgsFromVariant(varType); + if (!VariantTemplates) + return {}; + + return (*VariantTemplates)[i].getAsType(); +} + +static bool isVowel(char a) { + switch (a) { + case 'a': + case 'e': + case 'i': + case 'o': + case 'u': + return true; + default: + return false; + } +} + +static llvm::StringRef indefiniteArticleBasedOnVowel(char a) { + if (isVowel(a)) + return "an"; + return "a"; +} + +class StdVariantChecker : public Checker { + // Call descriptors to find relevant calls + CallDescription VariantConstructor{{"std", "variant", "variant"}}; + CallDescription VariantAssignmentOperator{{"std", "variant", "operator="}}; + CallDescription StdGet{{"std", "get"}, 1, 1}; + + BugType BadVariantType{this, "BadVariantType", "BadVariantType"}; + +public: + ProgramStateRef checkRegionChanges(ProgramStateRef State, + const InvalidatedSymbols *, + ArrayRef, + ArrayRef Regions, + const LocationContext *, + const CallEvent *Call) const { + if (!Call) + return State; + + return removeInformationStoredForDeadInstances( + *Call, State, Regions); + } + + bool evalCall(const CallEvent &Call, CheckerContext &C) const { + // Check if the call was not made from a system header. If it was then + // we do an early return because it is part of the implementation. + if (Call.isCalledFromSystemHeader()) + return false; + + if (StdGet.matches(Call)) + return handleStdGetCall(Call, C); + + // First check if a constructor call is happening. If it is a + // constructor call, check if it is an std::variant constructor call. + bool IsVariantConstructor = + isa(Call) && VariantConstructor.matches(Call); + bool IsVariantAssignmentOperatorCall = + isa(Call) && + VariantAssignmentOperator.matches(Call); + + if (IsVariantConstructor || IsVariantAssignmentOperatorCall) { + if (Call.getNumArgs() == 0 && IsVariantConstructor) { + handleDefaultConstructor(cast(&Call), C); + return true; + } + + // FIXME Later this checker should be extended to handle constructors + // with multiple arguments. + if (Call.getNumArgs() != 1) + return false; + + SVal ThisSVal; + if (IsVariantConstructor) { + const auto &AsConstructorCall = cast(Call); + ThisSVal = AsConstructorCall.getCXXThisVal(); + } else if (IsVariantAssignmentOperatorCall) { + const auto &AsMemberOpCall = cast(Call); + ThisSVal = AsMemberOpCall.getCXXThisVal(); + } else { + return false; + } + + handleConstructorAndAssignment(Call, C, ThisSVal); + return true; + } + return false; + } + +private: + // The default constructed std::variant must be handled separately + // by default the std::variant is going to hold a default constructed instance + // of the first type of the possible types + void handleDefaultConstructor(const CXXConstructorCall *ConstructorCall, + CheckerContext &C) const { + SVal ThisSVal = ConstructorCall->getCXXThisVal(); + + const auto *const ThisMemRegion = ThisSVal.getAsRegion(); + if (!ThisMemRegion) + return; + + std::optional DefaultType = getNthTemplateTypeArgFromVariant( + ThisSVal.getType(C.getASTContext())->getPointeeType().getTypePtr(), 0); + if (!DefaultType) + return; + + ProgramStateRef State = ConstructorCall->getState(); + State = State->set(ThisMemRegion, *DefaultType); + C.addTransition(State); + } + + bool handleStdGetCall(const CallEvent &Call, CheckerContext &C) const { + ProgramStateRef State = Call.getState(); + + const auto &ArgType = Call.getArgSVal(0) + .getType(C.getASTContext()) + ->getPointeeType() + .getTypePtr(); + // We have to make sure that the argument is an std::variant. + // There is another std::get with std::pair argument + if (!isStdVariant(ArgType)) + return false; + + // Get the mem region of the argument std::variant and look up the type + // information that we know about it. + const MemRegion *ArgMemRegion = Call.getArgSVal(0).getAsRegion(); + const QualType *StoredType = State->get(ArgMemRegion); + if (!StoredType) + return false; + + const CallExpr *CE = cast(Call.getOriginExpr()); + const FunctionDecl *FD = CE->getDirectCallee(); + if (FD->getTemplateSpecializationArgs()->size() < 1) + return false; + + const auto &TypeOut = FD->getTemplateSpecializationArgs()->asArray()[0]; + // std::get's first template parameter can be the type we want to get + // out of the std::variant or a natural number which is the position of + // the requested type in the argument type list of the std::variant's + // argument. + QualType RetrievedType; + switch (TypeOut.getKind()) { + case TemplateArgument::ArgKind::Type: + RetrievedType = TypeOut.getAsType(); + break; + case TemplateArgument::ArgKind::Integral: + // In the natural number case we look up which type corresponds to the + // number. + if (std::optional NthTemplate = + getNthTemplateTypeArgFromVariant( + ArgType, TypeOut.getAsIntegral().getSExtValue())) { + RetrievedType = *NthTemplate; + break; + } + [[fallthrough]]; + default: + return false; + } + + QualType RetrievedCanonicalType = RetrievedType.getCanonicalType(); + QualType StoredCanonicalType = StoredType->getCanonicalType(); + if (RetrievedCanonicalType == StoredCanonicalType) + return true; + + ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); + if (!ErrNode) + return false; + llvm::SmallString<128> Str; + llvm::raw_svector_ostream OS(Str); + std::string StoredTypeName = StoredType->getAsString(); + std::string RetrievedTypeName = RetrievedType.getAsString(); + OS << "std::variant " << ArgMemRegion->getDescriptiveName() << " held " + << indefiniteArticleBasedOnVowel(StoredTypeName[0]) << " \'" + << StoredTypeName << "\', not " + << indefiniteArticleBasedOnVowel(RetrievedTypeName[0]) << " \'" + << RetrievedTypeName << "\'"; + auto R = std::make_unique(BadVariantType, OS.str(), + ErrNode); + C.emitReport(std::move(R)); + return true; + } +}; + +bool clang::ento::shouldRegisterStdVariantChecker( + clang::ento::CheckerManager const &mgr) { + return true; +} + +void clang::ento::registerStdVariantChecker(clang::ento::CheckerManager &mgr) { + mgr.registerChecker(); +} \ No newline at end of file diff --git a/clang/lib/StaticAnalyzer/Checkers/TaggedUnionModeling.h b/clang/lib/StaticAnalyzer/Checkers/TaggedUnionModeling.h new file mode 100644 index 0000000000000..557e8a76506e6 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/TaggedUnionModeling.h @@ -0,0 +1,99 @@ +//===- TaggedUnionModeling.h -------------------------------------*- C++ -*-==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_TAGGEDUNIONMODELING_H +#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_TAGGEDUNIONMODELING_H + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "llvm/ADT/FoldingSet.h" +#include + +namespace clang::ento::tagged_union_modeling { + +// The implementation of all these functions can be found in the file +// StdVariantChecker.cpp under the same directory as this file. + +bool isCopyConstructorCall(const CallEvent &Call); +bool isCopyAssignmentCall(const CallEvent &Call); +bool isMoveAssignmentCall(const CallEvent &Call); +bool isMoveConstructorCall(const CallEvent &Call); +bool isStdType(const Type *Type, const std::string &TypeName); +bool isStdVariant(const Type *Type); + +// When invalidating regions, we also have to follow that by invalidating the +// corresponding custom data in the program state. +template +ProgramStateRef +removeInformationStoredForDeadInstances(const CallEvent &Call, + ProgramStateRef State, + ArrayRef Regions) { + // If we do not know anything about the call we shall not continue. + // If the call is happens within a system header it is implementation detail. + // We should not take it into consideration. + if (Call.isInSystemHeader()) + return State; + + for (const MemRegion *Region : Regions) + State = State->remove(Region); + + return State; +} + +template +void handleConstructorAndAssignment(const CallEvent &Call, CheckerContext &C, + const SVal &ThisSVal) { + ProgramStateRef State = Call.getState(); + + if (!State) + return; + + auto ArgSVal = Call.getArgSVal(0); + const auto *ThisRegion = ThisSVal.getAsRegion(); + const auto *ArgMemRegion = ArgSVal.getAsRegion(); + + // Make changes to the state according to type of constructor/assignment + bool IsCopy = isCopyConstructorCall(Call) || isCopyAssignmentCall(Call); + bool IsMove = isMoveConstructorCall(Call) || isMoveAssignmentCall(Call); + // First we handle copy and move operations + if (IsCopy || IsMove) { + const QualType *OtherQType = State->get(ArgMemRegion); + + // If the argument of a copy constructor or assignment is unknown then + // we will not know the argument of the copied to object. + if (!OtherQType) { + State = State->remove(ThisRegion); + } else { + // When move semantics is used we can only know that the moved from + // object must be in a destructible state. Other usage of the object + // than destruction is undefined. + if (IsMove) + State = State->remove(ArgMemRegion); + + State = State->set(ThisRegion, *OtherQType); + } + } else { + // Value constructor + auto ArgQType = ArgSVal.getType(C.getASTContext()); + const Type *ArgTypePtr = ArgQType.getTypePtr(); + + QualType WoPointer = ArgTypePtr->getPointeeType(); + State = State->set(ThisRegion, WoPointer); + } + + C.addTransition(State); +} + +} // namespace clang::ento::tagged_union_modeling + +#endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_TAGGEDUNIONMODELING_H \ No newline at end of file diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index 76fb7481f6519..d004c12bf2c1e 100644 --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -517,6 +517,26 @@ const ConstructionContext *CallEvent::getConstructionContext() const { return nullptr; } +const CallEventRef<> CallEvent::getCaller() const { + const auto *CallLocationContext = this->getLocationContext(); + if (!CallLocationContext || CallLocationContext->inTopFrame()) + return nullptr; + + const auto *CallStackFrameContext = CallLocationContext->getStackFrame(); + if (!CallStackFrameContext) + return nullptr; + + CallEventManager &CEMgr = State->getStateManager().getCallEventManager(); + return CEMgr.getCaller(CallStackFrameContext, State); +} + +bool CallEvent::isCalledFromSystemHeader() const { + if (const CallEventRef<> Caller = getCaller()) + return Caller->isInSystemHeader(); + + return false; +} + std::optional CallEvent::getReturnValueUnderConstruction() const { const auto *CC = getConstructionContext(); if (!CC) diff --git a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h index 8633a8beadbff..3ef7af2ea6c6a 100644 --- a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h +++ b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h @@ -249,6 +249,11 @@ namespace std { pair(const pair &other) : first(other.first), second(other.second) {} }; + + template + T2& get(pair& p) ; + template + T1& get(const pair& p) ; typedef __typeof__(sizeof(int)) size_t; @@ -264,6 +269,9 @@ namespace std { return static_cast(a); } + template< class T > + using remove_reference_t = typename remove_reference::type; + template void swap(T &a, T &b) { T c(std::move(a)); @@ -718,6 +726,11 @@ namespace std { template struct is_same : public false_type {}; template struct is_same<_Tp, _Tp> : public true_type {}; + #if __cplusplus >= 201703L + template< class T, class U > + inline constexpr bool is_same_v = is_same::value; + #endif + template ::value || is_reference<_Tp>::value > struct __add_const {typedef _Tp type;}; @@ -729,6 +742,9 @@ namespace std { template struct remove_const {typedef _Tp type;}; template struct remove_const {typedef _Tp type;}; + template< class T > + using remove_const_t = typename remove_const::type; + template struct add_lvalue_reference {typedef _Tp& type;}; template struct is_trivially_copy_assignable @@ -793,6 +809,9 @@ namespace std { return __result; } + template< bool B, class T = void > + using enable_if_t = typename enable_if::type; + template OutputIter copy_backward(InputIter II, InputIter IE, OutputIter OI) { return __copy_backward(II, IE, OI); @@ -1252,4 +1271,107 @@ template class packaged_task { // TODO: Add some actual implementation. }; + #if __cplusplus >= 201703L + + namespace detail + { + template + struct type_identity { using type = T; }; // or use std::type_identity (since C++20) + + template + auto try_add_pointer(int) -> type_identity::type*>; + template + auto try_add_pointer(...) -> type_identity; + } // namespace detail + + template + struct add_pointer : decltype(detail::try_add_pointer(0)) {}; + + template< class T > + using add_pointer_t = typename add_pointer::type; + + template struct remove_cv { typedef T type; }; + template struct remove_cv { typedef T type; }; + template struct remove_cv { typedef T type; }; + template struct remove_cv { typedef T type; }; + + template< class T > + using remove_cv_t = typename remove_cv::type; + + // This decay does not behave exactly like std::decay, but this is enough + // for testing the std::variant checker + template + struct decay{typedef remove_cv_t> type;}; + template + using decay_t = typename decay::type; + + // variant + template class variant; + // variant helper classes + template struct variant_size; + template struct variant_size; + template struct variant_size; + template struct variant_size; + template inline constexpr size_t variant_size_v = variant_size::value; + template + struct variant_size>; + template struct variant_alternative; + template struct variant_alternative; + template struct variant_alternative; + template struct variant_alternative; + template + using variant_alternative_t = typename variant_alternative::type; + template + struct variant_alternative>; + inline constexpr size_t variant_npos = -1; + template + constexpr variant_alternative_t>& + get(variant&); + template + constexpr variant_alternative_t>&& + get(variant&&); + template + constexpr const variant_alternative_t>& + get(const variant&); + template + constexpr const variant_alternative_t>&& + get(const variant&&); + template + constexpr T& get(variant&); + template + constexpr T&& get(variant&&); + template + constexpr const T& get(const variant&); + template + constexpr const T&& get(const variant&&); + template + constexpr add_pointer_t>> + get_if(variant*) noexcept; + template + constexpr add_pointer_t>> + get_if(const variant*) noexcept; + template + constexpr add_pointer_t get_if(variant*) noexcept; + template + constexpr add_pointer_t get_if(const variant*) noexcept; + + template + class variant { + public: + // constructors + constexpr variant()= default ; + constexpr variant(const variant&); + constexpr variant(variant&&); + template, decay_t>>> + constexpr variant(T&&); + // assignment + variant& operator=(const variant&); + variant& operator=(variant&&) ; + template, decay_t>>> + variant& operator=(T&&); + }; + #endif + } // namespace std diff --git a/clang/test/Analysis/diagnostics/explicit-suppression.cpp b/clang/test/Analysis/diagnostics/explicit-suppression.cpp index b98d0260b0965..24586e37fe207 100644 --- a/clang/test/Analysis/diagnostics/explicit-suppression.cpp +++ b/clang/test/Analysis/diagnostics/explicit-suppression.cpp @@ -19,6 +19,6 @@ class C { void testCopyNull(C *I, C *E) { std::copy(I, E, (C *)0); #ifndef SUPPRESSED - // expected-warning@../Inputs/system-header-simulator-cxx.h:741 {{Called C++ object pointer is null}} + // expected-warning@../Inputs/system-header-simulator-cxx.h:757 {{Called C++ object pointer is null}} #endif } diff --git a/clang/test/Analysis/std-variant-checker.cpp b/clang/test/Analysis/std-variant-checker.cpp new file mode 100644 index 0000000000000..7f136c06b19cc --- /dev/null +++ b/clang/test/Analysis/std-variant-checker.cpp @@ -0,0 +1,358 @@ +// RUN: %clang %s -std=c++17 -Xclang -verify --analyze \ +// RUN: -Xclang -analyzer-checker=core \ +// RUN: -Xclang -analyzer-checker=debug.ExprInspection \ +// RUN: -Xclang -analyzer-checker=core,alpha.core.StdVariant + +#include "Inputs/system-header-simulator-cxx.h" + +class Foo{}; + +void clang_analyzer_warnIfReached(); +void clang_analyzer_eval(int); + +//helper functions +void changeVariantType(std::variant &v) { + v = 25; +} + +void changesToInt(std::variant &v); +void changesToInt(std::variant *v); + +void cannotChangePtr(const std::variant &v); +void cannotChangePtr(const std::variant *v); + +char getUnknownChar(); + +void swap(std::variant &v1, std::variant &v2) { + std::variant tmp = v1; + v1 = v2; + v2 = tmp; +} + +void cantDo(const std::variant& v) { + std::variant vtmp = v; + vtmp = 5; + int a = std::get (vtmp); + (void) a; +} + +void changeVariantPtr(std::variant *v) { + *v = 'c'; +} + +using var_t = std::variant; +using var_tt = var_t; +using int_t = int; +using char_t = char; + +// A quick sanity check to see that std::variant's std::get +// is not being confused with std::pairs std::get. +void wontConfuseStdGets() { + std::pair p{15, '1'}; + int a = std::get(p); + char c = std::get(p); + (void)a; + (void)c; +} + +//----------------------------------------------------------------------------// +// std::get +//----------------------------------------------------------------------------// +void stdGetType() { + std::variant v = 25; + int a = std::get(v); + char c = std::get(v); // expected-warning {{std::variant 'v' held an 'int', not a 'char'}} + (void)a; + (void)c; +} + +void stdGetPointer() { + int *p = new int; + std::variant v = p; + int *a = std::get(v); + char c = std::get(v); // expected-warning {{std::variant 'v' held an 'int *', not a 'char'}} + (void)a; + (void)c; + delete p; +} + +void stdGetObject() { + std::variant v = Foo{}; + Foo f = std::get(v); + int i = std::get(v); // expected-warning {{std::variant 'v' held a 'Foo', not an 'int'}} + (void)i; +} + +void stdGetPointerAndPointee() { + int a = 5; + std::variant v = &a; + int *b = std::get(v); + int c = std::get(v); // expected-warning {{std::variant 'v' held an 'int *', not an 'int'}} + (void)c; + (void)b; +} + +void variantHoldingVariant() { + std::variant, std::variant> v = std::variant(25); + std::variant v1 = std::get>(v); + std::variant v2 = std::get>(v); // expected-warning {{std::variant 'v' held a 'std::variant', not a 'class std::variant'}} +} + +//----------------------------------------------------------------------------// +// Constructors and assignments +//----------------------------------------------------------------------------// +void copyConstructor() { + std::variant v = 25; + std::variant t(v); + int a = std::get (t); + char c = std::get (t); // expected-warning {{std::variant 't' held an 'int', not a 'char'}} + (void)a; + (void)c; +} + +void copyAssignmentOperator() { + std::variant v = 25; + std::variant t = 'c'; + t = v; + int a = std::get (t); + char c = std::get (t); // expected-warning {{std::variant 't' held an 'int', not a 'char'}} + (void)a; + (void)c; +} + +void assignmentOperator() { + std::variant v = 25; + int a = std::get (v); + (void)a; + v = 'c'; + char c = std::get(v); + a = std::get(v); // expected-warning {{std::variant 'v' held a 'char', not an 'int'}} + (void)a; + (void)c; +} + +void typeChangeThreeTimes() { + std::variant v = 25; + int a = std::get (v); + (void)a; + v = 'c'; + char c = std::get(v); + v = 25; + a = std::get(v); + (void)a; + v = 1.25f; + float f = std::get(v); + a = std::get(v); // expected-warning {{std::variant 'v' held a 'float', not an 'int'}} + (void)a; + (void)c; + (void)f; +} + +void defaultConstructor() { + std::variant v; + int i = std::get(v); + char c = std::get(v); // expected-warning {{std::variant 'v' held an 'int', not a 'char'}} + (void)i; + (void)c; +} + +// Verify that we handle temporary objects correctly +void temporaryObjectsConstructor() { + std::variant v(std::variant('c')); + char c = std::get(v); + int a = std::get(v); // expected-warning {{std::variant 'v' held a 'char', not an 'int'}} + (void)a; + (void)c; +} + +void temporaryObjectsAssignment() { + std::variant v = std::variant('c'); + char c = std::get(v); + int a = std::get(v); // expected-warning {{std::variant 'v' held a 'char', not an 'int'}} + (void)a; + (void)c; +} + +// Verify that we handle pointer types correctly +void pointerTypeHeld() { + int *p = new int; + std::variant v = p; + int *a = std::get(v); + char c = std::get(v); // expected-warning {{std::variant 'v' held an 'int *', not a 'char'}} + (void)a; + (void)c; + delete p; +} + +std::variant get_unknown_variant(); +// Verify that the copy constructor is handles properly when the std::variant +// has no previously activated type and we copy an object of unknown value in it. +void copyFromUnknownVariant() { + std::variant u = get_unknown_variant(); + std::variant v(u); + int a = std::get(v); // no-waring + char c = std::get(v); // no-warning + (void)a; + (void)c; +} + +// Verify that the copy constructor is handles properly when the std::variant +// has previously activated type and we copy an object of unknown value in it. +void copyFromUnknownVariantBef() { + std::variant v = 25; + std::variant u = get_unknown_variant(); + v = u; + int a = std::get(v); // no-waring + char c = std::get(v); // no-warning + (void)a; + (void)c; +} + +//----------------------------------------------------------------------------// +// typedef +//----------------------------------------------------------------------------// + +void typefdefedVariant() { + var_t v = 25; + int a = std::get(v); + char c = std::get(v); // expected-warning {{std::variant 'v' held an 'int', not a 'char'}} + (void)a; + (void)c; +} + +void typedefedTypedfefedVariant() { + var_tt v = 25; + int a = std::get(v); + char c = std::get(v); // expected-warning {{std::variant 'v' held an 'int', not a 'char'}} + (void)a; + (void)c; +} + +void typedefedGet() { + std::variant v = 25; + int a = std::get(v); + char c = std::get(v); // expected-warning {{std::variant 'v' held an 'int', not a 'char'}} + (void)a; + (void)c; +} + +void typedefedPack() { + std::variant v = 25; + int a = std::get(v); + char c = std::get(v); // expected-warning {{std::variant 'v' held an 'int', not a 'char'}} + (void)a; + (void)c; +} + +void fromVariable() { + char o = 'c'; + std::variant v(o); + char c = std::get(v); + int a = std::get(v); // expected-warning {{std::variant 'v' held a 'char', not an 'int'}} + (void)a; + (void)c; +} + +void unknowValueButKnownType() { + char o = getUnknownChar(); + std::variant v(o); + char c = std::get(v); + int a = std::get(v); // expected-warning {{std::variant 'v' held a 'char', not an 'int'}} + (void)a; + (void)c; +} + +void createPointer() { + std::variant *v = new std::variant(15); + int a = std::get(*v); + char c = std::get(*v); // expected-warning {{std::variant held an 'int', not a 'char'}} + (void)a; + (void)c; + delete v; +} + +//----------------------------------------------------------------------------// +// Passing std::variants to functions +//----------------------------------------------------------------------------// + +// Verifying that we are not invalidating the memory region of a variant if +// a non inlined or inlined function takes it as a constant reference or pointer +void constNonInlineRef() { + std::variant v = 'c'; + cannotChangePtr(v); + char c = std::get(v); + int a = std::get(v); // expected-warning {{std::variant 'v' held a 'char', not an 'int'}} + (void)a; + (void)c; +} + +void contNonInlinePtr() { + std::variant v = 'c'; + cannotChangePtr(&v); + char c = std::get(v); + int a = std::get(v); // expected-warning {{std::variant 'v' held a 'char', not an 'int'}} + (void)a; + (void)c; +} + +void copyInAFunction() { + std::variant v = 'c'; + cantDo(v); + char c = std::get(v); + int a = std::get(v); // expected-warning {{std::variant 'v' held a 'char', not an 'int'}} + (void)a; + (void)c; + +} + +// Verifying that we can keep track of the type stored in std::variant when +// it is passed to an inlined function as a reference or pointer +void changeThruPointers() { + std::variant v = 15; + changeVariantPtr(&v); + char c = std::get (v); + int a = std::get (v); // expected-warning {{std::variant 'v' held a 'char', not an 'int'}} + (void)a; + (void)c; +} + +void functionCallWithCopyAssignment() { + var_t v1 = 15; + var_t v2 = 'c'; + swap(v1, v2); + int a = std::get (v2); + (void)a; + char c = std::get (v1); + a = std::get (v1); // expected-warning {{std::variant 'v1' held a 'char', not an 'int'}} + (void)a; + (void)c; +} + +void inlineFunctionCall() { + std::variant v = 'c'; + changeVariantType(v); + int a = std::get (v); + char c = std::get (v); // expected-warning {{std::variant 'v' held an 'int', not a 'char'}} + (void)a; + (void)c; +} + +// Verifying that we invalidate the mem region of std::variant when it is +// passed as a non const reference or a pointer to a non inlined function. +void nonInlineFunctionCall() { + std::variant v = 'c'; + changesToInt(v); + int a = std::get (v); // no-waring + char c = std::get (v); // no-warning + (void)a; + (void)c; +} + +void nonInlineFunctionCallPtr() { + std::variant v = 'c'; + changesToInt(&v); + int a = std::get (v); // no-warning + char c = std::get (v); // no-warning + (void)a; + (void)c; +} \ No newline at end of file